mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
refactor(Hyprland): updates to Lua syntax/dispatchers
This commit is contained in:
@@ -493,6 +493,31 @@ func splitHyprlandAction(action string) (dispatcher, params string) {
|
||||
return strings.ToLower(strings.TrimSpace(action[:idx])), strings.TrimSpace(action[idx+1:])
|
||||
}
|
||||
|
||||
func isKnownHyprlandDispatcher(dispatcher string) bool {
|
||||
switch dispatcher {
|
||||
case "exec", "execr", "spawn",
|
||||
"killactive", "forcekillactive", "closewindow", "killwindow",
|
||||
"signal", "signalwindow", "togglefloating", "setfloating", "settiled",
|
||||
"workspace", "renameworkspace", "fullscreen", "fullscreenstate", "fakefullscreen",
|
||||
"movetoworkspace", "movetoworkspacesilent", "pseudo", "movefocus",
|
||||
"movewindow", "swapwindow", "centerwindow", "togglegroup", "changegroupactive",
|
||||
"movegroupwindow", "focusmonitor", "movecursortocorner", "movecursor",
|
||||
"workspaceopt", "exit", "movecurrentworkspacetomonitor", "focusworkspaceoncurrentmonitor",
|
||||
"moveworkspacetomonitor", "togglespecialworkspace", "forcerendererreload",
|
||||
"resizeactive", "moveactive", "cyclenext", "focuswindowbyclass", "focuswindow",
|
||||
"tagwindow", "toggleswallow", "submap", "pass", "sendshortcut", "sendkeystate",
|
||||
"layoutmsg", "splitratio", "dpms", "movewindowpixel", "resizewindowpixel",
|
||||
"swapnext", "swapactiveworkspaces", "pin", "mouse", "bringactivetotop",
|
||||
"alterzorder", "focusurgentorlast", "focuscurrentorlast", "lockgroups",
|
||||
"lockactivegroup", "moveintogroup", "moveoutofgroup", "movewindoworgroup",
|
||||
"moveintoorcreategroup", "setignoregrouplock", "denywindowfromgroup", "event",
|
||||
"global", "setprop", "forceidle":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func firstParam(params string) (head, rest string) {
|
||||
params = strings.TrimSpace(params)
|
||||
if params == "" {
|
||||
@@ -551,29 +576,181 @@ func dispatcherActiveMoveResize(funcName, params string) string {
|
||||
)
|
||||
}
|
||||
|
||||
func dispatcherWindowMoveResize(funcName, params string) string {
|
||||
geometry, window := splitCommaParams(params)
|
||||
x, y, relative, ok := xyParams(geometry)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if !isBareLuaNumber(x) || !isBareLuaNumber(y) {
|
||||
return ""
|
||||
}
|
||||
fields := []luaField{
|
||||
luaNumberOrStringField("x", x),
|
||||
luaNumberOrStringField("y", y),
|
||||
luaBoolField("relative", relative),
|
||||
}
|
||||
if window != "" {
|
||||
fields = append(fields, luaStringField("window", window))
|
||||
}
|
||||
return luaDispatcherTableCall(funcName, fields...)
|
||||
}
|
||||
|
||||
func splitCommaParams(params string) (left, right string) {
|
||||
left = strings.TrimSpace(params)
|
||||
if idx := strings.Index(left, ","); idx >= 0 {
|
||||
right = strings.TrimSpace(left[idx+1:])
|
||||
left = strings.TrimSpace(left[:idx])
|
||||
}
|
||||
return left, right
|
||||
}
|
||||
|
||||
func luaHyprctlDispatchFunction(action string) string {
|
||||
return fmt.Sprintf(`function() hl.exec_cmd(%s) end`, strconv.Quote("hyprctl dispatch "+strings.TrimSpace(action)))
|
||||
}
|
||||
|
||||
func luaToggleActionValue(params string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(params)) {
|
||||
case "on", "enable", "enabled", "set", "lock":
|
||||
return "on"
|
||||
case "off", "disable", "disabled", "unset", "unlock":
|
||||
return "off"
|
||||
default:
|
||||
return "toggle"
|
||||
}
|
||||
}
|
||||
|
||||
func dispatcherToggleTableCall(funcName, params string) string {
|
||||
return luaDispatcherTableCall(funcName, luaStringField("action", luaToggleActionValue(params)))
|
||||
}
|
||||
|
||||
func dispatcherCycleNext(params string) string {
|
||||
params = strings.TrimSpace(strings.ToLower(params))
|
||||
if params == "" {
|
||||
return `hl.dsp.window.cycle_next()`
|
||||
}
|
||||
fields := []luaField{}
|
||||
for _, field := range strings.Fields(params) {
|
||||
switch field {
|
||||
case "prev", "previous", "b":
|
||||
fields = append(fields, luaBoolField("next", false))
|
||||
case "next", "f":
|
||||
fields = append(fields, luaBoolField("next", true))
|
||||
case "tiled":
|
||||
fields = append(fields, luaBoolField("tiled", true))
|
||||
case "floating":
|
||||
fields = append(fields, luaBoolField("floating", true))
|
||||
}
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.cycle_next", fields...)
|
||||
}
|
||||
|
||||
func dispatcherSwapNext(params string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(params)) {
|
||||
case "prev", "previous", "b":
|
||||
return `hl.dsp.window.swap({ prev = true })`
|
||||
default:
|
||||
return `hl.dsp.window.swap({ next = true })`
|
||||
}
|
||||
}
|
||||
|
||||
func dispatcherGroupActive(params string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(params)) {
|
||||
case "f", "next", "forward":
|
||||
return `hl.dsp.group.next()`
|
||||
case "b", "prev", "previous", "backward":
|
||||
return `hl.dsp.group.prev()`
|
||||
}
|
||||
if isBareLuaNumber(params) {
|
||||
return luaDispatcherTableCall("hl.dsp.group.active", luaNumberOrStringField("index", params))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func dispatcherMoveGroupWindow(params string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(params)) {
|
||||
case "b", "prev", "previous", "backward":
|
||||
return `hl.dsp.group.move_window({ forward = false })`
|
||||
default:
|
||||
return `hl.dsp.group.move_window({ forward = true })`
|
||||
}
|
||||
}
|
||||
|
||||
func dispatcherCursorMove(params string) string {
|
||||
x, y, _, ok := xyParams(params)
|
||||
if !ok || !isBareLuaNumber(x) || !isBareLuaNumber(y) {
|
||||
return ""
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.cursor.move", luaNumberOrStringField("x", x), luaNumberOrStringField("y", y))
|
||||
}
|
||||
|
||||
func dispatcherSignal(params string) string {
|
||||
signal, window := firstParam(params)
|
||||
if signal == "" || !isBareLuaNumber(signal) {
|
||||
return ""
|
||||
}
|
||||
fields := []luaField{luaNumberOrStringField("signal", signal)}
|
||||
if window != "" {
|
||||
fields = append(fields, luaStringField("window", window))
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.signal", fields...)
|
||||
}
|
||||
|
||||
func dispatcherSignalWindow(params string) string {
|
||||
window, rest := firstParam(params)
|
||||
signal, _ := firstParam(rest)
|
||||
if signal == "" || !isBareLuaNumber(signal) {
|
||||
return ""
|
||||
}
|
||||
fields := []luaField{luaNumberOrStringField("signal", signal)}
|
||||
if window != "" {
|
||||
fields = append(fields, luaStringField("window", window))
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.signal", fields...)
|
||||
}
|
||||
|
||||
func dispatcherTagWindow(params string) string {
|
||||
tag, window := firstParam(params)
|
||||
if tag == "" {
|
||||
return ""
|
||||
}
|
||||
fields := []luaField{luaStringField("tag", tag)}
|
||||
if window != "" {
|
||||
fields = append(fields, luaStringField("window", window))
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.tag", fields...)
|
||||
}
|
||||
|
||||
func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
dispatcher, params := splitHyprlandAction(action)
|
||||
switch dispatcher {
|
||||
case "spawn", "exec":
|
||||
return fmt.Sprintf(`hl.dsp.exec_cmd(%s)`, strconv.Quote(params)), true
|
||||
case "execr":
|
||||
return fmt.Sprintf(`hl.dsp.exec_raw(%s)`, strconv.Quote(params)), true
|
||||
case "killactive":
|
||||
return `hl.dsp.window.kill()`, true
|
||||
case "forcekillactive":
|
||||
return `hl.dsp.window.kill()`, true
|
||||
case "closewindow":
|
||||
if params == "" {
|
||||
return `hl.dsp.window.close()`, true
|
||||
}
|
||||
return fmt.Sprintf(`hl.dsp.window.close(%s)`, strconv.Quote(params)), true
|
||||
return luaDispatcherTableCall("hl.dsp.window.close", luaStringField("window", params)), true
|
||||
case "killwindow":
|
||||
if params == "" {
|
||||
return `hl.dsp.window.kill()`, true
|
||||
}
|
||||
return fmt.Sprintf(`hl.dsp.window.kill(%s)`, strconv.Quote(params)), true
|
||||
return luaDispatcherTableCall("hl.dsp.window.kill", luaStringField("window", params)), true
|
||||
case "togglefloating":
|
||||
return `hl.dsp.window.float({ action = "toggle" })`, true
|
||||
return dispatcherToggleTableCall("hl.dsp.window.float", "toggle"), true
|
||||
case "setfloating":
|
||||
return `hl.dsp.window.float({ action = "set" })`, true
|
||||
return dispatcherToggleTableCall("hl.dsp.window.float", "on"), true
|
||||
case "settiled":
|
||||
return `hl.dsp.window.float({ action = "unset" })`, true
|
||||
return dispatcherToggleTableCall("hl.dsp.window.float", "off"), true
|
||||
case "fullscreen":
|
||||
mode := strings.TrimSpace(params)
|
||||
switch mode {
|
||||
@@ -582,6 +759,7 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
case "1":
|
||||
return `hl.dsp.window.fullscreen({ mode = "maximized", action = "toggle" })`, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "fullscreenstate":
|
||||
internal, rest := firstParam(params)
|
||||
client, _ := firstParam(rest)
|
||||
@@ -591,10 +769,15 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
luaNumberOrStringField("client", client),
|
||||
), true
|
||||
}
|
||||
case "fakefullscreen":
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "pin":
|
||||
if params == "" {
|
||||
return `hl.dsp.window.pin()`, true
|
||||
}
|
||||
return dispatcherToggleTableCall("hl.dsp.window.pin", params), true
|
||||
case "pseudo":
|
||||
return dispatcherToggleTableCall("hl.dsp.window.pseudo", params), true
|
||||
case "centerwindow":
|
||||
return `hl.dsp.window.center()`, true
|
||||
case "resizewindow":
|
||||
@@ -612,14 +795,28 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.swap", luaStringField("direction", params)), true
|
||||
case "swapnext":
|
||||
return dispatcherSwapNext(params), true
|
||||
case "resizeactive":
|
||||
if expr := dispatcherActiveMoveResize("hl.dsp.window.resize", params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "moveactive":
|
||||
if expr := dispatcherActiveMoveResize("hl.dsp.window.move", params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "resizewindowpixel":
|
||||
if expr := dispatcherWindowMoveResize("hl.dsp.window.resize", params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "movewindowpixel":
|
||||
if expr := dispatcherWindowMoveResize("hl.dsp.window.move", params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "workspace":
|
||||
if params == "" {
|
||||
return "", false
|
||||
@@ -662,6 +859,8 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
if workspace != "" && monitor != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.workspace.move", luaStringField("workspace", workspace), luaStringField("monitor", monitor)), true
|
||||
}
|
||||
case "workspaceopt":
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "swapactiveworkspaces":
|
||||
monitor1, rest := firstParam(params)
|
||||
monitor2, _ := firstParam(rest)
|
||||
@@ -680,14 +879,25 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.focus", luaStringField("window", params)), true
|
||||
}
|
||||
case "focuswindowbyclass":
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.focus", luaStringField("window", "class:"+params)), true
|
||||
}
|
||||
case "focuscurrentorlast":
|
||||
return `hl.dsp.focus({ last = true })`, true
|
||||
case "focusurgentorlast":
|
||||
return `hl.dsp.focus({ urgent_or_last = true })`, true
|
||||
case "cyclenext":
|
||||
if expr := dispatcherCycleNext(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "layoutmsg":
|
||||
if params != "" {
|
||||
return fmt.Sprintf(`hl.dsp.layout(%s)`, strconv.Quote(params)), true
|
||||
}
|
||||
case "splitratio":
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "alterzorder":
|
||||
mode, window := firstParam(params)
|
||||
if mode != "" {
|
||||
@@ -707,6 +917,22 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
luaStringField("value", value),
|
||||
), true
|
||||
}
|
||||
case "bringactivetotop":
|
||||
return `hl.dsp.window.bring_to_top()`, true
|
||||
case "toggleswallow":
|
||||
return `hl.dsp.window.toggle_swallow()`, true
|
||||
case "signal":
|
||||
if expr := dispatcherSignal(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
case "signalwindow":
|
||||
if expr := dispatcherSignalWindow(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
case "tagwindow":
|
||||
if expr := dispatcherTagWindow(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
case "dpms":
|
||||
dpmsAction := strings.TrimSpace(params)
|
||||
switch dpmsAction {
|
||||
@@ -753,8 +979,57 @@ func luaActionStringFromKnownHyprlandAction(action string) (string, bool) {
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.send_key_state", fields...), true
|
||||
}
|
||||
case "movecursortocorner":
|
||||
if params != "" && isBareLuaNumber(params) {
|
||||
return luaDispatcherTableCall("hl.dsp.cursor.move_to_corner", luaNumberOrStringField("corner", params)), true
|
||||
}
|
||||
case "movecursor":
|
||||
if expr := dispatcherCursorMove(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
case "togglegroup":
|
||||
return `hl.dsp.group.toggle()`, true
|
||||
case "changegroupactive":
|
||||
if expr := dispatcherGroupActive(params); expr != "" {
|
||||
return expr, true
|
||||
}
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "movegroupwindow":
|
||||
return dispatcherMoveGroupWindow(params), true
|
||||
case "moveintogroup":
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.window.move", luaStringField("into_group", params)), true
|
||||
}
|
||||
case "moveintoorcreategroup":
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.window.move", luaStringField("into_or_create_group", params)), true
|
||||
}
|
||||
case "moveoutofgroup":
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.window.move", luaStringField("out_of_group", params)), true
|
||||
}
|
||||
return luaDispatcherTableCall("hl.dsp.window.move", luaBoolField("out_of_group", true)), true
|
||||
case "movewindoworgroup":
|
||||
if params != "" {
|
||||
return luaDispatcherTableCall("hl.dsp.window.move", luaStringField("direction", params), luaBoolField("group_aware", true)), true
|
||||
}
|
||||
case "lockgroups":
|
||||
return dispatcherToggleTableCall("hl.dsp.group.lock", params), true
|
||||
case "lockactivegroup":
|
||||
return dispatcherToggleTableCall("hl.dsp.group.lock_active", params), true
|
||||
case "denywindowfromgroup":
|
||||
return dispatcherToggleTableCall("hl.dsp.window.deny_from_group", params), true
|
||||
case "setignoregrouplock":
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
case "forcerendererreload":
|
||||
return `hl.dsp.force_renderer_reload()`, true
|
||||
case "forceidle":
|
||||
if params != "" && isBareLuaNumber(params) {
|
||||
return fmt.Sprintf(`hl.dsp.force_idle(%s)`, params), true
|
||||
}
|
||||
}
|
||||
if isKnownHyprlandDispatcher(dispatcher) {
|
||||
return luaHyprctlDispatchFunction(action), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -764,7 +1039,7 @@ func luaActionStringFromHyprlangAction(action string) string {
|
||||
if expr, ok := luaActionStringFromKnownHyprlandAction(action); ok {
|
||||
return expr
|
||||
}
|
||||
return fmt.Sprintf(`hl.dispatch(%s)`, strconv.Quote(action))
|
||||
return action
|
||||
}
|
||||
|
||||
func luaExprToInternalAction(expr string) string {
|
||||
|
||||
@@ -882,23 +882,20 @@ func parseLuaStringLiteral(line string, i int) (value string, next int, ok bool)
|
||||
return "", i, false
|
||||
}
|
||||
|
||||
// parseLuaFirstArgExpr parses a single Lua expression starting at i, stopping when parentheses
|
||||
// opened from the first '(' are balanced (handles nested () and {} and double-quoted strings).
|
||||
// parseLuaFirstArgExpr parses a single Lua expression starting at i, stopping at
|
||||
// the next top-level comma. It handles nested calls/tables and inline functions.
|
||||
func parseLuaFirstArgExpr(line string, start int) (expr string, next int, ok bool) {
|
||||
start = skipLuaWS(line, start)
|
||||
if start >= len(line) {
|
||||
return "", start, false
|
||||
}
|
||||
// Find first '(' of the call (e.g. hl.dsp.exec_cmd(...)
|
||||
firstParen := strings.IndexByte(line[start:], '(')
|
||||
if firstParen < 0 {
|
||||
return "", start, false
|
||||
}
|
||||
i := start + firstParen
|
||||
depth := 0
|
||||
i := start
|
||||
parenDepth := 0
|
||||
braceDepth := 0
|
||||
bracketDepth := 0
|
||||
functionDepth := 0
|
||||
inStr := byte(0)
|
||||
esc := false
|
||||
exprStart := start
|
||||
for ; i < len(line); i++ {
|
||||
c := line[i]
|
||||
if inStr != 0 {
|
||||
@@ -915,19 +912,66 @@ func parseLuaFirstArgExpr(line string, start int) (expr string, next int, ok boo
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c == '[' && i+1 < len(line) && line[i+1] == '[' {
|
||||
if end := strings.Index(line[i+2:], "]]"); end >= 0 {
|
||||
i += end + 3
|
||||
continue
|
||||
}
|
||||
return "", start, false
|
||||
}
|
||||
if luaWordAt(line, i, "function") {
|
||||
functionDepth++
|
||||
i += len("function") - 1
|
||||
continue
|
||||
}
|
||||
if luaWordAt(line, i, "end") && functionDepth > 0 {
|
||||
functionDepth--
|
||||
i += len("end") - 1
|
||||
continue
|
||||
}
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
inStr = c
|
||||
case '(':
|
||||
depth++
|
||||
parenDepth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return strings.TrimSpace(line[exprStart : i+1]), i + 1, true
|
||||
if parenDepth > 0 {
|
||||
parenDepth--
|
||||
}
|
||||
case '{':
|
||||
braceDepth++
|
||||
case '}':
|
||||
if braceDepth > 0 {
|
||||
braceDepth--
|
||||
}
|
||||
case '[':
|
||||
bracketDepth++
|
||||
case ']':
|
||||
if bracketDepth > 0 {
|
||||
bracketDepth--
|
||||
}
|
||||
case ',':
|
||||
if parenDepth == 0 && braceDepth == 0 && bracketDepth == 0 && functionDepth == 0 {
|
||||
return strings.TrimSpace(line[start:i]), i, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", start, false
|
||||
expr = strings.TrimSpace(line[start:i])
|
||||
return expr, i, expr != ""
|
||||
}
|
||||
|
||||
func luaWordAt(line string, idx int, word string) bool {
|
||||
if idx < 0 || idx+len(word) > len(line) || line[idx:idx+len(word)] != word {
|
||||
return false
|
||||
}
|
||||
before := idx == 0 || !isLuaIdentByte(line[idx-1])
|
||||
afterIdx := idx + len(word)
|
||||
after := afterIdx >= len(line) || !isLuaIdentByte(line[afterIdx])
|
||||
return before && after
|
||||
}
|
||||
|
||||
func isLuaIdentByte(c byte) bool {
|
||||
return c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||
}
|
||||
|
||||
// parseLuaBindInvocation parses one hl.bind("KEY", expr [, opts]) on a single line.
|
||||
@@ -1012,19 +1056,28 @@ func luaExprToDispatcherParams(expr string) (dispatcher, params string) {
|
||||
}
|
||||
}
|
||||
return "exec", strings.TrimSpace(strings.TrimPrefix(expr, "hl.dsp.exec_cmd"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.exec_raw("):
|
||||
return "execr", luaCallStringArgValue(expr, "hl.dsp.exec_raw")
|
||||
case strings.HasPrefix(expr, "hl.dispatch("):
|
||||
if arg := luaCallStringArgValue(expr, "hl.dispatch"); arg != "" {
|
||||
return splitDispatchCommand(arg)
|
||||
}
|
||||
return "", ""
|
||||
case strings.Contains(expr, "hl.exec_cmd("):
|
||||
if arg := luaEmbeddedCallStringArgValue(expr, "hl.exec_cmd"); strings.HasPrefix(arg, "hyprctl dispatch ") {
|
||||
return splitDispatchCommand(strings.TrimSpace(strings.TrimPrefix(arg, "hyprctl dispatch ")))
|
||||
}
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.close("):
|
||||
if window := luaTableStringField(expr, "window"); window != "" {
|
||||
return "closewindow", window
|
||||
}
|
||||
if arg := luaCallStringArgValue(expr, "hl.dsp.window.close"); arg != "" {
|
||||
return "closewindow", arg
|
||||
}
|
||||
return "closewindow", ""
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.kill("):
|
||||
if luaTableBoolFieldValue(expr, "force") {
|
||||
return "forcekillactive", ""
|
||||
if window := luaTableStringField(expr, "window"); window != "" {
|
||||
return "killwindow", window
|
||||
}
|
||||
if arg := luaCallStringArgValue(expr, "hl.dsp.window.kill"); arg != "" {
|
||||
return "killwindow", arg
|
||||
@@ -1043,23 +1096,50 @@ func luaExprToDispatcherParams(expr string) (dispatcher, params string) {
|
||||
client := luaStringValue(luaTableScalarField(expr, "client"))
|
||||
return joinDispatcherParams("fullscreenstate", internal, client)
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.float("):
|
||||
switch luaTableStringField(expr, "action") {
|
||||
case "set":
|
||||
switch luaToggleActionToLegacy(luaTableStringField(expr, "action")) {
|
||||
case "on":
|
||||
return "setfloating", ""
|
||||
case "unset":
|
||||
case "off":
|
||||
return "settiled", ""
|
||||
default:
|
||||
return "togglefloating", ""
|
||||
}
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.pseudo("):
|
||||
action := luaToggleActionToLegacy(luaTableStringField(expr, "action"))
|
||||
if action == "" || action == "toggle" {
|
||||
return "pseudo", ""
|
||||
}
|
||||
return "pseudo", action
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.pin("):
|
||||
if action := luaTableStringField(expr, "action"); action != "" && action != "toggle" {
|
||||
if action := luaToggleActionToLegacy(luaTableStringField(expr, "action")); action != "" && action != "toggle" {
|
||||
return "pin", action
|
||||
}
|
||||
return "pin", ""
|
||||
case strings.Contains(expr, "hl.dsp.window.center()"):
|
||||
return "centerwindow", ""
|
||||
case strings.Contains(expr, "hl.dsp.window.bring_to_top()"):
|
||||
return "bringactivetotop", ""
|
||||
case strings.Contains(expr, "hl.dsp.window.toggle_swallow()"):
|
||||
return "toggleswallow", ""
|
||||
case strings.Contains(expr, "hl.dsp.group.toggle()"):
|
||||
return "togglegroup", ""
|
||||
case strings.Contains(expr, "hl.dsp.group.next()"):
|
||||
return "changegroupactive", "f"
|
||||
case strings.Contains(expr, "hl.dsp.group.prev()"):
|
||||
return "changegroupactive", "b"
|
||||
case strings.HasPrefix(expr, "hl.dsp.group.active("):
|
||||
return "changegroupactive", luaStringValue(luaTableScalarField(expr, "index"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.group.move_window("):
|
||||
if forward, ok := luaTableBoolField(expr, "forward"); ok && !forward {
|
||||
return "movegroupwindow", "b"
|
||||
}
|
||||
return "movegroupwindow", "f"
|
||||
case strings.HasPrefix(expr, "hl.dsp.group.lock_active("):
|
||||
return "lockactivegroup", luaToggleActionToLockArg(luaTableStringField(expr, "action"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.group.lock("):
|
||||
return "lockgroups", luaToggleActionToLockArg(luaTableStringField(expr, "action"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.deny_from_group("):
|
||||
return "denywindowfromgroup", luaToggleActionToLegacy(luaTableStringField(expr, "action"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.focus("):
|
||||
switch {
|
||||
case luaTableStringField(expr, "direction") != "":
|
||||
@@ -1093,8 +1173,23 @@ func luaExprToDispatcherParams(expr string) (dispatcher, params string) {
|
||||
if raw, ok := luaTableBoolField(expr, "relative"); ok && !raw {
|
||||
prefix = "exact "
|
||||
}
|
||||
return "moveactive", prefix + x + " " + y
|
||||
params := prefix + x + " " + y
|
||||
if window := luaTableStringField(expr, "window"); window != "" {
|
||||
return "movewindowpixel", params + "," + window
|
||||
}
|
||||
return "moveactive", params
|
||||
case luaTableStringField(expr, "into_group") != "":
|
||||
return "moveintogroup", luaTableStringField(expr, "into_group")
|
||||
case luaTableStringField(expr, "into_or_create_group") != "":
|
||||
return "moveintoorcreategroup", luaTableStringField(expr, "into_or_create_group")
|
||||
case luaTableBoolFieldValue(expr, "out_of_group"):
|
||||
return "moveoutofgroup", ""
|
||||
case luaTableStringField(expr, "out_of_group") != "":
|
||||
return "moveoutofgroup", luaTableStringField(expr, "out_of_group")
|
||||
case luaTableStringField(expr, "direction") != "":
|
||||
if luaTableBoolFieldValue(expr, "group_aware") {
|
||||
return "movewindoworgroup", luaTableStringField(expr, "direction")
|
||||
}
|
||||
return "movewindow", luaTableStringField(expr, "direction")
|
||||
case luaTableStringField(expr, "monitor") != "":
|
||||
return "movewindow", "mon:" + luaTableStringField(expr, "monitor")
|
||||
@@ -1123,10 +1218,41 @@ func luaExprToDispatcherParams(expr string) (dispatcher, params string) {
|
||||
if relative, ok := luaTableBoolField(expr, "relative"); ok && !relative {
|
||||
prefix = "exact "
|
||||
}
|
||||
return "resizeactive", prefix + x + " " + y
|
||||
params := prefix + x + " " + y
|
||||
if window := luaTableStringField(expr, "window"); window != "" {
|
||||
return "resizewindowpixel", params + "," + window
|
||||
}
|
||||
return "resizeactive", params
|
||||
}
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.swap("):
|
||||
switch {
|
||||
case luaTableBoolFieldValue(expr, "next"):
|
||||
return "swapnext", ""
|
||||
case luaTableBoolFieldValue(expr, "prev"):
|
||||
return "swapnext", "prev"
|
||||
}
|
||||
return "swapwindow", luaTableStringField(expr, "direction")
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.cycle_next("):
|
||||
parts := []string{}
|
||||
if next, ok := luaTableBoolField(expr, "next"); ok && !next {
|
||||
parts = append(parts, "prev")
|
||||
}
|
||||
if luaTableBoolFieldValue(expr, "tiled") {
|
||||
parts = append(parts, "tiled")
|
||||
}
|
||||
if luaTableBoolFieldValue(expr, "floating") {
|
||||
parts = append(parts, "floating")
|
||||
}
|
||||
return "cyclenext", strings.Join(parts, " ")
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.signal("):
|
||||
signal := luaStringValue(luaTableScalarField(expr, "signal"))
|
||||
window := luaTableStringField(expr, "window")
|
||||
if window != "" {
|
||||
return joinDispatcherParams("signalwindow", window, signal)
|
||||
}
|
||||
return "signal", signal
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.tag("):
|
||||
return joinDispatcherParams("tagwindow", luaTableStringField(expr, "tag"), luaTableStringField(expr, "window"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.window.alter_zorder("):
|
||||
mode := luaTableStringField(expr, "mode")
|
||||
if mode == "" {
|
||||
@@ -1182,12 +1308,20 @@ func luaExprToDispatcherParams(expr string) (dispatcher, params string) {
|
||||
return joinDispatcherParams("sendshortcut", luaTableModsField(expr), luaTableStringField(expr, "key"), luaTableStringField(expr, "window"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.send_key_state("):
|
||||
return joinDispatcherParams("sendkeystate", luaTableModsField(expr), luaTableStringField(expr, "key"), luaTableStringField(expr, "state"), luaTableStringField(expr, "window"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.cursor.move_to_corner("):
|
||||
return "movecursortocorner", luaStringValue(luaTableScalarField(expr, "corner"))
|
||||
case strings.HasPrefix(expr, "hl.dsp.cursor.move("):
|
||||
return joinDispatcherParams("movecursor", luaStringValue(luaTableScalarField(expr, "x")), luaStringValue(luaTableScalarField(expr, "y")))
|
||||
case strings.Contains(expr, "hl.dsp.force_renderer_reload()"):
|
||||
return "forcerendererreload", ""
|
||||
case strings.HasPrefix(expr, "hl.dsp.force_idle("):
|
||||
return "forceidle", luaCallScalarArgValue(expr, "hl.dsp.force_idle")
|
||||
case strings.Contains(expr, "hl.dsp.exit()"):
|
||||
return "exit", ""
|
||||
default:
|
||||
return "exec", "hyprctl dispatch lua:" + expr
|
||||
return expr, ""
|
||||
}
|
||||
return "exec", "hyprctl dispatch lua:" + expr
|
||||
return expr, ""
|
||||
}
|
||||
|
||||
func splitDispatchCommand(command string) (dispatcher, params string) {
|
||||
@@ -1213,6 +1347,53 @@ func joinDispatcherParams(dispatcher string, values ...string) (string, string)
|
||||
return dispatcher, strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func luaEmbeddedCallStringArgValue(expr, funcName string) string {
|
||||
idx := strings.Index(expr, funcName+"(")
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
return luaCallStringArgValue(expr[idx:], funcName)
|
||||
}
|
||||
|
||||
func luaCallScalarArgValue(callExpr, funcName string) string {
|
||||
callExpr = strings.TrimSpace(callExpr)
|
||||
prefix := funcName + "("
|
||||
if !strings.HasPrefix(callExpr, prefix) {
|
||||
return ""
|
||||
}
|
||||
inner := strings.TrimSpace(callExpr[len(prefix):])
|
||||
if inner == "" {
|
||||
return ""
|
||||
}
|
||||
if s := luaCallStringArgValue(callExpr, funcName); s != "" {
|
||||
return s
|
||||
}
|
||||
re := regexp.MustCompile(`^-?\d+(?:\.\d+)?`)
|
||||
return re.FindString(inner)
|
||||
}
|
||||
|
||||
func luaToggleActionToLegacy(action string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(action)) {
|
||||
case "on", "enable", "enabled", "set", "lock":
|
||||
return "on"
|
||||
case "off", "disable", "disabled", "unset", "unlock":
|
||||
return "off"
|
||||
default:
|
||||
return "toggle"
|
||||
}
|
||||
}
|
||||
|
||||
func luaToggleActionToLockArg(action string) string {
|
||||
switch luaToggleActionToLegacy(action) {
|
||||
case "on":
|
||||
return "lock"
|
||||
case "off":
|
||||
return "unlock"
|
||||
default:
|
||||
return "toggle"
|
||||
}
|
||||
}
|
||||
|
||||
func extractLuaCallStringArg(callExpr, funcName string) string {
|
||||
callExpr = strings.TrimSpace(callExpr)
|
||||
prefix := funcName + "("
|
||||
|
||||
@@ -74,15 +74,31 @@ func TestHyprlandLuaBindRoundTripHelpers(t *testing.T) {
|
||||
{`hl.dispatch("workspace 2")`, "workspace", "2"},
|
||||
{`hl.dispatch([[customdispatcher arg one]])`, "customdispatcher", "arg one"},
|
||||
{`hl.dsp.window.fullscreen({ mode = "maximized", action = "toggle" })`, "fullscreen", "1"},
|
||||
{`hl.dsp.window.float({ action = "on" })`, "setfloating", ""},
|
||||
{`hl.dsp.window.close({ window = "class:^(kitty)$" })`, "closewindow", "class:^(kitty)$"},
|
||||
{`hl.dsp.focus({ workspace = "e+1" })`, "workspace", "e+1"},
|
||||
{`hl.dsp.focus({ workspace = "2", on_current_monitor = true })`, "focusworkspaceoncurrentmonitor", "2"},
|
||||
{`hl.dsp.window.move({ monitor = "l" })`, "movewindow", "mon:l"},
|
||||
{`hl.dsp.window.move({ direction = "r", group_aware = true })`, "movewindoworgroup", "r"},
|
||||
{`hl.dsp.window.move({ into_group = "l" })`, "moveintogroup", "l"},
|
||||
{`hl.dsp.window.move({ out_of_group = true })`, "moveoutofgroup", ""},
|
||||
{`hl.dsp.window.move({ workspace = "special:magic", follow = false })`, "movetoworkspacesilent", "special:magic"},
|
||||
{`hl.dsp.window.resize({ x = -100, y = 0, relative = true })`, "resizeactive", "-100 0"},
|
||||
{`hl.dsp.window.resize({ x = 1280, y = 720, relative = false })`, "resizeactive", "exact 1280 720"},
|
||||
{`hl.dsp.window.resize({ x = 100, y = 50, relative = true, window = "class:^(app)$" })`, "resizewindowpixel", "100 50,class:^(app)$"},
|
||||
{`hl.dsp.window.cycle_next({ next = false, tiled = true })`, "cyclenext", "prev tiled"},
|
||||
{`hl.dsp.group.next()`, "changegroupactive", "f"},
|
||||
{`hl.dsp.group.prev()`, "changegroupactive", "b"},
|
||||
{`hl.dsp.group.active({ index = 2 })`, "changegroupactive", "2"},
|
||||
{`hl.dsp.group.move_window({ forward = false })`, "movegroupwindow", "b"},
|
||||
{`hl.dsp.group.lock({ action = "on" })`, "lockgroups", "lock"},
|
||||
{`hl.dsp.group.lock_active({ action = "off" })`, "lockactivegroup", "unlock"},
|
||||
{`hl.dsp.window.deny_from_group({ action = "toggle" })`, "denywindowfromgroup", "toggle"},
|
||||
{`function() hl.exec_cmd("hyprctl dispatch splitratio +0.1") end`, "splitratio", "+0.1"},
|
||||
{`hl.dsp.layout("togglesplit")`, "layoutmsg", "togglesplit"},
|
||||
{`hl.dsp.dpms({ action = "toggle" })`, "dpms", "toggle"},
|
||||
{`hl.dsp.workspace.rename({ workspace = "1", name = "work" })`, "renameworkspace", "1 work"},
|
||||
{`hl.dsp.no_op()`, "hl.dsp.no_op()", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -126,6 +142,21 @@ hl.bind("SUPER + N", hl.dsp.exec_cmd("dms ipc call notepad toggle"), { descripti
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteLuaBindLineLeavesCustomLuaDispatcherRaw(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
writeLuaBindLine(&sb, &hyprlandOverrideBind{
|
||||
Key: "Super+u",
|
||||
Action: "hl.dsp.no_op()",
|
||||
Description: "Custom Lua",
|
||||
})
|
||||
|
||||
want := `hl.unbind("SUPER + U")
|
||||
hl.bind("SUPER + U", hl.dsp.no_op(), { description = "Custom Lua" })`
|
||||
if got := strings.TrimSpace(sb.String()); got != want {
|
||||
t.Fatalf("writeLuaBindLine() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaActionStringFromHyprlangActionUsesNativeDispatchers(t *testing.T) {
|
||||
tests := []struct {
|
||||
action string
|
||||
@@ -138,6 +169,22 @@ func TestLuaActionStringFromHyprlangActionUsesNativeDispatchers(t *testing.T) {
|
||||
{"resizeactive exact 1280 720", `hl.dsp.window.resize({ x = 1280, y = 720, relative = false })`},
|
||||
{"dpms toggle", `hl.dsp.dpms({ action = "toggle" })`},
|
||||
{"renameworkspace 1 work", `hl.dsp.workspace.rename({ workspace = "1", name = "work" })`},
|
||||
{"changegroupactive f", `hl.dsp.group.next()`},
|
||||
{"changegroupactive b", `hl.dsp.group.prev()`},
|
||||
{"changegroupactive 2", `hl.dsp.group.active({ index = 2 })`},
|
||||
{"moveintogroup l", `hl.dsp.window.move({ into_group = "l" })`},
|
||||
{"moveoutofgroup", `hl.dsp.window.move({ out_of_group = true })`},
|
||||
{"movewindoworgroup r", `hl.dsp.window.move({ direction = "r", group_aware = true })`},
|
||||
{"movegroupwindow b", `hl.dsp.group.move_window({ forward = false })`},
|
||||
{"lockgroups lock", `hl.dsp.group.lock({ action = "on" })`},
|
||||
{"lockactivegroup unlock", `hl.dsp.group.lock_active({ action = "off" })`},
|
||||
{"denywindowfromgroup toggle", `hl.dsp.window.deny_from_group({ action = "toggle" })`},
|
||||
{"cyclenext prev", `hl.dsp.window.cycle_next({ next = false })`},
|
||||
{"setfloating", `hl.dsp.window.float({ action = "on" })`},
|
||||
{"settiled", `hl.dsp.window.float({ action = "off" })`},
|
||||
{"bringactivetotop", `hl.dsp.window.bring_to_top()`},
|
||||
{"toggleswallow", `hl.dsp.window.toggle_swallow()`},
|
||||
{"forceidle 300", `hl.dsp.force_idle(300)`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -155,20 +202,34 @@ func TestLuaActionStringFromHyprlangActionUsesNativeDispatchers(t *testing.T) {
|
||||
|
||||
func TestLuaActionStringFallsBackForUnsupportedResizePercentages(t *testing.T) {
|
||||
got := luaActionStringFromHyprlangAction("resizeactive exact 100% 100%")
|
||||
want := `hl.dispatch("resizeactive exact 100% 100%")`
|
||||
want := `function() hl.exec_cmd("hyprctl dispatch resizeactive exact 100% 100%") end`
|
||||
if got != want {
|
||||
t.Fatalf("luaActionStringFromHyprlangAction() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaActionStringFallsBackToDispatchWithoutHyprctl(t *testing.T) {
|
||||
got := luaActionStringFromHyprlangAction("customdispatcher USER_INPUT")
|
||||
want := `hl.dispatch("customdispatcher USER_INPUT")`
|
||||
func TestParseLuaBindLineHandlesFunctionDispatcherFallback(t *testing.T) {
|
||||
line := `hl.bind("SUPER + R", function() hl.exec_cmd("hyprctl dispatch resizeactive exact 100% 100%") end, { description = "Unsupported Resize" })`
|
||||
got, ok := parseLuaBindOverrideLine(line)
|
||||
if !ok {
|
||||
t.Fatalf("expected line to parse")
|
||||
}
|
||||
if got.Action != "resizeactive exact 100% 100%" {
|
||||
t.Fatalf("Action = %q, want resizeactive exact 100%% 100%%", got.Action)
|
||||
}
|
||||
if got.Description != "Unsupported Resize" {
|
||||
t.Fatalf("Description = %q, want Unsupported Resize", got.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaActionStringLeavesCustomLuaDispatcherRaw(t *testing.T) {
|
||||
got := luaActionStringFromHyprlangAction("hl.dsp.no_op()")
|
||||
want := `hl.dsp.no_op()`
|
||||
if got != want {
|
||||
t.Fatalf("luaActionStringFromHyprlangAction() = %q, want %q", got, want)
|
||||
}
|
||||
if strings.Contains(got, "hyprctl dispatch") {
|
||||
t.Fatalf("expected hl.dispatch fallback without hyprctl dispatch wrapper, got %q", got)
|
||||
if strings.Contains(got, "hl.dispatch") || strings.Contains(got, "hyprctl dispatch") {
|
||||
t.Fatalf("expected custom Lua dispatcher expression to stay raw, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1416,7 +1416,7 @@ Item {
|
||||
id: customCompositorField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root._inputHeight
|
||||
placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
||||
placeholderText: KeybindsService.currentProvider === "hyprland" ? I18n.tr("e.g., hl.dsp.focus({ workspace = \"3\" })") : I18n.tr("e.g., focus-workspace 3, resize-column -10")
|
||||
text: root._actionType === "compositor" ? root.editAction : ""
|
||||
onTextChanged: {
|
||||
if (root._actionType !== "compositor")
|
||||
|
||||
Reference in New Issue
Block a user