From 07a8f8c280b6494ead4e2025aa876dc181c0e77e Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 29 Oct 2025 20:28:59 +0800 Subject: [PATCH] feat(custom-button): Add JSON parsing support --- Assets/Translations/en.json | 4 + Modules/Bar/Widgets/CustomButton.qml | 119 +++++++++--------- .../WidgetSettings/CustomButtonSettings.qml | 10 ++ Services/BarWidgetRegistry.qml | 3 +- 4 files changed, 77 insertions(+), 59 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index cdb755bf..4de2506d 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -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": { diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 29514254..135a4b91 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -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]) diff --git a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index f87ba209..129613ab 100644 --- a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -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 diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 4e8b5621..e3511b36 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -85,7 +85,8 @@ Singleton { "textCommand": "", "textStream": false, "textIntervalMs": 3000, - "textCollapse": "" + "textCollapse": "", + "parseJson": false }, "KeyboardLayout": { "allowUserSettings": true,