mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
243 lines
7.6 KiB
QML
243 lines
7.6 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
import qs.Services.UI
|
|
import qs.Widgets
|
|
import qs.Modules.Panels.Settings
|
|
import qs.Modules.Bar.Extras
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property ShellScreen screen
|
|
|
|
// Widget properties passed from Bar.qml for per-instance settings
|
|
property string widgetId: ""
|
|
property string section: ""
|
|
property int sectionWidgetIndex: -1
|
|
property int sectionWidgetsCount: 0
|
|
|
|
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
|
property var widgetSettings: {
|
|
if (section && sectionWidgetIndex >= 0) {
|
|
var widgets = Settings.data.bar.widgets[section]
|
|
if (widgets && sectionWidgetIndex < widgets.length) {
|
|
return widgets[sectionWidgetIndex]
|
|
}
|
|
}
|
|
return {}
|
|
}
|
|
|
|
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
|
|
|
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
|
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
|
|
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 string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "")
|
|
readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false)
|
|
readonly property bool hideTextInVerticalBar: widgetSettings.hideTextInVerticalBar !== undefined ? widgetSettings.hideTextInVerticalBar : (widgetMetadata.hideTextInVerticalBar || false)
|
|
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
|
|
|
|
readonly property bool shouldShowText: !isVerticalBar || !hideTextInVerticalBar
|
|
|
|
implicitWidth: pill.width
|
|
implicitHeight: pill.height
|
|
|
|
BarPill {
|
|
id: pill
|
|
|
|
oppositeDirection: BarService.getPillDirection(root)
|
|
icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon
|
|
text: shouldShowText ? _dynamicText : ""
|
|
density: Settings.data.bar.density
|
|
autoHide: false
|
|
forceOpen: _dynamicText !== ""
|
|
tooltipText: {
|
|
var tooltipLines = []
|
|
|
|
if (hasExec) {
|
|
if (leftClickExec !== "") {
|
|
tooltipLines.push(`Left click: ${leftClickExec}.`)
|
|
}
|
|
if (rightClickExec !== "") {
|
|
tooltipLines.push(`Right click: ${rightClickExec}.`)
|
|
}
|
|
if (middleClickExec !== "") {
|
|
tooltipLines.push(`Middle click: ${middleClickExec}.`)
|
|
}
|
|
}
|
|
|
|
if (_dynamicTooltip !== "") {
|
|
if (tooltipLines.length > 0) {
|
|
tooltipLines.push("")
|
|
}
|
|
tooltipLines.push(_dynamicTooltip)
|
|
}
|
|
|
|
if (tooltipLines.length === 0) {
|
|
return "Custom button, configure in settings."
|
|
} else {
|
|
return tooltipLines.join("\n")
|
|
}
|
|
}
|
|
|
|
onClicked: root.onClicked()
|
|
onRightClicked: root.onRightClicked()
|
|
onMiddleClicked: root.onMiddleClicked()
|
|
}
|
|
|
|
// Internal state for dynamic text
|
|
property string _dynamicText: ""
|
|
property string _dynamicIcon: ""
|
|
property string _dynamicTooltip: ""
|
|
|
|
// Periodically run the text command (if set)
|
|
Timer {
|
|
id: refreshTimer
|
|
interval: Math.max(250, textIntervalMs)
|
|
repeat: true
|
|
running: shouldShowText && !textStream && textCommand && textCommand.length > 0
|
|
triggeredOnStart: true
|
|
onTriggered: root.runTextCommand()
|
|
}
|
|
|
|
// Restart exited text stream commands after a delay
|
|
Timer {
|
|
id: restartTimer
|
|
interval: 1000
|
|
running: shouldShowText && textStream && !textProc.running
|
|
onTriggered: root.runTextCommand()
|
|
}
|
|
|
|
SplitParser {
|
|
id: textStdoutSplit
|
|
onRead: line => root.parseDynamicContent(line)
|
|
}
|
|
|
|
StdioCollector {
|
|
id: textStdoutCollect
|
|
onStreamFinished: () => root.parseDynamicContent(this.text)
|
|
}
|
|
|
|
Process {
|
|
id: textProc
|
|
stdout: textStream ? textStdoutSplit : textStdoutCollect
|
|
stderr: StdioCollector {}
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (textStream) {
|
|
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseDynamicContent(content) {
|
|
var contentStr = String(content || "").trim()
|
|
|
|
if (parseJson) {
|
|
var lineToParse = contentStr
|
|
|
|
if (!textStream && contentStr.includes('\n')) {
|
|
const lines = contentStr.split('\n').filter(line => line.trim() !== '')
|
|
if (lines.length > 0) {
|
|
lineToParse = lines[lines.length - 1]
|
|
}
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(lineToParse)
|
|
const text = parsed.text || ""
|
|
const icon = parsed.icon || ""
|
|
const tooltip = parsed.tooltip || ""
|
|
|
|
if (checkCollapse(text)) {
|
|
_dynamicText = ""
|
|
_dynamicIcon = ""
|
|
_dynamicTooltip = ""
|
|
return
|
|
}
|
|
|
|
_dynamicText = text
|
|
_dynamicIcon = icon
|
|
_dynamicTooltip = tooltip
|
|
return
|
|
} catch (e) {
|
|
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`)
|
|
}
|
|
}
|
|
|
|
if (checkCollapse(contentStr)) {
|
|
_dynamicText = ""
|
|
_dynamicIcon = ""
|
|
_dynamicTooltip = ""
|
|
return
|
|
}
|
|
|
|
_dynamicText = contentStr
|
|
_dynamicIcon = ""
|
|
_dynamicTooltip = ""
|
|
}
|
|
|
|
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])
|
|
Logger.i("CustomButton", `Executing command: ${leftClickExec}`)
|
|
} else if (!hasExec) {
|
|
// No script was defined, open settings
|
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
|
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
|
settingsPanel.open()
|
|
}
|
|
}
|
|
|
|
function onRightClicked() {
|
|
if (rightClickExec) {
|
|
Quickshell.execDetached(["sh", "-c", rightClickExec])
|
|
Logger.i("CustomButton", `Executing command: ${rightClickExec}`)
|
|
}
|
|
}
|
|
|
|
function onMiddleClicked() {
|
|
if (middleClickExec) {
|
|
Quickshell.execDetached(["sh", "-c", 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
|
|
}
|
|
}
|