cd3fd91b3bf93cc5a4a3063f33967683f9578852

Author
outfoxxed <outfoxxed@outfoxxed.me>
Committer
outfoxxed <outfoxxed@outfoxxed.me>
Date

Message

Window containment/swallowing MVP

Diff

  1diff --git a/README.md b/README.md
  2index 802f9442020d73473bb0526542a7a70fe130f73d..c5cfe5410393dc68fdd993481b53a83e5f26a568 100644
  3--- a/README.md
  4+++ b/README.md
  5@@ -116,6 +116,7 @@ plugin {
  6    - `prioritize_hovered` - prioritize the tab group under the mouse when multiple are stacked. use the lowest group if none is under the mouse.
  7    - `require_hovered` - affect the tab group under the mouse. do nothing if none are hovered.
  8    - `wrap` - wrap to the opposite size of the tab bar if moving off the end
  9+ - `hy3:setswallow, <true | false | toggle>` - set the containing node's window swallow state
 10  - `hy3:debugnodes` - print the node tree into the hyprland log
 11 
 12 ## Installing
 13diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp
 14index 525379ea9cb6eb2d6c634a2ef216906dbb757bf8..59c85df68d795466bfa212220a17d2a068427b26 100644
 15--- a/src/Hy3Layout.cpp
 16+++ b/src/Hy3Layout.cpp
 17@@ -16,6 +16,59 @@ std::unique_ptr<HOOK_CALLBACK_FN> urgentHookPtr
 18 std::unique_ptr<HOOK_CALLBACK_FN> tickHookPtr
 19     = std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::tickHook);
 20 
 21+bool performContainment(Hy3Node& node, bool contained, CWindow* window) {
 22+	if (node.data.type == Hy3NodeType::Group) {
 23+		auto& group = node.data.as_group;
 24+		contained |= group.containment;
 25+
 26+		auto iter = node.data.as_group.children.begin();
 27+		while (iter != node.data.as_group.children.end()) {
 28+			switch ((*iter)->data.type) {
 29+			case Hy3NodeType::Group: return performContainment(**iter, contained, window);
 30+			case Hy3NodeType::Window:
 31+				if (contained) {
 32+					auto wpid = (*iter)->data.as_window->getPID();
 33+					auto ppid = getPPIDof(window->getPID());
 34+					while (ppid > 10) { // `> 10` yoinked from HL swallow
 35+						if (ppid == wpid) {
 36+							node.layout->nodes.push_back({
 37+							    .parent = &node,
 38+							    .data = window,
 39+							    .workspace_id = node.workspace_id,
 40+							    .layout = node.layout,
 41+							});
 42+
 43+							auto& child_node = node.layout->nodes.back();
 44+
 45+							group.children.insert(std::next(iter), &child_node);
 46+							child_node.markFocused();
 47+							node.recalcSizePosRecursive();
 48+
 49+							return true;
 50+						}
 51+
 52+						ppid = getPPIDof(ppid);
 53+					}
 54+				}
 55+			}
 56+
 57+			iter = std::next(iter);
 58+		}
 59+	}
 60+
 61+	return false;
 62+}
 63+
 64+void Hy3Layout::onWindowCreated(CWindow* window) {
 65+	for (auto& node: this->nodes) {
 66+		if (node.parent == nullptr && performContainment(node, false, window)) {
 67+			return;
 68+		}
 69+	}
 70+
 71+	IHyprLayout::onWindowCreated(window);
 72+}
 73+
 74 void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
 75 	if (window->m_bIsFloating) return;
 76 
 77@@ -938,6 +991,18 @@ hastab:
 78 	tab_node->recalcSizePosRecursive();
 79 }
 80 
 81+void Hy3Layout::setNodeSwallow(int workspace, SetSwallowOption option) {
 82+	auto* node = this->getWorkspaceFocusedNode(workspace);
 83+	if (node == nullptr || node->parent == nullptr) return;
 84+
 85+	auto* containment = &node->parent->data.as_group.containment;
 86+	switch (option) {
 87+	case SetSwallowOption::NoSwallow: *containment = false;
 88+	case SetSwallowOption::Swallow: *containment = true;
 89+	case SetSwallowOption::Toggle: *containment = !*containment;
 90+	}
 91+}
 92+
 93 void Hy3Layout::killFocusedNode(int workspace) {
 94 	if (g_pCompositor->m_pLastWindow != nullptr && g_pCompositor->m_pLastWindow->m_bIsFloating) {
 95 		g_pCompositor->closeWindow(g_pCompositor->m_pLastWindow);
 96diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp
 97index 1a352493531d3a45aef8ed64ca5ca5c77c24144b..b77ed7a0813c83d0ed81b385cd69f6d3a3c1c419 100644
 98--- a/src/Hy3Layout.hpp
 99+++ b/src/Hy3Layout.hpp
100@@ -44,8 +44,15 @@ enum class TabFocusMousePriority {
101 	Require,
102 };
103 
104+enum class SetSwallowOption {
105+	NoSwallow,
106+	Swallow,
107+	Toggle,
108+};
109+
110 class Hy3Layout: public IHyprLayout {
111 public:
112+	virtual void onWindowCreated(CWindow*);
113 	virtual void onWindowCreatedTiling(CWindow*);
114 	virtual void onWindowRemovedTiling(CWindow*);
115 	virtual void onWindowFocusChange(CWindow*);
116@@ -75,6 +82,7 @@ public:
117 	void shiftFocus(int workspace, ShiftDirection, bool visible);
118 	void changeFocus(int workspace, FocusShift);
119 	void focusTab(int workspace, TabFocus target, TabFocusMousePriority, bool wrap_scroll, int index);
120+	void setNodeSwallow(int workspace, SetSwallowOption);
121 	void killFocusedNode(int workspace);
122 
123 	bool shouldRenderSelected(CWindow*);
124diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp
125index 85b105502b0993965c133df2a69e8b207608e0b8..97436e3cd972ea49c053330264db590c7b13e135 100644
126--- a/src/Hy3Node.cpp
127+++ b/src/Hy3Node.cpp
128@@ -525,6 +525,15 @@ std::string Hy3Node::debugNode() {
129 
130 		buf << "] size ratio: ";
131 		buf << this->size_ratio;
132+
133+		if (this->data.as_group.ephemeral) {
134+			buf << ", ephemeral";
135+		}
136+
137+		if (this->data.as_group.containment) {
138+			buf << ", containment";
139+		}
140+
141 		for (auto* child: this->data.as_group.children) {
142 			buf << "\n|-";
143 			if (child == nullptr) {
144diff --git a/src/Hy3Node.hpp b/src/Hy3Node.hpp
145index 59107bb6c17fac56a0e3b1937104689c8e303667..e98f2cb51437a50ee51d4a38d62ff1c87e9f7788 100644
146--- a/src/Hy3Node.hpp
147+++ b/src/Hy3Node.hpp
148@@ -27,6 +27,7 @@ struct Hy3GroupData {
149 	bool group_focused = true;
150 	Hy3Node* focused_child = nullptr;
151 	bool ephemeral = false;
152+	bool containment = false;
153 	Hy3TabGroup* tab_bar = nullptr;
154 
155 	Hy3GroupData(Hy3GroupLayout layout);
156diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp
157index 1480a2432e7869b47bca904617f0fac8412e8514..6abb0ffb117d6c6f842905fc7c0a6a9529bb31a4 100644
158--- a/src/dispatchers.cpp
159+++ b/src/dispatchers.cpp
160@@ -126,6 +126,22 @@ void dispatch_focustab(std::string value) {
161 	g_Hy3Layout->focusTab(workspace, focus, mouse, wrap_scroll, index);
162 }
163 
164+void dispatch_setswallow(std::string arg) {
165+	int workspace = workspace_for_action();
166+	if (workspace == -1) return;
167+
168+	SetSwallowOption option;
169+	if (arg == "true") {
170+		option = SetSwallowOption::Swallow;
171+	} else if (arg == "false") {
172+		option = SetSwallowOption::NoSwallow;
173+	} else if (arg == "toggle") {
174+		option = SetSwallowOption::Toggle;
175+	} else return;
176+
177+	g_Hy3Layout->setNodeSwallow(workspace, option);
178+}
179+
180 void dispatch_killactive(std::string value) {
181 	int workspace = workspace_for_action();
182 	if (workspace == -1) return;
183@@ -151,6 +167,7 @@ void registerDispatchers() {
184 	HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow);
185 	HyprlandAPI::addDispatcher(PHANDLE, "hy3:changefocus", dispatch_changefocus);
186 	HyprlandAPI::addDispatcher(PHANDLE, "hy3:focustab", dispatch_focustab);
187+	HyprlandAPI::addDispatcher(PHANDLE, "hy3:setswallow", dispatch_setswallow);
188 	HyprlandAPI::addDispatcher(PHANDLE, "hy3:killactive", dispatch_killactive);
189 	HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug);
190 }