add support for streaming command outputs in CustomButton

This commit is contained in:
shouya
2025-10-20 21:55:32 +09:00
parent 2a47662d09
commit ea2e0df837
4 changed files with 62 additions and 17 deletions
+6 -1
View File
@@ -1073,9 +1073,14 @@
"description": "Command to execute when the button is middle-clicked." "description": "Command to execute when the button is middle-clicked."
}, },
"dynamic-text": "Dynamic text", "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": { "display-command-output": {
"label": "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": { "refresh-interval": {
"label": "Refresh interval", "label": "Refresh interval",
+41 -15
View File
@@ -36,6 +36,7 @@ Item {
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
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 int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000) readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
@@ -84,29 +85,45 @@ Item {
id: refreshTimer id: refreshTimer
interval: Math.max(250, textIntervalMs) interval: Math.max(250, textIntervalMs)
repeat: true repeat: true
running: (textCommand && textCommand.length > 0) running: !textStream && textCommand && textCommand.length > 0
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: root.runTextCommand()
if (!textCommand || textCommand.length === 0) }
return
if (textProc.running) // Restart exited text stream commands after a delay
return Timer {
textProc.command = ["sh", "-lc", textCommand] id: restartTimer
textProc.running = true 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 { Process {
id: textProc id: textProc
stdout: StdioCollector {} stdout: textStream ? textStdoutSplit : textStdoutCollect
stderr: StdioCollector {} stderr: StdioCollector {}
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
var out = String(stdout.text || "").trim() if (textStream) {
if (out.indexOf("\n") !== -1) { Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`)
out = out.split("\n")[0] return
} }
_dynamicText = out }
}
} }
function onClicked() { function onClicked() {
@@ -134,4 +151,13 @@ Item {
Logger.i("CustomButton", `Executing command: ${middleClickExec}`) 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
}
} }
@@ -14,6 +14,7 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
@@ -22,6 +23,7 @@ ColumnLayout {
settings.rightClickExec = rightClickExecInput.text settings.rightClickExec = rightClickExecInput.text
settings.middleClickExec = middleClickExecInput.text settings.middleClickExec = middleClickExecInput.text
settings.textCommand = textCommandInput.text settings.textCommand = textCommandInput.text
settings.textStream = valueTextStream
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10) settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
return settings return settings
} }
@@ -90,11 +92,21 @@ ColumnLayout {
label: I18n.tr("bar.widget-settings.custom-button.dynamic-text") 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 { NTextInput {
id: textCommandInput id: textCommandInput
Layout.fillWidth: true Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.custom-button.display-command-output.label") 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") placeholderText: I18n.tr("placeholders.command-example")
text: widgetData?.textCommand || widgetMetadata.textCommand text: widgetData?.textCommand || widgetMetadata.textCommand
} }
@@ -102,6 +114,7 @@ ColumnLayout {
NTextInput { NTextInput {
id: textIntervalInput id: textIntervalInput
Layout.fillWidth: true Layout.fillWidth: true
visible: !valueTextStream
label: I18n.tr("bar.widget-settings.custom-button.refresh-interval.label") label: I18n.tr("bar.widget-settings.custom-button.refresh-interval.label")
description: I18n.tr("bar.widget-settings.custom-button.refresh-interval.description") description: I18n.tr("bar.widget-settings.custom-button.refresh-interval.description")
placeholderText: String(widgetMetadata.textIntervalMs || 3000) placeholderText: String(widgetMetadata.textIntervalMs || 3000)
+1
View File
@@ -78,6 +78,7 @@ Singleton {
"rightClickExec": "", "rightClickExec": "",
"middleClickExec": "", "middleClickExec": "",
"textCommand": "", "textCommand": "",
"textStream": false,
"textIntervalMs": 3000 "textIntervalMs": 3000
}, },
"KeyboardLayout": { "KeyboardLayout": {