1435be18d41008fc1d52e16316faa49b752ecbbe

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

Message

Refactor

Diff

This diff is truncated to protect this page.

  1diff --git a/CMakeLists.txt b/CMakeLists.txt
  2index 5ad5ce0cfeaecc65293900920107a56ee1928868..d886f50a8d00eb7ab5d2e6a046555b2fd004fea1 100644
  3--- a/CMakeLists.txt
  4+++ b/CMakeLists.txt
  5@@ -15,7 +15,9 @@ pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo)
  6 
  7 add_library(hy3 SHARED
  8 	src/main.cpp
  9+	src/dispatchers.cpp
 10 	src/Hy3Layout.cpp
 11+	src/Hy3Node.cpp
 12 	src/TabGroup.cpp
 13 	src/SelectionHook.cpp
 14 )
 15diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp
 16index a73796d897d6fd3021409b2a26f980573bc7036d..02f1e33dc1c435db573141e58dc6cc4379563f37 100644
 17--- a/src/Hy3Layout.cpp
 18+++ b/src/Hy3Layout.cpp
 19@@ -1,1487 +1,1170 @@
 20-#include "Hy3Layout.hpp"
 21-#include "globals.hpp"
 22-#include "SelectionHook.hpp"
 23+#include <sstream>
 24 
 25 #include <hyprland/src/Compositor.hpp>
 26 #include <hyprland/src/plugins/PluginAPI.hpp>
 27 
 28-#include <sstream>
 29-
 30-void errorNotif() {
 31-	HyprlandAPI::addNotificationV2(
 32-	    PHANDLE,
 33-	    {
 34-	        {"text", "Something has gone very wrong. Check the log for details."},
 35-	        {"time", (uint64_t) 10000},
 36-	        {"color", CColor(1.0, 0.0, 0.0, 1.0)},
 37-	        {"icon", ICON_ERROR},
 38-	    }
 39-	);
 40-}
 41-
 42-Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {}
 43+#include "globals.hpp"
 44+#include "Hy3Layout.hpp"
 45+#include "SelectionHook.hpp"
 46 
 47-Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) {
 48-	this->layout = from.layout;
 49-	this->children = std::move(from.children);
 50-	this->group_focused = from.group_focused;
 51-	this->focused_child = from.focused_child;
 52-	from.focused_child = nullptr;
 53-	this->tab_bar = from.tab_bar;
 54-	from.tab_bar = nullptr;
 55-}
 56+std::unique_ptr<HOOK_CALLBACK_FN> renderHookPtr
 57+    = std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::renderHook);
 58+std::unique_ptr<HOOK_CALLBACK_FN> windowTitleHookPtr
 59+    = std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::windowGroupUpdateRecursiveHook);
 60+std::unique_ptr<HOOK_CALLBACK_FN> urgentHookPtr
 61+    = std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::windowGroupUrgentHook);
 62+std::unique_ptr<HOOK_CALLBACK_FN> tickHookPtr
 63+    = std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::tickHook);
 64 
 65-Hy3GroupData::~Hy3GroupData() {
 66-	if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy();
 67-}
 68+void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
 69+	if (window->m_bIsFloating) return;
 70 
 71-Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {}
 72+	auto* existing = this->getNodeFromWindow(window);
 73+	if (existing != nullptr) {
 74+		Debug::log(
 75+		    WARN,
 76+		    "Attempted to add a window(%p) that is already tiled(as %p) to the "
 77+		    "layout",
 78+		    window,
 79+		    existing
 80+		);
 81+		return;
 82+	}
 83 
 84-Hy3NodeData::Hy3NodeData(CWindow* window): type(Hy3NodeType::Window) { this->as_window = window; }
 85+	auto* monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID);
 86 
 87-Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeType::Group) {
 88-	new (&this->as_group) Hy3GroupData(std::move(group));
 89-}
 90+	Hy3Node* opening_into;
 91+	Hy3Node* opening_after = nullptr;
 92 
 93-Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {}
 94+	if (monitor->activeWorkspace != -1) {
 95+		auto* root = this->getWorkspaceRootGroup(monitor->activeWorkspace);
 96 
 97-Hy3NodeData::~Hy3NodeData() {
 98-	switch (this->type) {
 99-	case Hy3NodeType::Window: break;
100-	case Hy3NodeType::Group:
101-		this->as_group.~Hy3GroupData();
102+		if (root != nullptr) {
103+			opening_after = root->getFocusedNode();
104 
105-		// who ever thought calling the dtor after a move was a good idea?
106-		this->type = Hy3NodeType::Window;
107-		break;
108+			// opening_after->parent cannot be nullptr
109+			if (opening_after == root) {
110+				opening_after = root->data.as_group.focused_child;
111+			}
112+		}
113 	}
114-}
115 
116-Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) {
117-	Debug::log(
118-	    LOG,
119diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp
120index bc4e6c6e4941d96dc9be5051f089b956f24bb3d4..d6d4186a3dd2128a53d03be764a778d86f88aff2 100644
121--- a/src/Hy3Layout.hpp
122+++ b/src/Hy3Layout.hpp
123@@ -1,24 +1,13 @@
124 #pragma once
125 
126-#include <hyprland/src/layout/IHyprLayout.hpp>
127-#include <list>
128-
129-struct Hy3Node;
130-#include "TabGroup.hpp"
131-
132 class Hy3Layout;
133-struct Hy3Node;
134 
135-enum class Hy3GroupLayout {
136-	SplitH,
137-	SplitV,
138-	Tabbed,
139-};
140+#include <list>
141 
142-enum class Hy3NodeType {
143-  Window,
144-	Group,
145-};
146+#include <hyprland/src/layout/IHyprLayout.hpp>
147+
148+#include "Hy3Node.hpp"
149+#include "TabGroup.hpp"
150 
151 enum class ShiftDirection {
152 	Left,
153@@ -49,93 +38,6 @@ enum class TabFocusMousePriority {
154 	Require,
155 };
156 
157-struct Hy3GroupData {
158-	Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
159-	std::list<Hy3Node*> children;
160-	bool group_focused = true;
161-	Hy3Node* focused_child = nullptr;
162-	Hy3TabGroup* tab_bar = nullptr;
163-
164-	bool hasChild(Hy3Node* child);
165-
166-	Hy3GroupData(Hy3GroupLayout layout);
167-	~Hy3GroupData();
168-
169-private:
170-	Hy3GroupData(Hy3GroupData&&);
171-	Hy3GroupData(const Hy3GroupData&) = delete;
172-
173-	friend class Hy3NodeData;
174-};
175-
176-class Hy3NodeData {
177-public:
178-	Hy3NodeType type;
179-	union {
180-		Hy3GroupData as_group;
181-		CWindow* as_window;
182-	};
183-
184-	bool operator==(const Hy3NodeData&) const;
185-
186-	Hy3NodeData();
187-	~Hy3NodeData();
188-	Hy3NodeData(CWindow* window);
189-	Hy3NodeData(Hy3GroupLayout layout);
190-	Hy3NodeData& operator=(CWindow*);
191-	Hy3NodeData& operator=(Hy3GroupLayout);
192-
193-	// private: - I give up, C++ wins
194-	Hy3NodeData(Hy3GroupData);
195-	Hy3NodeData(Hy3NodeData&&);
196-	Hy3NodeData& operator=(Hy3NodeData&&);
197-};
198-
199-struct Hy3Node {
200-	Hy3Node* parent = nullptr;
201-	Hy3NodeData data;
202-	Vector2D position;
203-	Vector2D size;
204-	Vector2D gap_pos_offset;
205-	Vector2D gap_size_offset;
206-	float size_ratio = 1.0;
207-	int workspace_id = -1;
208-	bool hidden = false;
209-	bool valid = true;
210-	Hy3Layout* layout = nullptr;
211-
212-	void recalcSizePosRecursive(bool no_animation = false);
213-	std::string debugNode();
214-	void markFocused();
215-	void focus();
216-	bool focusWindow();
217-	void raiseToTop();
218-	Hy3Node* getFocusedNode();
219-	void updateDecos();
220-	void setHidden(bool);
221-	void updateTabBar(bool no_animation = false);
222-	void updateTabBarRecursive();
223diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp
224new file mode 100644
225index 0000000000000000000000000000000000000000..7264cdf55c73d7d191f4993f082b634dad665a09
226--- /dev/null
227+++ b/src/Hy3Node.cpp
228@@ -0,0 +1,680 @@
229+#include <hyprland/src/Compositor.hpp>
230+#include <hyprland/src/plugins/PluginAPI.hpp>
231+
232+#include "globals.hpp"
233+#include "Hy3Node.hpp"
234+
235+// Hy3GroupData //
236+
237+Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {}
238+
239+Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) {
240+	this->layout = from.layout;
241+	this->children = std::move(from.children);
242+	this->group_focused = from.group_focused;
243+	this->focused_child = from.focused_child;
244+	from.focused_child = nullptr;
245+	this->tab_bar = from.tab_bar;
246+	from.tab_bar = nullptr;
247+}
248+
249+Hy3GroupData::~Hy3GroupData() {
250+	if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy();
251+}
252+
253+bool Hy3GroupData::hasChild(Hy3Node* node) {
254+	Debug::log(LOG, "Searching for child %p of %p", this, node);
255+	for (auto child: this->children) {
256+		if (child == node) return true;
257+
258+		if (child->data.type == Hy3NodeType::Group) {
259+			if (child->data.as_group.hasChild(node)) return true;
260+		}
261+	}
262+
263+	return false;
264+}
265+
266+// Hy3NodeData //
267+
268+Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {}
269+
270+Hy3NodeData::Hy3NodeData(CWindow* window): type(Hy3NodeType::Window) { this->as_window = window; }
271+
272+Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {}
273+
274+Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeType::Group) {
275+	new (&this->as_group) Hy3GroupData(std::move(group));
276+}
277+
278+Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) {
279+	Debug::log(
280+	    LOG,
281+	    "Move CTor type matches? %d is group? %d",
282+	    this->type == from.type,
283+	    this->type == Hy3NodeType::Group
284+	);
285+
286+	switch (from.type) {
287+	case Hy3NodeType::Window: this->as_window = from.as_window; break;
288+	case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break;
289+	}
290+}
291+
292+Hy3NodeData::~Hy3NodeData() {
293+	switch (this->type) {
294+	case Hy3NodeType::Window: break;
295+	case Hy3NodeType::Group:
296+		this->as_group.~Hy3GroupData();
297+
298+		// who ever thought calling the dtor after a move was a good idea?
299+		this->type = Hy3NodeType::Window;
300+		break;
301+	}
302+}
303+
304+Hy3NodeData& Hy3NodeData::operator=(CWindow* window) {
305+	*this = Hy3NodeData(window);
306+
307+	return *this;
308+}
309+
310+Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) {
311+	*this = Hy3NodeData(layout);
312+
313+	return *this;
314+}
315+
316+Hy3NodeData& Hy3NodeData::operator=(Hy3NodeData&& from) {
317+	Debug::log(
318+	    LOG,
319+	    "operator= type matches? %d is group? %d",
320+	    this->type == from.type,
321+	    this->type == Hy3NodeType::Group
322+	);
323+
324+	if (this->type == Hy3NodeType::Group) {
325+		this->as_group.~Hy3GroupData();
326+	}
327+
328diff --git a/src/Hy3Node.hpp b/src/Hy3Node.hpp
329new file mode 100644
330index 0000000000000000000000000000000000000000..449486a4159bd65f3001400d4f71a5dc07fc5acf
331--- /dev/null
332+++ b/src/Hy3Node.hpp
333@@ -0,0 +1,112 @@
334+#pragma once
335+
336+struct Hy3Node;
337+enum class Hy3GroupLayout;
338+
339+#include <list>
340+
341+#include <hyprland/src/Window.hpp>
342+
343+#include "Hy3Layout.hpp"
344+#include "TabGroup.hpp"
345+
346+enum class Hy3GroupLayout {
347+	SplitH,
348+	SplitV,
349+	Tabbed,
350+};
351+
352+enum class Hy3NodeType {
353+	Window,
354+	Group,
355+};
356+
357+struct Hy3GroupData {
358+	Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
359+	std::list<Hy3Node*> children;
360+	bool group_focused = true;
361+	Hy3Node* focused_child = nullptr;
362+	Hy3TabGroup* tab_bar = nullptr;
363+
364+	Hy3GroupData(Hy3GroupLayout layout);
365+	~Hy3GroupData();
366+
367+	bool hasChild(Hy3Node* child);
368+
369+private:
370+	Hy3GroupData(Hy3GroupData&&);
371+	Hy3GroupData(const Hy3GroupData&) = delete;
372+
373+	friend class Hy3NodeData;
374+};
375+
376+class Hy3NodeData {
377+public:
378+	Hy3NodeType type;
379+	union {
380+		Hy3GroupData as_group;
381+		CWindow* as_window;
382+	};
383+
384+	Hy3NodeData();
385+	Hy3NodeData(CWindow* window);
386+	Hy3NodeData(Hy3GroupLayout layout);
387+	~Hy3NodeData();
388+
389+	Hy3NodeData& operator=(CWindow*);
390+	Hy3NodeData& operator=(Hy3GroupLayout);
391+
392+	bool operator==(const Hy3NodeData&) const;
393+
394+	// private: - I give up, C++ wins
395+	Hy3NodeData(Hy3GroupData);
396+	Hy3NodeData(Hy3NodeData&&);
397+	Hy3NodeData& operator=(Hy3NodeData&&);
398+};
399+
400+struct Hy3Node {
401+	Hy3Node* parent = nullptr;
402+	Hy3NodeData data;
403+	Vector2D position;
404+	Vector2D size;
405+	Vector2D gap_pos_offset;
406+	Vector2D gap_size_offset;
407+	float size_ratio = 1.0;
408+	int workspace_id = -1;
409+	bool hidden = false;
410+	Hy3Layout* layout = nullptr;
411+
412+	bool operator==(const Hy3Node&) const;
413+
414+	void focus();
415+	bool focusWindow();
416+	void markFocused();
417+	void raiseToTop();
418+	Hy3Node* getFocusedNode();
419+	bool isIndirectlyFocused();
420+
421+	void recalcSizePosRecursive(bool no_animation = false);
422+	void updateTabBar(bool no_animation = false);
423+	void updateTabBarRecursive();
424+	void updateDecos();
425+
426+	std::string getTitle();
427+	bool isUrgent();
428+	void setHidden(bool);
429+
430+	Hy3Node* findNodeForTabGroup(Hy3TabGroup&);
431+	void appendAllWindows(std::vector<CWindow*>&);
432+	std::string debugNode();
433diff --git a/src/SelectionHook.cpp b/src/SelectionHook.cpp
434index bbf63ae1a4a7c41410ffdad35850ef11f94b1a98..a99f0b6fa7806ecfe79ce6976c903944612cab77 100644
435--- a/src/SelectionHook.cpp
436+++ b/src/SelectionHook.cpp
437@@ -1,7 +1,8 @@
438-#include "globals.hpp"
439 #include <hyprland/src/Compositor.hpp>
440 #include <hyprland/src/plugins/PluginAPI.hpp>
441 
442+#include "globals.hpp"
443+
444 namespace selection_hook {
445 inline CFunctionHook* g_LastSelectionHook = nullptr;
446 
447diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp
448index e0942eb013a32086c90c3e4ee6e530feb135e281..89fd18ceafd3fe9c78066c891ed677abe950ac52 100644
449--- a/src/TabGroup.cpp
450+++ b/src/TabGroup.cpp
451@@ -1,7 +1,3 @@
452-#include "TabGroup.hpp"
453-#include "globals.hpp"
454-#include "Hy3Layout.hpp"
455-
456 #include <cairo/cairo.h>
457 #include <hyprland/src/Compositor.hpp>
458 #include <hyprland/src/helpers/Color.hpp>
459@@ -10,6 +6,10 @@
460 #include <pango/pangocairo.h>
461 #include <pixman.h>
462 
463+#include "globals.hpp"
464+#include "Hy3Layout.hpp"
465+#include "TabGroup.hpp"
466+
467 Hy3TabBarEntry::Hy3TabBarEntry(Hy3TabBar& tab_bar, Hy3Node& node): tab_bar(tab_bar), node(node) {
468 	this->focused.create(
469 	    AVARTYPE_FLOAT,
470diff --git a/src/TabGroup.hpp b/src/TabGroup.hpp
471index 3e016febcdfe86be435d6a2bd240b2e1f34ffa58..04952762464e522aed0540cecce0d72c3a37480c 100644
472--- a/src/TabGroup.hpp
473+++ b/src/TabGroup.hpp
474@@ -1,14 +1,15 @@
475 #pragma once
476 
477-#include <hyprland/src/Compositor.hpp>
478+class Hy3TabGroup;
479+class Hy3TabBar;
480+
481 #include <list>
482 #include <memory>
483 #include <vector>
484 
485-class Hy3TabGroup;
486-class Hy3TabBar;
487+#include <hyprland/src/render/Texture.hpp>
488 
489-#include "Hy3Layout.hpp"
490+#include "Hy3Node.hpp"
491 
492 struct Hy3TabBarEntry {
493 	std::string window_title;
494diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp
495new file mode 100644
496index 0000000000000000000000000000000000000000..5937bd79ca687c0f307a660f7b81dd55a558e370
497--- /dev/null
498+++ b/src/dispatchers.cpp
499@@ -0,0 +1,147 @@
500+#include <optional>
501+
502+#include <hyprland/src/Compositor.hpp>
503+#include <hyprland/src/plugins/PluginAPI.hpp>
504+
505+#include "dispatchers.hpp"
506+#include "globals.hpp"
507+
508+int workspace_for_action() {
509+	if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1;
510+
511+	int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace;
512+
513+	if (workspace_id == -1) return -1;
514+	auto* workspace = g_pCompositor->getWorkspaceByID(workspace_id);
515+	if (workspace == nullptr) return -1;
516+	if (workspace->m_bHasFullscreenWindow) return -1;
517+
518+	return workspace_id;
519+}
520+
521+void dispatch_makegroup(std::string arg) {
522+	int workspace = workspace_for_action();
523+	if (workspace == -1) return;
524+
525+	if (arg == "h") {
526+		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitH);
527+	} else if (arg == "v") {
528+		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitV);
529+	} else if (arg == "tab") {
530+		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::Tabbed);
531+	} else if (arg == "opposite") {
532+		g_Hy3Layout->makeOppositeGroupOnWorkspace(workspace);
533+	}
534+}
535+
536+std::optional<ShiftDirection> parseShiftArg(std::string arg) {
537+	if (arg == "l" || arg == "left") return ShiftDirection::Left;
538+	else if (arg == "r" || arg == "right") return ShiftDirection::Right;
539+	else if (arg == "u" || arg == "up") return ShiftDirection::Up;
540+	else if (arg == "d" || arg == "down") return ShiftDirection::Down;
541+	else return {};
542+}
543+
544+void dispatch_movewindow(std::string value) {
545+	int workspace = workspace_for_action();
546+	if (workspace == -1) return;
547+
548+	auto args = CVarList(value);
549+
550+	if (auto shift = parseShiftArg(args[0])) {
551+		auto once = args[1] == "once";
552+		g_Hy3Layout->shiftWindow(workspace, shift.value(), once);
553+	}
554+}
555+
556+void dispatch_movefocus(std::string value) {
557+	int workspace = workspace_for_action();
558+	if (workspace == -1) return;
559+
560+	auto args = CVarList(value);
561+
562+	if (auto shift = parseShiftArg(args[0])) {
563+		g_Hy3Layout->shiftFocus(workspace, shift.value(), args[1] == "visible");
564+	}
565+}
566+
567+void dispatch_changefocus(std::string arg) {
568+	int workspace = workspace_for_action();
569+	if (workspace == -1) return;
570+
571+	if (arg == "top") g_Hy3Layout->changeFocus(workspace, FocusShift::Top);
572+	else if (arg == "bottom") g_Hy3Layout->changeFocus(workspace, FocusShift::Bottom);
573+	else if (arg == "raise") g_Hy3Layout->changeFocus(workspace, FocusShift::Raise);
574+	else if (arg == "lower") g_Hy3Layout->changeFocus(workspace, FocusShift::Lower);
575+	else if (arg == "tab") g_Hy3Layout->changeFocus(workspace, FocusShift::Tab);
576+	else if (arg == "tabnode") g_Hy3Layout->changeFocus(workspace, FocusShift::TabNode);
577+}
578+
579+void dispatch_focustab(std::string value) {
580+	int workspace = workspace_for_action();
581+	if (workspace == -1) return;
582+
583+	auto i = 0;
584+	auto args = CVarList(value);
585+
586+	TabFocus focus;
587+	auto mouse = TabFocusMousePriority::Ignore;
588+	bool wrap_scroll = false;
589+	int index = 0;
590+
591+	if (args[i] == "l" || args[i] == "left") focus = TabFocus::Left;
592+	else if (args[i] == "r" || args[i] == "right") focus = TabFocus::Right;
593+	else if (args[i] == "index") {
594+		i++;
595+		focus = TabFocus::Index;
596+		if (!isNumber(args[i])) return;
597+		index = std::stoi(args[i]);
598+		Debug::log(LOG, "Focus index '%s' -> %d, errno: %d", args[i].c_str(), index, errno);
599diff --git a/src/dispatchers.hpp b/src/dispatchers.hpp
600new file mode 100644
601index 0000000000000000000000000000000000000000..be858c46edb38a6e0de9c9901dc9045e050747a6
602--- /dev/null
603+++ b/src/dispatchers.hpp
604@@ -0,0 +1,3 @@
605+#pragma once
606+
607+void registerDispatchers();
608diff --git a/src/globals.hpp b/src/globals.hpp
609index ed7522fef96055c56c7629d770f20e9b46a4c2e5..856f1936573d8add87f9c7d1b76e9e2a9e515a97 100644
610--- a/src/globals.hpp
611+++ b/src/globals.hpp
612@@ -3,3 +3,15 @@
613 
614 inline HANDLE PHANDLE = nullptr;
615 inline std::unique_ptr<Hy3Layout> g_Hy3Layout;
616+
617+inline void errorNotif() {
618+	HyprlandAPI::addNotificationV2(
619+	    PHANDLE,
620+	    {
621+	        {"text", "Something has gone very wrong. Check the log for details."},
622+	        {"time", (uint64_t) 10000},
623+	        {"color", CColor(1.0, 0.0, 0.0, 1.0)},
624+	        {"icon", ICON_ERROR},
625+	    }
626+	);
627+}
628diff --git a/src/main.cpp b/src/main.cpp
629index 3b5b322aac93de0fba4453bdfcfba33a34b63690..85e91e5bc96965185fd034a72ce858af5805642d 100644
630--- a/src/main.cpp
631+++ b/src/main.cpp
632@@ -3,155 +3,12 @@
633 #include <hyprland/src/Compositor.hpp>
634 #include <hyprland/src/plugins/PluginAPI.hpp>
635 
636+#include "dispatchers.hpp"
637 #include "globals.hpp"
638 #include "SelectionHook.hpp"
639 
640 APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; }
641 
642-// return a window if a window action makes sense now
643-CWindow* window_for_action() {
644-	if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return nullptr;
645-
646-	auto* window = g_pCompositor->m_pLastWindow;
647-
648-	if (!window) return nullptr;
649-
650-	const auto workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID);
651-	if (workspace->m_bHasFullscreenWindow) return nullptr;
652-
653-	return window;
654-}
655-
656-int workspace_for_action() {
657-	if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1;
658-
659-	int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace;
660-
661-	if (workspace_id == -1) return -1;
662-	auto* workspace = g_pCompositor->getWorkspaceByID(workspace_id);
663-	if (workspace == nullptr) return -1;
664-	if (workspace->m_bHasFullscreenWindow) return -1;
665-
666-	return workspace_id;
667-}
668-
669-void dispatch_makegroup(std::string arg) {
670-	int workspace = workspace_for_action();
671-	if (workspace == -1) return;
672-
673-	if (arg == "h") {
674-		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitH);
675-	} else if (arg == "v") {
676-		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitV);
677-	} else if (arg == "tab") {
678-		g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::Tabbed);
679-	} else if (arg == "opposite") {
680-		g_Hy3Layout->makeOppositeGroupOnWorkspace(workspace);
681-	}
682-}
683-
684-std::optional<ShiftDirection> parseShiftArg(std::string arg) {
685-	if (arg == "l" || arg == "left") return ShiftDirection::Left;
686-	else if (arg == "r" || arg == "right") return ShiftDirection::Right;
687-	else if (arg == "u" || arg == "up") return ShiftDirection::Up;
688-	else if (arg == "d" || arg == "down") return ShiftDirection::Down;
689-	else return {};
690-}
691-
692-void dispatch_movewindow(std::string value) {
693-	int workspace = workspace_for_action();
694-	if (workspace == -1) return;
695-
696-	auto args = CVarList(value);
697-
698-	if (auto shift = parseShiftArg(args[0])) {
699-		auto once = args[1] == "once";
700-		g_Hy3Layout->shiftWindow(workspace, shift.value(), once);
701-	}
702-}
703-
704-void dispatch_movefocus(std::string value) {
705-	int workspace = workspace_for_action();
706-	if (workspace == -1) return;
707-
708-	auto args = CVarList(value);
709-
710-	if (auto shift = parseShiftArg(args[0])) {
711-		g_Hy3Layout->shiftFocus(workspace, shift.value(), args[1] == "visible");
712-	}
713-}
714-
715-void dispatch_changefocus(std::string arg) {
716-	int workspace = workspace_for_action();
717-	if (workspace == -1) return;
718-
719-	if (arg == "top") g_Hy3Layout->changeFocus(workspace, FocusShift::Top);
720-	else if (arg == "bottom") g_Hy3Layout->changeFocus(workspace, FocusShift::Bottom);
721-	else if (arg == "raise") g_Hy3Layout->changeFocus(workspace, FocusShift::Raise);
722-	else if (arg == "lower") g_Hy3Layout->changeFocus(workspace, FocusShift::Lower);
723-	else if (arg == "tab") g_Hy3Layout->changeFocus(workspace, FocusShift::Tab);
724-	else if (arg == "tabnode") g_Hy3Layout->changeFocus(workspace, FocusShift::TabNode);
725-}
726-
727-void dispatch_focustab(std::string value) {
728-	int workspace = workspace_for_action();
729-	if (workspace == -1) return;
730-
731-	auto i = 0;