-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIntervalCommandWidget.qml
More file actions
235 lines (207 loc) · 7.69 KB
/
IntervalCommandWidget.qml
File metadata and controls
235 lines (207 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
// Variant properties — set by WidgetHost at instantiation (not reactive)
property var variantId: null
property var variantData: null
// Config resolution: variant → base → hardcoded default
// Uses ?? (nullish coalescing) not || to correctly handle falsy values (0, false, "")
property string command: (variantData?.command ?? pluginData.command ?? "").replace(/[\r\n]+/g, " ").trim()
property string iconName: variantData?.icon ?? pluginData.icon ?? "info"
property int refreshInterval: ((variantData?.refreshInterval ?? pluginData.refreshInterval ?? 10)) * 1000
property string clickCommand: (variantData?.clickCommand ?? pluginData.clickCommand ?? "").replace(/[\r\n]+/g, " ").trim()
property bool popoutEnabled: variantData?.popoutEnabled ?? pluginData.popoutEnabled ?? false
property int popoutRefreshInterval: ((variantData?.popoutRefreshInterval ?? pluginData.popoutRefreshInterval ?? 5)) * 1000
property bool useAccentColor: variantData?.useAccentColor ?? pluginData.useAccentColor ?? false
// State
property string outputText: command === "" ? "No command set" : "..."
property string popoutText: ""
onCommandChanged: {
if (commandProcess.running) {
commandProcess.killed = true;
commandProcess.running = false;
}
if (command === "") {
outputText = "No command set";
} else {
outputText = "...";
}
// Restart the refresh timer so it runs the new command immediately (triggeredOnStart)
// and resets the interval counter
refreshTimer.restart();
}
// Re-fetch variant data when settings change (variantData from WidgetHost is not reactive)
Connections {
target: pluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === root.pluginId && root.variantId) {
root.variantData = pluginService.getPluginVariantData(root.pluginId, root.variantId);
}
}
}
// Click handler — only used when popout is disabled
pillClickAction: popoutEnabled ? null : (x, y, width, section, screen) => {
if (clickCommand !== "") {
clickProcess.command = ["sh", "-c", root.clickCommand];
clickProcess.running = true;
}
}
// Process to run the configured command on a timer
Process {
id: commandProcess
command: ["sh", "-c", root.command + "; echo"]
running: false
stdout: SplitParser {
property bool captured: false
onRead: data => {
if (!captured) {
let line = data.trim();
if (line === "") return;
if (line.length > 50) {
line = line.substring(0, 50);
}
root.outputText = line;
captured = true;
commandProcess.hasEverCaptured = true;
}
}
}
property bool killed: false
property bool hasEverCaptured: false
onRunningChanged: {
if (!running && !killed && !commandProcess.stdout.captured && hasEverCaptured) {
root.outputText = "N/A";
}
killed = false;
}
}
// Process to run the click command silently
Process {
id: clickProcess
running: false
}
// Process to run the click command and capture full output for popout
Process {
id: popoutProcess
command: ["sh", "-c", root.clickCommand + " | sed 's/\\x1b\\[[0-9;?]*[a-zA-Z]//g; s/\\x1b\\][^\\x07]*\\x07//g'; echo"]
running: false
stdout: SplitParser {
property string buffer: ""
onRead: data => {
buffer = buffer === "" ? data : buffer + "\n" + data;
root.popoutText = buffer.replace(/\n+$/, "");
}
}
onRunningChanged: {
if (!running) {
let text = popoutProcess.stdout.buffer.replace(/\n+$/, "");
root.popoutText = text || root.popoutText || "No output";
}
}
}
// Timer for popout refresh — only runs while popout is open
Timer {
id: popoutTimer
interval: root.popoutRefreshInterval
running: false
repeat: true
triggeredOnStart: true
onTriggered: {
if (root.clickCommand !== "") {
popoutProcess.stdout.buffer = "";
popoutProcess.running = true;
}
}
}
// Timer to periodically execute the command
Timer {
id: refreshTimer
interval: root.refreshInterval
running: root.command !== ""
repeat: true
triggeredOnStart: true
onTriggered: {
if (root.command !== "") {
if (commandProcess.running) {
commandProcess.killed = true;
commandProcess.running = false;
}
commandProcess.stdout.captured = false;
commandProcess.running = true;
}
}
}
// Horizontal bar layout
horizontalBarPill: Component {
Row {
spacing: Theme.spacingXS
DankIcon {
name: root.iconName
size: root.iconSize
color: root.useAccentColor ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.outputText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Vertical bar layout
verticalBarPill: Component {
Column {
spacing: Theme.spacingXS
DankIcon {
name: root.iconName
size: root.iconSize
color: root.useAccentColor ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: root.outputText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
popoutWidth: variantData?.popoutWidth ?? pluginData.popoutWidth ?? 600
popoutHeight: variantData?.popoutHeight ?? pluginData.popoutHeight ?? 450
popoutContent: Component {
PopoutComponent {
id: popout
Component.onCompleted: {
popoutTimer.running = true;
}
Component.onDestruction: {
popoutTimer.running = false;
}
Flickable {
id: popoutFlickable
width: parent.width
height: Math.min(popoutTextItem.implicitHeight, root.popoutHeight)
contentWidth: width
contentHeight: popoutTextItem.implicitHeight
clip: true
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
Text {
id: popoutTextItem
width: popoutFlickable.width
text: root.popoutText || "Running..."
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: Theme.surfaceText
wrapMode: Text.WordWrap
}
}
}
}
}