From ea2e0df837ff4b1c68f3cc8cbc6411558ea8d228 Mon Sep 17 00:00:00 2001 From: shouya <526598+shouya@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:55:32 +0900 Subject: [PATCH] add support for streaming command outputs in CustomButton --- Assets/Translations/en.json | 7 ++- Modules/Bar/Widgets/CustomButton.qml | 56 ++++++++++++++----- .../WidgetSettings/CustomButtonSettings.qml | 15 ++++- Services/BarWidgetRegistry.qml | 1 + 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 88acd5c9..4f5f9004 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1073,9 +1073,14 @@ "description": "Command to execute when the button is middle-clicked." }, "dynamic-text": "Dynamic text", + "text-stream": { + "label": "Stream", + "description": "Streamed lines from the command will be displayed as text on the button." + }, "display-command-output": { "label": "Display Command Output", - "description": "Enter a command to run at a regular interval. The first line of its output will be displayed as text." + "description": "Enter a command to run at a regular interval. The first line of its output will be displayed as text.", + "stream-description": "Enter a command to run continuously." }, "refresh-interval": { "label": "Refresh interval", diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 4015c4f4..a3109860 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -36,6 +36,7 @@ Item { readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec 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 bool hasExec: (leftClickExec || rightClickExec || middleClickExec) @@ -84,29 +85,45 @@ Item { id: refreshTimer interval: Math.max(250, textIntervalMs) repeat: true - running: (textCommand && textCommand.length > 0) + running: !textStream && textCommand && textCommand.length > 0 triggeredOnStart: true - onTriggered: { - if (!textCommand || textCommand.length === 0) - return - if (textProc.running) - return - textProc.command = ["sh", "-lc", textCommand] - textProc.running = true + onTriggered: root.runTextCommand() + } + + // Restart exited text stream commands after a delay + Timer { + id: restartTimer + interval: 1000 + running: textStream && !textProc.running + onTriggered: root.runTextCommand() + } + + SplitParser { + id: textStdoutSplit + onRead: (line) => _dynamicText = String(line || "").trim() + } + + StdioCollector { + id: textStdoutCollect + onStreamFinished: () => { + var out = String(this.text || "").trim() + if (out.indexOf("\n") !== -1) { + out = out.split("\n")[0] + } + _dynamicText = out } } Process { id: textProc - stdout: StdioCollector {} + stdout: textStream ? textStdoutSplit : textStdoutCollect stderr: StdioCollector {} onExited: (exitCode, exitStatus) => { - var out = String(stdout.text || "").trim() - if (out.indexOf("\n") !== -1) { - out = out.split("\n")[0] - } - _dynamicText = out - } + if (textStream) { + Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`) + return + } + } } function onClicked() { @@ -134,4 +151,13 @@ Item { Logger.i("CustomButton", `Executing command: ${middleClickExec}`) } } + + function runTextCommand() { + if (!textCommand || textCommand.length === 0) + return + if (textProc.running) + return + textProc.command = ["sh", "-lc", textCommand] + textProc.running = true + } } diff --git a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index c3a6aefe..cbd50a5f 100644 --- a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -14,6 +14,7 @@ ColumnLayout { property var widgetMetadata: null property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream function saveSettings() { var settings = Object.assign({}, widgetData || {}) @@ -22,6 +23,7 @@ ColumnLayout { settings.rightClickExec = rightClickExecInput.text settings.middleClickExec = middleClickExecInput.text settings.textCommand = textCommandInput.text + settings.textStream = valueTextStream settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10) return settings } @@ -90,11 +92,21 @@ ColumnLayout { label: I18n.tr("bar.widget-settings.custom-button.dynamic-text") } + NToggle { + id: textStreamInput + label: I18n.tr("bar.widget-settings.custom-button.text-stream.label") + description: I18n.tr("bar.widget-settings.custom-button.text-stream.description") + checked: valueTextStream + onToggled: checked => valueTextStream = checked + } + NTextInput { id: textCommandInput Layout.fillWidth: true label: I18n.tr("bar.widget-settings.custom-button.display-command-output.label") - description: I18n.tr("bar.widget-settings.custom-button.display-command-output.description") + description: valueTextStream ? + I18n.tr("bar.widget-settings.custom-button.display-command-output.stream-description") : + I18n.tr("bar.widget-settings.custom-button.display-command-output.description") placeholderText: I18n.tr("placeholders.command-example") text: widgetData?.textCommand || widgetMetadata.textCommand } @@ -102,6 +114,7 @@ ColumnLayout { NTextInput { id: textIntervalInput Layout.fillWidth: true + visible: !valueTextStream label: I18n.tr("bar.widget-settings.custom-button.refresh-interval.label") description: I18n.tr("bar.widget-settings.custom-button.refresh-interval.description") placeholderText: String(widgetMetadata.textIntervalMs || 3000) diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index a5823db6..09d04b72 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -78,6 +78,7 @@ Singleton { "rightClickExec": "", "middleClickExec": "", "textCommand": "", + "textStream": false, "textIntervalMs": 3000 }, "KeyboardLayout": {