Merge pull request #533 from shouya/main

Add support for streaming command outputs in CustomButton
This commit is contained in:
Lemmy
2025-10-22 10:16:55 -04:00
committed by GitHub
4 changed files with 62 additions and 17 deletions
+6 -1
View File
@@ -1079,9 +1079,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",
+41 -15
View File
@@ -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)
@@ -83,29 +84,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() {
@@ -133,4 +150,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
}
}
@@ -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)
+1
View File
@@ -83,6 +83,7 @@ Singleton {
"rightClickExec": "",
"middleClickExec": "",
"textCommand": "",
"textStream": false,
"textIntervalMs": 3000
},
"KeyboardLayout": {