8f56e6ea73db758152b117d507197c46c629afa8

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

Message

fixup: chase 0.55

Diff

This diff is truncated to protect this page.

  1diff --git a/README.md b/README.md
  2index d22016f103d5dfef0f3634ff8efd9fa7279c8e23..320d21567c3dc7da31ef423f9b79e63fd62a910a 100644
  3--- a/README.md
  4+++ b/README.md
  5@@ -387,3 +387,78 @@ plugin {
  6  - `hy3:equalize, [workspace]` - equalize window sizes in group
  7    - no argument: equalizes immediate siblings of the focused window
  8    - `workspace`: equalizes all windows across the entire workspace tree
  9+
 10+### Lua dispatchers
 11+
 12+When using Hyprland's Lua config, hy3 exposes dispatcher factories under `hl.plugin.hy3`.
 13+The returned functions can be passed to `hl.bind(...)`.
 14+
 15+```lua
 16+local hy3 = hl.plugin.hy3
 17+
 18+-- all factories return dispatcher functions and dispatchers return no values
 19+-- option tables are optional except for focus_tab
 20+
 21+hy3.make_group("h" | "v" | "tab" | "opposite", {
 22+	toggle = true | false,              -- default: false
 23+	ephemeral = true | false | "force", -- default: false
 24+})
 25+
 26+hy3.change_group("h" | "v" | "tab" | "untab" | "toggletab" | "opposite")
 27+
 28+hy3.set_ephemeral(true | false | "true" | "false")
 29+
 30+hy3.move_focus("l" | "r" | "u" | "d" | "left" | "right" | "up" | "down", {
 31+	visible = true | false, -- default: false
 32+	warp = true | false,    -- default: follows cursor:no_warps
 33+})
 34+
 35+hy3.toggle_focus_layer({
 36+	warp = true | false, -- default: true
 37+})
 38+
 39+hy3.warp_cursor()
 40+
 41+hy3.move_window("l" | "r" | "u" | "d" | "left" | "right" | "up" | "down", {
 42+	once = true | false,    -- default: false
 43+	visible = true | false, -- default: false
 44+})
 45+
 46+hy3.move_to_workspace("<workspace>", {
 47+	follow = true | false, -- default: false
 48+	warp = true | false,   -- default: follows cursor:no_warps when follow = true
 49+})
 50+
 51+hy3.change_focus("top" | "bottom" | "raise" | "lower" | "tab" | "tabnode")
 52+
 53+-- direction and index are mutually exclusive
 54+hy3.focus_tab({
 55+	direction = "l" | "r" | "left" | "right",
 56+	mouse = "ignore" | "prioritize_hovered" | "require_hovered", -- default: "ignore"
 57+	wrap = true | false, -- default: false
 58+})
 59+
 60+hy3.focus_tab({
 61+	index = <number>,
 62+	mouse = "ignore" | "prioritize_hovered" | "require_hovered", -- default: "ignore"
 63+	wrap = true | false, -- default: false
 64+})
 65+
 66+hy3.set_swallow(true | false | "true" | "false" | "toggle")
 67+
 68+hy3.kill_active()
 69+
 70+hy3.expand("expand" | "shrink" | "base" | "maximize" | "fullscreen", {
 71+	fullscreen = "" | "intermediate_maximize" | "fullscreen_maximize" | "maximize_only",
 72+})
 73+
 74+hy3.lock_tab(nil | "" | "toggle" | "lock" | "unlock")
 75+
 76+hy3.equalize({
 77+	scope = "" | "group" | "workspace", -- default: "group"
 78+	workspace = true | false,           -- overrides scope if present
 79+	recursive = true | false,           -- overrides workspace if present
 80+})
 81+
 82+hy3.debug_nodes()
 83+```
 84diff --git a/flake.lock b/flake.lock
 85index 09a4f55cfc35abe0dc596b28ea9e2a4ba17d2da2..09c670f232f96aa0c9879835e049e79366d130f3 100644
 86--- a/flake.lock
 87+++ b/flake.lock
 88@@ -20,11 +20,11 @@
 89         ]
 90       },
 91       "locked": {
 92-        "lastModified": 1774211390,
 93-        "narHash": "sha256-sTtAgCCaX8VNNZlQFACd3i1IQ+DB0Wf3COgiFS152ds=",
 94+        "lastModified": 1777499565,
 95+        "narHash": "sha256-nU55VWk99Pn1QzQDDjFISocC4SgDZ3Xp+zb6ji3JclM=",
 96         "owner": "hyprwm",
 97         "repo": "aquamarine",
 98-        "rev": "f62a4dbfa4e5584f14ad4c62afedf6e4b433cf70",
 99+        "rev": "813c1e8981893c11e118b19c125d6bc282f51765",
100         "type": "github"
101       },
102       "original": {
103@@ -87,11 +87,11 @@
104         ]
105       },
106       "locked": {
107-        "lastModified": 1772461003,
108-        "narHash": "sha256-pVICsV7FtcEeVwg5y/LFh3XFUkVJninm/P1j/JHzEbM=",
109+        "lastModified": 1776511930,
110+        "narHash": "sha256-fCpwFiTW0rT7oKJqr3cqHMnkwypSwQKpbtUEtxdkgrM=",
111         "owner": "hyprwm",
112         "repo": "hyprcursor",
113-        "rev": "b62396457b9cfe2ebf24fe05404b09d2a40f8ed7",
114+        "rev": "39435900785d0c560c6ae8777d29f28617d031ef",
115         "type": "github"
116       },
117       "original": {
118@@ -116,11 +116,11 @@
119         ]
120       },
121       "locked": {
122-        "lastModified": 1772461523,
123-        "narHash": "sha256-mI6A51do+hEUzeJKk9YSWfVHdI/SEEIBi2tp5Whq5mI=",
124+        "lastModified": 1776426399,
125+        "narHash": "sha256-RUESLKNikIeEq9ymGJ6nmcDXiSFQpUW1IhJ245nL3xM=",
126         "owner": "hyprwm",
127         "repo": "hyprgraphics",
128-        "rev": "7d63c04b4a2dd5e59ef943b4b143f46e713df804",
129+        "rev": "68d064434787cf1ed4a2fe257c03c5f52f33cf84",
130         "type": "github"
131       },
132       "original": {
133@@ -146,17 +146,17 @@
134         "xdph": "xdph"
135       },
136       "locked": {
137-        "lastModified": 1775343634,
138-        "narHash": "sha256-y5X04LWh/RDH7FE3y1xEDD+RP8mkfGGiXXZ9u0hky2g=",
139+        "lastModified": 1778333727,
140+        "narHash": "sha256-RGYGbW6aH3jUwGVB9BrSYcKsQj7Dl2K32ao5aWbTK40=",
141         "owner": "hyprwm",
142         "repo": "hyprland",
143-        "rev": "3a7bd8fea2ca9711da5523dc185c05ea30ec0f35",
144+        "rev": "af923e30d1d24f1f4a4f5cb8308065173c1d9539",
145         "type": "github"
146       },
147       "original": {
148         "owner": "hyprwm",
149         "repo": "hyprland",
150-        "rev": "3a7bd8fea2ca9711da5523dc185c05ea30ec0f35",
151+        "rev": "af923e30d1d24f1f4a4f5cb8308065173c1d9539",
152         "type": "github"
153       }
154     },
155@@ -193,11 +193,11 @@
156         ]
157       },
158       "locked": {
159-        "lastModified": 1772467975,
160-        "narHash": "sha256-kipyuDBxrZq+beYpZqWzGvFWm4QbayW9agAvi94vDXY=",
161+        "lastModified": 1776426575,
162+        "narHash": "sha256-KI6nIfVihn/DPaeB5Et46Xg3dkNHrrEtUd5LBBVomB0=",
163         "owner": "hyprwm",
164         "repo": "hyprland-guiutils",
165-        "rev": "5e1c6b9025aaf4d578f3eff7c0eb1f0c197a9507",
166+        "rev": "a968d211048e3ed538e47b84cb3649299578f19d",
167         "type": "github"
168       },
169       "original": {
170@@ -247,11 +247,11 @@
171         ]
172       },
173       "locked": {
174-        "lastModified": 1772459629,
175-        "narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=",
176+        "lastModified": 1777320127,
177+        "narHash": "sha256-Qu+Wf2Bp5qUjyn2YpZNq8a7JyzTGowhT1knrwE38a9U=",
178         "owner": "hyprwm",
179         "repo": "hyprlang",
180-        "rev": "7615ee388de18239a4ab1400946f3d0e498a8186",
181+        "rev": "090117506ddc3d7f26e650ff344d378c2ec329cc",
182         "type": "github"
183       },
184       "original": {
185@@ -324,11 +324,11 @@
186         ]
187       },
188diff --git a/flake.nix b/flake.nix
189index d1c3e3ff3b3949188438a79c2f8309db4ec74295..5d0104d5a53a11930f169ca5b3c99d8ef8101dfb 100644
190--- a/flake.nix
191+++ b/flake.nix
192@@ -1,6 +1,6 @@
193 {
194   inputs = {
195-    hyprland.url = "github:hyprwm/hyprland/3a7bd8fea2ca9711da5523dc185c05ea30ec0f35";
196+    hyprland.url = "github:hyprwm/hyprland/af923e30d1d24f1f4a4f5cb8308065173c1d9539";
197   };
198 
199   outputs = {
200diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp
201index 6532640a7aad50c35730fac917f5893e9340713b..9dbbadc968f8f2afc6b520fdb28003b40fe14065 100644
202--- a/src/Hy3Layout.cpp
203+++ b/src/Hy3Layout.cpp
204@@ -20,6 +20,7 @@
205 #include <hyprutils/math/Vector2D.hpp>
206 #include <ranges>
207 
208+#include "config/shared/ConfigErrors.hpp"
209 #include "config/shared/complex/ComplexDataTypes.hpp"
210 #include "log.hpp"
211 #include "Hy3Layout.hpp"
212@@ -32,7 +33,7 @@ using namespace Desktop::View;
213 
214 static CollapsePolicy nodeCollapsePolicy() {
215 	static const auto node_collapse_policy =
216-	    ConfigValue<Hyprlang::INT>("plugin:hy3:node_collapse_policy");
217+	    CConfigValue<Config::INTEGER>("plugin:hy3:node_collapse_policy");
218 
219 	switch (*node_collapse_policy) {
220 	case 0: return CollapsePolicy::SingleNodeGroups;
221@@ -238,7 +239,7 @@ void Hy3Layout::insertNode(UP<Hy3Node> node_up, std::optional<Vector2D> focalPoi
222 		opening_into = opening_after->parent.get();
223 	} else {
224 		static const auto tab_first_window =
225-		    ConfigValue<Hyprlang::INT>("plugin:hy3:tab_first_window");
226+		    CConfigValue<Config::INTEGER>("plugin:hy3:tab_first_window");
227 
228 		// Use space work area if available, fall back to monitor
229 		CBox wa_box(monitor->m_position, monitor->m_size);
230@@ -273,10 +274,10 @@ void Hy3Layout::insertNode(UP<Hy3Node> node_up, std::optional<Vector2D> focalPoi
231 
232 	{
233 		// clang-format off
234-		static const auto at_enable = ConfigValue<Hyprlang::INT>("plugin:hy3:autotile:enable");
235-		static const auto at_ephemeral = ConfigValue<Hyprlang::INT>("plugin:hy3:autotile:ephemeral_groups");
236-		static const auto at_trigger_width = ConfigValue<Hyprlang::INT>("plugin:hy3:autotile:trigger_width");
237-		static const auto at_trigger_height = ConfigValue<Hyprlang::INT>("plugin:hy3:autotile:trigger_height");
238+		static const auto at_enable = CConfigValue<Config::INTEGER>("plugin:hy3:autotile:enable");
239+		static const auto at_ephemeral = CConfigValue<Config::INTEGER>("plugin:hy3:autotile:ephemeral_groups");
240+		static const auto at_trigger_width = CConfigValue<Config::INTEGER>("plugin:hy3:autotile:trigger_width");
241+		static const auto at_trigger_height = CConfigValue<Config::INTEGER>("plugin:hy3:autotile:trigger_height");
242 		// clang-format on
243 
244 		this->updateAutotileWorkspaces();
245@@ -402,11 +403,12 @@ void Hy3Layout::onWindowFocusChange(PHLWINDOW window) {
246 
247 void Hy3Layout::updateGroupBorderColors() {
248 	if (!this->root) return;
249-	static auto active_color = ConfigValue<Hyprlang::CUSTOMTYPE, Config::CGradientValueData>("general:col.active_border");
250+	static auto active_color = CConfigValue<Config::IComplexConfigValue>("general:col.active_border");
251+	auto* const active_color_data = sc<Config::CGradientValueData*>(active_color.ptr());
252 
253 	for (auto& w: this->root->windows()) {
254 		if (this->shouldRenderSelected(&w)) {
255-			w.m_ruleApplicator->inactiveBorderColor().set(*active_color, Desktop::Types::PRIORITY_LAYOUT);
256+			w.m_ruleApplicator->inactiveBorderColor().set(*active_color_data, Desktop::Types::PRIORITY_LAYOUT);
257 		} else {
258 			w.m_ruleApplicator->inactiveBorderColor().unset(Desktop::Types::PRIORITY_LAYOUT);
259 		}
260@@ -415,7 +417,7 @@ void Hy3Layout::updateGroupBorderColors() {
261 	}
262 }
263 
264-void Hy3Layout::recalculate() { this->recalcGeometry(); }
265+void Hy3Layout::recalculate(Layout::eRecalculateReason) { this->recalcGeometry(); }
266 
267 void Hy3Layout::recalcGeometry(bool no_animation) {
268 	auto algo = m_parent.lock();
269@@ -506,7 +508,7 @@ void Hy3Layout::resizeTarget(const Vector2D& delta, SP<Layout::ITarget> target,
270 	auto horizontal_neighbor = node->findNeighbor(target_edge_x);
271 	auto vertical_neighbor = node->findNeighbor(target_edge_y);
272 
273-	static const auto animate = ConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
274+	static const auto animate = CConfigValue<Config::INTEGER>("misc:animate_manual_resizes");
275 
276 	if (horizontal_neighbor) {
277 		horizontal_neighbor->resize(reverse(target_edge_x), resize_delta.x, *animate == 0);
278@@ -537,7 +539,7 @@ void Hy3Layout::moveTargetInDirection(SP<Layout::ITarget> t, Math::eDirection di
279 	this->shiftNode(*node, shift, false, false);
280 }
281 
282-std::expected<void, std::string> Hy3Layout::layoutMsg(const std::string_view& sv) {
283+Config::ErrorResult Hy3Layout::layoutMsg(const std::string_view& sv) {
284 	std::string content(sv);
285 
286 	if (content == "togglesplit") {
287@@ -1057,13 +1059,13 @@ bottom:
288 
289 Hy3Node* findTabBarAt(Hy3Node& node, Vector2D pos, Hy3Node** focused_node) {
290 	// clang-format off
291-	static const auto p_gaps_in = ConfigValue<Hyprlang::CUSTOMTYPE, Config::CCssGapData>("general:gaps_in");
292-	static const auto tab_bar_height = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:height");
293-	static const auto tab_bar_padding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:padding");
294+	static const auto p_gaps_in = CConfigValue<Config::IComplexConfigValue>("general:gaps_in");
295+	static const auto tab_bar_height = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:height");
296+	static const auto tab_bar_padding = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:padding");
297 	// clang-format on
298 
299 	auto workspace_rule = Config::workspaceRuleMgr()->getWorkspaceRuleFor(node.layout()->workspace());
300-	auto gaps_in = workspace_rule.and_then([](auto r) { return r.m_gapsIn; }).value_or(*p_gaps_in);
301+	auto gaps_in = workspace_rule.and_then([](auto r) { return r.m_gapsIn; }).value_or(*sc<Config::CCssGapData*>(p_gaps_in.ptr()));
302 
303 	auto inset = *tab_bar_height + *tab_bar_padding + gaps_in.m_top;
304diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp
305index d4bb5bfd9119518309f685d8f446d49a905b66af..8506995652d31f212f0dd6a08039b2600cc61ab8 100644
306--- a/src/Hy3Layout.hpp
307+++ b/src/Hy3Layout.hpp
308@@ -107,11 +107,11 @@ public:
309 	void movedTarget(SP<Layout::ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt) override;
310 	void removeTarget(SP<Layout::ITarget> target) override;
311 	void resizeTarget(const Vector2D& delta, SP<Layout::ITarget> target, Layout::eRectCorner corner = Layout::CORNER_NONE) override;
312-	void recalculate() override;
313+	void recalculate(Layout::eRecalculateReason reason) override;
314 	void recalcGeometry(bool no_animation = false);
315 	void swapTargets(SP<Layout::ITarget> a, SP<Layout::ITarget> b) override;
316 	void moveTargetInDirection(SP<Layout::ITarget> t, Math::eDirection dir, bool silent) override;
317-	std::expected<void, std::string> layoutMsg(const std::string_view& sv) override;
318+	Config::ErrorResult layoutMsg(const std::string_view& sv) override;
319 	std::optional<Vector2D> predictSizeForNewTarget() override;
320 	SP<Layout::ITarget> getNextCandidate(SP<Layout::ITarget> old) override;
321 
322diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp
323index 260ee45ab5e3af022a720644d56553b402514e0b..7b2c1440786974855c7ffda62d01f50ee8846a3b 100644
324--- a/src/Hy3Node.cpp
325+++ b/src/Hy3Node.cpp
326@@ -326,10 +326,10 @@ Hy3Node& Hy3Node::getPlacementActor() {
327 
328 void Hy3Node::recalcSizePosRecursive(CBox offsets, bool no_animation) {
329 	// clang-format off
330-	static const auto p_gaps_in = ConfigValue<Hyprlang::CUSTOMTYPE, Config::CCssGapData>("general:gaps_in");
331-	static const auto tab_bar_height = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:height");
332-	static const auto tab_bar_padding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:padding");
333-	static const auto group_inset = ConfigValue<Hyprlang::INT>("plugin:hy3:group_inset");
334+	static const auto p_gaps_in = CConfigValue<Config::IComplexConfigValue>("general:gaps_in");
335+	static const auto tab_bar_height = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:height");
336+	static const auto tab_bar_padding = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:padding");
337+	static const auto group_inset = CConfigValue<Config::INTEGER>("plugin:hy3:group_inset");
338 	// clang-format on
339 
340 	this->logicalBox = CBox(
341@@ -351,7 +351,7 @@ void Hy3Node::recalcSizePosRecursive(CBox offsets, bool no_animation) {
342 
343 	auto& group = this->as_group();
344 	auto workspace_rule = Config::workspaceRuleMgr()->getWorkspaceRuleFor(this->layout()->workspace());
345-	auto gaps_in = workspace_rule.and_then([](auto r) { return r.m_gapsIn; }).value_or(*p_gaps_in);
346+	auto gaps_in = workspace_rule.and_then([](auto r) { return r.m_gapsIn; }).value_or(*sc<Config::CCssGapData*>(p_gaps_in.ptr()));
347 
348 	auto expand_focused = group.expand_focused != ExpandFocusType::NotExpanded;
349 	bool directly_contains_expanded =
350diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp
351index 4d04d27960ef02bd1c0e957f799d352943fdbc01..3c39cf20b3dd3390bbfc43c258a967f9fea55f55 100644
352--- a/src/TabGroup.cpp
353+++ b/src/TabGroup.cpp
354@@ -184,22 +184,22 @@ void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
355 	auto opacity = opacity_mul * this->fade_opacity->value();
356 
357 	// clang-format off
358-	static const auto s_radius = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:radius");
359-	static const auto border_width = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:border_width");
360-	static const auto s_opacity = ConfigValue<Hyprlang::FLOAT>("plugin:hy3:tabs:opacity");
361-	static const auto blur = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:blur");
362-	static const auto col_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active");
363-	static const auto col_border_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active.border");
364-	static const auto col_focused = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.focused");
365-	static const auto col_border_focused = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.focused.border");
366-	static const auto col_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.urgent");
367-	static const auto col_border_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.urgent.border");
368-	static const auto col_locked = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.locked");
369-	static const auto col_border_locked = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.locked.border");
370-	static const auto col_active_alt_monitor = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active_alt_monitor");
371-	static const auto col_border_active_alt_monitor = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active_alt_monitor.border");
372-	static const auto col_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.inactive");
373-	static const auto col_border_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.inactive.border");
374+	static const auto s_radius = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:radius");
375+	static const auto border_width = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:border_width");
376+	static const auto s_opacity = CConfigValue<Config::FLOAT>("plugin:hy3:tabs:opacity");
377+	static const auto blur = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:blur");
378+	static const auto col_active = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active");
379+	static const auto col_border_active = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active.border");
380+	static const auto col_focused = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.focused");
381+	static const auto col_border_focused = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.focused.border");
382+	static const auto col_urgent = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.urgent");
383+	static const auto col_border_urgent = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.urgent.border");
384+	static const auto col_locked = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.locked");
385+	static const auto col_border_locked = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.locked.border");
386+	static const auto col_active_alt_monitor = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active_alt_monitor");
387+	static const auto col_border_active_alt_monitor = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active_alt_monitor.border");
388+	static const auto col_inactive = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.inactive");
389+	static const auto col_border_inactive = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.inactive.border");
390 	// clang-format on
391 
392 	auto radius = std::min((double) *s_radius * scale, std::min(box.width * 0.5, box.height * 0.5));
393@@ -236,17 +236,17 @@ void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
394 
395 void Hy3TabBarEntry::renderText(float scale, CBox& box, float opacity) {
396 	// clang-format off
397-	static const auto render_text = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:render_text");
398-	static const auto text_center = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:text_center");
399-	static const auto text_font = ConfigValue<Hyprlang::STRING>("plugin:hy3:tabs:text_font");
400-	static const auto text_height = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:text_height");
401-	static const auto text_padding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:text_padding");
402-	static const auto col_text_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active.text");
403-	static const auto col_text_focused = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.focused.text");
404-	static const auto col_text_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.urgent.text");
405-	static const auto col_text_locked = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.locked.text");
406-	static const auto col_text_active_alt_monitor = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active_alt_monitor.text");
407-	static const auto col_text_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.inactive.text");
408+	static const auto render_text = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:render_text");
409+	static const auto text_center = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:text_center");
410+	static const auto text_font = CConfigValue<Config::STRING>("plugin:hy3:tabs:text_font");
411+	static const auto text_height = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:text_height");
412+	static const auto text_padding = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:text_padding");
413+	static const auto col_text_active = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active.text");
414+	static const auto col_text_focused = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.focused.text");
415+	static const auto col_text_urgent = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.urgent.text");
416+	static const auto col_text_locked = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.locked.text");
417+	static const auto col_text_active_alt_monitor = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.active_alt_monitor.text");
418+	static const auto col_text_inactive = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:col.inactive.text");
419 	// clang-format on
420 
421 	if (!*render_text) {
422@@ -281,7 +281,7 @@ void Hy3TabBarEntry::renderText(float scale, CBox& box, float opacity) {
423 		auto* layout = pango_layout_new(context);
424 		pango_layout_set_text(layout, this->window_title.c_str(), -1);
425 
426-		auto* font_desc = pango_font_description_from_string(*text_font);
427+		auto* font_desc = pango_font_description_from_string((*text_font).c_str());
428 		pango_font_description_set_size(font_desc, *text_height * scale * PANGO_SCALE);
429 		pango_layout_set_font_description(layout, font_desc);
430 		pango_font_description_free(font_desc);
431@@ -606,7 +606,7 @@ Hy3TabGroup::Hy3TabGroup(Hy3Node& node) {
432 }
433 
434 void Hy3TabGroup::updateWithGroup(Hy3Node& node, bool warp) {
435-	static const auto bar_height = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:height");
436+	static const auto bar_height = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:height");
437 
438 	auto tpos = node.visualBox.pos();
439 	auto tsize = Vector2D(node.visualBox.w, *bar_height);
440@@ -644,9 +644,9 @@ void Hy3TabBar::damageBox(const Vector2D* position, const Vector2D* size) {
441 }
442 
443 void Hy3TabGroup::tick() {
444-	static const auto enter_from_top = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:from_top");
445-	static const auto padding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:padding");
446-	static const auto no_gaps_when_only = ConfigValue<Hyprlang::INT>("plugin:hy3:no_gaps_when_only");
447+	static const auto enter_from_top = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:from_top");
448+	static const auto padding = CConfigValue<Config::INTEGER>("plugin:hy3:tabs:padding");
449+	static const auto no_gaps_when_only = CConfigValue<Config::INTEGER>("plugin:hy3:no_gaps_when_only");
450 
451 	this->bar.tick();
452 
453@@ -734,9 +734,9 @@ std::pair<CBox, CBox> Hy3TabGroup::getRenderBB() const {
454diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp
455index 7c1f2137590ea9df17635bb0b7982e6edf50f882..1bf65e9b617ce97fc900dea7f1d163365a4da4eb 100644
456--- a/src/dispatchers.cpp
457+++ b/src/dispatchers.cpp
458@@ -1,10 +1,14 @@
459 #include <optional>
460+#include <string>
461+#include <string_view>
462 
463 #include <hyprland/src/Compositor.hpp>
464 #include <hyprland/src/desktop/DesktopTypes.hpp>
465 #include <hyprland/src/plugins/PluginAPI.hpp>
466 #include <hyprutils/string/String.hpp>
467 #include <hyprutils/string/VarList.hpp>
468+#include <hyprland/src/config/lua/LuaBindings.hpp>
469+#include <hyprland/src/config/lua/bindings/LuaBindingsInternal.hpp>
470 
471 #include "dispatchers.hpp"
472 #include "log.hpp"
473@@ -12,6 +16,7 @@
474 #include "src/SharedDefs.hpp"
475 
476 using Hyprutils::String::CVarList;
477+namespace LuaInternal = Config::Lua::Bindings::Internal;
478 
479 static Hy3Layout* hy3InstanceForAction(bool allow_fullscreen = false) {
480 	auto workspace = workspace_for_action(allow_fullscreen);
481@@ -19,13 +24,162 @@ static Hy3Layout* hy3InstanceForAction(bool allow_fullscreen = false) {
482 	return hy3InstanceForWorkspace(workspace);
483 }
484 
485-static SDispatchResult dispatch_makegroup(std::string value) {
486+static void luaCheckArgCount(lua_State* L, const char* fn, int min, int max) {
487+	const int count = lua_gettop(L);
488+	if (count < min || count > max) {
489+		if (min == max)
490+			luaL_error(L, "%s: expected %d argument(s), got %d", fn, min, count);
491+		else
492+			luaL_error(L, "%s: expected %d-%d argument(s), got %d", fn, min, max, count);
493+	}
494+}
495+
496+static bool luaHasOptionsTable(lua_State* L, int idx, const char* fn) {
497+	if (lua_isnoneornil(L, idx)) return false;
498+	if (!lua_istable(L, idx))
499+		luaL_error(L, "%s: options must be a table", fn);
500+	return true;
501+}
502+
503+static std::string luaStringArg(lua_State* L, int idx, const char* fn, const char* name) {
504+	if (lua_isnoneornil(L, idx)) {
505+		luaL_error(L, "%s: missing required argument '%s'", fn, name);
506+		return {};
507+	}
508+
509+	if (!lua_isstring(L, idx)) {
510+		luaL_error(L, "%s: argument '%s' must be a string", fn, name);
511+		return {};
512+	}
513+
514+	return lua_tostring(L, idx);
515+}
516+
517+static std::optional<bool> luaBoolLikeArg(lua_State* L, int idx, const char* fn, const char* name) {
518+	if (lua_isboolean(L, idx)) return lua_toboolean(L, idx);
519+
520+	if (lua_isstring(L, idx)) {
521+		std::string value = lua_tostring(L, idx);
522+		if (value == "true") return true;
523+		if (value == "false") return false;
524+	}
525+
526+	luaL_error(L, "%s: argument '%s' must be a boolean", fn, name);
527+	return {};
528+}
529+
530+static int luaOptionalBoolMode(std::optional<bool> value) {
531+	if (!value) return -1;
532+	return *value ? 1 : 0;
533+}
534+
535+static std::optional<bool> luaOptionalBoolFromUpvalue(lua_State* L, int idx) {
536+	auto mode = static_cast<int>(lua_tointeger(L, lua_upvalueindex(idx)));
537+	if (mode < 0) return {};
538+	return mode != 0;
539+}
540+
541+struct SMakeGroupAction {
542+	Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
543+	bool opposite = false;
544+};
545+
546+static std::optional<SMakeGroupAction> parseMakeGroupArg(std::string_view arg) {
547+	if (arg == "h") return SMakeGroupAction { .layout = Hy3GroupLayout::SplitH };
548+	if (arg == "v") return SMakeGroupAction { .layout = Hy3GroupLayout::SplitV };
549+	if (arg == "tab") return SMakeGroupAction { .layout = Hy3GroupLayout::Tabbed };
550+	if (arg == "opposite") return SMakeGroupAction { .opposite = true };
551+	return {};
552+}
553+
554+static SMakeGroupAction luaMakeGroupActionArg(lua_State* L, int idx, const char* fn) {
555+	auto value = luaStringArg(L, idx, fn, "layout");
556+	auto action = parseMakeGroupArg(value);
557+	if (!action)
558diff --git a/src/globals.hpp b/src/globals.hpp
559index eb69328d94e6bb9326f6ca60cd74afa37e65a1cb..6acbe1c14fa388045e9a51e6c98c37c080627474 100644
560--- a/src/globals.hpp
561+++ b/src/globals.hpp
562@@ -4,11 +4,13 @@
563 #include <vector>
564 
565 #include <hyprland/src/desktop/Workspace.hpp>
566+#include <hyprland/src/config/ConfigValue.hpp>
567 #include <hyprland/src/plugins/PluginAPI.hpp>
568 #include <hyprlang.hpp>
569 
570 #include "Hy3Layout.hpp"
571 #include "TabGroup.hpp"
572+#include "config/shared/complex/ComplexDataType.hpp"
573 
574 inline HANDLE PHANDLE = nullptr;
575 
576@@ -42,44 +44,3 @@ inline void errorNotif() {
577 	    }
578 	);
579 }
580-
581-class HyprlangUnspecifiedCustomType {};
582-
583-// abandon hope all ye who enter here
584-template <typename T, typename V = HyprlangUnspecifiedCustomType>
585-class ConfigValue {
586-public:
587-	ConfigValue(const std::string& option) {
588-		this->static_data_ptr = HyprlandAPI::getConfigValue(PHANDLE, option)->getDataStaticPtr();
589-	}
590-
591-	template <typename U = T>
592-	typename std::enable_if<std::is_same<U, Hyprlang::CUSTOMTYPE>::value, const V&>::type
593-	operator*() const {
594-		return *(V*) ((Hyprlang::CUSTOMTYPE*) *this->static_data_ptr)->getData();
595-	}
596-
597-	template <typename U = T>
598-	typename std::enable_if<std::is_same<U, Hyprlang::CUSTOMTYPE>::value, const V*>::type
599-	operator->() const {
600-		return &**this;
601-	}
602-
603-	// Bullshit microptimization case for strings
604-	template <typename U = T>
605-	typename std::enable_if<std::is_same<U, Hyprlang::STRING>::value, const char*>::type
606-	operator*() const {
607-		return *(const char**) this->static_data_ptr;
608-	}
609-
610-	template <typename U = T>
611-	typename std::enable_if<
612-	    !std::is_same<U, Hyprlang::CUSTOMTYPE>::value && !std::is_same<U, Hyprlang::STRING>::value,
613-	    const T&>::type
614-	operator*() const {
615-		return *(T*) *this->static_data_ptr;
616-	}
617-
618-private:
619-	void* const* static_data_ptr;
620-};
621diff --git a/src/main.cpp b/src/main.cpp
622index fa89ef2597df84064e4f38bf250acef8e2f36d3b..d4ef18216b177133de3b384d37ebc2d952cadf6a 100644
623--- a/src/main.cpp
624+++ b/src/main.cpp
625@@ -1,9 +1,9 @@
626 #include <hyprland/src/Compositor.hpp>
627+#include <hyprland/src/config/values/ConfigValues.hpp>
628 #include <hyprland/src/plugins/PluginAPI.hpp>
629 #include <hyprland/src/render/Renderer.hpp>
630 #include <hyprland/src/render/OpenGL.hpp>
631 #include <hyprland/src/version.h>
632-#include <hyprlang.hpp>
633 
634 #include "dispatchers.hpp"
635 #include "globals.hpp"
636@@ -31,56 +31,52 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
637 #endif
638 
639 #define CONF(NAME, TYPE, VALUE)                                                                    \
640-	HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:" NAME, Hyprlang::CConfigValue((TYPE) VALUE))
641-
642-	using Hyprlang::FLOAT;
643-	using Hyprlang::INT;
644-	using Hyprlang::STRING;
645+	HyprlandAPI::addConfigValueV2(PHANDLE, makeShared<Config::Values::TYPE>("plugin:hy3:" NAME, "", VALUE))
646 
647 	// general
648-	CONF("no_gaps_when_only", INT, 0);
649-	CONF("node_collapse_policy", INT, 2);
650-	CONF("group_inset", INT, 10);
651-	CONF("tab_first_window", INT, 0);
652+	CONF("no_gaps_when_only", Int, 0);
653+	CONF("node_collapse_policy", Int, 2);
654+	CONF("group_inset", Int, 10);
655+	CONF("tab_first_window", Int, 0);
656 
657 	// tabs
658-	CONF("tabs:height", INT, 22);
659-	CONF("tabs:padding", INT, 5);
660-	CONF("tabs:from_top", INT, 0);
661-	CONF("tabs:radius", INT, 6);
662-	CONF("tabs:border_width", INT, 2);
663-	CONF("tabs:render_text", INT, 1);
664-	CONF("tabs:text_center", INT, 1);
665-	CONF("tabs:text_font", STRING, "Sans");
666-	CONF("tabs:text_height", INT, 8);
667-	CONF("tabs:text_padding", INT, 3);
668-	CONF("tabs:opacity", FLOAT, 1.0);
669-	CONF("tabs:blur", INT, 1);
670-	CONF("tabs:col.active", INT, 0x4033ccff);
671-	CONF("tabs:col.active.border", INT, 0xee33ccff);
672-	CONF("tabs:col.active.text", INT, 0xffffffff);
673-	CONF("tabs:col.active_alt_monitor", INT, 0x40606060);
674-	CONF("tabs:col.active_alt_monitor.border", INT, 0xee808080);
675-	CONF("tabs:col.active_alt_monitor.text", INT, 0xffffffff);
676-	CONF("tabs:col.focused", INT, 0x40606060);
677-	CONF("tabs:col.focused.border", INT, 0xee808080);
678-	CONF("tabs:col.focused.text", INT, 0xffffffff);
679-	CONF("tabs:col.inactive", INT, 0x20303030);
680-	CONF("tabs:col.inactive.border", INT, 0xaa606060);
681-	CONF("tabs:col.inactive.text", INT, 0xffffffff);
682-	CONF("tabs:col.urgent", INT, 0x40ff2233);
683-	CONF("tabs:col.urgent.border", INT, 0xeeff2233);
684-	CONF("tabs:col.urgent.text", INT, 0xffffffff);
685-	CONF("tabs:col.locked", INT, 0x40909033);
686-	CONF("tabs:col.locked.border", INT, 0xee909033);
687-	CONF("tabs:col.locked.text", INT, 0xffffffff);
688+	CONF("tabs:height", Int, 22);
689+	CONF("tabs:padding", Int, 5);
690+	CONF("tabs:from_top", Int, 0);
691+	CONF("tabs:radius", Int, 6);
692+	CONF("tabs:border_width", Int, 2);
693+	CONF("tabs:render_text", Int, 1);
694+	CONF("tabs:text_center", Int, 1);
695+	CONF("tabs:text_font", String, "Sans");
696+	CONF("tabs:text_height", Int, 8);
697+	CONF("tabs:text_padding", Int, 3);
698+	CONF("tabs:opacity", Float, 1.0);
699+	CONF("tabs:blur", Int, 1);
700+	CONF("tabs:col.active", Int, 0x4033ccff);
701+	CONF("tabs:col.active.border", Int, 0xee33ccff);
702+	CONF("tabs:col.active.text", Int, 0xffffffff);
703+	CONF("tabs:col.active_alt_monitor", Int, 0x40606060);
704+	CONF("tabs:col.active_alt_monitor.border", Int, 0xee808080);
705+	CONF("tabs:col.active_alt_monitor.text", Int, 0xffffffff);
706+	CONF("tabs:col.focused", Int, 0x40606060);
707+	CONF("tabs:col.focused.border", Int, 0xee808080);
708+	CONF("tabs:col.focused.text", Int, 0xffffffff);
709+	CONF("tabs:col.inactive", Int, 0x20303030);
710+	CONF("tabs:col.inactive.border", Int, 0xaa606060);
711+	CONF("tabs:col.inactive.text", Int, 0xffffffff);
712+	CONF("tabs:col.urgent", Int, 0x40ff2233);
713+	CONF("tabs:col.urgent.border", Int, 0xeeff2233);
714+	CONF("tabs:col.urgent.text", Int, 0xffffffff);
715+	CONF("tabs:col.locked", Int, 0x40909033);
716+	CONF("tabs:col.locked.border", Int, 0xee909033);
717+	CONF("tabs:col.locked.text", Int, 0xffffffff);
718 
719 	// autotiling
720-	CONF("autotile:enable", INT, 0);
721-	CONF("autotile:ephemeral_groups", INT, 1);
722-	CONF("autotile:trigger_height", INT, 0);
723-	CONF("autotile:trigger_width", INT, 0);
724-	CONF("autotile:workspaces", STRING, "all");