mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-05 21:32:07 -04:00
- niri only for now - requires quickshell-git, hidden otherwise - Add, Edit, Delete keybinds - Large suite of pre-defined and custom actions - Works with niri 25.11+ include feature
1051 lines
31 KiB
QML
1051 lines
31 KiB
QML
pragma Singleton
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtCore
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import qs.Common
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property bool shortcutInhibitorAvailable: {
|
|
try {
|
|
return typeof ShortcutInhibitor !== "undefined";
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
property bool available: CompositorService.isNiri && shortcutInhibitorAvailable
|
|
property string currentProvider: "niri"
|
|
property bool loading: false
|
|
property bool saving: false
|
|
property bool fixing: false
|
|
property string lastError: ""
|
|
property bool dmsBindsIncluded: true
|
|
|
|
property var _rawData: null
|
|
property var keybinds: ({})
|
|
property var _allBinds: ({})
|
|
property var _categories: []
|
|
property var _flatCache: []
|
|
property var displayList: []
|
|
property int _dataVersion: 0
|
|
|
|
readonly property var categoryOrder: ["DMS", "Execute", "Workspace", "Window", "Monitor", "Screenshot", "System", "Overview", "Alt-Tab", "Other"]
|
|
|
|
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"
|
|
}
|
|
]
|
|
})
|
|
|
|
signal bindsLoaded
|
|
signal bindSaved(string key)
|
|
signal bindRemoved(string key)
|
|
signal dmsBindsFixed
|
|
|
|
Component.onCompleted: {
|
|
if (available)
|
|
Qt.callLater(loadBinds);
|
|
}
|
|
|
|
Connections {
|
|
target: CompositorService
|
|
function onCompositorChanged() {
|
|
if (CompositorService.isNiri)
|
|
Qt.callLater(root.loadBinds);
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: loadProcess
|
|
running: false
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
try {
|
|
root._rawData = JSON.parse(text);
|
|
root._processData();
|
|
} catch (e) {
|
|
console.error("[KeybindsService] Failed to parse binds:", e);
|
|
}
|
|
root.loading = false;
|
|
}
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
if (exitCode !== 0) {
|
|
console.warn("[KeybindsService] Load process failed with code:", exitCode);
|
|
root.loading = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: saveProcess
|
|
running: false
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
if (!text.trim())
|
|
return;
|
|
root.lastError = text.trim();
|
|
ToastService.showError(I18n.tr("Failed to save keybind"), "", root.lastError, "keybinds");
|
|
}
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
root.saving = false;
|
|
if (exitCode === 0) {
|
|
root.lastError = "";
|
|
root.loadBinds(false);
|
|
} else {
|
|
console.error("[KeybindsService] Save failed with code:", exitCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: removeProcess
|
|
running: false
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
if (!text.trim())
|
|
return;
|
|
root.lastError = text.trim();
|
|
ToastService.showError(I18n.tr("Failed to remove keybind"), "", root.lastError, "keybinds");
|
|
}
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
if (exitCode === 0) {
|
|
root.lastError = "";
|
|
root.loadBinds(false);
|
|
} else {
|
|
console.error("[KeybindsService] Remove failed with code:", exitCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: fixProcess
|
|
running: false
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
if (!text.trim())
|
|
return;
|
|
root.lastError = text.trim();
|
|
ToastService.showError(I18n.tr("Failed to add binds include"), "", root.lastError, "keybinds");
|
|
}
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
root.fixing = false;
|
|
if (exitCode === 0) {
|
|
root.lastError = "";
|
|
root.dmsBindsIncluded = true;
|
|
root.dmsBindsFixed();
|
|
ToastService.showSuccess(I18n.tr("Binds include added"), I18n.tr("dms/binds.kdl is now included in config.kdl"), "", "keybinds");
|
|
Qt.callLater(root.forceReload);
|
|
} else {
|
|
console.error("[KeybindsService] Fix failed with code:", exitCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
function fixDmsBindsInclude() {
|
|
if (fixing || dmsBindsIncluded)
|
|
return;
|
|
fixing = true;
|
|
const niriConfigDir = configDir + "/niri";
|
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
const backupPath = `${niriConfigDir}/config.kdl.dmsbackup${timestamp}`;
|
|
const script = `mkdir -p "${niriConfigDir}/dms" && touch "${niriConfigDir}/dms/binds.kdl" && cp "${niriConfigDir}/config.kdl" "${backupPath}" && echo 'include "dms/binds.kdl"' >> "${niriConfigDir}/config.kdl"`;
|
|
fixProcess.command = ["sh", "-c", script];
|
|
fixProcess.running = true;
|
|
}
|
|
|
|
function forceReload() {
|
|
_allBinds = {};
|
|
_flatCache = [];
|
|
_categories = [];
|
|
loadBinds(true);
|
|
}
|
|
|
|
function loadBinds(showLoading) {
|
|
if (loading || !available)
|
|
return;
|
|
const hasData = Object.keys(_allBinds).length > 0;
|
|
loading = showLoading !== false && !hasData;
|
|
loadProcess.command = ["dms", "keybinds", "show", currentProvider];
|
|
loadProcess.running = true;
|
|
}
|
|
|
|
function _processData() {
|
|
keybinds = _rawData || {};
|
|
if (currentProvider === "niri")
|
|
dmsBindsIncluded = _rawData?.dmsBindsIncluded ?? true;
|
|
if (!_rawData?.binds) {
|
|
_allBinds = {};
|
|
_categories = [];
|
|
_flatCache = [];
|
|
displayList = [];
|
|
_dataVersion++;
|
|
bindsLoaded();
|
|
return;
|
|
}
|
|
|
|
const processed = {};
|
|
const bindsData = _rawData.binds;
|
|
for (const cat in bindsData) {
|
|
const binds = bindsData[cat];
|
|
for (let i = 0; i < binds.length; i++) {
|
|
const bind = binds[i];
|
|
const targetCat = isDmsAction(bind.action) ? "DMS" : cat;
|
|
if (!processed[targetCat])
|
|
processed[targetCat] = [];
|
|
processed[targetCat].push(bind);
|
|
}
|
|
}
|
|
|
|
const sortedCats = Object.keys(processed).sort((a, b) => {
|
|
const ai = categoryOrder.indexOf(a);
|
|
const bi = categoryOrder.indexOf(b);
|
|
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
});
|
|
|
|
const grouped = [];
|
|
const actionMap = {};
|
|
for (let ci = 0; ci < sortedCats.length; ci++) {
|
|
const category = sortedCats[ci];
|
|
const binds = processed[category];
|
|
if (!binds)
|
|
continue;
|
|
for (let i = 0; i < binds.length; i++) {
|
|
const bind = binds[i];
|
|
const action = bind.action || "";
|
|
const keyData = {
|
|
key: bind.key || "",
|
|
source: bind.source || "config",
|
|
isOverride: bind.source === "dms"
|
|
};
|
|
if (actionMap[action]) {
|
|
actionMap[action].keys.push(keyData);
|
|
if (!actionMap[action].desc && bind.desc) {
|
|
actionMap[action].desc = bind.desc;
|
|
}
|
|
} else {
|
|
const entry = {
|
|
category: category,
|
|
action: action,
|
|
desc: bind.desc || "",
|
|
keys: [keyData]
|
|
};
|
|
actionMap[action] = entry;
|
|
grouped.push(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
const list = [];
|
|
for (const cat of sortedCats) {
|
|
list.push({
|
|
id: "cat:" + cat,
|
|
type: "category",
|
|
name: cat
|
|
});
|
|
const binds = processed[cat];
|
|
if (!binds)
|
|
continue;
|
|
for (const bind of binds)
|
|
list.push({
|
|
id: "bind:" + bind.key,
|
|
type: "bind",
|
|
key: bind.key,
|
|
desc: bind.desc
|
|
});
|
|
}
|
|
|
|
_allBinds = processed;
|
|
_categories = sortedCats;
|
|
_flatCache = grouped;
|
|
displayList = list;
|
|
_dataVersion++;
|
|
bindsLoaded();
|
|
}
|
|
|
|
function isDmsAction(action) {
|
|
if (!action)
|
|
return false;
|
|
return action.startsWith("spawn dms ipc call ");
|
|
}
|
|
|
|
function getCategories() {
|
|
return _categories;
|
|
}
|
|
|
|
function getFlatBinds() {
|
|
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))
|
|
return;
|
|
saving = true;
|
|
const cmd = ["dms", "keybinds", "set", currentProvider, bindData.key, bindData.action, "--desc", bindData.desc || ""];
|
|
if (originalKey && originalKey !== bindData.key) {
|
|
cmd.push("--replace-key", originalKey);
|
|
}
|
|
saveProcess.command = cmd;
|
|
saveProcess.running = true;
|
|
bindSaved(bindData.key);
|
|
}
|
|
|
|
function removeBind(key) {
|
|
if (!key)
|
|
return;
|
|
removeProcess.command = ["dms", "keybinds", "remove", currentProvider, key];
|
|
removeProcess.running = true;
|
|
bindRemoved(key);
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
function getCompositorCategories() {
|
|
return Object.keys(compositorActions);
|
|
}
|
|
|
|
function getCompositorActions(category) {
|
|
return compositorActions[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;
|
|
}
|
|
|
|
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(" ");
|
|
}
|
|
|
|
function buildShellAction(shellCmd) {
|
|
if (!shellCmd)
|
|
return "";
|
|
return "spawn sh -c \"" + shellCmd.replace(/"/g, "\\\"") + "\"";
|
|
}
|
|
|
|
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)
|
|
};
|
|
}
|
|
|
|
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, "\"");
|
|
}
|
|
}
|