mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
354 lines
10 KiB
QML
354 lines
10 KiB
QML
pragma Singleton
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtCore
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import qs.Common
|
|
import "../Common/KeybindActions.js" as Actions
|
|
|
|
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: 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: Actions.getActionTypes()
|
|
readonly property var dmsActions: getDmsActions()
|
|
|
|
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 = Actions.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 getCategories() {
|
|
return _categories;
|
|
}
|
|
|
|
function getFlatBinds() {
|
|
return _flatCache;
|
|
}
|
|
|
|
function saveBind(originalKey, bindData) {
|
|
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)
|
|
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 isDmsAction(action) {
|
|
return Actions.isDmsAction(action);
|
|
}
|
|
|
|
function isValidAction(action) {
|
|
return Actions.isValidAction(action);
|
|
}
|
|
|
|
function getActionType(action) {
|
|
return Actions.getActionType(action);
|
|
}
|
|
|
|
function getActionLabel(action) {
|
|
return Actions.getActionLabel(action);
|
|
}
|
|
|
|
function getCompositorCategories() {
|
|
return Actions.getCompositorCategories();
|
|
}
|
|
|
|
function getCompositorActions(category) {
|
|
return Actions.getCompositorActions(category);
|
|
}
|
|
|
|
function getDmsActions() {
|
|
return Actions.getDmsActions(CompositorService.isNiri, CompositorService.isHyprland);
|
|
}
|
|
|
|
function buildSpawnAction(command, args) {
|
|
return Actions.buildSpawnAction(command, args);
|
|
}
|
|
|
|
function buildShellAction(shellCmd) {
|
|
return Actions.buildShellAction(shellCmd);
|
|
}
|
|
|
|
function parseSpawnCommand(action) {
|
|
return Actions.parseSpawnCommand(action);
|
|
}
|
|
|
|
function parseShellCommand(action) {
|
|
return Actions.parseShellCommand(action);
|
|
}
|
|
}
|