CustomButton: add script execution/polling support with text display

This commit is contained in:
Ly-sec 2025-09-15 14:37:29 +02:00
parent 7def695c0e
commit dbf1020636
3 changed files with 106 additions and 25 deletions

View file

@ -1,12 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.SettingsPanel import qs.Modules.SettingsPanel
NIconButton { Item {
id: root id: root
// Widget properties passed from Bar.qml // Widget properties passed from Bar.qml
@ -35,33 +36,80 @@ NIconButton {
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
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 int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
enabled: hasExec implicitWidth: pill.width
allowClickWhenDisabled: true // we want to be able to open config with left click when its not setup properly implicitHeight: pill.height
colorBorder: Color.transparent
colorBorderHover: Color.transparent NPill {
sizeRatio: 0.8 id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: customIcon icon: customIcon
text: _dynamicText
autoHide: false
forceOpen: _dynamicText !== ""
forceClose: false
disableOpen: true
tooltipText: { tooltipText: {
if (!hasExec) { if (!hasExec) {
return "Custom Button - Configure in settings" return "Custom Button - Configure in settings"
} else { } else {
var lines = [] var lines = []
if (leftClickExec !== "") { if (leftClickExec !== "") {
lines.push(`Left click: <i>${leftClickExec}</i>.`) lines.push(`Left click: ${leftClickExec}.`)
} }
if (rightClickExec !== "") { if (rightClickExec !== "") {
lines.push(`Right click: <i>${rightClickExec}</i>.`) lines.push(`Right click: ${rightClickExec}.`)
} }
if (middleClickExec !== "") { if (middleClickExec !== "") {
lines.push(`Middle click: <i>${middleClickExec}</i>.`) lines.push(`Middle click: ${middleClickExec}.`)
} }
return lines.join("<br/>") return lines.join("\n")
} }
} }
onClicked: { onClicked: root.onClicked()
onRightClicked: root.onRightClicked()
onMiddleClicked: root.onMiddleClicked()
}
// Internal state for dynamic text
property string _dynamicText: ""
// Periodically run the text command (if set)
Timer {
id: refreshTimer
interval: Math.max(250, textIntervalMs)
repeat: true
running: (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
}
}
Process {
id: textProc
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: (exitCode, exitStatus) => {
var out = String(stdout.text || "").trim()
if (out.indexOf("\n") !== -1) {
out = out.split("\n")[0]
}
_dynamicText = out
}
}
function onClicked() {
if (leftClickExec) { if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", leftClickExec]) Quickshell.execDetached(["sh", "-c", leftClickExec])
Logger.log("CustomButton", `Executing command: ${leftClickExec}`) Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
@ -73,14 +121,14 @@ NIconButton {
} }
} }
onRightClicked: { function onRightClicked() {
if (rightClickExec) { if (rightClickExec) {
Quickshell.execDetached(["sh", "-c", rightClickExec]) Quickshell.execDetached(["sh", "-c", rightClickExec])
Logger.log("CustomButton", `Executing command: ${rightClickExec}`) Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
} }
} }
onMiddleClicked: { function onMiddleClicked() {
if (middleClickExec) { if (middleClickExec) {
Quickshell.execDetached(["sh", "-c", middleClickExec]) Quickshell.execDetached(["sh", "-c", middleClickExec])
Logger.log("CustomButton", `Executing command: ${middleClickExec}`) Logger.log("CustomButton", `Executing command: ${middleClickExec}`)

View file

@ -19,6 +19,8 @@ ColumnLayout {
settings.leftClickExec = leftClickExecInput.text settings.leftClickExec = leftClickExecInput.text
settings.rightClickExec = rightClickExecInput.text settings.rightClickExec = rightClickExecInput.text
settings.middleClickExec = middleClickExecInput.text settings.middleClickExec = middleClickExecInput.text
settings.textCommand = textCommandInput.text
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
return settings return settings
} }
@ -228,4 +230,33 @@ ColumnLayout {
placeholderText: "Enter command to execute (app or custom script)" placeholderText: "Enter command to execute (app or custom script)"
text: widgetData.middleClickExec || widgetMetadata.middleClickExec text: widgetData.middleClickExec || widgetMetadata.middleClickExec
} }
NDivider {
Layout.fillWidth: true
}
NText {
text: "Dynamic Text"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NTextInput {
id: textCommandInput
Layout.fillWidth: true
label: "Text Command"
description: "Shell command to run periodically (first line becomes the text)."
placeholderText: "echo \"Hello World\""
text: widgetData?.textCommand || widgetMetadata.textCommand
}
NTextInput {
id: textIntervalInput
Layout.fillWidth: true
label: "Refresh Interval"
description: "Interval in milliseconds."
placeholderText: String(widgetMetadata.textIntervalMs || 3000)
text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : ""
}
} }

View file

@ -61,7 +61,9 @@ Singleton {
"icon": "heart", "icon": "heart",
"leftClickExec": "", "leftClickExec": "",
"rightClickExec": "", "rightClickExec": "",
"middleClickExec": "" "middleClickExec": "",
"textCommand": "",
"textIntervalMs": 3000
}, },
"Microphone": { "Microphone": {
"allowUserSettings": true, "allowUserSettings": true,