feat: Custom buttons now support wheel actions

This commit is contained in:
loner
2025-11-21 10:58:15 +08:00
parent 857d1dbbb6
commit 694fefeebd
4 changed files with 226 additions and 1 deletions
+17
View File
@@ -145,6 +145,23 @@
"label": "Middle click", "label": "Middle click",
"update-text": "Update displayed text on 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": { "parse-json": {
"description": "Parse the command output as a JSON object to dynamically set text and icon.", "description": "Parse the command output as a JSON object to dynamically set text and icon.",
"label": "Parse output as JSON" "label": "Parse output as JSON"
+98 -1
View File
@@ -39,12 +39,21 @@ Item {
readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
readonly property bool middleClickUpdateText: widgetSettings.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText 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 string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "")
readonly property bool textStream: widgetSettings.textStream !== undefined ? widgetSettings.textStream : (widgetMetadata.textStream || false) 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 int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "") 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 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 implicitWidth: pill.width
implicitHeight: pill.height implicitHeight: pill.height
@@ -73,6 +82,16 @@ Item {
if (middleClickExec !== "") { if (middleClickExec !== "") {
tooltipLines.push(`Middle click: ${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 !== "") { if (_dynamicTooltip !== "") {
@@ -92,6 +111,7 @@ Item {
onClicked: root.onClicked() onClicked: root.onClicked()
onRightClicked: root.onRightClicked() onRightClicked: root.onRightClicked()
onMiddleClicked: root.onMiddleClicked() onMiddleClicked: root.onMiddleClicked()
onWheel: delta => root.onWheel(delta)
} }
// Internal state for dynamic text // Internal state for dynamic text
@@ -382,4 +402,81 @@ Item {
textProc.command = ["sh", "-lc", textCommand]; textProc.command = ["sh", "-lc", textCommand];
textProc.running = true; 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()
}
}
}
}
} }
@@ -28,6 +28,13 @@ ColumnLayout {
settings.rightClickUpdateText = rightClickUpdateText.checked; settings.rightClickUpdateText = rightClickUpdateText.checked;
settings.middleClickExec = middleClickExecInput.text; settings.middleClickExec = middleClickExecInput.text;
settings.middleClickUpdateText = middleClickUpdateText.checked; 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.textCommand = textCommandInput.text;
settings.textCollapse = textCollapseInput.text; settings.textCollapse = textCollapseInput.text;
settings.textStream = valueTextStream; 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 { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
} }
+7
View File
@@ -124,6 +124,13 @@ Singleton {
"textIntervalMs": 3000, "textIntervalMs": 3000,
"textCollapse": "", "textCollapse": "",
"parseJson": false, "parseJson": false,
"wheelExec": "",
"wheelUpExec": "",
"wheelDownExec": "",
"wheelMode": "unified",
"wheelUpdateText": false,
"wheelUpUpdateText": false,
"wheelDownUpdateText": false,
"maxTextLength": { "maxTextLength": {
"horizontal": 10, "horizontal": 10,
"vertical": 10 "vertical": 10