From 694fefeebdb712fa5071369f149d4edfcbfec3f9 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 21 Nov 2025 10:58:15 +0800 Subject: [PATCH] feat: Custom buttons now support wheel actions --- Assets/Translations/en.json | 17 +++ Modules/Bar/Widgets/CustomButton.qml | 99 ++++++++++++++++- .../WidgetSettings/CustomButtonSettings.qml | 104 ++++++++++++++++++ Services/UI/BarWidgetRegistry.qml | 7 ++ 4 files changed, 226 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ec385399..cfebc8b5 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -145,6 +145,23 @@ "label": "Middle click", "update-text": "Update displayed text on middle-click" }, + "wheel": { + "description": "Command to execute when the scroll wheel is used.\nUse $delta for the scroll wheel delta in the command", + "label": "Scroll wheel", + "update-text": "Update displayed text on scroll" + }, + "wheel-mode-separate": { + "label": "Separate wheel commands", + "description": "Enable separate commands for wheel up and down" + }, + "wheel-up": { + "description": "Command to execute when the scroll wheel is scrolled up.", + "label": "Wheel up command" + }, + "wheel-down": { + "description": "Command to execute when the scroll wheel is scrolled down.", + "label": "Wheel down command" + }, "parse-json": { "description": "Parse the command output as a JSON object to dynamically set text and icon.", "label": "Parse output as JSON" diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index e73bad54..e4a9586a 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -39,12 +39,21 @@ 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 wheelExec: widgetSettings.wheelExec || widgetMetadata.wheelExec + readonly property string wheelUpExec: widgetSettings.wheelUpExec || widgetMetadata.wheelUpExec + readonly property string wheelDownExec: widgetSettings.wheelDownExec || widgetMetadata.wheelDownExec + readonly property string wheelMode: widgetSettings.wheelMode || widgetMetadata.wheelMode + readonly property bool wheelUpdateText: widgetSettings.wheelUpdateText ?? widgetMetadata.wheelUpdateText + readonly property bool wheelUpUpdateText: widgetSettings.wheelUpUpdateText ?? widgetMetadata.wheelUpUpdateText + readonly property bool wheelDownUpdateText: widgetSettings.wheelDownUpdateText ?? widgetMetadata.wheelDownUpdateText readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "") readonly property bool textStream: widgetSettings.textStream !== undefined ? widgetSettings.textStream : (widgetMetadata.textStream || false) readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000) readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "") readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false) - readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) + readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec || + (wheelMode === "unified" && wheelExec) || + (wheelMode === "separate" && (wheelUpExec || wheelDownExec))) implicitWidth: pill.width implicitHeight: pill.height @@ -73,6 +82,16 @@ Item { if (middleClickExec !== "") { tooltipLines.push(`Middle click: ${middleClickExec}.`); } + if (wheelMode === "unified" && wheelExec !== "") { + tooltipLines.push(`Wheel: ${wheelExec}.`); + } else if (wheelMode === "separate") { + if (wheelUpExec !== "") { + tooltipLines.push(`Wheel up: ${wheelUpExec}.`); + } + if (wheelDownExec !== "") { + tooltipLines.push(`Wheel down: ${wheelDownExec}.`); + } + } } if (_dynamicTooltip !== "") { @@ -92,6 +111,7 @@ Item { onClicked: root.onClicked() onRightClicked: root.onRightClicked() onMiddleClicked: root.onMiddleClicked() + onWheel: delta => root.onWheel(delta) } // Internal state for dynamic text @@ -382,4 +402,81 @@ Item { textProc.command = ["sh", "-lc", textCommand]; textProc.running = true; } + + function onWheel(delta) { + if (wheelMode === "unified" && wheelExec) { + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = wheelExec.replace(/\$delta([+\-*/]\d+)?/g, function(match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let 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) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-c", command]) + Logger.i("CustomButton", `Executing command: ${command}`) + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpExec) || (delta < 0 && wheelDownExec)) { + let commandExec = delta > 0 ? wheelUpExec : wheelDownExec; + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = commandExec.replace(/\$delta([+\-*/]\d+)?/g, function(match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let 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) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-c", command]) + Logger.i("CustomButton", `Executing command: ${command}`) + } + } + + if (!textStream) { + if (wheelMode === "unified" && wheelUpdateText) { + runTextCommand() + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpUpdateText) || (delta < 0 && wheelDownUpdateText)) { + runTextCommand() + } + } + } + } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index e209471b..4d7a7d3b 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -28,6 +28,13 @@ ColumnLayout { settings.rightClickUpdateText = rightClickUpdateText.checked; settings.middleClickExec = middleClickExecInput.text; settings.middleClickUpdateText = middleClickUpdateText.checked; + settings.wheelMode = separateWheelToggle.internalChecked ? "separate" : "unified"; + settings.wheelExec = wheelExecInput.text; + settings.wheelUpExec = wheelUpExecInput.text; + settings.wheelDownExec = wheelDownExecInput.text; + settings.wheelUpdateText = wheelUpdateText.checked; + settings.wheelUpUpdateText = wheelUpUpdateText.checked; + settings.wheelDownUpdateText = wheelDownUpdateText.checked; settings.textCommand = textCommandInput.text; settings.textCollapse = textCollapseInput.text; settings.textStream = valueTextStream; @@ -141,6 +148,103 @@ ColumnLayout { } } + // Wheel command settings + NToggle { + id: separateWheelToggle + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.label", "Separate wheel commands") + description: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.description", "Enable separate commands for wheel up and down") + property bool internalChecked: (widgetData?.wheelMode || widgetMetadata?.wheelMode || "unified") === "separate" + checked: internalChecked + onToggled: checked => { + internalChecked = checked + } + } + + ColumnLayout { + Layout.fillWidth: true + + RowLayout { + id: unifiedWheelLayout + visible: !separateWheelToggle.checked + spacing: Style.marginM + + NTextInput { + id: wheelExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelExec || widgetMetadata?.wheelExec || "" + } + + NToggle { + id: wheelUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: widgetData?.wheelUpdateText ?? widgetMetadata?.wheelUpdateText + onToggled: isChecked => checked = isChecked + } + } + + ColumnLayout { + id: separateWheelLayout + visible: separateWheelToggle.checked + spacing: Style.marginS + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelUpExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-up.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel-up.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec || "" + } + + NToggle { + id: wheelUpUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: (widgetData?.wheelUpUpdateText !== undefined) ? widgetData.wheelUpUpdateText : (widgetMetadata?.wheelUpUpdateText ?? false) + onToggled: isChecked => checked = isChecked + } + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelDownExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-down.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel-down.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec || "" + } + + NToggle { + id: wheelDownUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelDownUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: (widgetData?.wheelDownUpdateText !== undefined) ? widgetData.wheelDownUpdateText : (widgetMetadata?.wheelDownUpdateText ?? false) + onToggled: isChecked => checked = isChecked + } + } + } + } + NDivider { Layout.fillWidth: true } diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index f9423f68..456e5be3 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -124,6 +124,13 @@ Singleton { "textIntervalMs": 3000, "textCollapse": "", "parseJson": false, + "wheelExec": "", + "wheelUpExec": "", + "wheelDownExec": "", + "wheelMode": "unified", + "wheelUpdateText": false, + "wheelUpUpdateText": false, + "wheelDownUpdateText": false, "maxTextLength": { "horizontal": 10, "vertical": 10