feat(custom-button): Add JSON parsing support

This commit is contained in:
loner
2025-10-29 20:28:59 +08:00
parent db7e67c686
commit 07a8f8c280
4 changed files with 77 additions and 59 deletions
+4
View File
@@ -1120,6 +1120,10 @@
"collapse-condition": {
"label": "Collapse condition",
"description": "If the output text matches this value, the button will collapse."
},
"parse-json": {
"label": "Parse output as JSON",
"description": "Parse the command output as a JSON object to dynamically set text and icon."
}
},
"media-mini": {
+61 -58
View File
@@ -39,6 +39,7 @@ Item {
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 string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "")
readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false)
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
implicitWidth: pill.width
@@ -48,7 +49,7 @@ Item {
id: pill
oppositeDirection: BarService.getPillDirection(root)
icon: customIcon
icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon
text: _dynamicText
density: Settings.data.bar.density
autoHide: false
@@ -78,6 +79,7 @@ Item {
// Internal state for dynamic text
property string _dynamicText: ""
property string _dynamicIcon: ""
// Periodically run the text command (if set)
Timer {
@@ -99,67 +101,12 @@ Item {
SplitParser {
id: textStdoutSplit
onRead: function (line) {
var lineStr = String(line || "").trim()
var shouldCollapse = false
if (textCollapse && textCollapse.length > 0) {
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
// Treat as regex
var pattern = textCollapse.substring(1, textCollapse.length - 1)
try {
var regex = new RegExp(pattern)
shouldCollapse = regex.test(lineStr)
} catch (e) {
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`)
shouldCollapse = (textCollapse === lineStr) // Fallback to exact match on invalid regex
}
} else {
// Treat as plain string
shouldCollapse = (textCollapse === lineStr)
}
}
if (shouldCollapse) {
_dynamicText = ""
} else {
_dynamicText = lineStr
}
}
onRead: (line) => root.parseDynamicContent(line)
}
StdioCollector {
id: textStdoutCollect
onStreamFinished: () => {
var out = String(this.text || "").trim()
if (out.indexOf("\n") !== -1) {
out = out.split("\n")[0]
}
var shouldCollapse = false
if (textCollapse && textCollapse.length > 0) {
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
// Treat as regex
var pattern = textCollapse.substring(1, textCollapse.length - 1)
try {
var regex = new RegExp(pattern)
shouldCollapse = regex.test(out)
} catch (e) {
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`)
shouldCollapse = (textCollapse === out) // Fallback to exact match on invalid regex
}
} else {
// Treat as plain string
shouldCollapse = (textCollapse === out)
}
}
if (shouldCollapse) {
_dynamicText = ""
} else {
_dynamicText = out
}
}
onStreamFinished: () => root.parseDynamicContent(this.text)
}
Process {
@@ -174,6 +121,62 @@ Item {
}
}
function parseDynamicContent(content) {
var contentStr = String(content || "").trim()
if (contentStr.indexOf("\n") !== -1) {
contentStr = contentStr.split("\n")[0]
}
if (parseJson) {
try {
var parsed = JSON.parse(contentStr)
var text = parsed.text || ""
if (checkCollapse(text)) {
_dynamicText = ""
_dynamicIcon = ""
return
}
_dynamicText = text
_dynamicIcon = parsed.icon || ""
return
} catch (e) {
// Not a valid JSON, treat as plain text
}
}
if (checkCollapse(contentStr)) {
_dynamicText = ""
_dynamicIcon = ""
return
}
_dynamicText = contentStr
_dynamicIcon = ""
}
function checkCollapse(text) {
if (!textCollapse || textCollapse.length === 0) {
return false
}
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
// Treat as regex
var pattern = textCollapse.substring(1, textCollapse.length - 1)
try {
var regex = new RegExp(pattern)
return regex.test(text)
} catch (e) {
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`)
return (textCollapse === text) // Fallback to exact match on invalid regex
}
} else {
// Treat as plain string
return (textCollapse === text)
}
}
function onClicked() {
if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", leftClickExec])
@@ -15,6 +15,7 @@ ColumnLayout {
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream
property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
@@ -25,6 +26,7 @@ ColumnLayout {
settings.textCommand = textCommandInput.text
settings.textCollapse = textCollapseInput.text
settings.textStream = valueTextStream
settings.parseJson = valueParseJson
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
return settings
}
@@ -101,6 +103,14 @@ ColumnLayout {
onToggled: checked => valueTextStream = checked
}
NToggle {
id: parseJsonInput
label: I18n.tr("bar.widget-settings.custom-button.parse-json.label", "Parse output as JSON")
description: I18n.tr("bar.widget-settings.custom-button.parse-json.description", "Parse the command output as a JSON object to dynamically set text and icon.")
checked: valueParseJson
onToggled: checked => valueParseJson = checked
}
NTextInput {
id: textCommandInput
Layout.fillWidth: true
+2 -1
View File
@@ -85,7 +85,8 @@ Singleton {
"textCommand": "",
"textStream": false,
"textIntervalMs": 3000,
"textCollapse": ""
"textCollapse": "",
"parseJson": false
},
"KeyboardLayout": {
"allowUserSettings": true,