diff --git a/quickshell/Common/KeyUtils.js b/quickshell/Common/KeyUtils.js new file mode 100644 index 00000000..8f59d2a0 --- /dev/null +++ b/quickshell/Common/KeyUtils.js @@ -0,0 +1,143 @@ +.pragma library + +const KEY_MAP = { + 16777234: "Left", + 16777236: "Right", + 16777235: "Up", + 16777237: "Down", + 44: "Comma", + 46: "Period", + 47: "Slash", + 59: "Semicolon", + 39: "Apostrophe", + 91: "BracketLeft", + 93: "BracketRight", + 92: "Backslash", + 45: "Minus", + 61: "Equal", + 96: "grave", + 32: "space", + 16777225: "Print", + 16777220: "Return", + 16777221: "Return", + 16777217: "Tab", + 16777219: "BackSpace", + 16777223: "Delete", + 16777222: "Insert", + 16777232: "Home", + 16777233: "End", + 16777238: "Page_Up", + 16777239: "Page_Down", + 16777216: "Escape", + 16777252: "Caps_Lock", + 16777253: "Num_Lock", + 16777254: "Scroll_Lock", + 16777224: "Pause", + 16777330: "XF86AudioRaiseVolume", + 16777328: "XF86AudioLowerVolume", + 16777329: "XF86AudioMute", + 16842808: "XF86AudioMicMute", + 16777344: "XF86AudioPlay", + 16777345: "XF86AudioPause", + 16777346: "XF86AudioStop", + 16777347: "XF86AudioNext", + 16777348: "XF86AudioPrev", + 16842792: "XF86AudioRecord", + 16842798: "XF86MonBrightnessUp", + 16842797: "XF86MonBrightnessDown", + 16842800: "XF86KbdBrightnessUp", + 16842799: "XF86KbdBrightnessDown", + 16842796: "XF86PowerOff", + 16842803: "XF86Sleep", + 16842804: "XF86WakeUp", + 16842802: "XF86Eject", + 16842791: "XF86Calculator", + 16842806: "XF86Explorer", + 16842794: "XF86HomePage", + 16777426: "XF86Search", + 16777427: "XF86Mail", + 16777442: "XF86Launch0", + 16777443: "XF86Launch1", + 33: "1", + 64: "2", + 35: "3", + 36: "4", + 37: "5", + 94: "6", + 38: "7", + 42: "8", + 40: "9", + 41: "0", + 60: "Comma", + 62: "Period", + 63: "Slash", + 58: "Semicolon", + 34: "Apostrophe", + 123: "BracketLeft", + 125: "BracketRight", + 124: "Backslash", + 95: "Minus", + 43: "Equal", + 126: "grave" +}; + +function xkbKeyFromQtKey(qk) { + if (qk >= 65 && qk <= 90) + return String.fromCharCode(qk); + if (qk >= 48 && qk <= 57) + return String.fromCharCode(qk); + if (qk >= 16777264 && qk <= 16777298) + return "F" + (qk - 16777264 + 1); + return KEY_MAP[qk] || ""; +} + +function modsFromEvent(mods) { + var result = []; + if (mods & 0x04000000) + result.push("Ctrl"); + if (mods & 0x02000000) + result.push("Shift"); + var hasAlt = mods & 0x08000000; + var hasSuper = mods & 0x10000000; + if (hasAlt && hasSuper) { + result.push("Mod"); + } else { + if (hasAlt) + result.push("Alt"); + if (hasSuper) + result.push("Super"); + } + return result; +} + +function formatToken(mods, key) { + return (mods.length ? mods.join("+") + "+" : "") + key; +} + +function normalizeKeyCombo(keyCombo) { + if (!keyCombo) + return ""; + return keyCombo.toLowerCase().replace(/\bmod\b/g, "super").replace(/\bsuper\b/g, "super"); +} + +function getConflictingBinds(keyCombo, currentAction, allBinds) { + if (!keyCombo) + return []; + var conflicts = []; + var normalizedKey = normalizeKeyCombo(keyCombo); + for (var i = 0; i < allBinds.length; i++) { + var bind = allBinds[i]; + if (bind.action === currentAction) + continue; + for (var k = 0; k < bind.keys.length; k++) { + if (normalizeKeyCombo(bind.keys[k].key) === normalizedKey) { + conflicts.push({ + action: bind.action, + desc: bind.desc || bind.action + }); + break; + } + } + } + return conflicts; +} diff --git a/quickshell/Common/KeybindActions.js b/quickshell/Common/KeybindActions.js new file mode 100644 index 00000000..fcfacacf --- /dev/null +++ b/quickshell/Common/KeybindActions.js @@ -0,0 +1,323 @@ +.pragma library + +const ACTION_TYPES = [ + { id: "dms", label: "DMS Action", icon: "widgets" }, + { id: "compositor", label: "Compositor", icon: "desktop_windows" }, + { id: "spawn", label: "Run Command", icon: "terminal" }, + { id: "shell", label: "Shell Command", icon: "code" } +]; + +const DMS_ACTIONS = [ + { id: "spawn dms ipc call spotlight toggle", label: "App Launcher: Toggle" }, + { id: "spawn dms ipc call spotlight open", label: "App Launcher: Open" }, + { id: "spawn dms ipc call spotlight close", label: "App Launcher: Close" }, + { id: "spawn dms ipc call clipboard toggle", label: "Clipboard: Toggle" }, + { id: "spawn dms ipc call clipboard open", label: "Clipboard: Open" }, + { id: "spawn dms ipc call clipboard close", label: "Clipboard: Close" }, + { id: "spawn dms ipc call notifications toggle", label: "Notifications: Toggle" }, + { id: "spawn dms ipc call notifications open", label: "Notifications: Open" }, + { id: "spawn dms ipc call notifications close", label: "Notifications: Close" }, + { id: "spawn dms ipc call processlist toggle", label: "Task Manager: Toggle" }, + { id: "spawn dms ipc call processlist open", label: "Task Manager: Open" }, + { id: "spawn dms ipc call processlist close", label: "Task Manager: Close" }, + { id: "spawn dms ipc call processlist focusOrToggle", label: "Task Manager: Focus or Toggle" }, + { id: "spawn dms ipc call settings toggle", label: "Settings: Toggle" }, + { id: "spawn dms ipc call settings open", label: "Settings: Open" }, + { id: "spawn dms ipc call settings close", label: "Settings: Close" }, + { id: "spawn dms ipc call settings focusOrToggle", label: "Settings: Focus or Toggle" }, + { id: "spawn dms ipc call powermenu toggle", label: "Power Menu: Toggle" }, + { id: "spawn dms ipc call powermenu open", label: "Power Menu: Open" }, + { id: "spawn dms ipc call powermenu close", label: "Power Menu: Close" }, + { id: "spawn dms ipc call control-center toggle", label: "Control Center: Toggle" }, + { id: "spawn dms ipc call control-center open", label: "Control Center: Open" }, + { id: "spawn dms ipc call control-center close", label: "Control Center: Close" }, + { id: "spawn dms ipc call notepad toggle", label: "Notepad: Toggle" }, + { id: "spawn dms ipc call notepad open", label: "Notepad: Open" }, + { id: "spawn dms ipc call notepad close", label: "Notepad: Close" }, + { id: "spawn dms ipc call dash toggle", label: "Dashboard: Toggle" }, + { id: "spawn dms ipc call dash open overview", label: "Dashboard: Overview" }, + { id: "spawn dms ipc call dash open media", label: "Dashboard: Media" }, + { id: "spawn dms ipc call dash open weather", label: "Dashboard: Weather" }, + { id: "spawn dms ipc call dankdash wallpaper", label: "Wallpaper Browser" }, + { id: "spawn dms ipc call file browse wallpaper", label: "File: Browse Wallpaper" }, + { id: "spawn dms ipc call file browse profile", label: "File: Browse Profile" }, + { id: "spawn dms ipc call keybinds toggle niri", label: "Keybinds Cheatsheet: Toggle", compositor: "niri" }, + { id: "spawn dms ipc call keybinds open niri", label: "Keybinds Cheatsheet: Open", compositor: "niri" }, + { id: "spawn dms ipc call keybinds close", label: "Keybinds Cheatsheet: Close" }, + { id: "spawn dms ipc call lock lock", label: "Lock Screen" }, + { id: "spawn dms ipc call lock demo", label: "Lock Screen: Demo" }, + { id: "spawn dms ipc call inhibit toggle", label: "Idle Inhibit: Toggle" }, + { id: "spawn dms ipc call inhibit enable", label: "Idle Inhibit: Enable" }, + { id: "spawn dms ipc call inhibit disable", label: "Idle Inhibit: Disable" }, + { id: "spawn dms ipc call audio increment", label: "Volume Up" }, + { id: "spawn dms ipc call audio increment 1", label: "Volume Up (1%)" }, + { id: "spawn dms ipc call audio increment 5", label: "Volume Up (5%)" }, + { id: "spawn dms ipc call audio increment 10", label: "Volume Up (10%)" }, + { id: "spawn dms ipc call audio decrement", label: "Volume Down" }, + { id: "spawn dms ipc call audio decrement 1", label: "Volume Down (1%)" }, + { id: "spawn dms ipc call audio decrement 5", label: "Volume Down (5%)" }, + { id: "spawn dms ipc call audio decrement 10", label: "Volume Down (10%)" }, + { id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" }, + { id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" }, + { id: "spawn dms ipc call brightness increment", label: "Brightness Up" }, + { id: "spawn dms ipc call brightness increment 1", label: "Brightness Up (1%)" }, + { id: "spawn dms ipc call brightness increment 5", label: "Brightness Up (5%)" }, + { id: "spawn dms ipc call brightness increment 10", label: "Brightness Up (10%)" }, + { id: "spawn dms ipc call brightness decrement", label: "Brightness Down" }, + { id: "spawn dms ipc call brightness decrement 1", label: "Brightness Down (1%)" }, + { id: "spawn dms ipc call brightness decrement 5", label: "Brightness Down (5%)" }, + { id: "spawn dms ipc call brightness decrement 10", label: "Brightness Down (10%)" }, + { id: "spawn dms ipc call brightness toggleExponential", label: "Brightness: Toggle Exponential" }, + { id: "spawn dms ipc call theme toggle", label: "Theme: Toggle Light/Dark" }, + { id: "spawn dms ipc call theme light", label: "Theme: Light Mode" }, + { id: "spawn dms ipc call theme dark", label: "Theme: Dark Mode" }, + { id: "spawn dms ipc call night toggle", label: "Night Mode: Toggle" }, + { id: "spawn dms ipc call night enable", label: "Night Mode: Enable" }, + { id: "spawn dms ipc call night disable", label: "Night Mode: Disable" }, + { id: "spawn dms ipc call bar toggle index 0", label: "Bar: Toggle (Primary)" }, + { id: "spawn dms ipc call bar reveal index 0", label: "Bar: Reveal (Primary)" }, + { id: "spawn dms ipc call bar hide index 0", label: "Bar: Hide (Primary)" }, + { id: "spawn dms ipc call bar toggleAutoHide index 0", label: "Bar: Toggle Auto-Hide (Primary)" }, + { id: "spawn dms ipc call bar autoHide index 0", label: "Bar: Enable Auto-Hide (Primary)" }, + { id: "spawn dms ipc call bar manualHide index 0", label: "Bar: Disable Auto-Hide (Primary)" }, + { id: "spawn dms ipc call dock toggle", label: "Dock: Toggle" }, + { id: "spawn dms ipc call dock reveal", label: "Dock: Reveal" }, + { id: "spawn dms ipc call dock hide", label: "Dock: Hide" }, + { id: "spawn dms ipc call dock toggleAutoHide", label: "Dock: Toggle Auto-Hide" }, + { id: "spawn dms ipc call dock autoHide", label: "Dock: Enable Auto-Hide" }, + { id: "spawn dms ipc call dock manualHide", label: "Dock: Disable Auto-Hide" }, + { id: "spawn dms ipc call mpris playPause", label: "Media: Play/Pause" }, + { id: "spawn dms ipc call mpris play", label: "Media: Play" }, + { id: "spawn dms ipc call mpris pause", label: "Media: Pause" }, + { id: "spawn dms ipc call mpris previous", label: "Media: Previous Track" }, + { id: "spawn dms ipc call mpris next", label: "Media: Next Track" }, + { id: "spawn dms ipc call mpris stop", label: "Media: Stop" }, + { id: "spawn dms ipc call niri screenshot", label: "Screenshot: Interactive", compositor: "niri" }, + { id: "spawn dms ipc call niri screenshotScreen", label: "Screenshot: Full Screen", compositor: "niri" }, + { id: "spawn dms ipc call niri screenshotWindow", label: "Screenshot: Window", compositor: "niri" }, + { id: "spawn dms ipc call hypr toggleOverview", label: "Hyprland: Toggle Overview", compositor: "hyprland" }, + { id: "spawn dms ipc call hypr openOverview", label: "Hyprland: Open Overview", compositor: "hyprland" }, + { id: "spawn dms ipc call hypr closeOverview", label: "Hyprland: Close Overview", compositor: "hyprland" }, + { id: "spawn dms ipc call wallpaper next", label: "Wallpaper: Next" }, + { id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" } +]; + +const COMPOSITOR_ACTIONS = { + "Window": [ + { id: "close-window", label: "Close Window" }, + { id: "fullscreen-window", label: "Fullscreen" }, + { id: "maximize-column", label: "Maximize Column" }, + { id: "center-column", label: "Center Column" }, + { id: "toggle-window-floating", label: "Toggle Floating" }, + { id: "switch-preset-column-width", label: "Cycle Column Width" }, + { id: "switch-preset-window-height", label: "Cycle Window Height" }, + { 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" } + ], + "Focus": [ + { id: "focus-column-left", label: "Focus Left" }, + { id: "focus-column-right", label: "Focus Right" }, + { id: "focus-window-down", label: "Focus Down" }, + { id: "focus-window-up", label: "Focus Up" }, + { id: "focus-column-first", label: "Focus First Column" }, + { id: "focus-column-last", label: "Focus Last Column" } + ], + "Move": [ + { id: "move-column-left", label: "Move Left" }, + { id: "move-column-right", label: "Move Right" }, + { id: "move-window-down", label: "Move Down" }, + { id: "move-window-up", label: "Move Up" }, + { id: "move-column-to-first", label: "Move to First" }, + { id: "move-column-to-last", label: "Move to Last" } + ], + "Workspace": [ + { id: "focus-workspace-down", label: "Focus Workspace Down" }, + { id: "focus-workspace-up", label: "Focus Workspace Up" }, + { id: "focus-workspace-previous", label: "Focus Previous Workspace" }, + { id: "move-column-to-workspace-down", label: "Move to Workspace Down" }, + { id: "move-column-to-workspace-up", label: "Move to Workspace Up" }, + { id: "move-workspace-down", label: "Move Workspace Down" }, + { id: "move-workspace-up", label: "Move Workspace Up" } + ], + "Monitor": [ + { id: "focus-monitor-left", label: "Focus Monitor Left" }, + { id: "focus-monitor-right", label: "Focus Monitor Right" }, + { id: "focus-monitor-down", label: "Focus Monitor Down" }, + { id: "focus-monitor-up", label: "Focus Monitor Up" }, + { id: "move-column-to-monitor-left", label: "Move to Monitor Left" }, + { id: "move-column-to-monitor-right", label: "Move to Monitor Right" }, + { id: "move-column-to-monitor-down", label: "Move to Monitor Down" }, + { id: "move-column-to-monitor-up", label: "Move to Monitor Up" } + ], + "Screenshot": [ + { id: "screenshot", label: "Screenshot (Interactive)" }, + { id: "screenshot-screen", label: "Screenshot Screen" }, + { id: "screenshot-window", label: "Screenshot Window" } + ], + "System": [ + { id: "toggle-overview", label: "Toggle Overview" }, + { id: "show-hotkey-overlay", label: "Show Hotkey Overlay" }, + { 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" }, + { id: "quit", label: "Quit Niri" }, + { id: "suspend", label: "Suspend" } + ], + "Alt-Tab": [ + { id: "next-window", label: "Next Window" }, + { id: "previous-window", label: "Previous Window" } + ] +}; + +const CATEGORY_ORDER = ["DMS", "Execute", "Workspace", "Window", "Monitor", "Screenshot", "System", "Overview", "Alt-Tab", "Other"]; + +function getActionTypes() { + return ACTION_TYPES; +} + +function getDmsActions(isNiri, isHyprland) { + const result = []; + for (let i = 0; i < DMS_ACTIONS.length; i++) { + const action = DMS_ACTIONS[i]; + if (!action.compositor) { + result.push(action); + continue; + } + switch (action.compositor) { + case "niri": + if (isNiri) + result.push(action); + break; + case "hyprland": + if (isHyprland) + result.push(action); + break; + } + } + return result; +} + +function getCompositorCategories() { + return Object.keys(COMPOSITOR_ACTIONS); +} + +function getCompositorActions(category) { + return COMPOSITOR_ACTIONS[category] || []; +} + +function getCategoryOrder() { + return CATEGORY_ORDER; +} + +function findDmsAction(actionId) { + for (let i = 0; i < DMS_ACTIONS.length; i++) { + if (DMS_ACTIONS[i].id === actionId) + return DMS_ACTIONS[i]; + } + return null; +} + +function findCompositorAction(actionId) { + for (const cat in COMPOSITOR_ACTIONS) { + const acts = COMPOSITOR_ACTIONS[cat]; + for (let i = 0; i < acts.length; i++) { + if (acts[i].id === actionId) + return acts[i]; + } + } + return null; +} + +function getActionLabel(action) { + if (!action) + return ""; + + const dmsAct = findDmsAction(action); + if (dmsAct) + return dmsAct.label; + + const compAct = findCompositorAction(action); + if (compAct) + return compAct.label; + + if (action.startsWith("spawn sh -c ")) + return action.slice(12).replace(/^["']|["']$/g, ""); + if (action.startsWith("spawn ")) + return action.slice(6); + return action; +} + +function getActionType(action) { + if (!action) + return "compositor"; + if (action.startsWith("spawn dms ipc call ")) + return "dms"; + if (action.startsWith("spawn sh -c ") || action.startsWith("spawn bash -c ")) + return "shell"; + if (action.startsWith("spawn ")) + return "spawn"; + return "compositor"; +} + +function isDmsAction(action) { + if (!action) + return false; + return action.startsWith("spawn dms ipc call "); +} + +function isValidAction(action) { + if (!action) + return false; + switch (action) { + case "spawn": + case "spawn ": + case "spawn sh -c \"\"": + case "spawn sh -c ''": + return false; + } + return true; +} + +function isKnownCompositorAction(action) { + if (!action) + return false; + return findCompositorAction(action) !== null; +} + +function buildSpawnAction(command, args) { + if (!command) + return ""; + let parts = [command]; + if (args && args.length > 0) + parts = parts.concat(args.filter(function(a) { return a; })); + return "spawn " + parts.join(" "); +} + +function buildShellAction(shellCmd) { + if (!shellCmd) + return ""; + return "spawn sh -c \"" + shellCmd.replace(/"/g, "\\\"") + "\""; +} + +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; }); + return { + command: parts[0] || "", + args: parts.slice(1) + }; +} + +function parseShellCommand(action) { + if (!action) + return ""; + if (!action.startsWith("spawn sh -c ")) + return ""; + var content = action.slice(12); + if ((content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'"))) + content = content.slice(1, -1); + return content.replace(/\\"/g, "\""); +} diff --git a/quickshell/Services/KeybindsService.qml b/quickshell/Services/KeybindsService.qml index cd79d2da..0a4c49d5 100644 --- a/quickshell/Services/KeybindsService.qml +++ b/quickshell/Services/KeybindsService.qml @@ -7,6 +7,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import qs.Common +import "../Common/KeybindActions.js" as Actions Singleton { id: root @@ -35,625 +36,11 @@ Singleton { property var displayList: [] property int _dataVersion: 0 - readonly property var categoryOrder: ["DMS", "Execute", "Workspace", "Window", "Monitor", "Screenshot", "System", "Overview", "Alt-Tab", "Other"] - + readonly property var categoryOrder: Actions.getCategoryOrder() readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) readonly property string dmsBindsPath: configDir + "/niri/dms/binds.kdl" - - readonly property var actionTypes: [ - { - id: "dms", - label: "DMS Action", - icon: "widgets" - }, - { - id: "compositor", - label: "Compositor", - icon: "desktop_windows" - }, - { - id: "spawn", - label: "Run Command", - icon: "terminal" - }, - { - id: "shell", - label: "Shell Command", - icon: "code" - } - ] - - readonly property var dmsActions: [ - { - id: "spawn dms ipc call spotlight toggle", - label: "App Launcher: Toggle" - }, - { - id: "spawn dms ipc call spotlight open", - label: "App Launcher: Open" - }, - { - id: "spawn dms ipc call spotlight close", - label: "App Launcher: Close" - }, - { - id: "spawn dms ipc call clipboard toggle", - label: "Clipboard: Toggle" - }, - { - id: "spawn dms ipc call clipboard open", - label: "Clipboard: Open" - }, - { - id: "spawn dms ipc call clipboard close", - label: "Clipboard: Close" - }, - { - id: "spawn dms ipc call notifications toggle", - label: "Notifications: Toggle" - }, - { - id: "spawn dms ipc call notifications open", - label: "Notifications: Open" - }, - { - id: "spawn dms ipc call notifications close", - label: "Notifications: Close" - }, - { - id: "spawn dms ipc call processlist toggle", - label: "Task Manager: Toggle" - }, - { - id: "spawn dms ipc call processlist open", - label: "Task Manager: Open" - }, - { - id: "spawn dms ipc call processlist close", - label: "Task Manager: Close" - }, - { - id: "spawn dms ipc call processlist focusOrToggle", - label: "Task Manager: Focus or Toggle" - }, - { - id: "spawn dms ipc call settings toggle", - label: "Settings: Toggle" - }, - { - id: "spawn dms ipc call settings open", - label: "Settings: Open" - }, - { - id: "spawn dms ipc call settings close", - label: "Settings: Close" - }, - { - id: "spawn dms ipc call settings focusOrToggle", - label: "Settings: Focus or Toggle" - }, - { - id: "spawn dms ipc call powermenu toggle", - label: "Power Menu: Toggle" - }, - { - id: "spawn dms ipc call powermenu open", - label: "Power Menu: Open" - }, - { - id: "spawn dms ipc call powermenu close", - label: "Power Menu: Close" - }, - { - id: "spawn dms ipc call control-center toggle", - label: "Control Center: Toggle" - }, - { - id: "spawn dms ipc call control-center open", - label: "Control Center: Open" - }, - { - id: "spawn dms ipc call control-center close", - label: "Control Center: Close" - }, - { - id: "spawn dms ipc call notepad toggle", - label: "Notepad: Toggle" - }, - { - id: "spawn dms ipc call notepad open", - label: "Notepad: Open" - }, - { - id: "spawn dms ipc call notepad close", - label: "Notepad: Close" - }, - { - id: "spawn dms ipc call dash toggle", - label: "Dashboard: Toggle" - }, - { - id: "spawn dms ipc call dash open overview", - label: "Dashboard: Overview" - }, - { - id: "spawn dms ipc call dash open media", - label: "Dashboard: Media" - }, - { - id: "spawn dms ipc call dash open weather", - label: "Dashboard: Weather" - }, - { - id: "spawn dms ipc call dankdash wallpaper", - label: "Wallpaper Browser" - }, - { - id: "spawn dms ipc call file browse wallpaper", - label: "File: Browse Wallpaper" - }, - { - id: "spawn dms ipc call file browse profile", - label: "File: Browse Profile" - }, - { - id: "spawn dms ipc call keybinds toggle niri", - label: "Keybinds Cheatsheet: Toggle" - }, - { - id: "spawn dms ipc call keybinds open niri", - label: "Keybinds Cheatsheet: Open" - }, - { - id: "spawn dms ipc call keybinds close", - label: "Keybinds Cheatsheet: Close" - }, - { - id: "spawn dms ipc call lock lock", - label: "Lock Screen" - }, - { - id: "spawn dms ipc call lock demo", - label: "Lock Screen: Demo" - }, - { - id: "spawn dms ipc call inhibit toggle", - label: "Idle Inhibit: Toggle" - }, - { - id: "spawn dms ipc call inhibit enable", - label: "Idle Inhibit: Enable" - }, - { - id: "spawn dms ipc call inhibit disable", - label: "Idle Inhibit: Disable" - }, - { - id: "spawn dms ipc call audio increment", - label: "Volume Up" - }, - { - id: "spawn dms ipc call audio increment 1", - label: "Volume Up (1%)" - }, - { - id: "spawn dms ipc call audio increment 5", - label: "Volume Up (5%)" - }, - { - id: "spawn dms ipc call audio increment 10", - label: "Volume Up (10%)" - }, - { - id: "spawn dms ipc call audio decrement", - label: "Volume Down" - }, - { - id: "spawn dms ipc call audio decrement 1", - label: "Volume Down (1%)" - }, - { - id: "spawn dms ipc call audio decrement 5", - label: "Volume Down (5%)" - }, - { - id: "spawn dms ipc call audio decrement 10", - label: "Volume Down (10%)" - }, - { - id: "spawn dms ipc call audio mute", - label: "Volume Mute Toggle" - }, - { - id: "spawn dms ipc call audio micmute", - label: "Microphone Mute Toggle" - }, - { - id: "spawn dms ipc call brightness increment", - label: "Brightness Up" - }, - { - id: "spawn dms ipc call brightness increment 1", - label: "Brightness Up (1%)" - }, - { - id: "spawn dms ipc call brightness increment 5", - label: "Brightness Up (5%)" - }, - { - id: "spawn dms ipc call brightness increment 10", - label: "Brightness Up (10%)" - }, - { - id: "spawn dms ipc call brightness decrement", - label: "Brightness Down" - }, - { - id: "spawn dms ipc call brightness decrement 1", - label: "Brightness Down (1%)" - }, - { - id: "spawn dms ipc call brightness decrement 5", - label: "Brightness Down (5%)" - }, - { - id: "spawn dms ipc call brightness decrement 10", - label: "Brightness Down (10%)" - }, - { - id: "spawn dms ipc call brightness toggleExponential", - label: "Brightness: Toggle Exponential" - }, - { - id: "spawn dms ipc call theme toggle", - label: "Theme: Toggle Light/Dark" - }, - { - id: "spawn dms ipc call theme light", - label: "Theme: Light Mode" - }, - { - id: "spawn dms ipc call theme dark", - label: "Theme: Dark Mode" - }, - { - id: "spawn dms ipc call night toggle", - label: "Night Mode: Toggle" - }, - { - id: "spawn dms ipc call night enable", - label: "Night Mode: Enable" - }, - { - id: "spawn dms ipc call night disable", - label: "Night Mode: Disable" - }, - { - id: "spawn dms ipc call bar toggle index 0", - label: "Bar: Toggle (Primary)" - }, - { - id: "spawn dms ipc call bar reveal index 0", - label: "Bar: Reveal (Primary)" - }, - { - id: "spawn dms ipc call bar hide index 0", - label: "Bar: Hide (Primary)" - }, - { - id: "spawn dms ipc call bar toggleAutoHide index 0", - label: "Bar: Toggle Auto-Hide (Primary)" - }, - { - id: "spawn dms ipc call bar autoHide index 0", - label: "Bar: Enable Auto-Hide (Primary)" - }, - { - id: "spawn dms ipc call bar manualHide index 0", - label: "Bar: Disable Auto-Hide (Primary)" - }, - { - id: "spawn dms ipc call dock toggle", - label: "Dock: Toggle" - }, - { - id: "spawn dms ipc call dock reveal", - label: "Dock: Reveal" - }, - { - id: "spawn dms ipc call dock hide", - label: "Dock: Hide" - }, - { - id: "spawn dms ipc call dock toggleAutoHide", - label: "Dock: Toggle Auto-Hide" - }, - { - id: "spawn dms ipc call dock autoHide", - label: "Dock: Enable Auto-Hide" - }, - { - id: "spawn dms ipc call dock manualHide", - label: "Dock: Disable Auto-Hide" - }, - { - id: "spawn dms ipc call mpris playPause", - label: "Media: Play/Pause" - }, - { - id: "spawn dms ipc call mpris play", - label: "Media: Play" - }, - { - id: "spawn dms ipc call mpris pause", - label: "Media: Pause" - }, - { - id: "spawn dms ipc call mpris previous", - label: "Media: Previous Track" - }, - { - id: "spawn dms ipc call mpris next", - label: "Media: Next Track" - }, - { - id: "spawn dms ipc call mpris stop", - label: "Media: Stop" - }, - { - id: "spawn dms ipc call niri screenshot", - label: "Screenshot: Interactive", - compositor: "niri" - }, - { - id: "spawn dms ipc call niri screenshotScreen", - label: "Screenshot: Full Screen", - compositor: "niri" - }, - { - id: "spawn dms ipc call niri screenshotWindow", - label: "Screenshot: Window", - compositor: "niri" - }, - { - id: "spawn dms ipc call hypr toggleOverview", - label: "Hyprland: Toggle Overview", - compositor: "hyprland" - }, - { - id: "spawn dms ipc call hypr openOverview", - label: "Hyprland: Open Overview", - compositor: "hyprland" - }, - { - id: "spawn dms ipc call hypr closeOverview", - label: "Hyprland: Close Overview", - compositor: "hyprland" - }, - { - id: "spawn dms ipc call wallpaper next", - label: "Wallpaper: Next" - }, - { - id: "spawn dms ipc call wallpaper prev", - label: "Wallpaper: Previous" - } - ] - - readonly property var compositorActions: ({ - "Window": [ - { - id: "close-window", - label: "Close Window" - }, - { - id: "fullscreen-window", - label: "Fullscreen" - }, - { - id: "maximize-column", - label: "Maximize Column" - }, - { - id: "center-column", - label: "Center Column" - }, - { - id: "toggle-window-floating", - label: "Toggle Floating" - }, - { - id: "switch-preset-column-width", - label: "Cycle Column Width" - }, - { - id: "switch-preset-window-height", - label: "Cycle Window Height" - }, - { - 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" - } - ], - "Focus": [ - { - id: "focus-column-left", - label: "Focus Left" - }, - { - id: "focus-column-right", - label: "Focus Right" - }, - { - id: "focus-window-down", - label: "Focus Down" - }, - { - id: "focus-window-up", - label: "Focus Up" - }, - { - id: "focus-column-first", - label: "Focus First Column" - }, - { - id: "focus-column-last", - label: "Focus Last Column" - } - ], - "Move": [ - { - id: "move-column-left", - label: "Move Left" - }, - { - id: "move-column-right", - label: "Move Right" - }, - { - id: "move-window-down", - label: "Move Down" - }, - { - id: "move-window-up", - label: "Move Up" - }, - { - id: "move-column-to-first", - label: "Move to First" - }, - { - id: "move-column-to-last", - label: "Move to Last" - } - ], - "Workspace": [ - { - id: "focus-workspace-down", - label: "Focus Workspace Down" - }, - { - id: "focus-workspace-up", - label: "Focus Workspace Up" - }, - { - id: "focus-workspace-previous", - label: "Focus Previous Workspace" - }, - { - id: "move-column-to-workspace-down", - label: "Move to Workspace Down" - }, - { - id: "move-column-to-workspace-up", - label: "Move to Workspace Up" - }, - { - id: "move-workspace-down", - label: "Move Workspace Down" - }, - { - id: "move-workspace-up", - label: "Move Workspace Up" - } - ], - "Monitor": [ - { - id: "focus-monitor-left", - label: "Focus Monitor Left" - }, - { - id: "focus-monitor-right", - label: "Focus Monitor Right" - }, - { - id: "focus-monitor-down", - label: "Focus Monitor Down" - }, - { - id: "focus-monitor-up", - label: "Focus Monitor Up" - }, - { - id: "move-column-to-monitor-left", - label: "Move to Monitor Left" - }, - { - id: "move-column-to-monitor-right", - label: "Move to Monitor Right" - }, - { - id: "move-column-to-monitor-down", - label: "Move to Monitor Down" - }, - { - id: "move-column-to-monitor-up", - label: "Move to Monitor Up" - } - ], - "Screenshot": [ - { - id: "screenshot", - label: "Screenshot (Interactive)" - }, - { - id: "screenshot-screen", - label: "Screenshot Screen" - }, - { - id: "screenshot-window", - label: "Screenshot Window" - } - ], - "System": [ - { - id: "toggle-overview", - label: "Toggle Overview" - }, - { - id: "show-hotkey-overlay", - label: "Show Hotkey Overlay" - }, - { - 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" - }, - { - id: "quit", - label: "Quit Niri" - }, - { - id: "suspend", - label: "Suspend" - } - ], - "Alt-Tab": [ - { - id: "next-window", - label: "Next Window" - }, - { - id: "previous-window", - label: "Previous Window" - } - ] - }) + readonly property var actionTypes: Actions.getActionTypes() + readonly property var dmsActions: getDmsActions() signal bindsLoaded signal bindSaved(string key) @@ -803,6 +190,7 @@ Singleton { keybinds = _rawData || {}; if (currentProvider === "niri") dmsBindsIncluded = _rawData?.dmsBindsIncluded ?? true; + if (!_rawData?.binds) { _allBinds = {}; _categories = []; @@ -819,7 +207,7 @@ Singleton { const binds = bindsData[cat]; for (let i = 0; i < binds.length; i++) { const bind = binds[i]; - const targetCat = isDmsAction(bind.action) ? "DMS" : cat; + const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat; if (!processed[targetCat]) processed[targetCat] = []; processed[targetCat].push(bind); @@ -849,9 +237,8 @@ Singleton { }; if (actionMap[action]) { actionMap[action].keys.push(keyData); - if (!actionMap[action].desc && bind.desc) { + if (!actionMap[action].desc && bind.desc) actionMap[action].desc = bind.desc; - } } else { const entry = { category: category, @@ -892,12 +279,6 @@ Singleton { bindsLoaded(); } - function isDmsAction(action) { - if (!action) - return false; - return action.startsWith("spawn dms ipc call "); - } - function getCategories() { return _categories; } @@ -906,28 +287,13 @@ Singleton { return _flatCache; } - function isValidAction(action) { - if (!action) - return false; - switch (action) { - case "spawn": - case "spawn ": - case "spawn sh -c \"\"": - case "spawn sh -c ''": - return false; - default: - return true; - } - } - function saveBind(originalKey, bindData) { - if (!bindData.key || !isValidAction(bindData.action)) + if (!bindData.key || !Actions.isValidAction(bindData.action)) return; saving = true; const cmd = ["dms", "keybinds", "set", currentProvider, bindData.key, bindData.action, "--desc", bindData.desc || ""]; - if (originalKey && originalKey !== bindData.key) { + if (originalKey && originalKey !== bindData.key) cmd.push("--replace-key", originalKey); - } saveProcess.command = cmd; saveProcess.running = true; bindSaved(bindData.key); @@ -941,110 +307,47 @@ Singleton { bindRemoved(key); } + function isDmsAction(action) { + return Actions.isDmsAction(action); + } + + function isValidAction(action) { + return Actions.isValidAction(action); + } + function getActionType(action) { - if (!action) - return "compositor"; - if (action.startsWith("spawn dms ipc call ")) - return "dms"; - if (action.startsWith("spawn sh -c ") || action.startsWith("spawn bash -c ")) - return "shell"; - if (action.startsWith("spawn ")) - return "spawn"; - return "compositor"; + return Actions.getActionType(action); } function getActionLabel(action) { - if (!action) - return ""; - - for (let i = 0; i < dmsActions.length; i++) { - if (dmsActions[i].id === action) - return dmsActions[i].label; - } - - for (const cat in compositorActions) { - const acts = compositorActions[cat]; - for (let i = 0; i < acts.length; i++) { - if (acts[i].id === action) - return acts[i].label; - } - } - - if (action.startsWith("spawn sh -c ")) - return action.slice(12).replace(/^["']|["']$/g, ""); - if (action.startsWith("spawn ")) - return action.slice(6); - return action; + return Actions.getActionLabel(action); } function getCompositorCategories() { - return Object.keys(compositorActions); + return Actions.getCompositorCategories(); } function getCompositorActions(category) { - return compositorActions[category] || []; + return Actions.getCompositorActions(category); } function getDmsActions() { - const result = []; - for (let i = 0; i < dmsActions.length; i++) { - const action = dmsActions[i]; - if (!action.compositor) { - result.push(action); - continue; - } - switch (action.compositor) { - case "niri": - if (CompositorService.isNiri) - result.push(action); - break; - case "hyprland": - if (CompositorService.isHyprland) - result.push(action); - break; - } - } - return result; + return Actions.getDmsActions(CompositorService.isNiri, CompositorService.isHyprland); } function buildSpawnAction(command, args) { - if (!command) - return ""; - let parts = [command]; - if (args?.length > 0) - parts = parts.concat(args.filter(a => a)); - return "spawn " + parts.join(" "); + return Actions.buildSpawnAction(command, args); } function buildShellAction(shellCmd) { - if (!shellCmd) - return ""; - return "spawn sh -c \"" + shellCmd.replace(/"/g, "\\\"") + "\""; + return Actions.buildShellAction(shellCmd); } function parseSpawnCommand(action) { - if (!action?.startsWith("spawn ")) - return { - command: "", - args: [] - }; - const rest = action.slice(6); - const parts = rest.split(" ").filter(p => p); - return { - command: parts[0] || "", - args: parts.slice(1) - }; + return Actions.parseSpawnCommand(action); } function parseShellCommand(action) { - if (!action) - return ""; - if (!action.startsWith("spawn sh -c ")) - return ""; - let content = action.slice(12); - if ((content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'"))) { - content = content.slice(1, -1); - } - return content.replace(/\\"/g, "\""); + return Actions.parseShellCommand(action); } } diff --git a/quickshell/Widgets/KeybindItem.qml b/quickshell/Widgets/KeybindItem.qml index 47f384ab..eda483ef 100644 --- a/quickshell/Widgets/KeybindItem.qml +++ b/quickshell/Widgets/KeybindItem.qml @@ -6,6 +6,8 @@ import Quickshell.Wayland import qs.Common import qs.Services import qs.Widgets +import "../Common/KeyUtils.js" as KeyUtils +import "../Common/KeybindActions.js" as Actions Item { id: root @@ -34,6 +36,8 @@ Item { return false; } readonly property string _originalKey: editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : "" + readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : [] + readonly property bool hasConflict: _conflicts.length > 0 signal toggleExpand signal saveBind(string originalKey, var newData) @@ -49,7 +53,7 @@ Item { } onEditActionChanged: { - _actionType = KeybindsService.getActionType(editAction); + _actionType = Actions.getActionType(editAction); } function resetEdits() { @@ -59,22 +63,8 @@ Item { editAction = bindData.action || ""; editDesc = bindData.desc || ""; hasChanges = false; - _actionType = KeybindsService.getActionType(editAction); - useCustomCompositor = _actionType === "compositor" && !isKnownCompositorAction(editAction); - } - - function isKnownCompositorAction(action) { - if (!action) - return false; - const cats = KeybindsService.getCompositorCategories(); - for (const cat of cats) { - const actions = KeybindsService.getCompositorActions(cat); - for (const act of actions) { - if (act.id === action) - return true; - } - } - return false; + _actionType = Actions.getActionType(editAction); + useCustomCompositor = _actionType === "compositor" && !Actions.isKnownCompositorAction(editAction); } function startAddingNewKey() { @@ -107,7 +97,7 @@ Item { function canSave() { if (!editKey) return false; - if (!KeybindsService.isValidAction(editAction)) + if (!Actions.isValidAction(editAction)) return false; return true; } @@ -136,150 +126,6 @@ Item { recording = false; } - function modsFromEvent(mods) { - const result = []; - if (mods & Qt.ControlModifier) - result.push("Ctrl"); - if (mods & Qt.ShiftModifier) - result.push("Shift"); - const hasAlt = mods & Qt.AltModifier; - const hasSuper = mods & Qt.MetaModifier; - if (hasAlt && hasSuper) { - result.push("Mod"); - } else { - if (hasAlt) - result.push("Alt"); - if (hasSuper) - result.push("Super"); - } - return result; - } - - function normalizeKeyCombo(keyCombo) { - return keyCombo.toLowerCase().replace(/\bmod\b/g, "super").replace(/\bsuper\b/g, "super"); - } - - function getConflictingBinds(keyCombo) { - if (!keyCombo) - return []; - const conflicts = []; - const allBinds = KeybindsService.getFlatBinds(); - const normalizedKey = normalizeKeyCombo(keyCombo); - for (let i = 0; i < allBinds.length; i++) { - const bind = allBinds[i]; - if (bind.action === bindData.action) - continue; - for (let k = 0; k < bind.keys.length; k++) { - if (normalizeKeyCombo(bind.keys[k].key) === normalizedKey) { - conflicts.push({ - action: bind.action, - desc: bind.desc || bind.action - }); - break; - } - } - } - return conflicts; - } - - readonly property var _conflicts: editKey ? getConflictingBinds(editKey) : [] - readonly property bool hasConflict: _conflicts.length > 0 - - readonly property var _keyMap: ({ - [Qt.Key_Left]: "Left", - [Qt.Key_Right]: "Right", - [Qt.Key_Up]: "Up", - [Qt.Key_Down]: "Down", - [Qt.Key_Comma]: "Comma", - [Qt.Key_Period]: "Period", - [Qt.Key_Slash]: "Slash", - [Qt.Key_Semicolon]: "Semicolon", - [Qt.Key_Apostrophe]: "Apostrophe", - [Qt.Key_BracketLeft]: "BracketLeft", - [Qt.Key_BracketRight]: "BracketRight", - [Qt.Key_Backslash]: "Backslash", - [Qt.Key_Minus]: "Minus", - [Qt.Key_Equal]: "Equal", - [Qt.Key_QuoteLeft]: "grave", - [Qt.Key_Space]: "space", - [Qt.Key_Print]: "Print", - [Qt.Key_Return]: "Return", - [Qt.Key_Enter]: "Return", - [Qt.Key_Tab]: "Tab", - [Qt.Key_Backspace]: "BackSpace", - [Qt.Key_Delete]: "Delete", - [Qt.Key_Insert]: "Insert", - [Qt.Key_Home]: "Home", - [Qt.Key_End]: "End", - [Qt.Key_PageUp]: "Page_Up", - [Qt.Key_PageDown]: "Page_Down", - [Qt.Key_Escape]: "Escape", - [Qt.Key_CapsLock]: "Caps_Lock", - [Qt.Key_NumLock]: "Num_Lock", - [Qt.Key_ScrollLock]: "Scroll_Lock", - [Qt.Key_Pause]: "Pause", - [Qt.Key_VolumeUp]: "XF86AudioRaiseVolume", - [Qt.Key_VolumeDown]: "XF86AudioLowerVolume", - [Qt.Key_VolumeMute]: "XF86AudioMute", - [Qt.Key_MicMute]: "XF86AudioMicMute", - [Qt.Key_MediaPlay]: "XF86AudioPlay", - [Qt.Key_MediaPause]: "XF86AudioPause", - [Qt.Key_MediaStop]: "XF86AudioStop", - [Qt.Key_MediaNext]: "XF86AudioNext", - [Qt.Key_MediaPrevious]: "XF86AudioPrev", - [Qt.Key_MediaRecord]: "XF86AudioRecord", - [Qt.Key_MonBrightnessUp]: "XF86MonBrightnessUp", - [Qt.Key_MonBrightnessDown]: "XF86MonBrightnessDown", - [Qt.Key_KeyboardBrightnessUp]: "XF86KbdBrightnessUp", - [Qt.Key_KeyboardBrightnessDown]: "XF86KbdBrightnessDown", - [Qt.Key_PowerOff]: "XF86PowerOff", - [Qt.Key_Sleep]: "XF86Sleep", - [Qt.Key_WakeUp]: "XF86WakeUp", - [Qt.Key_Eject]: "XF86Eject", - [Qt.Key_Calculator]: "XF86Calculator", - [Qt.Key_Explorer]: "XF86Explorer", - [Qt.Key_HomePage]: "XF86HomePage", - [Qt.Key_Search]: "XF86Search", - [Qt.Key_LaunchMail]: "XF86Mail", - [Qt.Key_Launch0]: "XF86Launch0", - [Qt.Key_Launch1]: "XF86Launch1", - [Qt.Key_Exclam]: "1", - [Qt.Key_At]: "2", - [Qt.Key_NumberSign]: "3", - [Qt.Key_Dollar]: "4", - [Qt.Key_Percent]: "5", - [Qt.Key_AsciiCircum]: "6", - [Qt.Key_Ampersand]: "7", - [Qt.Key_Asterisk]: "8", - [Qt.Key_ParenLeft]: "9", - [Qt.Key_ParenRight]: "0", - [Qt.Key_Less]: "Comma", - [Qt.Key_Greater]: "Period", - [Qt.Key_Question]: "Slash", - [Qt.Key_Colon]: "Semicolon", - [Qt.Key_QuoteDbl]: "Apostrophe", - [Qt.Key_BraceLeft]: "BracketLeft", - [Qt.Key_BraceRight]: "BracketRight", - [Qt.Key_Bar]: "Backslash", - [Qt.Key_Underscore]: "Minus", - [Qt.Key_Plus]: "Equal", - [Qt.Key_AsciiTilde]: "grave" - }) - - function xkbKeyFromQtKey(qk) { - if (qk >= Qt.Key_A && qk <= Qt.Key_Z) - return String.fromCharCode(qk); - if (qk >= Qt.Key_0 && qk <= Qt.Key_9) - return String.fromCharCode(qk); - if (qk >= Qt.Key_F1 && qk <= Qt.Key_F35) - return "F" + (qk - Qt.Key_F1 + 1); - return _keyMap[qk] || ""; - } - - function formatToken(mods, key) { - return (mods.length ? mods.join("+") + "+" : "") + key; - } - Column { id: contentColumn width: parent.width @@ -330,12 +176,11 @@ Item { } StyledText { - id: keyChipText text: modelData.key font.pixelSize: Theme.fontSizeSmall - font.weight: isSelected ? Font.Medium : Font.Normal + font.weight: parent.isSelected ? Font.Medium : Font.Normal isMonospace: true - color: isSelected ? Theme.primaryText : Theme.surfaceVariantText + color: parent.isSelected ? Theme.primaryText : Theme.surfaceVariantText anchors.centerIn: parent width: parent.width - Theme.spacingS horizontalAlignment: Text.AlignHCenter @@ -491,16 +336,16 @@ Item { Rectangle { anchors.fill: parent radius: parent.radius - color: editKeyChipArea.pressed ? Theme.surfaceTextHover : (editKeyChipArea.containsMouse && !isSelected ? Theme.surfaceTextHover : "transparent") + color: editKeyChipArea.pressed ? Theme.surfaceTextHover : (editKeyChipArea.containsMouse && !parent.isSelected ? Theme.surfaceTextHover : "transparent") } StyledText { id: editKeyChipText text: modelData.key font.pixelSize: Theme.fontSizeSmall - font.weight: isSelected ? Font.Medium : Font.Normal + font.weight: parent.isSelected ? Font.Medium : Font.Normal isMonospace: true - color: isSelected ? Theme.primaryText : Theme.surfaceVariantText + color: parent.isSelected ? Theme.primaryText : Theme.surfaceVariantText anchors.centerIn: parent } @@ -632,24 +477,11 @@ Item { return; } - const mods = []; - if (event.modifiers & Qt.ControlModifier) - mods.push("Ctrl"); - if (event.modifiers & Qt.ShiftModifier) - mods.push("Shift"); - if ((event.modifiers & Qt.AltModifier) && (event.modifiers & Qt.MetaModifier)) { - mods.push("Mod"); - } else { - if (event.modifiers & Qt.AltModifier) - mods.push("Alt"); - if (event.modifiers & Qt.MetaModifier) - mods.push("Super"); - } - - const key = root.xkbKeyFromQtKey(event.key); + const mods = KeyUtils.modsFromEvent(event.modifiers); + const key = KeyUtils.xkbKeyFromQtKey(event.key); if (key) { root.updateEdit({ - key: root.formatToken(mods, key) + key: KeyUtils.formatToken(mods, key) }); root.stopRecording(); event.accepted = true; @@ -777,26 +609,31 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (typeDelegate.modelData.id === "dms") { + switch (typeDelegate.modelData.id) { + case "dms": root.updateEdit({ action: KeybindsService.dmsActions[0].id, desc: KeybindsService.dmsActions[0].label }); - } else if (typeDelegate.modelData.id === "compositor") { + break; + case "compositor": root.updateEdit({ action: "close-window", desc: "Close Window" }); - } else if (typeDelegate.modelData.id === "spawn") { + break; + case "spawn": root.updateEdit({ action: "spawn ", desc: "" }); - } else if (typeDelegate.modelData.id === "shell") { + break; + case "shell": root.updateEdit({ action: "spawn sh -c \"\"", desc: "" }); + break; } } onContainsMouseChanged: { @@ -952,13 +789,12 @@ Item { Layout.preferredHeight: 40 placeholderText: I18n.tr("e.g., focus-workspace 3, resize-column -10") text: root._actionType === "compositor" ? root.editAction : "" - onEditingFinished: { + onTextChanged: { if (root._actionType !== "compositor") return; - if (text.trim()) - root.updateEdit({ - action: text.trim() - }); + root.updateEdit({ + action: text + }); } } @@ -1015,20 +851,16 @@ Item { Layout.fillWidth: true Layout.preferredHeight: 40 placeholderText: I18n.tr("e.g., firefox, kitty --title foo") - readonly property var _parsed: root._actionType === "spawn" ? KeybindsService.parseSpawnCommand(root.editAction) : null + readonly property var _parsed: root._actionType === "spawn" ? Actions.parseSpawnCommand(root.editAction) : null text: _parsed ? (_parsed.command + " " + _parsed.args.join(" ")).trim() : "" - onEditingFinished: { + onTextChanged: { if (root._actionType !== "spawn") return; const parts = text.trim().split(" ").filter(p => p); - if (parts.length === 0) - return; - const changes = { - action: "spawn " + parts.join(" ") - }; - if (!root.editDesc) - changes.desc = parts[0]; - root.updateEdit(changes); + const action = parts.length > 0 ? "spawn " + parts.join(" ") : "spawn "; + root.updateEdit({ + action: action + }); } } } @@ -1051,15 +883,13 @@ Item { Layout.fillWidth: true Layout.preferredHeight: 40 placeholderText: I18n.tr("e.g., notify-send 'Hello' && sleep 1") - text: root._actionType === "shell" ? KeybindsService.parseShellCommand(root.editAction) : "" - onEditingFinished: { + text: root._actionType === "shell" ? Actions.parseShellCommand(root.editAction) : "" + onTextChanged: { if (root._actionType !== "shell") return; - if (text.trim()) { - root.updateEdit({ - action: KeybindsService.buildShellAction(text.trim()) - }); - } + root.updateEdit({ + action: Actions.buildShellAction(text) + }); } } } @@ -1082,7 +912,7 @@ Item { Layout.preferredHeight: 40 placeholderText: I18n.tr("Hotkey overlay title (optional)") text: root.editDesc - onEditingFinished: root.updateEdit({ + onTextChanged: root.updateEdit({ desc: text }) }