mirror of
https://github.com/Trensa-Organization/hy3.git
synced 2025-03-15 10:43:40 +01:00
WIP Window Movement
Code is bad but it does mostly work
This commit is contained in:
parent
0e854287ff
commit
0bdf28f3bc
3 changed files with 366 additions and 69 deletions
|
@ -1,14 +1,5 @@
|
|||
#include "Hy3Layout.hpp"
|
||||
#include "src/Window.hpp"
|
||||
#include "src/debug/Log.hpp"
|
||||
#include "src/helpers/Vector2D.hpp"
|
||||
#include "src/helpers/Workspace.hpp"
|
||||
#include "src/managers/XWaylandManager.hpp"
|
||||
#include "src/managers/input/InputManager.hpp"
|
||||
#include "src/render/Renderer.hpp"
|
||||
#include <cairo/cairo.h>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include <src/Compositor.hpp>
|
||||
|
||||
Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {}
|
||||
|
@ -23,6 +14,8 @@ Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeData::Group) {
|
|||
new(&this->as_group) Hy3GroupData(std::move(group));
|
||||
}
|
||||
|
||||
Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {}
|
||||
|
||||
Hy3NodeData::~Hy3NodeData() {
|
||||
switch (this->type) {
|
||||
case Hy3NodeData::Window:
|
||||
|
@ -80,6 +73,18 @@ Hy3NodeData& Hy3NodeData::operator=(const Hy3NodeData& from) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
Hy3NodeData& Hy3NodeData::operator=(CWindow* window) {
|
||||
*this = Hy3NodeData(window);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) {
|
||||
*this = Hy3NodeData(layout);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const {
|
||||
if (this->type != rhs.type) return false;
|
||||
switch (this->type) {
|
||||
|
@ -185,6 +190,59 @@ void Hy3Node::recalcSizePosRecursive(bool force) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Hy3GroupData::hasChild(Hy3Node* node) {
|
||||
Debug::log(LOG, "Searching for child %p of %p", this, node);
|
||||
for (auto child: this->children) {
|
||||
if (child == node) return true;
|
||||
|
||||
if (child->data.type == Hy3NodeData::Group) {
|
||||
if (child->data.as_group.hasChild(node)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Hy3Node::removeChild(Hy3Node* child, bool childSwallows) {
|
||||
if (this->data.type != Hy3NodeData::Group) return false;
|
||||
Hy3GroupData& group = this->data.as_group;
|
||||
|
||||
if (group.children.remove(child)) {
|
||||
if (group.children.empty()) {
|
||||
Debug::log(LOG, "Group %p is empty, removing", this);
|
||||
this->parent->removeChild(this, childSwallows);
|
||||
this->layout->nodes.remove(*this);
|
||||
} else if (childSwallows && group.children.size() == 1) {
|
||||
auto remaining = group.children.front();
|
||||
Debug::log(LOG, "Group %p has only one child(%p), swallowing", this, remaining);
|
||||
swapNodeData(*this, *remaining);
|
||||
this->layout->nodes.remove(*remaining);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void swapNodeData(Hy3Node& a, Hy3Node& b) {
|
||||
Hy3NodeData aData = std::move(a.data);
|
||||
a.data = b.data;
|
||||
b.data = aData;
|
||||
|
||||
if (a.data.type == Hy3NodeData::Group) {
|
||||
for (auto child: a.data.as_group.children) {
|
||||
child->parent = &a;
|
||||
}
|
||||
}
|
||||
|
||||
if (b.data.type == Hy3NodeData::Group) {
|
||||
for (auto child: b.data.as_group.children) {
|
||||
child->parent = &b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Hy3Layout::getWorkspaceNodeCount(const int& id) {
|
||||
int count = 0;
|
||||
|
||||
|
@ -341,7 +399,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
|
|||
} else {
|
||||
if ((opening_into = this->getWorkspaceRootGroup(window->m_iWorkspaceID)) == nullptr) {
|
||||
this->nodes.push_back({
|
||||
.data = Hy3NodeData(Hy3GroupData(Hy3GroupLayout::SplitH)),
|
||||
.data = Hy3GroupLayout::SplitH,
|
||||
.position = monitor->vecPosition,
|
||||
.size = monitor->vecSize,
|
||||
.workspace_id = window->m_iWorkspaceID,
|
||||
|
@ -359,7 +417,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
|
|||
|
||||
this->nodes.push_back({
|
||||
.parent = opening_into,
|
||||
.data = Hy3NodeData(window),
|
||||
.data = window,
|
||||
.workspace_id = window->m_iWorkspaceID,
|
||||
.layout = this,
|
||||
});
|
||||
|
@ -422,7 +480,7 @@ void Hy3Layout::onWindowFocusChange(CWindow* window) {
|
|||
&& node->parent->data.as_group.children.size() == 1)
|
||||
{
|
||||
auto parent = node->parent;
|
||||
std::swap(parent->data, node->data);
|
||||
swapNodeData(*parent, *node);
|
||||
this->nodes.remove(*node);
|
||||
node = parent;
|
||||
}
|
||||
|
@ -455,40 +513,6 @@ void Hy3Layout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreenMode mod
|
|||
}
|
||||
|
||||
std::any Hy3Layout::layoutMessage(SLayoutMessageHeader header, std::string content) {
|
||||
if (header.pWindow == nullptr) return "";
|
||||
auto* node = this->getNodeFromWindow(header.pWindow);
|
||||
if (node == nullptr) return "";
|
||||
|
||||
if (content == "splith" || content == "splitv") {
|
||||
Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
|
||||
if (content == "splitv") {
|
||||
layout = Hy3GroupLayout::SplitV;
|
||||
}
|
||||
|
||||
if (node->parent != nullptr
|
||||
&& node->parent->data.as_group.children.size() == 1
|
||||
&& (node->parent->data.as_group.layout == Hy3GroupLayout::SplitH
|
||||
|| node->parent->data.as_group.layout == Hy3GroupLayout::SplitV))
|
||||
{
|
||||
node->parent->data.as_group.layout = layout;
|
||||
node->parent->recalcSizePosRecursive();
|
||||
return "";
|
||||
}
|
||||
|
||||
Hy3NodeData node_data = Hy3NodeData(Hy3GroupData(layout));
|
||||
std::swap(node->data, node_data);
|
||||
|
||||
this->nodes.push_back({
|
||||
.parent = node,
|
||||
.data = node_data,
|
||||
.workspace_id = node->workspace_id,
|
||||
.layout = this,
|
||||
});
|
||||
|
||||
node->data.as_group.children.push_back(&this->nodes.back());
|
||||
node->recalcSizePosRecursive();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -497,10 +521,12 @@ SWindowRenderLayoutHints Hy3Layout::requestRenderHints(CWindow* pWindow) {
|
|||
}
|
||||
|
||||
void Hy3Layout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) {
|
||||
Debug::log(LOG, "SwitchWindows: %p %p", pWindowA, pWindowB);
|
||||
; // empty
|
||||
}
|
||||
|
||||
void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) {
|
||||
Debug::log(LOG, "AlterSplitRatio: %p %f", pWindow, delta);
|
||||
; // empty
|
||||
}
|
||||
|
||||
|
@ -523,3 +549,225 @@ void Hy3Layout::onEnable() {
|
|||
|
||||
void Hy3Layout::onDisable() {
|
||||
}
|
||||
|
||||
void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return;
|
||||
|
||||
if (node->parent->data.as_group.children.size() == 1
|
||||
&& (node->parent->data.as_group.layout == Hy3GroupLayout::SplitH
|
||||
|| node->parent->data.as_group.layout == Hy3GroupLayout::SplitV))
|
||||
{
|
||||
node->parent->data.as_group.layout = layout;
|
||||
node->parent->recalcSizePosRecursive();
|
||||
return;
|
||||
}
|
||||
|
||||
this->nodes.push_back({
|
||||
.parent = node,
|
||||
.data = node->data.as_window,
|
||||
.workspace_id = node->workspace_id,
|
||||
.layout = this,
|
||||
});
|
||||
|
||||
node->data = layout;
|
||||
node->data.as_group.children.push_back(&this->nodes.back());
|
||||
node->recalcSizePosRecursive();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftWindow(CWindow* window, ShiftDirection direction) {
|
||||
Debug::log(LOG, "ShiftWindow %p %d", window, direction);
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return;
|
||||
|
||||
auto& group = node->parent->data.as_group;
|
||||
|
||||
enum class ShiftAction {
|
||||
ShiftUp,
|
||||
ShiftDown,
|
||||
BreakGroup,
|
||||
};
|
||||
|
||||
ShiftAction action;
|
||||
|
||||
switch (group.layout) {
|
||||
case Hy3GroupLayout::SplitH:
|
||||
case Hy3GroupLayout::Tabbed:
|
||||
switch (direction) {
|
||||
case ShiftDirection::Left:
|
||||
action = ShiftAction::ShiftUp;
|
||||
break;
|
||||
case ShiftDirection::Right:
|
||||
action = ShiftAction::ShiftDown;
|
||||
break;
|
||||
case ShiftDirection::Up:
|
||||
case ShiftDirection::Down:
|
||||
action = ShiftAction::BreakGroup;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Hy3GroupLayout::SplitV:
|
||||
switch (direction) {
|
||||
case ShiftDirection::Up:
|
||||
action = ShiftAction::ShiftUp;
|
||||
break;
|
||||
case ShiftDirection::Down:
|
||||
action = ShiftAction::ShiftDown;
|
||||
break;
|
||||
case ShiftDirection::Left:
|
||||
case ShiftDirection::Right:
|
||||
action = ShiftAction::BreakGroup;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Debug::log(LOG, "ShiftWindow 2 %d", action);
|
||||
|
||||
Hy3Node* target_group;
|
||||
switch (action) {
|
||||
case ShiftAction::ShiftUp:
|
||||
if (node == group.children.front()) {
|
||||
action = ShiftAction::BreakGroup;
|
||||
} else {
|
||||
auto iter = std::find(group.children.begin(), group.children.end(), node);
|
||||
auto target = *std::prev(iter);
|
||||
if (target->data.type == Hy3NodeData::Group) {
|
||||
target_group = target;
|
||||
goto entergroup;
|
||||
} else {
|
||||
swapNodeData(*node, *target);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ShiftAction::ShiftDown:
|
||||
if (node == group.children.back()) {
|
||||
action = ShiftAction::BreakGroup;
|
||||
} else {
|
||||
auto iter = std::find(group.children.begin(), group.children.end(), node);
|
||||
auto target = *std::next(iter);
|
||||
if (target->data.type == Hy3NodeData::Group) {
|
||||
target_group = target;
|
||||
goto entergroup;
|
||||
} else {
|
||||
swapNodeData(*node, *target);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ShiftAction::BreakGroup:
|
||||
break;
|
||||
}
|
||||
|
||||
if (action != ShiftAction::BreakGroup) {
|
||||
node->parent->recalcSizePosRecursive();
|
||||
return;
|
||||
}
|
||||
|
||||
goto noentergroup;
|
||||
entergroup: {
|
||||
auto common_parent = this->findCommonParentNode(*target_group, *node);
|
||||
if (common_parent == nullptr) {
|
||||
Debug::log(ERR, "Could not find common parent of nodes %p, %p while moving", target_group, node);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* parent = node->parent;
|
||||
node->parent = target_group;
|
||||
node->size_ratio = 1.0;
|
||||
|
||||
Hy3GroupData& target_group_data = target_group->data.as_group;
|
||||
switch (target_group_data.layout) {
|
||||
case Hy3GroupLayout::SplitH:
|
||||
case Hy3GroupLayout::Tabbed:
|
||||
switch (direction) {
|
||||
case ShiftDirection::Left:
|
||||
target_group_data.children.push_back(node);
|
||||
break;
|
||||
case ShiftDirection::Right:
|
||||
case ShiftDirection::Up:
|
||||
case ShiftDirection::Down:
|
||||
target_group_data.children.push_front(node);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Hy3GroupLayout::SplitV:
|
||||
switch (direction) {
|
||||
case ShiftDirection::Up:
|
||||
target_group_data.children.push_back(node);
|
||||
break;
|
||||
case ShiftDirection::Down:
|
||||
case ShiftDirection::Left:
|
||||
case ShiftDirection::Right:
|
||||
target_group_data.children.push_front(node);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// this might cause the target group to get swallowed and invalidate its pointers,
|
||||
// so its done after touching it.
|
||||
parent->removeChild(node, true);
|
||||
common_parent->recalcSizePosRecursive();
|
||||
return;
|
||||
}
|
||||
noentergroup:
|
||||
|
||||
// break out of group
|
||||
Hy3Node* breakout_origin = node->parent;
|
||||
target_group = breakout_origin->parent;
|
||||
// break parents until we reach one going the shift direction or nullptr
|
||||
while (target_group != nullptr
|
||||
&& !(((target_group->data.as_group.layout == Hy3GroupLayout::SplitH
|
||||
|| target_group->data.as_group.layout == Hy3GroupLayout::Tabbed)
|
||||
&& (direction == ShiftDirection::Left || direction == ShiftDirection::Right))
|
||||
|| (target_group->data.as_group.layout == Hy3GroupLayout::SplitV
|
||||
&& (direction == ShiftDirection::Up || direction == ShiftDirection::Down))))
|
||||
{
|
||||
breakout_origin = target_group;
|
||||
target_group = breakout_origin->parent;
|
||||
}
|
||||
|
||||
if (target_group != nullptr) {
|
||||
auto* parent = node->parent;
|
||||
node->parent = target_group;
|
||||
node->size_ratio = 1.0;
|
||||
|
||||
bool insert_after = direction == ShiftDirection::Right || direction == ShiftDirection::Down;
|
||||
|
||||
Hy3GroupData& target_group_data = target_group->data.as_group;
|
||||
|
||||
auto iter = std::find(target_group_data.children.begin(), target_group_data.children.end(), breakout_origin);
|
||||
if (insert_after) {
|
||||
iter = std::next(iter);
|
||||
}
|
||||
|
||||
target_group_data.children.insert(iter, node);
|
||||
parent->removeChild(node, true);
|
||||
target_group->recalcSizePosRecursive();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Hy3Node* Hy3Layout::findCommonParentNode(Hy3Node& a, Hy3Node& b) {
|
||||
Hy3Node* last_node = nullptr;
|
||||
Hy3Node* searcher = &a;
|
||||
|
||||
while (searcher != nullptr) {
|
||||
if (searcher->data.type == Hy3NodeData::Group) {
|
||||
for (auto child: searcher->data.as_group.children) {
|
||||
if (last_node == child) continue; // dont rescan already scanned tree
|
||||
if (child == &b) return searcher;
|
||||
if (child->data.type == Hy3NodeData::Group && child->data.as_group.hasChild(&b)) {
|
||||
return searcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_node = searcher;
|
||||
searcher = searcher->parent;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -12,14 +12,32 @@ enum class Hy3GroupLayout {
|
|||
Tabbed,
|
||||
};
|
||||
|
||||
enum class ShiftDirection {
|
||||
Left,
|
||||
Up,
|
||||
Down,
|
||||
Right,
|
||||
};
|
||||
|
||||
struct Hy3GroupData {
|
||||
Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
|
||||
std::list<Hy3Node*> children;
|
||||
|
||||
bool hasChild(Hy3Node* child);
|
||||
|
||||
Hy3GroupData(Hy3GroupLayout layout);
|
||||
|
||||
private:
|
||||
Hy3GroupData(Hy3GroupData&&) = default;
|
||||
Hy3GroupData(const Hy3GroupData&) = default;
|
||||
|
||||
friend class Hy3NodeData;
|
||||
};
|
||||
|
||||
struct Hy3NodeData {
|
||||
void swapNodeData(Hy3Node& a, Hy3Node& b);
|
||||
|
||||
class Hy3NodeData {
|
||||
public:
|
||||
enum { Group, Window } type;
|
||||
union {
|
||||
Hy3GroupData as_group;
|
||||
|
@ -29,12 +47,19 @@ struct Hy3NodeData {
|
|||
bool operator==(const Hy3NodeData&) const;
|
||||
|
||||
Hy3NodeData();
|
||||
Hy3NodeData(CWindow* window);
|
||||
Hy3NodeData(Hy3GroupData group);
|
||||
~Hy3NodeData();
|
||||
Hy3NodeData(CWindow*);
|
||||
Hy3NodeData(Hy3GroupLayout);
|
||||
Hy3NodeData& operator=(CWindow*);
|
||||
Hy3NodeData& operator=(Hy3GroupLayout);
|
||||
|
||||
//private: - I give up, C++ wins
|
||||
Hy3NodeData(Hy3GroupData);
|
||||
Hy3NodeData(const Hy3NodeData&);
|
||||
Hy3NodeData(Hy3NodeData&&);
|
||||
Hy3NodeData& operator=(const Hy3NodeData&);
|
||||
~Hy3NodeData();
|
||||
|
||||
friend void swapNodeData(Hy3Node&, Hy3Node&);
|
||||
};
|
||||
|
||||
struct Hy3Node {
|
||||
|
@ -48,6 +73,11 @@ struct Hy3Node {
|
|||
Hy3Layout* layout = nullptr;
|
||||
|
||||
void recalcSizePosRecursive(bool force = false);
|
||||
// remove a child node, returns true on success.
|
||||
// fails if not a group
|
||||
// if only a single child node remains && childSwallows, replace this group with said child.
|
||||
// if no children remain, remove this node from its parent.
|
||||
bool removeChild(Hy3Node* child, bool childSwallows = false);
|
||||
|
||||
bool operator==(const Hy3Node&) const;
|
||||
};
|
||||
|
@ -72,9 +102,12 @@ public:
|
|||
virtual void onEnable();
|
||||
virtual void onDisable();
|
||||
|
||||
void makeGroupOn(CWindow*, Hy3GroupLayout);
|
||||
void shiftWindow(CWindow*, ShiftDirection);
|
||||
|
||||
Hy3Node* findCommonParentNode(Hy3Node&, Hy3Node&);
|
||||
|
||||
private:
|
||||
// std::list is used over std::vector because it does not invalidate references
|
||||
// when mutated.
|
||||
std::list<Hy3Node> nodes;
|
||||
CWindow* lastActiveWindow = nullptr;
|
||||
|
||||
|
|
48
src/main.cpp
48
src/main.cpp
|
@ -10,28 +10,44 @@ APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
|||
return HYPRLAND_API_VERSION;
|
||||
}
|
||||
|
||||
void splith(std::string) {
|
||||
SLayoutMessageHeader header;
|
||||
header.pWindow = g_pCompositor->m_pLastWindow;
|
||||
// return a window if a window action makes sense now
|
||||
CWindow* window_for_action() {
|
||||
if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return nullptr;
|
||||
|
||||
if (!header.pWindow) return;
|
||||
auto* window = g_pCompositor->m_pLastWindow;
|
||||
|
||||
const auto workspace = g_pCompositor->getWorkspaceByID(header.pWindow->m_iWorkspaceID);
|
||||
if (workspace->m_bHasFullscreenWindow) return;
|
||||
if (!window) return nullptr;
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "splith");
|
||||
const auto workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID);
|
||||
if (workspace->m_bHasFullscreenWindow) return nullptr;
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
void splitv(std::string) {
|
||||
SLayoutMessageHeader header;
|
||||
header.pWindow = g_pCompositor->m_pLastWindow;
|
||||
void dispatch_makegroup(std::string arg) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
|
||||
if (!header.pWindow) return;
|
||||
if (arg == "h") {
|
||||
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitH);
|
||||
} else if (arg == "v") {
|
||||
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitV);
|
||||
}
|
||||
}
|
||||
|
||||
const auto workspace = g_pCompositor->getWorkspaceByID(header.pWindow->m_iWorkspaceID);
|
||||
if (workspace->m_bHasFullscreenWindow) return;
|
||||
void dispatch_movewindow(std::string arg) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "splitv");
|
||||
if (arg == "l") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Left);
|
||||
} else if (arg == "u") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Up);
|
||||
} else if (arg == "d") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Down);
|
||||
} else if (arg == "r") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Right);
|
||||
}
|
||||
}
|
||||
|
||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||
|
@ -40,8 +56,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
g_Hy3Layout = std::make_unique<Hy3Layout>();
|
||||
HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get());
|
||||
|
||||
HyprlandAPI::addDispatcher(PHANDLE, "splith", splith);
|
||||
HyprlandAPI::addDispatcher(PHANDLE, "splitv", splitv);
|
||||
HyprlandAPI::addDispatcher(PHANDLE, "hy3_makegroup", dispatch_makegroup);
|
||||
HyprlandAPI::addDispatcher(PHANDLE, "hy3_movewindow", dispatch_movewindow);
|
||||
|
||||
return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"};
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue