mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
feat: Implement automatic IPC registration for CustomButton widgets
- Add automatic registration/unregistration of CustomButton instances to CustomButtonIPCService - Enable CustomButton widgets to register themselves with unique identifiers - Support external control via IPC commands using 'qs -c noctalia-loner ipc call cb <action> "[identifier]"' format Supported actions: left, right, middle, up (separate wheel mode), down (separate wheel mode),wheel (unified wheel mode), refresh (textCommand)
This commit is contained in:
@@ -103,6 +103,8 @@
|
||||
"hide-mode-label": "Hide mode",
|
||||
"hide-mode-max-transparent": "Max expanded but transparent",
|
||||
"icon-description": "Select an icon from the library.",
|
||||
"ipc-identifier-description": "Unique identifier for IPC commands. Use this identifier with 'qs -c noctalia-loner ipc call cb [action] [identifier]' to control this button via IPC.",
|
||||
"ipc-identifier-label": "IPC Identifier",
|
||||
"left-click-description": "Command to execute when the button is left-clicked.",
|
||||
"left-click-label": "Left click",
|
||||
"left-click-update-text": "Update displayed text on left-click",
|
||||
@@ -1358,6 +1360,7 @@
|
||||
"placeholders": {
|
||||
"command-example": "echo \"Hello World\"",
|
||||
"enter-command": "Enter command to execute (app or custom script)",
|
||||
"enter-ipc-identifier": "Enter unique identifier for IPC commands",
|
||||
"enter-text-to-collapse": "e.g., 'nothing is playing'. Use /regex/ for patterns.",
|
||||
"enter-tooltip": "Enter tooltip",
|
||||
"enter-width-pixels": "Enter width in pixels",
|
||||
|
||||
@@ -7,6 +7,7 @@ import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Services.Control
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -39,6 +40,7 @@ Item {
|
||||
readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText
|
||||
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|
||||
readonly property bool middleClickUpdateText: widgetSettings.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText
|
||||
readonly property string ipcIdentifier: widgetSettings.ipcIdentifier !== undefined ? widgetSettings.ipcIdentifier : (widgetMetadata.ipcIdentifier || "")
|
||||
readonly property string wheelExec: widgetSettings.wheelExec || widgetMetadata.wheelExec
|
||||
readonly property string wheelUpExec: widgetSettings.wheelUpExec || widgetMetadata.wheelUpExec
|
||||
readonly property string wheelDownExec: widgetSettings.wheelDownExec || widgetMetadata.wheelDownExec
|
||||
@@ -607,4 +609,61 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to handle registration attempts
|
||||
Timer {
|
||||
id: registrationTimer
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
// Only register if ipcIdentifier is set
|
||||
if (ipcIdentifier && ipcIdentifier.trim() !== "") {
|
||||
// Try to access the service through the global application object
|
||||
try {
|
||||
if (typeof Qt !== 'undefined' && Qt.application && Qt.application.customButtonIPCService) {
|
||||
var service = Qt.application.customButtonIPCService;
|
||||
var success = service.registerButton(root);
|
||||
if (success) {
|
||||
Logger.i("CustomButton", `Successfully registered button with identifier: '${ipcIdentifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButton", `Failed to register button with identifier: '${ipcIdentifier}'`);
|
||||
}
|
||||
} else {
|
||||
Logger.w("CustomButton", `Service not available for button with identifier '${ipcIdentifier}'`);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Error during registration of button with identifier '${ipcIdentifier}': ${e.message}`);
|
||||
}
|
||||
} else {
|
||||
Logger.d("CustomButton", `No IPC identifier set for button, skipping registration`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register this button with the IPC service when component is completed
|
||||
Component.onCompleted: {
|
||||
registrationTimer.start();
|
||||
}
|
||||
|
||||
// Unregister this button when component is destroyed
|
||||
Component.onDestruction: {
|
||||
if (ipcIdentifier && ipcIdentifier.trim() !== "") {
|
||||
// Try to access the service through the global application object for unregistration
|
||||
try {
|
||||
if (typeof Qt !== 'undefined' && Qt.application && Qt.application.customButtonIPCService) {
|
||||
var service = Qt.application.customButtonIPCService;
|
||||
var success = service.unregisterButton(root);
|
||||
if (success) {
|
||||
Logger.i("CustomButton", `Successfully unregistered button with identifier: '${ipcIdentifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButton", `Failed to unregister button with identifier: '${ipcIdentifier}'`);
|
||||
}
|
||||
} else {
|
||||
Logger.w("CustomButton", `Service not available for unregistration of button with identifier '${ipcIdentifier}'`);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Error during unregistration of button with identifier '${ipcIdentifier}': ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ ColumnLayout {
|
||||
property bool valueShowIcon: (widgetData.showIcon !== undefined) ? widgetData.showIcon : widgetMetadata.showIcon
|
||||
property bool valueEnableColorization: widgetData.enableColorization || false
|
||||
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon || "none"
|
||||
property string valueIpcIdentifier: widgetData.ipcIdentifier !== undefined ? widgetData.ipcIdentifier : widgetMetadata.ipcIdentifier || ""
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
@@ -52,6 +53,7 @@ ColumnLayout {
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
|
||||
settings.enableColorization = valueEnableColorization;
|
||||
settings.colorizeSystemIcon = valueColorizeSystemIcon;
|
||||
settings.ipcIdentifier = valueIpcIdentifier;
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -142,6 +144,15 @@ ColumnLayout {
|
||||
onSelected: key => valueColorizeSystemIcon = key
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.ipc-identifier-label")
|
||||
description: I18n.tr("bar.custom-button.ipc-identifier-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-ipc-identifier")
|
||||
text: valueIpcIdentifier
|
||||
onTextChanged: valueIpcIdentifier = text
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Control
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Registry to store references to active custom buttons by their user-defined identifier
|
||||
property var customButtonRegistry: ({})
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("CustomButtonIPCService", "Service started");
|
||||
|
||||
// Make this service globally accessible
|
||||
if (typeof Qt !== 'undefined' && Qt && Qt.application) {
|
||||
Qt.application.customButtonIPCService = root;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger left click if configured
|
||||
if (button.leftClickExec || button.textCommand) {
|
||||
button.onClicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}'`);
|
||||
} 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) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger right click if configured
|
||||
if (button.rightClickExec) {
|
||||
button.onRightClicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}'`);
|
||||
} 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) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger middle click if configured
|
||||
if (button.middleClickExec) {
|
||||
button.onMiddleClicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}'`);
|
||||
} 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) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger wheel up if in separate mode and configured
|
||||
if (button.wheelMode === "separate" && button.wheelUpExec) {
|
||||
button.onWheel(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`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle wheel down: cb down "identifier"
|
||||
function down(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (!button) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger wheel down if in separate mode and configured
|
||||
if (button.wheelMode === "separate" && button.wheelDownExec) {
|
||||
button.onWheel(-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`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle wheel action: cb wheel "identifier"
|
||||
function wheel(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (!button) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger unified wheel if in unified mode and configured
|
||||
if (button.wheelMode === "unified" && button.wheelExec) {
|
||||
button.onWheel(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`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle refresh: cb refresh "identifier"
|
||||
function refresh(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (!button) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger text command refresh if configured and not streaming
|
||||
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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,8 @@ Singleton {
|
||||
"vertical": 10
|
||||
},
|
||||
"enableColorization": false,
|
||||
"colorizeSystemIcon": "none"
|
||||
"colorizeSystemIcon": "none",
|
||||
"ipcIdentifier": ""
|
||||
},
|
||||
"KeyboardLayout": {
|
||||
"displayMode": "onhover",
|
||||
|
||||
@@ -133,6 +133,11 @@ ShellRoot {
|
||||
screenDetector: screenDetector
|
||||
}
|
||||
|
||||
// CustomButtonIPCService handles IPC commands for custom buttons
|
||||
CustomButtonIPCService {
|
||||
id: customButtonIPCService
|
||||
}
|
||||
|
||||
// Container for plugins Main.qml instances (must be in graphics scene)
|
||||
Item {
|
||||
id: pluginContainer
|
||||
|
||||
Reference in New Issue
Block a user