summaryrefslogtreecommitdiff
path: root/src/config/lua/bindings/LuaBindingsDispatchers.cpp
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-04-26 15:16:36 +0100
committerGitHub <noreply@github.com>2026-04-26 15:16:36 +0100
commit5ba33f846129f3e6c0505b19f94634b24505e828 (patch)
treeaaacabe678006040c342935481a86607c66607fd /src/config/lua/bindings/LuaBindingsDispatchers.cpp
parent329b2b356dd92ac07f4e2aa69cbce82ee9ba9d19 (diff)
config/lua: init lua config manager, use lua if available (#13817)
Adds a new lua config manager, which will automatically be used if there is a lua config present.
Diffstat (limited to 'src/config/lua/bindings/LuaBindingsDispatchers.cpp')
-rw-r--r--src/config/lua/bindings/LuaBindingsDispatchers.cpp1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/src/config/lua/bindings/LuaBindingsDispatchers.cpp b/src/config/lua/bindings/LuaBindingsDispatchers.cpp
new file mode 100644
index 00000000..61270f9c
--- /dev/null
+++ b/src/config/lua/bindings/LuaBindingsDispatchers.cpp
@@ -0,0 +1,1324 @@
+#include "LuaBindingsInternal.hpp"
+
+#include <hyprutils/string/String.hpp>
+
+#include "../../supplementary/executor/Executor.hpp"
+
+#include "../../../managers/SeatManager.hpp"
+#include "../../../devices/IKeyboard.hpp"
+#include "../../../desktop/rule/windowRule/WindowRule.hpp"
+
+using namespace Config;
+using namespace Config::Lua;
+using namespace Config::Lua::Bindings;
+using namespace Hyprutils::String;
+
+namespace CA = Config::Actions;
+
+static int dsp_moveCursorToCorner(lua_State* L) {
+ Internal::checkResult(L, CA::moveCursorToCorner((int)lua_tonumber(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveCursor(lua_State* L) {
+ Internal::checkResult(L, CA::moveCursor(Vector2D{lua_tonumber(L, lua_upvalueindex(1)), lua_tonumber(L, lua_upvalueindex(2))}));
+ return 0;
+}
+
+static int dsp_toggleGroup(lua_State* L) {
+ Internal::checkResult(L, CA::toggleGroup(Internal::windowFromUpval(L, 1)));
+ return 0;
+}
+
+static int dsp_changeGroupActive(lua_State* L) {
+ Internal::checkResult(L, CA::changeGroupActive(lua_toboolean(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_setGroupActive(lua_State* L) {
+ Internal::checkResult(L, CA::setGroupActive((int)lua_tonumber(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveGroupWindow(lua_State* L) {
+ Internal::checkResult(L, CA::moveGroupWindow(lua_toboolean(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int dsp_lockGroups(lua_State* L) {
+ Internal::checkResult(L, CA::lockGroups(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1)))));
+ return 0;
+}
+
+static int dsp_lockActiveGroup(lua_State* L) {
+ Internal::checkResult(L, CA::lockActiveGroup(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1)))));
+ return 0;
+}
+
+static int hlCursorMoveToCorner(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.cursor.move_to_corner: expected a table { corner, window? }");
+
+ lua_pushnumber(L, Internal::requireTableFieldNum(L, 1, "corner", "hl.cursor.move_to_corner"));
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveCursorToCorner, 2);
+ return 1;
+}
+
+static int hlCursorMove(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.cursor.move: expected a table { x, y }");
+
+ lua_pushnumber(L, Internal::requireTableFieldNum(L, 1, "x", "hl.cursor.move"));
+ lua_pushnumber(L, Internal::requireTableFieldNum(L, 1, "y", "hl.cursor.move"));
+ lua_pushcclosure(L, dsp_moveCursor, 2);
+ return 1;
+}
+
+static int hlGroupToggle(lua_State* L) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_toggleGroup, 1);
+ return 1;
+}
+
+static int hlGroupNext(lua_State* L) {
+ lua_pushboolean(L, true);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_changeGroupActive, 2);
+ return 1;
+}
+
+static int hlGroupPrev(lua_State* L) {
+ lua_pushboolean(L, false);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_changeGroupActive, 2);
+ return 1;
+}
+
+static int hlGroupMoveWindow(lua_State* L) {
+ bool forward = true;
+ if (lua_istable(L, 1)) {
+ auto f = Internal::tableOptBool(L, 1, "forward");
+ if (f)
+ forward = *f;
+ }
+ lua_pushboolean(L, forward);
+ lua_pushcclosure(L, dsp_moveGroupWindow, 1);
+ return 1;
+}
+
+static int hlGroupActive(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.group.active: expected a table { index, window? }");
+
+ lua_pushnumber(L, Internal::requireTableFieldNum(L, 1, "index", "hl.group.active"));
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_setGroupActive, 2);
+ return 1;
+}
+
+static int hlGroupLock(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ lua_pushcclosure(L, dsp_lockGroups, 1);
+ return 1;
+}
+
+static int hlGroupLockActive(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ lua_pushcclosure(L, dsp_lockActiveGroup, 1);
+ return 1;
+}
+
+static int dsp_execCmd(lua_State* L) {
+ auto proc = lua_tostring(L, lua_upvalueindex(1));
+
+ if (std::string_view{proc}.empty())
+ return Internal::configError(L, "Invalid process string");
+
+ std::optional<uint64_t> pid;
+ auto ruleRet = Internal::buildRuleFromTable(L, lua_upvalueindex(2));
+
+ if (!ruleRet)
+ return ruleRet.error();
+
+ if (*ruleRet)
+ pid = Config::Supplementary::executor()->spawn(Config::Supplementary::SExecRequest{.exec = proc, .rule = std::move(*ruleRet)});
+ else
+ pid = Config::Supplementary::executor()->spawn(proc);
+
+ if (!pid.has_value())
+ return Internal::configError(L, "Failed to start process");
+ return 0;
+}
+
+static int dsp_execRaw(lua_State* L) {
+ auto proc = Config::Supplementary::executor()->spawnRaw(lua_tostring(L, lua_upvalueindex(1)));
+ if (!proc || !*proc)
+ return Internal::configError(L, "Failed to start process");
+ return 0;
+}
+
+static int dsp_exit(lua_State* L) {
+ Internal::checkResult(L, CA::exit());
+ return 0;
+}
+
+static int dsp_submap(lua_State* L) {
+ Internal::checkResult(L, CA::setSubmap(lua_tostring(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int dsp_pass(lua_State* L) {
+ const auto PWINDOW = g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(1)));
+ if (!PWINDOW)
+ return Internal::configError(L, "hl.pass: window not found");
+ Internal::checkResult(L, CA::pass(PWINDOW));
+ return 0;
+}
+
+static int dsp_layoutMsg(lua_State* L) {
+ Internal::checkResult(L, CA::layoutMessage(lua_tostring(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int dsp_dpms(lua_State* L) {
+ auto action = sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1)));
+ std::optional<PHLMONITOR> mon = std::nullopt;
+
+ if (!lua_isnil(L, lua_upvalueindex(2))) {
+ auto m = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(2)));
+ if (m)
+ mon = m;
+ }
+
+ Internal::checkResult(L, CA::dpms(action, mon));
+ return 0;
+}
+
+static int dsp_event(lua_State* L) {
+ Internal::checkResult(L, CA::event(lua_tostring(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int dsp_global(lua_State* L) {
+ Internal::checkResult(L, CA::global(lua_tostring(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int dsp_forceRendererReload(lua_State* L) {
+ Internal::checkResult(L, CA::forceRendererReload());
+ return 0;
+}
+
+static int dsp_forceIdle(lua_State* L) {
+ Internal::checkResult(L, CA::forceIdle((float)lua_tonumber(L, lua_upvalueindex(1))));
+ return 0;
+}
+
+static int hlExecCmd(lua_State* L) {
+ const auto proc = luaL_checkstring(L, 1);
+ const bool hasRuleArg = !lua_isnoneornil(L, 2);
+
+ lua_pushstring(L, proc);
+
+ if (hasRuleArg)
+ lua_pushvalue(L, 2);
+ else
+ lua_pushnil(L);
+
+ lua_pushcclosure(L, dsp_execCmd, 2);
+ return 1;
+}
+
+static int hlExecRaw(lua_State* L) {
+ lua_pushstring(L, luaL_checkstring(L, 1));
+ lua_pushcclosure(L, dsp_execRaw, 1);
+ return 1;
+}
+
+static int hlExit(lua_State* L) {
+ lua_pushcclosure(L, dsp_exit, 0);
+ return 1;
+}
+
+static int hlSubmap(lua_State* L) {
+ lua_pushstring(L, luaL_checkstring(L, 1));
+ lua_pushcclosure(L, dsp_submap, 1);
+ return 1;
+}
+
+static int hlPass(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.pass: expected a table { window }");
+
+ const auto w = Internal::requireTableFieldWindowSelector(L, 1, "window", "hl.pass");
+ lua_pushstring(L, w.c_str());
+ lua_pushcclosure(L, dsp_pass, 1);
+ return 1;
+}
+
+static int hlLayout(lua_State* L) {
+ lua_pushstring(L, luaL_checkstring(L, 1));
+ lua_pushcclosure(L, dsp_layoutMsg, 1);
+ return 1;
+}
+
+static int hlDpms(lua_State* L) {
+ CA::eTogglableAction action = Internal::tableToggleAction(L, 1);
+ std::optional<std::string> monStr;
+
+ if (lua_istable(L, 1))
+ monStr = Internal::tableOptMonitorSelector(L, 1, "monitor", "hl.dpms");
+
+ lua_pushnumber(L, (int)action);
+ if (monStr)
+ lua_pushstring(L, monStr->c_str());
+ else
+ lua_pushnil(L);
+ lua_pushcclosure(L, dsp_dpms, 2);
+ return 1;
+}
+
+static int hlEvent(lua_State* L) {
+ lua_pushstring(L, luaL_checkstring(L, 1));
+ lua_pushcclosure(L, dsp_event, 1);
+ return 1;
+}
+
+static int hlGlobal(lua_State* L) {
+ lua_pushstring(L, luaL_checkstring(L, 1));
+ lua_pushcclosure(L, dsp_global, 1);
+ return 1;
+}
+
+static int hlForceRendererReload(lua_State* L) {
+ lua_pushcclosure(L, dsp_forceRendererReload, 0);
+ return 1;
+}
+
+static int hlForceIdle(lua_State* L) {
+ lua_pushnumber(L, luaL_checknumber(L, 1));
+ lua_pushcclosure(L, dsp_forceIdle, 1);
+ return 1;
+}
+
+static std::expected<uint32_t, std::string> resolveKeycode(const std::string& key) {
+ if (isNumber(key) && std::stoi(key) > 9)
+ return (uint32_t)std::stoi(key);
+
+ if (key.starts_with("code:") && isNumber(key.substr(5)))
+ return (uint32_t)std::stoi(key.substr(5));
+
+ if (key.starts_with("mouse:") && isNumber(key.substr(6))) {
+ uint32_t code = std::stoi(key.substr(6));
+ if (code < 272)
+ return std::unexpected("invalid mouse button");
+ return code;
+ }
+
+ const auto KEYSYM = xkb_keysym_from_name(key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);
+
+ const auto KB = g_pSeatManager->m_keyboard;
+ if (!KB)
+ return std::unexpected("no keyboard");
+
+ const auto KEYPAIRSTRING = std::format("{}{}", rc<uintptr_t>(KB.get()), key);
+
+ if (g_pKeybindManager->m_keyToCodeCache.contains(KEYPAIRSTRING))
+ return g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING];
+
+ xkb_keymap* km = KB->m_xkbKeymap;
+ xkb_state* ks = KB->m_xkbState;
+ xkb_keycode_t keycode_min = xkb_keymap_min_keycode(km);
+ xkb_keycode_t keycode_max = xkb_keymap_max_keycode(km);
+ uint32_t keycode = 0;
+
+ for (xkb_keycode_t kc = keycode_min; kc <= keycode_max; ++kc) {
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(ks, kc);
+ if (sym == KEYSYM) {
+ keycode = kc;
+ g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING] = keycode;
+ }
+ }
+
+ if (!keycode)
+ return std::unexpected("key not found");
+
+ return keycode;
+}
+
+static int dsp_sendShortcut(lua_State* L) {
+ const uint32_t modMask = g_pKeybindManager->stringToModMask(lua_tostring(L, lua_upvalueindex(1)));
+ const std::string key = lua_tostring(L, lua_upvalueindex(2));
+
+ auto keycodeResult = resolveKeycode(key);
+ if (!keycodeResult)
+ return Internal::configError(L, "send_shortcut: {}", keycodeResult.error());
+
+ PHLWINDOW window = nullptr;
+ if (!lua_isnil(L, lua_upvalueindex(3))) {
+ window = g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(3)));
+ if (!window)
+ return Internal::configError(L, "send_shortcut: window not found");
+ }
+
+ Internal::checkResult(L, CA::pass(modMask, *keycodeResult, window));
+ return 0;
+}
+
+static int dsp_sendKeyState(lua_State* L) {
+ const uint32_t modMask = g_pKeybindManager->stringToModMask(lua_tostring(L, lua_upvalueindex(1)));
+ const std::string key = lua_tostring(L, lua_upvalueindex(2));
+ const uint32_t keyState = (uint32_t)lua_tonumber(L, lua_upvalueindex(3));
+
+ auto keycodeResult = resolveKeycode(key);
+ if (!keycodeResult)
+ return Internal::configError(L, "send_key_state: {}", keycodeResult.error());
+
+ PHLWINDOW window = nullptr;
+ if (!lua_isnil(L, lua_upvalueindex(4))) {
+ window = g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(4)));
+ if (!window)
+ return Internal::configError(L, "send_key_state: window not found");
+ }
+
+ Internal::checkResult(L, CA::sendKeyState(modMask, *keycodeResult, keyState, window));
+ return 0;
+}
+
+static int hlSendShortcut(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "send_shortcut: expected a table { mods, key, window? }");
+
+ const auto mods = Internal::requireTableFieldStr(L, 1, "mods", "hl.send_shortcut");
+ const auto key = Internal::requireTableFieldStr(L, 1, "key", "hl.send_shortcut");
+
+ lua_pushstring(L, mods.c_str());
+ lua_pushstring(L, key.c_str());
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_sendShortcut, 3);
+ return 1;
+}
+
+static int hlSendKeyState(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "send_key_state: expected a table { mods, key, state, window? }");
+
+ const auto mods = Internal::requireTableFieldStr(L, 1, "mods", "hl.send_key_state");
+ const auto key = Internal::requireTableFieldStr(L, 1, "key", "hl.send_key_state");
+ const auto stateStr = Internal::requireTableFieldStr(L, 1, "state", "hl.send_key_state");
+
+ uint32_t keyState = 0;
+ if (stateStr == "down")
+ keyState = 1;
+ else if (stateStr == "repeat")
+ keyState = 2;
+ else if (stateStr != "up")
+ return Internal::configError(L, "send_key_state: 'state' must be \"down\", \"up\", or \"repeat\"");
+
+ lua_pushstring(L, mods.c_str());
+ lua_pushstring(L, key.c_str());
+ lua_pushnumber(L, keyState);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_sendKeyState, 4);
+ return 1;
+}
+
+static int dsp_moveToWorkspace(lua_State* L) {
+ auto ws = Internal::resolveWorkspaceStr(lua_tostring(L, lua_upvalueindex(1)));
+ if (!ws)
+ return Internal::configError(L, "Invalid workspace");
+
+ bool silent = lua_toboolean(L, lua_upvalueindex(2));
+ Internal::checkResult(L, CA::moveToWorkspace(ws, silent, Internal::windowFromUpval(L, 3)));
+ return 0;
+}
+
+static int dsp_moveToMonitor(lua_State* L) {
+ auto mon = Internal::resolveMonitorStr(lua_tostring(L, lua_upvalueindex(1)));
+ if (!mon)
+ return Internal::configError(L, "Invalid monitor / monitor doesn't exist");
+
+ bool silent = lua_toboolean(L, lua_upvalueindex(2));
+ Internal::checkResult(L, CA::moveToWorkspace(mon->m_activeWorkspace, silent, Internal::windowFromUpval(L, 3)));
+ return 0;
+}
+
+static int dsp_closeWindow(lua_State* L) {
+ Internal::checkResult(L, CA::closeWindow(Internal::windowFromUpval(L, 1)));
+ return 0;
+}
+
+static int dsp_killWindow(lua_State* L) {
+ Internal::checkResult(L, CA::killWindow(Internal::windowFromUpval(L, 1)));
+ return 0;
+}
+
+static int dsp_signalWindow(lua_State* L) {
+ Internal::checkResult(L, CA::signalWindow((int)lua_tonumber(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_floatWindow(lua_State* L) {
+ Internal::checkResult(L, CA::floatWindow(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_fullscreenWindow(lua_State* L) {
+ Internal::checkResult(L, CA::fullscreenWindow(sc<eFullscreenMode>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_fullscreenWindowWithAction(lua_State* L) {
+ const auto mode = sc<eFullscreenMode>((int)lua_tonumber(L, lua_upvalueindex(1)));
+ const int actionRaw = (int)lua_tonumber(L, lua_upvalueindex(2));
+ auto maybeW = Internal::windowFromUpval(L, 3);
+
+ if (actionRaw == 0) {
+ Internal::checkResult(L, CA::fullscreenWindow(mode, maybeW));
+ return 0;
+ }
+
+ const auto target = maybeW.value_or(Desktop::focusState()->window());
+ if (!target)
+ return Internal::configError(L, "No target found.");
+
+ const bool currentlyMode = target->isEffectiveInternalFSMode(mode);
+
+ if (actionRaw == 1) {
+ if (!currentlyMode)
+ Internal::checkResult(L, CA::fullscreenWindow(mode, maybeW));
+ return 0;
+ }
+
+ if (actionRaw == 2) {
+ if (currentlyMode)
+ Internal::checkResult(L, CA::fullscreenWindow(mode, maybeW));
+ return 0;
+ }
+
+ return Internal::configError(L, "hl.window.fullscreen: invalid action");
+}
+
+static int dsp_fullscreenState(lua_State* L) {
+ const auto desiredInternal = sc<eFullscreenMode>((int)lua_tonumber(L, lua_upvalueindex(1)));
+ const auto desiredClient = sc<eFullscreenMode>((int)lua_tonumber(L, lua_upvalueindex(2)));
+ const int actionRaw = (int)lua_tonumber(L, lua_upvalueindex(3)); // 0: toggle, 1: set, 2: unset
+ auto maybeW = Internal::windowFromUpval(L, 4);
+
+ const auto target = maybeW.value_or(Desktop::focusState()->window());
+ if (!target)
+ return Internal::configError(L, "No target found.");
+
+ const auto CURRENT = target->m_fullscreenState;
+ const bool atDesiredState = CURRENT.internal == desiredInternal && CURRENT.client == desiredClient;
+
+ if (actionRaw == 0) {
+ Internal::checkResult(L, CA::fullscreenWindow(desiredInternal, desiredClient, maybeW));
+ return 0;
+ }
+
+ if (actionRaw == 1) {
+ if (!atDesiredState)
+ Internal::checkResult(L, CA::fullscreenWindow(desiredInternal, desiredClient, maybeW));
+ return 0;
+ }
+
+ if (actionRaw == 2) {
+ if (atDesiredState)
+ Internal::checkResult(L, CA::fullscreenWindow(desiredInternal, desiredClient, maybeW));
+ return 0;
+ }
+
+ return Internal::configError(L, "hl.window.fullscreen_state: invalid action");
+}
+
+static int dsp_pseudoWindow(lua_State* L) {
+ Internal::checkResult(L, CA::pseudoWindow(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveInDirection(lua_State* L) {
+ Internal::checkResult(L, CA::moveInDirection(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_swapInDirection(lua_State* L) {
+ Internal::checkResult(L, CA::swapInDirection(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_center(lua_State* L) {
+ Internal::checkResult(L, CA::center(Internal::windowFromUpval(L, 1)));
+ return 0;
+}
+
+static int dsp_cycleNext(lua_State* L) {
+ bool next = lua_toboolean(L, lua_upvalueindex(1));
+ int tiledRaw = (int)lua_tonumber(L, lua_upvalueindex(2));
+ int floatingRaw = (int)lua_tonumber(L, lua_upvalueindex(3));
+ std::optional<bool> tiled = tiledRaw < 0 ? std::nullopt : std::optional(tiledRaw > 0);
+ std::optional<bool> floating = floatingRaw < 0 ? std::nullopt : std::optional(floatingRaw > 0);
+ Internal::checkResult(L, CA::cycleNext(next, tiled, floating, Internal::windowFromUpval(L, 4)));
+ return 0;
+}
+
+static int dsp_swapNext(lua_State* L) {
+ Internal::checkResult(L, CA::swapNext(lua_toboolean(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_swapWithWindow(lua_State* L) {
+ auto source = Internal::windowFromUpval(L, 1);
+
+ const auto targetSelector = lua_tostring(L, lua_upvalueindex(2));
+ const auto target = g_pCompositor->getWindowByRegex(targetSelector);
+ if (!target)
+ return Internal::configError(L, "hl.window.swap: target window not found");
+
+ Internal::checkResult(L, CA::swapWith(target, source));
+ return 0;
+}
+
+static int dsp_tagWindow(lua_State* L) {
+ Internal::checkResult(L, CA::tag(lua_tostring(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_toggleSwallow(lua_State* L) {
+ Internal::checkResult(L, CA::toggleSwallow());
+ return 0;
+}
+
+static int dsp_resize(lua_State* L) {
+ Vector2D value{lua_tonumber(L, lua_upvalueindex(1)), lua_tonumber(L, lua_upvalueindex(2))};
+ Internal::checkResult(L, CA::resize(value, lua_toboolean(L, lua_upvalueindex(3)), Internal::windowFromUpval(L, 4)));
+ return 0;
+}
+
+static int dsp_move(lua_State* L) {
+ Vector2D value{lua_tonumber(L, lua_upvalueindex(1)), lua_tonumber(L, lua_upvalueindex(2))};
+ Internal::checkResult(L, CA::move(value, lua_toboolean(L, lua_upvalueindex(3)), Internal::windowFromUpval(L, 4)));
+ return 0;
+}
+
+static int dsp_pinWindow(lua_State* L) {
+ Internal::checkResult(L, CA::pinWindow(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_bringToTop(lua_State* L) {
+ Internal::checkResult(L, CA::alterZOrder("top"));
+ return 0;
+}
+
+static int dsp_alterZOrder(lua_State* L) {
+ Internal::checkResult(L, CA::alterZOrder(lua_tostring(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_setProp(lua_State* L) {
+ Internal::checkResult(L, CA::setProp(lua_tostring(L, lua_upvalueindex(1)), lua_tostring(L, lua_upvalueindex(2)), Internal::windowFromUpval(L, 3)));
+ return 0;
+}
+
+static int dsp_moveIntoGroup(lua_State* L) {
+ Internal::checkResult(L, CA::moveIntoGroup(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveOutOfGroup(lua_State* L) {
+ Internal::checkResult(L, CA::moveOutOfGroup(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveWindowOrGroup(lua_State* L) {
+ Internal::checkResult(L, CA::moveWindowOrGroup(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_moveIntoOrCreateGroup(lua_State* L) {
+ Internal::checkResult(L, CA::moveIntoOrCreateGroup(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1))), Internal::windowFromUpval(L, 2)));
+ return 0;
+}
+
+static int dsp_denyFromGroup(lua_State* L) {
+ Internal::checkResult(L, CA::denyWindowFromGroup(sc<CA::eTogglableAction>((int)lua_tonumber(L, lua_upvalueindex(1)))));
+ return 0;
+}
+
+static int dsp_mouseDrag(lua_State* L) {
+ Internal::checkResult(L, CA::mouse("movewindow"));
+ return 0;
+}
+
+static int dsp_mouseResize(lua_State* L) {
+ Internal::checkResult(L, CA::mouse("resizewindow"));
+ return 0;
+}
+
+static int hlWindowClose(lua_State* L) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_closeWindow, 1);
+ return 1;
+}
+
+static int hlWindowKill(lua_State* L) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_killWindow, 1);
+ return 1;
+}
+
+static int hlWindowSignal(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.signal: expected a table { signal, window? }");
+
+ lua_pushnumber(L, Internal::requireTableFieldNum(L, 1, "signal", "hl.window.signal"));
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_signalWindow, 2);
+ return 1;
+}
+
+static int hlWindowFloat(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_floatWindow, 2);
+ return 1;
+}
+
+static int hlWindowFullscreen(lua_State* L) {
+ eFullscreenMode mode = FSMODE_FULLSCREEN;
+ int action = 0; // 0: toggle, 1: set, 2: unset
+ if (lua_istable(L, 1)) {
+ auto m = Internal::tableOptStr(L, 1, "mode");
+ if (m) {
+ if (*m == "maximized" || *m == "1")
+ mode = FSMODE_MAXIMIZED;
+ else if (*m == "fullscreen" || *m == "0")
+ mode = FSMODE_FULLSCREEN;
+ else
+ return Internal::configError(L, "hl.window.fullscreen: invalid mode \"{}\" (expected fullscreen/maximized)", *m);
+ }
+
+ auto a = Internal::tableOptStr(L, 1, "action");
+ if (a) {
+ if (*a == "toggle")
+ action = 0;
+ else if (*a == "set")
+ action = 1;
+ else if (*a == "unset")
+ action = 2;
+ else
+ return Internal::configError(L, "hl.window.fullscreen: invalid action \"{}\" (expected toggle/set/unset)", *a);
+ }
+ }
+ lua_pushnumber(L, (int)mode);
+ if (action == 0) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_fullscreenWindow, 2);
+ } else {
+ lua_pushnumber(L, action);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_fullscreenWindowWithAction, 3);
+ }
+ return 1;
+}
+
+static int hlWindowFullscreenState(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.fullscreen_state: expected a table { internal, client, action?, window? }");
+
+ int action = 1; // default to set semantics
+ if (auto a = Internal::tableOptStr(L, 1, "action"); a) {
+ if (*a == "toggle")
+ action = 0;
+ else if (*a == "set")
+ action = 1;
+ else if (*a == "unset")
+ action = 2;
+ else
+ return Internal::configError(L, "hl.window.fullscreen_state: invalid action \"{}\" (expected toggle/set/unset)", *a);
+ }
+
+ auto im = Internal::tableOptNum(L, 1, "internal");
+ auto cm = Internal::tableOptNum(L, 1, "client");
+ if (!im || !cm)
+ return Internal::configError(L, "hl.window.fullscreen_state: 'internal' and 'client' are required");
+
+ lua_pushnumber(L, (int)*im);
+ lua_pushnumber(L, (int)*cm);
+ lua_pushnumber(L, action);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_fullscreenState, 4);
+ return 1;
+}
+
+static int hlWindowPseudo(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_pseudoWindow, 2);
+ return 1;
+}
+
+static int hlWindowMove(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.move: expected a table, e.g. { direction = \"left\" }");
+
+ auto dirStr = Internal::tableOptStr(L, 1, "direction");
+ if (dirStr) {
+ auto dir = Internal::parseDirectionStr(*dirStr);
+ if (dir == Math::DIRECTION_DEFAULT)
+ return Internal::configError(L, "hl.window.move: invalid direction \"{}\" (expected left/right/up/down)", *dirStr);
+
+ auto groupAware = Internal::tableOptBool(L, 1, "group_aware");
+ if (groupAware && *groupAware) {
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveWindowOrGroup, 2);
+ return 1;
+ }
+
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveInDirection, 2);
+ return 1;
+ }
+
+ auto x = Internal::tableOptNum(L, 1, "x");
+ auto y = Internal::tableOptNum(L, 1, "y");
+ if (x && y) {
+ bool relative = Internal::tableOptBool(L, 1, "relative").value_or(false);
+ lua_pushnumber(L, *x);
+ lua_pushnumber(L, *y);
+ lua_pushboolean(L, relative);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_move, 4);
+ return 1;
+ }
+
+ auto ws = Internal::tableOptWorkspaceSelector(L, 1, "workspace", "hl.window.move");
+ if (ws) {
+ auto follow = Internal::tableOptBool(L, 1, "follow");
+ bool silent = follow.has_value() && !*follow;
+ lua_pushstring(L, ws->c_str());
+ lua_pushboolean(L, silent);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveToWorkspace, 3);
+ return 1;
+ }
+
+ auto mon = Internal::tableOptMonitorSelector(L, 1, "monitor", "hl.window.move");
+ if (mon) {
+ auto follow = Internal::tableOptBool(L, 1, "follow");
+ bool silent = follow.has_value() && !*follow;
+ lua_pushstring(L, mon->c_str());
+ lua_pushboolean(L, silent);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveToMonitor, 3);
+ return 1;
+ }
+
+ auto intoGroup = Internal::tableOptStr(L, 1, "into_group");
+ if (intoGroup) {
+ auto dir = Internal::parseDirectionStr(*intoGroup);
+ if (dir == Math::DIRECTION_DEFAULT)
+ return Internal::configError(L, "hl.window.move: invalid into_group direction \"{}\"", *intoGroup);
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveIntoGroup, 2);
+ return 1;
+ }
+
+ auto intoOrCreateGroup = Internal::tableOptStr(L, 1, "into_or_create_group");
+ if (intoOrCreateGroup) {
+ auto dir = Internal::parseDirectionStr(*intoOrCreateGroup);
+ if (dir == Math::DIRECTION_DEFAULT)
+ return Internal::configError(L, "hl.window.move: invalid into_or_create_group direction \"{}\"", *intoOrCreateGroup);
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveIntoOrCreateGroup, 2);
+ return 1;
+ }
+
+ auto outOfGroup = Internal::tableOptStr(L, 1, "out_of_group");
+ if (outOfGroup) {
+ auto dir = Internal::parseDirectionStr(*outOfGroup);
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveOutOfGroup, 2);
+ return 1;
+ }
+
+ auto outOfGroupBool = Internal::tableOptBool(L, 1, "out_of_group");
+ if (outOfGroupBool && *outOfGroupBool) {
+ lua_pushnumber(L, (int)Math::DIRECTION_DEFAULT);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_moveOutOfGroup, 2);
+ return 1;
+ }
+
+ return Internal::configError(L, "hl.window.move: unrecognized arguments. Expected one of: direction, x+y(+relative), workspace, into_group, out_of_group");
+}
+
+static int hlWindowSwap(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.swap: expected a table, e.g. { direction = \"left\" }");
+
+ auto dirStr = Internal::tableOptStr(L, 1, "direction");
+ if (dirStr) {
+ auto dir = Internal::parseDirectionStr(*dirStr);
+ if (dir == Math::DIRECTION_DEFAULT)
+ return Internal::configError(L, "hl.window.swap: invalid direction \"{}\" (expected left/right/up/down)", *dirStr);
+ lua_pushnumber(L, (int)dir);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_swapInDirection, 2);
+ return 1;
+ }
+
+ auto target = Internal::tableOptWindowSelector(L, 1, "target", "hl.window.swap");
+ if (!target)
+ target = Internal::tableOptWindowSelector(L, 1, "with", "hl.window.swap");
+ if (!target)
+ target = Internal::tableOptWindowSelector(L, 1, "other", "hl.window.swap");
+
+ if (target) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushstring(L, target->c_str());
+ lua_pushcclosure(L, dsp_swapWithWindow, 2);
+ return 1;
+ }
+
+ auto next = Internal::tableOptBool(L, 1, "next");
+ if (next && *next) {
+ lua_pushboolean(L, true);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_swapNext, 2);
+ return 1;
+ }
+
+ auto prev = Internal::tableOptBool(L, 1, "prev");
+ if (prev && *prev) {
+ lua_pushboolean(L, false);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_swapNext, 2);
+ return 1;
+ }
+
+ return Internal::configError(L, "hl.window.swap: unrecognized arguments. Expected one of: direction, target/with/other, next, prev");
+}
+
+static int hlWindowCenter(lua_State* L) {
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_center, 1);
+ return 1;
+}
+
+static int hlWindowCycleNext(lua_State* L) {
+ bool next = true;
+ int tiled = -1;
+ int floating = -1;
+ if (lua_istable(L, 1)) {
+ auto n = Internal::tableOptBool(L, 1, "next");
+ if (n)
+ next = *n;
+ auto t = Internal::tableOptBool(L, 1, "tiled");
+ if (t)
+ tiled = *t ? 1 : 0;
+ auto f = Internal::tableOptBool(L, 1, "floating");
+ if (f)
+ floating = *f ? 1 : 0;
+ }
+ lua_pushboolean(L, next);
+ lua_pushnumber(L, tiled);
+ lua_pushnumber(L, floating);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_cycleNext, 4);
+ return 1;
+}
+
+static int hlWindowTag(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.tag: expected a table { tag, window? }");
+
+ const auto tag = Internal::requireTableFieldStr(L, 1, "tag", "hl.window.tag");
+ lua_pushstring(L, tag.c_str());
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_tagWindow, 2);
+ return 1;
+}
+
+static int hlWindowToggleSwallow(lua_State* L) {
+ lua_pushcclosure(L, dsp_toggleSwallow, 0);
+ return 1;
+}
+
+static int hlWindowResizeExact(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.resize: expected a table { x, y, relative?, window? }");
+ auto x = Internal::tableOptNum(L, 1, "x");
+ auto y = Internal::tableOptNum(L, 1, "y");
+ bool relative = Internal::tableOptBool(L, 1, "relative").value_or(false);
+ if (!x || !y)
+ return Internal::configError(L, "hl.window.resize: 'x' and 'y' are required");
+ lua_pushnumber(L, *x);
+ lua_pushnumber(L, *y);
+ lua_pushboolean(L, relative);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_resize, 4);
+ return 1;
+}
+
+static int hlWindowPin(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_pinWindow, 2);
+ return 1;
+}
+
+static int hlWindowBringToTop(lua_State* L) {
+ lua_pushcclosure(L, dsp_bringToTop, 0);
+ return 1;
+}
+
+static int hlWindowAlterZOrder(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.alter_zorder: expected a table { mode, window? }");
+
+ const auto mode = Internal::requireTableFieldStr(L, 1, "mode", "hl.window.alter_zorder");
+ lua_pushstring(L, mode.c_str());
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_alterZOrder, 2);
+ return 1;
+}
+
+static int hlWindowSetProp(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.set_prop: expected a table { prop, value, window? }");
+
+ const auto prop = Internal::requireTableFieldStr(L, 1, "prop", "hl.window.set_prop");
+ const auto value = Internal::requireTableFieldStr(L, 1, "value", "hl.window.set_prop");
+ lua_pushstring(L, prop.c_str());
+ lua_pushstring(L, value.c_str());
+ Internal::pushWindowUpval(L, 1);
+ lua_pushcclosure(L, dsp_setProp, 3);
+ return 1;
+}
+
+static int hlWindowDenyFromGroup(lua_State* L) {
+ const auto action = Internal::tableToggleAction(L, 1);
+
+ lua_pushnumber(L, (int)action);
+ lua_pushcclosure(L, dsp_denyFromGroup, 1);
+ return 1;
+}
+
+static int hlWindowDrag(lua_State* L) {
+ lua_pushcclosure(L, dsp_mouseDrag, 0);
+ return 1;
+}
+
+static int hlWindowResize(lua_State* L) {
+ if (lua_gettop(L) == 0 || lua_isnil(L, 1)) {
+ lua_pushcclosure(L, dsp_mouseResize, 0);
+ return 1;
+ }
+
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.window.resize: expected no args, or a table { x, y, relative?, window? }");
+
+ return hlWindowResizeExact(L);
+}
+
+static int dsp_moveFocus(lua_State* L) {
+ Internal::checkResult(L, CA::moveFocus(sc<Math::eDirection>((int)lua_tonumber(L, lua_upvalueindex(1)))));
+ return 0;
+}
+
+static int dsp_focusMonitor(lua_State* L) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(1)));
+ if (!PMONITOR)
+ return Internal::configError(L, "hl.focus.monitor: monitor not found");
+ Internal::checkResult(L, CA::focusMonitor(PMONITOR));
+ return 0;
+}
+
+static int dsp_focusWindowBySelector(lua_State* L) {
+ const auto PWINDOW = g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(1)));
+ if (!PWINDOW)
+ return Internal::configError(L, "hl.focus: window not found");
+ Internal::checkResult(L, CA::focus(PWINDOW));
+ return 0;
+}
+
+static int dsp_focusUrgentOrLast(lua_State* L) {
+ Internal::checkResult(L, CA::focusUrgentOrLast());
+ return 0;
+}
+
+static int dsp_focusCurrentOrLast(lua_State* L) {
+ Internal::checkResult(L, CA::focusCurrentOrLast());
+ return 0;
+}
+
+static int dsp_changeWorkspace(lua_State* L) {
+ Internal::checkResult(L, CA::changeWorkspace(std::string(lua_tostring(L, lua_upvalueindex(1)))));
+ return 0;
+}
+
+static int dsp_focusWorkspaceOnCurrentMonitor(lua_State* L) {
+ auto ws = Internal::resolveWorkspaceStr(lua_tostring(L, lua_upvalueindex(1)));
+ if (!ws)
+ return Internal::configError(L, "Invalid workspace");
+ Internal::checkResult(L, CA::changeWorkspaceOnCurrentMonitor(ws));
+ return 0;
+}
+
+static int hlFocus(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.focus: expected a table, e.g. { direction = \"left\" }");
+
+ auto dirStr = Internal::tableOptStr(L, 1, "direction");
+ if (dirStr) {
+ auto dir = Internal::parseDirectionStr(*dirStr);
+ if (dir == Math::DIRECTION_DEFAULT)
+ return Internal::configError(L, "hl.focus: invalid direction \"{}\" (expected left/right/up/down)", *dirStr);
+ lua_pushnumber(L, (int)dir);
+ lua_pushcclosure(L, dsp_moveFocus, 1);
+ return 1;
+ }
+
+ auto monStr = Internal::tableOptMonitorSelector(L, 1, "monitor", "hl.focus");
+ if (monStr) {
+ lua_pushstring(L, monStr->c_str());
+ lua_pushcclosure(L, dsp_focusMonitor, 1);
+ return 1;
+ }
+
+ auto wsStr = Internal::tableOptWorkspaceSelector(L, 1, "workspace", "hl.focus");
+ if (wsStr) {
+ auto onCurrentMon = Internal::tableOptBool(L, 1, "on_current_monitor");
+
+ lua_pushstring(L, wsStr->c_str());
+
+ if (onCurrentMon.value_or(false))
+ lua_pushcclosure(L, dsp_focusWorkspaceOnCurrentMonitor, 1);
+ else
+ lua_pushcclosure(L, dsp_changeWorkspace, 1);
+
+ return 1;
+ }
+
+ auto winStr = Internal::tableOptWindowSelector(L, 1, "window", "hl.focus");
+ if (winStr) {
+ lua_pushstring(L, winStr->c_str());
+ lua_pushcclosure(L, dsp_focusWindowBySelector, 1);
+ return 1;
+ }
+
+ auto urgent = Internal::tableOptBool(L, 1, "urgent_or_last");
+ if (urgent && *urgent) {
+ lua_pushcclosure(L, dsp_focusUrgentOrLast, 0);
+ return 1;
+ }
+
+ auto last = Internal::tableOptBool(L, 1, "last");
+ if (last && *last) {
+ lua_pushcclosure(L, dsp_focusCurrentOrLast, 0);
+ return 1;
+ }
+
+ return Internal::configError(L, "hl.focus: unrecognized arguments. Expected one of: direction, monitor, window, urgent_or_last, last");
+}
+
+static int dsp_toggleSpecial(lua_State* L) {
+ std::string name = lua_isnil(L, lua_upvalueindex(1)) ? "" : lua_tostring(L, lua_upvalueindex(1));
+ const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString("special:" + name);
+ if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID))
+ return Internal::configError(L, "Invalid special workspace");
+
+ auto ws = g_pCompositor->getWorkspaceByID(workspaceID);
+ if (!ws) {
+ const auto PMONITOR = Desktop::focusState()->monitor();
+ if (PMONITOR)
+ ws = g_pCompositor->createNewWorkspace(workspaceID, PMONITOR->m_id, workspaceName);
+ }
+ if (!ws)
+ return Internal::configError(L, "Could not resolve special workspace");
+
+ Internal::checkResult(L, CA::toggleSpecial(ws));
+ return 0;
+}
+
+static int dsp_renameWorkspace(lua_State* L) {
+ const auto PWS = g_pCompositor->getWorkspaceByString(lua_tostring(L, lua_upvalueindex(1)));
+ if (!PWS)
+ return Internal::configError(L, "hl.workspace.rename: no such workspace");
+ std::string name = lua_isnil(L, lua_upvalueindex(2)) ? "" : lua_tostring(L, lua_upvalueindex(2));
+ Internal::checkResult(L, CA::renameWorkspace(PWS, name));
+ return 0;
+}
+
+static int dsp_moveWorkspaceToMonitor(lua_State* L) {
+ const auto WORKSPACEID = getWorkspaceIDNameFromString(lua_tostring(L, lua_upvalueindex(1))).id;
+ if (WORKSPACEID == WORKSPACE_INVALID)
+ return Internal::configError(L, "Invalid workspace");
+ const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID);
+ if (!PWORKSPACE)
+ return Internal::configError(L, "Workspace not found");
+ const auto PMONITOR = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(2)));
+ if (!PMONITOR)
+ return Internal::configError(L, "Monitor not found");
+ Internal::checkResult(L, CA::moveToMonitor(PWORKSPACE, PMONITOR));
+ return 0;
+}
+
+static int dsp_moveCurrentWorkspaceToMonitor(lua_State* L) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(1)));
+ if (!PMONITOR)
+ return Internal::configError(L, "Monitor not found");
+ const auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;
+ if (!PCURRENTWORKSPACE)
+ return Internal::configError(L, "Invalid workspace");
+ Internal::checkResult(L, CA::moveToMonitor(PCURRENTWORKSPACE, PMONITOR));
+ return 0;
+}
+
+static int dsp_swapActiveWorkspaces(lua_State* L) {
+ const auto PMON1 = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(1)));
+ const auto PMON2 = g_pCompositor->getMonitorFromString(lua_tostring(L, lua_upvalueindex(2)));
+ if (!PMON1 || !PMON2)
+ return Internal::configError(L, "Monitor not found");
+ Internal::checkResult(L, CA::swapActiveWorkspaces(PMON1, PMON2));
+ return 0;
+}
+
+static int hlWorkspaceToggleSpecial(lua_State* L) {
+ lua_pushstring(L, lua_tostring(L, 1));
+ lua_pushcclosure(L, dsp_toggleSpecial, 1);
+ return 1;
+}
+
+static int hlWorkspaceRename(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.workspace.rename: expected a table { id, name? }");
+
+ const auto id = Internal::requireTableFieldWorkspaceSelector(L, 1, "id", "hl.workspace.rename");
+ auto name = Internal::tableOptStr(L, 1, "name");
+
+ lua_pushstring(L, id.c_str());
+ if (name)
+ lua_pushstring(L, name->c_str());
+ else
+ lua_pushnil(L);
+ lua_pushcclosure(L, dsp_renameWorkspace, 2);
+ return 1;
+}
+
+static int hlWorkspaceMove(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.workspace.move: expected a table, e.g. { monitor = \"DP-1\" }");
+
+ const auto mon = Internal::requireTableFieldMonitorSelector(L, 1, "monitor", "hl.workspace.move");
+
+ auto id = Internal::tableOptWorkspaceSelector(L, 1, "id", "hl.workspace.move");
+ if (id) {
+ lua_pushstring(L, id->c_str());
+ lua_pushstring(L, mon.c_str());
+ lua_pushcclosure(L, dsp_moveWorkspaceToMonitor, 2);
+ return 1;
+ }
+
+ lua_pushstring(L, mon.c_str());
+ lua_pushcclosure(L, dsp_moveCurrentWorkspaceToMonitor, 1);
+ return 1;
+}
+
+static int hlWorkspaceSwapMonitors(lua_State* L) {
+ if (!lua_istable(L, 1))
+ return Internal::configError(L, "hl.workspace.swap_monitors: expected a table { monitor1, monitor2 }");
+
+ const auto m1 = Internal::requireTableFieldMonitorSelector(L, 1, "monitor1", "hl.workspace.swap_monitors");
+ const auto m2 = Internal::requireTableFieldMonitorSelector(L, 1, "monitor2", "hl.workspace.swap_monitors");
+ lua_pushstring(L, m1.c_str());
+ lua_pushstring(L, m2.c_str());
+ lua_pushcclosure(L, dsp_swapActiveWorkspaces, 2);
+ return 1;
+}
+
+void Internal::registerDispatcherBindings(lua_State* L) {
+ lua_newtable(L);
+
+ {
+ lua_newtable(L);
+ Internal::setFn(L, "move_to_corner", hlCursorMoveToCorner);
+ Internal::setFn(L, "move", hlCursorMove);
+ lua_setfield(L, -2, "cursor");
+
+ lua_newtable(L);
+ Internal::setFn(L, "toggle", hlGroupToggle);
+ Internal::setFn(L, "next", hlGroupNext);
+ Internal::setFn(L, "prev", hlGroupPrev);
+ Internal::setFn(L, "active", hlGroupActive);
+ Internal::setFn(L, "move_window", hlGroupMoveWindow);
+ Internal::setFn(L, "lock", hlGroupLock);
+ Internal::setFn(L, "lock_active", hlGroupLockActive);
+ lua_setfield(L, -2, "group");
+
+ lua_newtable(L);
+ Internal::setFn(L, "close", hlWindowClose);
+ Internal::setFn(L, "kill", hlWindowKill);
+ Internal::setFn(L, "signal", hlWindowSignal);
+ Internal::setFn(L, "float", hlWindowFloat);
+ Internal::setFn(L, "fullscreen", hlWindowFullscreen);
+ Internal::setFn(L, "fullscreen_state", hlWindowFullscreenState);
+ Internal::setFn(L, "pseudo", hlWindowPseudo);
+ Internal::setFn(L, "move", hlWindowMove);
+ Internal::setFn(L, "swap", hlWindowSwap);
+ Internal::setFn(L, "center", hlWindowCenter);
+ Internal::setFn(L, "cycle_next", hlWindowCycleNext);
+ Internal::setFn(L, "tag", hlWindowTag);
+ Internal::setFn(L, "toggle_swallow", hlWindowToggleSwallow);
+ Internal::setFn(L, "pin", hlWindowPin);
+ Internal::setFn(L, "bring_to_top", hlWindowBringToTop);
+ Internal::setFn(L, "alter_zorder", hlWindowAlterZOrder);
+ Internal::setFn(L, "set_prop", hlWindowSetProp);
+ Internal::setFn(L, "deny_from_group", hlWindowDenyFromGroup);
+ Internal::setFn(L, "drag", hlWindowDrag);
+ Internal::setFn(L, "resize", hlWindowResize);
+ lua_setfield(L, -2, "window");
+
+ lua_newtable(L);
+ Internal::setFn(L, "rename", hlWorkspaceRename);
+ Internal::setFn(L, "move", hlWorkspaceMove);
+ Internal::setFn(L, "swap_monitors", hlWorkspaceSwapMonitors);
+ Internal::setFn(L, "toggle_special", hlWorkspaceToggleSpecial);
+ lua_setfield(L, -2, "workspace");
+
+ Internal::setFn(L, "exec_cmd", hlExecCmd);
+ Internal::setFn(L, "exec_raw", hlExecRaw);
+ Internal::setFn(L, "exit", hlExit);
+ Internal::setFn(L, "submap", hlSubmap);
+ Internal::setFn(L, "pass", hlPass);
+ Internal::setFn(L, "send_shortcut", hlSendShortcut);
+ Internal::setFn(L, "send_key_state", hlSendKeyState);
+ Internal::setFn(L, "layout", hlLayout);
+ Internal::setFn(L, "dpms", hlDpms);
+ Internal::setFn(L, "event", hlEvent);
+ Internal::setFn(L, "global", hlGlobal);
+ Internal::setFn(L, "force_renderer_reload", hlForceRendererReload);
+ Internal::setFn(L, "force_idle", hlForceIdle);
+ Internal::setFn(L, "focus", hlFocus);
+ }
+
+ lua_setfield(L, -2, "dsp");
+}