mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
319 lines
12 KiB
QML
319 lines
12 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
import qs.Services.UI
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
// Must be called at startup to force singleton instantiation,
|
|
// which registers the IpcHandler with the IPC system.
|
|
function init() {
|
|
Logger.i("CustomButtonIPCService", "Service started");
|
|
}
|
|
|
|
// Registry to store references to active custom buttons by their user-defined identifier
|
|
property var customButtonRegistry: ({})
|
|
|
|
// Register a custom button instance
|
|
function registerButton(button) {
|
|
if (!button || !button.ipcIdentifier) {
|
|
Logger.w("CustomButtonIPCService", "Cannot register button without ipcIdentifier");
|
|
return false;
|
|
}
|
|
|
|
customButtonRegistry[button.ipcIdentifier] = button;
|
|
Logger.d("CustomButtonIPCService", `Registered button with identifier: ${button.ipcIdentifier}`);
|
|
return true;
|
|
}
|
|
|
|
// Unregister a custom button instance
|
|
function unregisterButton(button) {
|
|
if (!button || !button.ipcIdentifier) {
|
|
return false;
|
|
}
|
|
|
|
if (customButtonRegistry[button.ipcIdentifier] === button) {
|
|
delete customButtonRegistry[button.ipcIdentifier];
|
|
Logger.d("CustomButtonIPCService", `Unregistered button with identifier: ${button.ipcIdentifier}`);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Find a button by identifier
|
|
function findButton(identifier) {
|
|
return customButtonRegistry[identifier] || null;
|
|
}
|
|
|
|
// Find button config from Settings for when the live widget is not loaded
|
|
function findButtonConfig(identifier) {
|
|
var screens = Quickshell.screens;
|
|
for (var i = 0; i < screens.length; i++) {
|
|
var widgets = Settings.getBarWidgetsForScreen(screens[i].name);
|
|
var config = _searchWidgetsForIdentifier(widgets, identifier);
|
|
if (config)
|
|
return config;
|
|
}
|
|
// Also check global widgets as a final fallback
|
|
var globalConfig = _searchWidgetsForIdentifier(Settings.data.bar.widgets, identifier);
|
|
if (globalConfig)
|
|
return globalConfig;
|
|
return null;
|
|
}
|
|
|
|
function _searchWidgetsForIdentifier(widgets, identifier) {
|
|
var sections = ["left", "center", "right"];
|
|
for (var s = 0; s < sections.length; s++) {
|
|
var list = widgets[sections[s]];
|
|
if (!list)
|
|
continue;
|
|
for (var j = 0; j < list.length; j++) {
|
|
var w = list[j];
|
|
if (w.id === "CustomButton" && w.ipcIdentifier === identifier) {
|
|
return w;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Resolve a command property from config with fallback to widgetMetadata defaults
|
|
function resolveCommand(config, prop) {
|
|
if (config[prop])
|
|
return config[prop];
|
|
var meta = BarWidgetRegistry.widgetMetadata["CustomButton"];
|
|
return meta ? (meta[prop] || "") : "";
|
|
}
|
|
|
|
// Substitute $delta expressions in a command string
|
|
function substituteWheelDelta(command, delta) {
|
|
var normalizedDelta = delta > 0 ? 1 : -1;
|
|
return command.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) {
|
|
if (operation) {
|
|
try {
|
|
var operator = operation.charAt(0);
|
|
var operand = parseInt(operation.substring(1));
|
|
var result;
|
|
switch (operator) {
|
|
case '+':
|
|
result = normalizedDelta + operand;
|
|
break;
|
|
case '-':
|
|
result = normalizedDelta - operand;
|
|
break;
|
|
case '*':
|
|
result = normalizedDelta * operand;
|
|
break;
|
|
case '/':
|
|
result = Math.floor(normalizedDelta / operand);
|
|
break;
|
|
default:
|
|
result = normalizedDelta;
|
|
}
|
|
return result.toString();
|
|
} catch (e) {
|
|
return normalizedDelta.toString();
|
|
}
|
|
} else {
|
|
return normalizedDelta.toString();
|
|
}
|
|
});
|
|
}
|
|
|
|
// IpcHandler for custom button commands using short alias 'cb'
|
|
IpcHandler {
|
|
target: "cb"
|
|
|
|
// Handle left click: cb left "identifier"
|
|
function left(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.leftClickExec || button.textCommand) {
|
|
button.clicked();
|
|
Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Fallback: read from Settings
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const cmd = resolveCommand(config, "leftClickExec");
|
|
if (cmd) {
|
|
Quickshell.execDetached(["sh", "-lc", cmd]);
|
|
Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`);
|
|
}
|
|
}
|
|
|
|
// Handle right click: cb right "identifier"
|
|
function right(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.rightClickExec) {
|
|
button.rightClicked();
|
|
Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const cmd = resolveCommand(config, "rightClickExec");
|
|
if (cmd) {
|
|
Quickshell.execDetached(["sh", "-lc", cmd]);
|
|
Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`);
|
|
}
|
|
}
|
|
|
|
// Handle middle click: cb middle "identifier"
|
|
function middle(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.middleClickExec) {
|
|
button.middleClicked();
|
|
Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const cmd = resolveCommand(config, "middleClickExec");
|
|
if (cmd) {
|
|
Quickshell.execDetached(["sh", "-lc", cmd]);
|
|
Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`);
|
|
}
|
|
}
|
|
|
|
// Handle wheel up: cb up "identifier"
|
|
function up(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.wheelMode === "separate" && button.wheelUpExec) {
|
|
button.wheeled(1);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
|
const cmd = resolveCommand(config, "wheelUpExec");
|
|
if (mode === "separate" && cmd) {
|
|
const resolved = substituteWheelDelta(cmd, 1);
|
|
Quickshell.execDetached(["sh", "-lc", resolved]);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`);
|
|
}
|
|
}
|
|
|
|
// Handle wheel down: cb down "identifier"
|
|
function down(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.wheelMode === "separate" && button.wheelDownExec) {
|
|
button.wheeled(-1);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
|
const cmd = resolveCommand(config, "wheelDownExec");
|
|
if (mode === "separate" && cmd) {
|
|
const resolved = substituteWheelDelta(cmd, -1);
|
|
Quickshell.execDetached(["sh", "-lc", resolved]);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`);
|
|
}
|
|
}
|
|
|
|
// Handle wheel action: cb wheel "identifier"
|
|
function wheel(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (button) {
|
|
if (button.wheelMode === "unified" && button.wheelExec) {
|
|
button.wheeled(1);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}'`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const config = findButtonConfig(identifier);
|
|
if (!config) {
|
|
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
|
return;
|
|
}
|
|
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
|
const cmd = resolveCommand(config, "wheelExec");
|
|
if (mode === "unified" && cmd) {
|
|
const resolved = substituteWheelDelta(cmd, 1);
|
|
Quickshell.execDetached(["sh", "-lc", resolved]);
|
|
Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}' (from settings)`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`);
|
|
}
|
|
}
|
|
|
|
// Handle refresh: cb refresh "identifier"
|
|
function refresh(identifier: string) {
|
|
const button = findButton(identifier);
|
|
if (!button) {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' is not currently loaded — refresh requires a live widget instance`);
|
|
return;
|
|
}
|
|
|
|
if (button.textCommand && button.textCommand.length > 0 && !button.textStream) {
|
|
button.runTextCommand();
|
|
Logger.i("CustomButtonIPCService", `Triggered refresh (text command) on button '${identifier}'`);
|
|
} else if (button.textStream) {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' uses streaming, manual refresh disabled`);
|
|
} else {
|
|
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no text command to refresh`);
|
|
}
|
|
}
|
|
}
|
|
}
|