diff --git a/core/cmd/dms/commands_keybinds.go b/core/cmd/dms/commands_keybinds.go index 0f8ba59a..51096187 100644 --- a/core/cmd/dms/commands_keybinds.go +++ b/core/cmd/dms/commands_keybinds.go @@ -63,6 +63,7 @@ func init() { keybindsSetCmd.Flags().Bool("allow-when-locked", false, "Allow when screen is locked") keybindsSetCmd.Flags().Int("cooldown-ms", 0, "Cooldown in milliseconds") keybindsSetCmd.Flags().Bool("no-repeat", false, "Disable key repeat") + keybindsSetCmd.Flags().Bool("no-inhibiting", false, "Keep bind active when shortcuts are inhibited (allow-inhibiting=false)") keybindsSetCmd.Flags().String("replace-key", "", "Original key to replace (removes old key)") keybindsSetCmd.Flags().String("flags", "", "Hyprland bind flags (e.g., 'e' for repeat, 'l' for locked, 'r' for release)") @@ -212,6 +213,9 @@ func runKeybindsSet(cmd *cobra.Command, args []string) { if v, _ := cmd.Flags().GetBool("no-repeat"); v { options["repeat"] = false } + if v, _ := cmd.Flags().GetBool("no-inhibiting"); v { + options["allow-inhibiting"] = false + } if v, _ := cmd.Flags().GetString("flags"); v != "" { options["flags"] = v } diff --git a/core/internal/keybinds/providers/niri.go b/core/internal/keybinds/providers/niri.go index 2d44d60f..a38e77a7 100644 --- a/core/internal/keybinds/providers/niri.go +++ b/core/internal/keybinds/providers/niri.go @@ -118,6 +118,9 @@ func (n *NiriProvider) categorizeByAction(action string) string { return "Overview" case action == "quit" || action == "power-off-monitors" || + action == "power-on-monitors" || + action == "suspend" || + action == "do-screen-transition" || action == "toggle-keyboard-shortcuts-inhibit" || strings.Contains(action, "dpms"): return "System" @@ -151,13 +154,16 @@ func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, co } bind := keybinds.Keybind{ - Key: keyStr, - Description: kb.Description, - Action: rawAction, - Subcategory: subcategory, - Source: source, - HideOnOverlay: kb.HideOnOverlay, - CooldownMs: kb.CooldownMs, + Key: keyStr, + Description: kb.Description, + Action: rawAction, + Subcategory: subcategory, + Source: source, + HideOnOverlay: kb.HideOnOverlay, + CooldownMs: kb.CooldownMs, + AllowWhenLocked: kb.AllowWhenLocked, + AllowInhibiting: kb.AllowInhibiting, + Repeat: kb.Repeat, } if source == "dms" && conflicts != nil { @@ -341,14 +347,10 @@ func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string { } if actionNode.Properties != nil { - if val, ok := actionNode.Properties.Get("focus"); ok { - parts = append(parts, "focus="+val.String()) - } - if val, ok := actionNode.Properties.Get("show-pointer"); ok { - parts = append(parts, "show-pointer="+val.String()) - } - if val, ok := actionNode.Properties.Get("write-to-disk"); ok { - parts = append(parts, "write-to-disk="+val.String()) + for _, propName := range []string{"focus", "show-pointer", "write-to-disk", "skip-confirmation", "delay-ms"} { + if val, ok := actionNode.Properties.Get(propName); ok { + parts = append(parts, propName+"="+val.String()) + } } } @@ -372,6 +374,9 @@ func (n *NiriProvider) extractOptions(node *document.Node) map[string]any { if val, ok := node.Properties.Get("allow-when-locked"); ok { opts["allow-when-locked"] = val.String() == "true" } + if val, ok := node.Properties.Get("allow-inhibiting"); ok { + opts["allow-inhibiting"] = val.String() == "true" + } return opts } @@ -405,6 +410,9 @@ func (n *NiriProvider) buildBindNode(bind *overrideBind) *document.Node { if v, ok := bind.Options["allow-when-locked"]; ok && v == true { node.AddProperty("allow-when-locked", true, "") } + if v, ok := bind.Options["allow-inhibiting"]; ok && v == false { + node.AddProperty("allow-inhibiting", false, "") + } } if bind.Description != "" { diff --git a/core/internal/keybinds/providers/niri_parser.go b/core/internal/keybinds/providers/niri_parser.go index b03b2689..46520ff3 100644 --- a/core/internal/keybinds/providers/niri_parser.go +++ b/core/internal/keybinds/providers/niri_parser.go @@ -12,14 +12,17 @@ import ( ) type NiriKeyBinding struct { - Mods []string - Key string - Action string - Args []string - Description string - HideOnOverlay bool - CooldownMs int - Source string + Mods []string + Key string + Action string + Args []string + Description string + HideOnOverlay bool + CooldownMs int + AllowWhenLocked bool + AllowInhibiting *bool + Repeat *bool + Source string } type NiriSection struct { @@ -269,8 +272,10 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin args = append(args, arg.ValueString()) } if actionNode.Properties != nil { - if val, ok := actionNode.Properties.Get("focus"); ok { - args = append(args, "focus="+val.String()) + for _, propName := range []string{"focus", "show-pointer", "write-to-disk", "skip-confirmation", "delay-ms"} { + if val, ok := actionNode.Properties.Get(propName); ok { + args = append(args, propName+"="+val.String()) + } } } } @@ -278,6 +283,9 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin var description string var hideOnOverlay bool var cooldownMs int + var allowWhenLocked bool + var allowInhibiting *bool + var repeat *bool if node.Properties != nil { if val, ok := node.Properties.Get("hotkey-overlay-title"); ok { switch val.ValueString() { @@ -290,17 +298,31 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin if val, ok := node.Properties.Get("cooldown-ms"); ok { cooldownMs, _ = strconv.Atoi(val.String()) } + if val, ok := node.Properties.Get("allow-when-locked"); ok { + allowWhenLocked = val.String() == "true" + } + if val, ok := node.Properties.Get("allow-inhibiting"); ok { + v := val.String() == "true" + allowInhibiting = &v + } + if val, ok := node.Properties.Get("repeat"); ok { + v := val.String() == "true" + repeat = &v + } } return &NiriKeyBinding{ - Mods: mods, - Key: key, - Action: action, - Args: args, - Description: description, - HideOnOverlay: hideOnOverlay, - CooldownMs: cooldownMs, - Source: p.currentSource, + Mods: mods, + Key: key, + Action: action, + Args: args, + Description: description, + HideOnOverlay: hideOnOverlay, + CooldownMs: cooldownMs, + AllowWhenLocked: allowWhenLocked, + AllowInhibiting: allowInhibiting, + Repeat: repeat, + Source: p.currentSource, } } diff --git a/core/internal/keybinds/types.go b/core/internal/keybinds/types.go index 033d7b51..109b53b6 100644 --- a/core/internal/keybinds/types.go +++ b/core/internal/keybinds/types.go @@ -1,15 +1,18 @@ package keybinds type Keybind struct { - Key string `json:"key"` - Description string `json:"desc"` - Action string `json:"action,omitempty"` - Subcategory string `json:"subcat,omitempty"` - Source string `json:"source,omitempty"` - HideOnOverlay bool `json:"hideOnOverlay,omitempty"` - CooldownMs int `json:"cooldownMs,omitempty"` - Flags string `json:"flags,omitempty"` // Hyprland bind flags: e=repeat, l=locked, r=release, o=long-press - Conflict *Keybind `json:"conflict,omitempty"` + Key string `json:"key"` + Description string `json:"desc"` + Action string `json:"action,omitempty"` + Subcategory string `json:"subcat,omitempty"` + Source string `json:"source,omitempty"` + HideOnOverlay bool `json:"hideOnOverlay,omitempty"` + CooldownMs int `json:"cooldownMs,omitempty"` + Flags string `json:"flags,omitempty"` // Hyprland bind flags: e=repeat, l=locked, r=release, o=long-press + AllowWhenLocked bool `json:"allowWhenLocked,omitempty"` + AllowInhibiting *bool `json:"allowInhibiting,omitempty"` // nil=default(true), false=explicitly disabled + Repeat *bool `json:"repeat,omitempty"` // nil=default(true), false=explicitly disabled + Conflict *Keybind `json:"conflict,omitempty"` } type DMSBindsStatus struct { diff --git a/quickshell/Common/KeybindActions.js b/quickshell/Common/KeybindActions.js index bab0dfa3..55fa2f42 100644 --- a/quickshell/Common/KeybindActions.js +++ b/quickshell/Common/KeybindActions.js @@ -121,7 +121,8 @@ const NIRI_ACTIONS = { { id: "expand-column-to-available-width", label: "Expand to Available Width" }, { id: "consume-or-expel-window-left", label: "Consume/Expel Left" }, { id: "consume-or-expel-window-right", label: "Consume/Expel Right" }, - { id: "toggle-column-tabbed-display", label: "Toggle Tabbed" } + { id: "toggle-column-tabbed-display", label: "Toggle Tabbed" }, + { id: "toggle-window-rule-opacity", label: "Toggle Window Opacity" } ], "Focus": [ { id: "focus-column-left", label: "Focus Left" }, @@ -168,6 +169,7 @@ const NIRI_ACTIONS = { "System": [ { id: "toggle-overview", label: "Toggle Overview" }, { id: "show-hotkey-overlay", label: "Show Hotkey Overlay" }, + { id: "do-screen-transition", label: "Screen Transition" }, { id: "power-off-monitors", label: "Power Off Monitors" }, { id: "power-on-monitors", label: "Power On Monitors" }, { id: "toggle-keyboard-shortcuts-inhibit", label: "Toggle Shortcuts Inhibit" }, @@ -420,6 +422,12 @@ const COMPOSITOR_ACTIONS = { const CATEGORY_ORDER = ["DMS", "Execute", "Workspace", "Tags", "Window", "Move/Resize", "Focus", "Move", "Layout", "Groups", "Monitor", "Scratchpad", "Screenshot", "System", "Pass-through", "Overview", "Alt-Tab", "Other"]; const NIRI_ACTION_ARGS = { + "quit": { + args: [{ name: "skip-confirmation", type: "bool", label: "Skip confirmation" }] + }, + "do-screen-transition": { + args: [{ name: "delay-ms", type: "number", label: "Delay (ms)", placeholder: "250" }] + }, "set-column-width": { args: [{ name: "value", type: "text", label: "Width", placeholder: "+10%, -10%, 50%" }] }, @@ -756,14 +764,14 @@ function getDmsActions(isNiri, isHyprland) { continue; } switch (action.compositor) { - case "niri": - if (isNiri) - result.push(action); - break; - case "hyprland": - if (isHyprland) - result.push(action); - break; + case "niri": + if (isNiri) + result.push(action); + break; + case "hyprland": + if (isHyprland) + result.push(action); + break; } } return result; @@ -856,13 +864,13 @@ function isValidAction(action) { if (!action) return false; switch (action) { - case "spawn": - case "spawn ": - case "spawn sh -c \"\"": - case "spawn sh -c ''": - case "spawn_shell": - case "spawn_shell ": - return false; + case "spawn": + case "spawn ": + case "spawn sh -c \"\"": + case "spawn sh -c ''": + case "spawn_shell": + case "spawn_shell ": + return false; } return true; } @@ -882,7 +890,7 @@ function buildSpawnAction(command, args) { return ""; let parts = [command]; if (args && args.length > 0) - parts = parts.concat(args.filter(function(a) { return a; })); + parts = parts.concat(args.filter(function (a) { return a; })); return "spawn " + parts.join(" "); } @@ -899,7 +907,7 @@ function parseSpawnCommand(action) { if (!action || !action.startsWith("spawn ")) return { command: "", args: [] }; const rest = action.slice(6); - const parts = rest.split(" ").filter(function(p) { return p; }); + const parts = rest.split(" ").filter(function (p) { return p; }); return { command: parts[0] || "", args: parts.slice(1) @@ -961,130 +969,138 @@ function parseCompositorActionArgs(compositor, action) { var argParts = parts.slice(1); switch (compositor) { - case "niri": - switch (base) { - case "move-column-to-workspace": - for (var i = 0; i < argParts.length; i++) { - if (argParts[i] === "focus=true" || argParts[i] === "focus=false") { - args.focus = argParts[i] === "focus=true"; - } else if (!args.index) { - args.index = argParts[i]; + case "niri": + switch (base) { + case "move-column-to-workspace": + for (var i = 0; i < argParts.length; i++) { + if (argParts[i] === "focus=true" || argParts[i] === "focus=false") { + args.focus = argParts[i] === "focus=true"; + } else if (!args.index) { + args.index = argParts[i]; + } + } + break; + case "move-column-to-workspace-down": + case "move-column-to-workspace-up": + for (var k = 0; k < argParts.length; k++) { + if (argParts[k] === "focus=true" || argParts[k] === "focus=false") + args.focus = argParts[k] === "focus=true"; + } + break; + default: + for (var j = 0; j < argParts.length; j++) { + var kv = argParts[j].split("="); + if (kv.length === 2) { + switch (kv[1]) { + case "true": + args[kv[0]] = true; + break; + case "false": + args[kv[0]] = false; + break; + default: + args[kv[0]] = kv[1]; + } + } else { + args.value = args.value ? (args.value + " " + argParts[j]) : argParts[j]; + } + } + } + break; + case "mangowc": + if (argConfig.args && argConfig.args.length > 0 && argParts.length > 0) { + var paramStr = argParts.join(" "); + var paramValues = paramStr.split(","); + for (var m = 0; m < argConfig.args.length && m < paramValues.length; m++) { + args[argConfig.args[m].name] = paramValues[m]; } } break; - case "move-column-to-workspace-down": - case "move-column-to-workspace-up": - for (var k = 0; k < argParts.length; k++) { - if (argParts[k] === "focus=true" || argParts[k] === "focus=false") - args.focus = argParts[k] === "focus=true"; + case "hyprland": + if (argConfig.args && argConfig.args.length > 0) { + switch (base) { + case "resizewindowpixel": + case "movewindowpixel": + var commaIdx = argParts.join(" ").indexOf(","); + if (commaIdx !== -1) { + var fullStr = argParts.join(" "); + args[argConfig.args[0].name] = fullStr.substring(0, commaIdx); + args[argConfig.args[1].name] = fullStr.substring(commaIdx + 1); + } else if (argParts.length > 0) { + args[argConfig.args[0].name] = argParts.join(" "); + } + break; + case "movetoworkspace": + case "movetoworkspacesilent": + case "tagwindow": + case "alterzorder": + if (argParts.length >= 2) { + args[argConfig.args[0].name] = argParts[0]; + args[argConfig.args[1].name] = argParts.slice(1).join(" "); + } else if (argParts.length === 1) { + args[argConfig.args[0].name] = argParts[0]; + } + break; + case "moveworkspacetomonitor": + case "swapactiveworkspaces": + case "renameworkspace": + case "fullscreenstate": + case "movecursor": + if (argParts.length >= 2) { + args[argConfig.args[0].name] = argParts[0]; + args[argConfig.args[1].name] = argParts[1]; + } else if (argParts.length === 1) { + args[argConfig.args[0].name] = argParts[0]; + } + break; + case "setprop": + if (argParts.length >= 3) { + args.window = argParts[0]; + args.property = argParts[1]; + args.value = argParts.slice(2).join(" "); + } else if (argParts.length === 2) { + args.window = argParts[0]; + args.property = argParts[1]; + } + break; + case "sendshortcut": + if (argParts.length >= 3) { + args.mod = argParts[0]; + args.key = argParts[1]; + args.window = argParts.slice(2).join(" "); + } else if (argParts.length >= 2) { + args.mod = argParts[0]; + args.key = argParts[1]; + } + break; + case "sendkeystate": + if (argParts.length >= 4) { + args.mod = argParts[0]; + args.key = argParts[1]; + args.state = argParts[2]; + args.window = argParts.slice(3).join(" "); + } + break; + case "signalwindow": + if (argParts.length >= 2) { + args.window = argParts[0]; + args.signal = argParts[1]; + } + break; + default: + if (argParts.length > 0) { + if (argConfig.args.length === 1) { + args[argConfig.args[0].name] = argParts.join(" "); + } else { + args.value = argParts.join(" "); + } + } + } } break; default: - if (base.startsWith("screenshot")) { - for (var j = 0; j < argParts.length; j++) { - var kv = argParts[j].split("="); - if (kv.length === 2) - args[kv[0]] = kv[1] === "true"; - } - } else if (argParts.length > 0) { + if (argParts.length > 0) args.value = argParts.join(" "); - } - } - break; - case "mangowc": - if (argConfig.args && argConfig.args.length > 0 && argParts.length > 0) { - var paramStr = argParts.join(" "); - var paramValues = paramStr.split(","); - for (var m = 0; m < argConfig.args.length && m < paramValues.length; m++) { - args[argConfig.args[m].name] = paramValues[m]; - } - } - break; - case "hyprland": - if (argConfig.args && argConfig.args.length > 0) { - switch (base) { - case "resizewindowpixel": - case "movewindowpixel": - var commaIdx = argParts.join(" ").indexOf(","); - if (commaIdx !== -1) { - var fullStr = argParts.join(" "); - args[argConfig.args[0].name] = fullStr.substring(0, commaIdx); - args[argConfig.args[1].name] = fullStr.substring(commaIdx + 1); - } else if (argParts.length > 0) { - args[argConfig.args[0].name] = argParts.join(" "); - } - break; - case "movetoworkspace": - case "movetoworkspacesilent": - case "tagwindow": - case "alterzorder": - if (argParts.length >= 2) { - args[argConfig.args[0].name] = argParts[0]; - args[argConfig.args[1].name] = argParts.slice(1).join(" "); - } else if (argParts.length === 1) { - args[argConfig.args[0].name] = argParts[0]; - } - break; - case "moveworkspacetomonitor": - case "swapactiveworkspaces": - case "renameworkspace": - case "fullscreenstate": - case "movecursor": - if (argParts.length >= 2) { - args[argConfig.args[0].name] = argParts[0]; - args[argConfig.args[1].name] = argParts[1]; - } else if (argParts.length === 1) { - args[argConfig.args[0].name] = argParts[0]; - } - break; - case "setprop": - if (argParts.length >= 3) { - args.window = argParts[0]; - args.property = argParts[1]; - args.value = argParts.slice(2).join(" "); - } else if (argParts.length === 2) { - args.window = argParts[0]; - args.property = argParts[1]; - } - break; - case "sendshortcut": - if (argParts.length >= 3) { - args.mod = argParts[0]; - args.key = argParts[1]; - args.window = argParts.slice(2).join(" "); - } else if (argParts.length >= 2) { - args.mod = argParts[0]; - args.key = argParts[1]; - } - break; - case "sendkeystate": - if (argParts.length >= 4) { - args.mod = argParts[0]; - args.key = argParts[1]; - args.state = argParts[2]; - args.window = argParts.slice(3).join(" "); - } - break; - case "signalwindow": - if (argParts.length >= 2) { - args.window = argParts[0]; - args.signal = argParts[1]; - } - break; - default: - if (argParts.length > 0) { - if (argConfig.args.length === 1) { - args[argConfig.args[0].name] = argParts.join(" "); - } else { - args.value = argParts.join(" "); - } - } - } - } - break; - default: - if (argParts.length > 0) - args.value = argParts.join(" "); } return { base: base, args: args }; @@ -1100,125 +1116,118 @@ function buildCompositorAction(compositor, base, args) { return base; switch (compositor) { - case "niri": - switch (base) { - case "move-column-to-workspace": - if (args.index) - parts.push(args.index); - if (args.focus === false) - parts.push("focus=false"); + case "niri": + switch (base) { + case "move-column-to-workspace": + if (args.index) + parts.push(args.index); + if (args.focus === false) + parts.push("focus=false"); + break; + case "move-column-to-workspace-down": + case "move-column-to-workspace-up": + if (args.focus === false) + parts.push("focus=false"); + break; + default: + if (args.value) + parts.push(args.value); + else if (args.index) + parts.push(args.index); + for (var prop in args) { + switch (prop) { + case "value": + case "index": + continue; + } + var val = args[prop]; + if (val === true) + parts.push(prop + "=true"); + else if (val === false) + parts.push(prop + "=false"); + else if (val !== undefined && val !== null && val !== "") + parts.push(prop + "=" + val); + } + } break; - case "move-column-to-workspace-down": - case "move-column-to-workspace-up": - if (args.focus === false) - parts.push("focus=false"); + case "mangowc": + var compositorArgs = ACTION_ARGS.mangowc; + if (compositorArgs && compositorArgs[base] && compositorArgs[base].args) { + var argConfig = compositorArgs[base].args; + var argValues = []; + for (var i = 0; i < argConfig.length; i++) { + var argDef = argConfig[i]; + var val = args[argDef.name]; + if (val === undefined || val === "") + val = argDef.default || ""; + if (val === "" && argValues.length === 0) + continue; + argValues.push(val); + } + if (argValues.length > 0) + parts.push(argValues.join(",")); + } else if (args.value) { + parts.push(args.value); + } + break; + case "hyprland": + var hyprArgs = ACTION_ARGS.hyprland; + if (hyprArgs && hyprArgs[base] && hyprArgs[base].args) { + var hyprConfig = hyprArgs[base].args; + switch (base) { + case "resizewindowpixel": + case "movewindowpixel": + if (args[hyprConfig[0].name]) + parts.push(args[hyprConfig[0].name]); + if (args[hyprConfig[1].name]) + parts[parts.length - 1] += "," + args[hyprConfig[1].name]; + break; + case "setprop": + if (args.window) + parts.push(args.window); + if (args.property) + parts.push(args.property); + if (args.value) + parts.push(args.value); + break; + case "sendshortcut": + if (args.mod) + parts.push(args.mod); + if (args.key) + parts.push(args.key); + if (args.window) + parts.push(args.window); + break; + case "sendkeystate": + if (args.mod) + parts.push(args.mod); + if (args.key) + parts.push(args.key); + if (args.state) + parts.push(args.state); + if (args.window) + parts.push(args.window); + break; + case "signalwindow": + if (args.window) + parts.push(args.window); + if (args.signal) + parts.push(args.signal); + break; + default: + for (var j = 0; j < hyprConfig.length; j++) { + var hVal = args[hyprConfig[j].name]; + if (hVal !== undefined && hVal !== "") + parts.push(hVal); + } + } + } else if (args.value) { + parts.push(args.value); + } break; default: - switch (base) { - case "screenshot": - if (args["show-pointer"] === true) - parts.push("show-pointer=true"); - else if (args["show-pointer"] === false) - parts.push("show-pointer=false"); - break; - case "screenshot-screen": - if (args["show-pointer"] === true) - parts.push("show-pointer=true"); - else if (args["show-pointer"] === false) - parts.push("show-pointer=false"); - if (args["write-to-disk"] === true) - parts.push("write-to-disk=true"); - break; - case "screenshot-window": - if (args["write-to-disk"] === true) - parts.push("write-to-disk=true"); - break; - } - if (args.value) { + if (args.value) parts.push(args.value); - } else if (args.index) { - parts.push(args.index); - } - } - break; - case "mangowc": - var compositorArgs = ACTION_ARGS.mangowc; - if (compositorArgs && compositorArgs[base] && compositorArgs[base].args) { - var argConfig = compositorArgs[base].args; - var argValues = []; - for (var i = 0; i < argConfig.length; i++) { - var argDef = argConfig[i]; - var val = args[argDef.name]; - if (val === undefined || val === "") - val = argDef.default || ""; - if (val === "" && argValues.length === 0) - continue; - argValues.push(val); - } - if (argValues.length > 0) - parts.push(argValues.join(",")); - } else if (args.value) { - parts.push(args.value); - } - break; - case "hyprland": - var hyprArgs = ACTION_ARGS.hyprland; - if (hyprArgs && hyprArgs[base] && hyprArgs[base].args) { - var hyprConfig = hyprArgs[base].args; - switch (base) { - case "resizewindowpixel": - case "movewindowpixel": - if (args[hyprConfig[0].name]) - parts.push(args[hyprConfig[0].name]); - if (args[hyprConfig[1].name]) - parts[parts.length - 1] += "," + args[hyprConfig[1].name]; - break; - case "setprop": - if (args.window) - parts.push(args.window); - if (args.property) - parts.push(args.property); - if (args.value) - parts.push(args.value); - break; - case "sendshortcut": - if (args.mod) - parts.push(args.mod); - if (args.key) - parts.push(args.key); - if (args.window) - parts.push(args.window); - break; - case "sendkeystate": - if (args.mod) - parts.push(args.mod); - if (args.key) - parts.push(args.key); - if (args.state) - parts.push(args.state); - if (args.window) - parts.push(args.window); - break; - case "signalwindow": - if (args.window) - parts.push(args.window); - if (args.signal) - parts.push(args.signal); - break; - default: - for (var j = 0; j < hyprConfig.length; j++) { - var hVal = args[hyprConfig[j].name]; - if (hVal !== undefined && hVal !== "") - parts.push(hVal); - } - } - } else if (args.value) { - parts.push(args.value); - } - break; - default: - if (args.value) - parts.push(args.value); } return parts.join(" "); @@ -1246,22 +1255,22 @@ function parseDmsActionArgs(action) { for (var i = 0; i < rest.length; i++) { var c = rest[i]; switch (c) { - case '"': - inQuotes = !inQuotes; - hadQuotes = true; - break; - case ' ': - if (inQuotes) { + case '"': + inQuotes = !inQuotes; + hadQuotes = true; + break; + case ' ': + if (inQuotes) { + current += c; + } else if (current || hadQuotes) { + tokens.push(current); + current = ""; + hadQuotes = false; + } + break; + default: current += c; - } else if (current || hadQuotes) { - tokens.push(current); - current = ""; - hadQuotes = false; - } - break; - default: - current += c; - break; + break; } } if (current || hadQuotes) diff --git a/quickshell/Services/KeybindsService.qml b/quickshell/Services/KeybindsService.qml index a25722a3..470831b2 100644 --- a/quickshell/Services/KeybindsService.qml +++ b/quickshell/Services/KeybindsService.qml @@ -384,7 +384,10 @@ Singleton { "source": bind.source || "config", "isOverride": bind.source === "dms", "cooldownMs": bind.cooldownMs || 0, - "flags": bind.flags || "" + "flags": bind.flags || "", + "allowWhenLocked": bind.allowWhenLocked || false, + "allowInhibiting": bind.allowInhibiting, + "repeat": bind.repeat }; if (actionMap[action]) { actionMap[action].keys.push(keyData); @@ -454,6 +457,12 @@ Singleton { cmd.push("--replace-key", originalKey); if (bindData.cooldownMs > 0) cmd.push("--cooldown-ms", String(bindData.cooldownMs)); + if (bindData.allowWhenLocked) + cmd.push("--allow-when-locked"); + if (bindData.repeat === false) + cmd.push("--no-repeat"); + if (bindData.allowInhibiting === false) + cmd.push("--no-inhibiting"); if (bindData.flags) cmd.push("--flags", bindData.flags); saveProcess.command = cmd; diff --git a/quickshell/Widgets/KeybindItem.qml b/quickshell/Widgets/KeybindItem.qml index 78241240..a68cc4a1 100644 --- a/quickshell/Widgets/KeybindItem.qml +++ b/quickshell/Widgets/KeybindItem.qml @@ -28,8 +28,14 @@ Item { property string editDesc: "" property int editCooldownMs: 0 property string editFlags: "" + property bool editAllowWhenLocked: false + property var editRepeat: undefined + property var editAllowInhibiting: undefined property int _savedCooldownMs: -1 property string _savedFlags: "" + property var _savedAllowWhenLocked: undefined + property var _savedRepeat: undefined + property var _savedAllowInhibiting: undefined property bool hasChanges: false property string _actionType: "" property bool addingNewKey: false @@ -115,6 +121,24 @@ Item { } else { editFlags = keys[i].flags || ""; } + if (_savedAllowWhenLocked !== undefined) { + editAllowWhenLocked = _savedAllowWhenLocked; + _savedAllowWhenLocked = undefined; + } else { + editAllowWhenLocked = keys[i].allowWhenLocked || false; + } + if (_savedRepeat !== undefined) { + editRepeat = _savedRepeat; + _savedRepeat = undefined; + } else { + editRepeat = keys[i].repeat; + } + if (_savedAllowInhibiting !== undefined) { + editAllowInhibiting = _savedAllowInhibiting; + _savedAllowInhibiting = undefined; + } else { + editAllowInhibiting = keys[i].allowInhibiting; + } hasChanges = false; _actionType = Actions.getActionType(editAction); useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(KeybindsService.currentProvider, editAction); @@ -136,6 +160,9 @@ Item { editDesc = bindData.desc || ""; editCooldownMs = editingKeyIndex >= 0 ? (keys[editingKeyIndex].cooldownMs || 0) : 0; editFlags = editingKeyIndex >= 0 ? (keys[editingKeyIndex].flags || "") : ""; + editAllowWhenLocked = editingKeyIndex >= 0 ? (keys[editingKeyIndex].allowWhenLocked || false) : false; + editRepeat = editingKeyIndex >= 0 ? keys[editingKeyIndex].repeat : undefined; + editAllowInhibiting = editingKeyIndex >= 0 ? keys[editingKeyIndex].allowInhibiting : undefined; hasChanges = false; _actionType = Actions.getActionType(editAction); useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(KeybindsService.currentProvider, editAction); @@ -156,6 +183,9 @@ Item { editKey = keys[index].key; editCooldownMs = keys[index].cooldownMs || 0; editFlags = keys[index].flags || ""; + editAllowWhenLocked = keys[index].allowWhenLocked || false; + editRepeat = keys[index].repeat; + editAllowInhibiting = keys[index].allowInhibiting; hasChanges = false; } @@ -170,10 +200,20 @@ Item { editCooldownMs = changes.cooldownMs; if (changes.flags !== undefined) editFlags = changes.flags; - const origKey = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : ""; - const origCooldown = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? (keys[editingKeyIndex].cooldownMs || 0) : 0; - const origFlags = editingKeyIndex >= 0 && editingKeyIndex < keys.length ? (keys[editingKeyIndex].flags || "") : ""; - hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || "") || editCooldownMs !== origCooldown || editFlags !== origFlags; + if (changes.allowWhenLocked !== undefined) + editAllowWhenLocked = changes.allowWhenLocked; + if (changes.repeat !== undefined) + editRepeat = changes.repeat; + if (changes.allowInhibiting !== undefined) + editAllowInhibiting = changes.allowInhibiting; + const hasKey = editingKeyIndex >= 0 && editingKeyIndex < keys.length; + const origKey = hasKey ? keys[editingKeyIndex].key : ""; + const origCooldown = hasKey ? (keys[editingKeyIndex].cooldownMs || 0) : 0; + const origFlags = hasKey ? (keys[editingKeyIndex].flags || "") : ""; + const origAllowWhenLocked = hasKey ? (keys[editingKeyIndex].allowWhenLocked || false) : false; + const origRepeat = hasKey ? keys[editingKeyIndex].repeat : undefined; + const origAllowInhibiting = hasKey ? keys[editingKeyIndex].allowInhibiting : undefined; + hasChanges = editKey !== origKey || editAction !== (bindData.action || "") || editDesc !== (bindData.desc || "") || editCooldownMs !== origCooldown || editFlags !== origFlags || editAllowWhenLocked !== origAllowWhenLocked || editRepeat !== origRepeat || editAllowInhibiting !== origAllowInhibiting; } function canSave() { @@ -193,12 +233,18 @@ Item { desc = expandedLoader.item.currentTitle; _savedCooldownMs = editCooldownMs; _savedFlags = editFlags; + _savedAllowWhenLocked = editAllowWhenLocked; + _savedRepeat = editRepeat; + _savedAllowInhibiting = editAllowInhibiting; saveBind(origKey, { "key": editKey, "action": editAction, "desc": desc, "cooldownMs": editCooldownMs, - "flags": editFlags + "flags": editFlags, + "allowWhenLocked": editAllowWhenLocked, + "repeat": editRepeat, + "allowInhibiting": editAllowInhibiting }); hasChanges = false; addingNewKey = false; @@ -1322,6 +1368,29 @@ Item { } } } + + RowLayout { + visible: optionsRow.argConfig?.base === "quit" + spacing: Theme.spacingXS + + DankToggle { + checked: optionsRow.parsedArgs?.args["skip-confirmation"] === true + onToggled: newChecked => { + const args = newChecked ? { + "skip-confirmation": true + } : {}; + root.updateEdit({ + "action": Actions.buildCompositorAction(KeybindsService.currentProvider, "quit", args) + }); + } + } + + StyledText { + text: I18n.tr("Skip confirmation") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } } } @@ -1633,6 +1702,82 @@ Item { } } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacingM + visible: KeybindsService.currentProvider === "niri" + + StyledText { + text: I18n.tr("Options") + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceVariantText + Layout.preferredWidth: root._labelWidth + } + + Flow { + Layout.fillWidth: true + spacing: Theme.spacingM + + RowLayout { + spacing: Theme.spacingXS + + DankToggle { + checked: root.editRepeat !== false + onToggled: newChecked => { + root.updateEdit({ + "repeat": newChecked ? undefined : false + }); + } + } + + StyledText { + text: I18n.tr("Repeat") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } + + RowLayout { + spacing: Theme.spacingXS + + DankToggle { + checked: root.editAllowWhenLocked + onToggled: newChecked => { + root.updateEdit({ + "allowWhenLocked": newChecked + }); + } + } + + StyledText { + text: I18n.tr("When locked") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } + + RowLayout { + spacing: Theme.spacingXS + + DankToggle { + checked: root.editAllowInhibiting !== false + onToggled: newChecked => { + root.updateEdit({ + "allowInhibiting": newChecked ? undefined : false + }); + } + } + + StyledText { + text: I18n.tr("Inhibitable") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + } + } + } + Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1