summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml93
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Bar.qml323
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Clock.qml228
-rw-r--r--accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml78
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml12
-rw-r--r--accounts/gkleen@sif/shell/quickshell/SystemTray.qml138
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml71
7 files changed, 620 insertions, 323 deletions
diff --git a/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
new file mode 100644
index 00000000..d7e8e7c5
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/ActiveWindowDisplay.qml
@@ -0,0 +1,93 @@
1import QtQuick
2import qs.Services
3import Quickshell
4import Quickshell.Widgets
5
6Item {
7 id: activeWindowDisplay
8
9 required property int maxWidth
10
11 property var activeWindow: {
12 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
13 return ws.output === bar.screen.name && ws.is_active;
14 })?.active_window_id;
15
16 return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null;
17 }
18 property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null
19
20 anchors.verticalCenter: parent.verticalCenter
21 width: activeWindowDisplayContent.width
22 height: parent.height
23
24 Row {
25 id: activeWindowDisplayContent
26
27 width: childrenRect.width
28 height: parent.height
29 anchors.verticalCenter: parent.verticalCenter
30 spacing: 8
31
32 IconImage {
33 id: activeWindowIcon
34
35 height: 14
36 width: 14
37
38 anchors.verticalCenter: parent.verticalCenter
39
40 source: {
41 let icon = activeWindowDisplay.windowEntry?.icon
42 if (typeof icon === 'string' || icon instanceof String) {
43 if (icon.includes("?path=")) {
44 const split = icon.split("?path=")
45 if (split.length !== 2)
46 return icon
47 const name = split[0]
48 const path = split[1]
49 const fileName = name.substring(
50 name.lastIndexOf("/") + 1)
51 return `file://${path}/${fileName}`
52 } else
53 icon = Quickshell.iconPath(icon);
54 return icon
55 }
56 return ""
57 }
58 asynchronous: true
59 smooth: true
60 mipmap: true
61 }
62
63 Text {
64 id: windowTitle
65
66 width: Math.min(implicitWidth, activeWindowDisplay.maxWidth - activeWindowIcon.width - activeWindowDisplayContent.spacing)
67
68 property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" }
69
70 elide: Text.ElideRight
71 maximumLineCount: 1
72 color: "white"
73 anchors.verticalCenter: parent.verticalCenter
74 text: {
75 if (!activeWindowDisplay.activeWindow)
76 return "";
77
78 var title = activeWindowDisplay.activeWindow.title;
79 var appName = activeWindowDisplay.windowEntry?.name;
80 if (appAliases[appName])
81 appName = appAliases[appName];
82 if (appName && title.endsWith(appName)) {
83 const oldTitle = title;
84 title = title.substring(0, title.length - appName.length);
85 title = title.replace(/\s*(—|-)\s*$/, "");
86 if (!title)
87 title = oldTitle;
88 }
89 return title;
90 }
91 }
92 }
93}
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml
index 52f875da..accad2a9 100644
--- a/accounts/gkleen@sif/shell/quickshell/Bar.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -1,10 +1,5 @@
1import Quickshell 1import Quickshell
2import Quickshell.Io
3import Quickshell.Services.SystemTray
4import Quickshell.Widgets
5import Custom as Custom
6import QtQuick 2import QtQuick
7import qs.Services
8 3
9 4
10PanelWindow { 5PanelWindow {
@@ -27,7 +22,7 @@ PanelWindow {
27 color: "transparent" 22 color: "transparent"
28 23
29 Rectangle { 24 Rectangle {
30 color: Qt.rgba(0, 0, 0, 0.66) 25 color: Qt.rgba(0, 0, 0, 0.75)
31 anchors.fill: parent 26 anchors.fill: parent
32 // bottomLeftRadius: 8 27 // bottomLeftRadius: 8
33 // bottomRightRadius: 8 28 // bottomRightRadius: 8
@@ -43,74 +38,7 @@ PanelWindow {
43 anchors.verticalCenter: parent.verticalCenter 38 anchors.verticalCenter: parent.verticalCenter
44 spacing: 8 39 spacing: 8
45 40
46 Row { 41 WorkspaceSwitcher {}
47 id: workspaces
48
49 property var ignoreWorkspaces: @ignore_workspaces@
50
51 height: parent.height
52 anchors.verticalCenter: parent.verticalCenter
53 spacing: 0
54
55 Repeater {
56 model: {
57 let currWorkspaces = NiriService.workspaces;
58 const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces);
59 currWorkspaces = currWorkspaces.filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name));
60 currWorkspaces.sort((a, b) => {
61 if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x)
62 return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x
63 if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y)
64 return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y
65 return a.idx - b.idx;
66 });
67 return currWorkspaces;
68 }
69
70 Rectangle {
71 property var workspaceData: modelData
72
73 width: wsLabel.contentWidth + 8
74 color: {
75 if (mouseArea.containsMouse) {
76 return "#33808080";
77 }
78 return "transparent";
79 }
80 height: parent.height
81 anchors.verticalCenter: parent.verticalCenter
82
83 MouseArea {
84 id: mouseArea
85
86 anchors.fill: parent
87 hoverEnabled: true
88 cursorShape: Qt.PointingHandCursor
89 enabled: true
90 onClicked: {
91 NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {})
92 }
93 }
94
95 Text {
96 id: wsLabel
97
98 font.pointSize: 10
99 font.family: "Fira Sans"
100 color: {
101 if (workspaceData.is_active)
102 return "#23fd00";
103 if (workspaceData.active_window_id === null)
104 return "#555";
105 return "white";
106 }
107 anchors.centerIn: parent
108
109 text: workspaceData.name ? workspaceData.name : workspaceData.idx
110 }
111 }
112 }
113 }
114 } 42 }
115 43
116 Row { 44 Row {
@@ -121,91 +49,8 @@ PanelWindow {
121 anchors.centerIn: parent 49 anchors.centerIn: parent
122 spacing: 5 50 spacing: 5
123 51
124 Item { 52 ActiveWindowDisplay {
125 id: activeWindowDisplay 53 maxWidth: bar.width - 2*Math.max(left.width, right.width) - 2*8
126
127 property var activeWindow: {
128 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
129 return ws.output === bar.screen.name && ws.is_active;
130 })?.active_window_id;
131
132 return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null;
133 }
134 property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null
135
136 anchors.verticalCenter: parent.verticalCenter
137 width: activeWindowDisplayContent.width
138 height: parent.height
139
140 Row {
141 id: activeWindowDisplayContent
142
143 width: childrenRect.width
144 height: parent.height
145 anchors.verticalCenter: parent.verticalCenter
146 spacing: 8
147
148 IconImage {
149 id: activeWindowIcon
150
151 height: 14
152 width: 14
153
154 anchors.verticalCenter: parent.verticalCenter
155
156 source: {
157 let icon = activeWindowDisplay.windowEntry?.icon
158 if (typeof icon === 'string' || icon instanceof String) {
159 if (icon.includes("?path=")) {
160 const split = icon.split("?path=")
161 if (split.length !== 2)
162 return icon
163 const name = split[0]
164 const path = split[1]
165 const fileName = name.substring(
166 name.lastIndexOf("/") + 1)
167 return `file://${path}/${fileName}`
168 } else
169 icon = Quickshell.iconPath(icon);
170 return icon
171 }
172 return ""
173 }
174 asynchronous: true
175 smooth: true
176 mipmap: true
177 }
178
179 Text {
180 id: windowTitle
181
182 width: Math.min(implicitWidth, bar.width - 2*Math.max(left.width, right.width) - 2*8 - activeWindowIcon.width - activeWindowDisplayContent.spacing)
183
184 property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" }
185
186 elide: Text.ElideRight
187 maximumLineCount: 1
188 color: "white"
189 anchors.verticalCenter: parent.verticalCenter
190 text: {
191 if (!activeWindowDisplay.activeWindow)
192 return "";
193
194 var title = activeWindowDisplay.activeWindow.title;
195 var appName = activeWindowDisplay.windowEntry?.name;
196 if (appAliases[appName])
197 appName = appAliases[appName];
198 if (appName && title.endsWith(appName)) {
199 const oldTitle = title;
200 title = title.substring(0, title.length - appName.length);
201 title = title.replace(/\s*(—|-)\s*$/, "");
202 if (!title)
203 title = oldTitle;
204 }
205 return title;
206 }
207 }
208 }
209 } 54 }
210 } 55 }
211 56
@@ -219,170 +64,20 @@ PanelWindow {
219 anchors.verticalCenter: parent.verticalCenter 64 anchors.verticalCenter: parent.verticalCenter
220 spacing: 0 65 spacing: 0
221 66
222 Item { 67 SystemTray {}
223 anchors.verticalCenter: parent.verticalCenter
224 width: systemTrayRow.childrenRect.width
225 height: parent.height
226 clip: true
227
228 Row {
229 id: systemTrayRow
230 anchors.centerIn: parent
231 width: childrenRect.width
232 height: parent.height
233 spacing: 0
234
235 Repeater {
236 model: SystemTray.items.values
237
238 delegate: Item {
239 property var trayItem: modelData
240 property string iconSource: {
241 let icon = trayItem && trayItem.icon
242 if (typeof icon === 'string' || icon instanceof String) {
243 if (icon.includes("?path=")) {
244 const split = icon.split("?path=")
245 if (split.length !== 2)
246 return icon
247 const name = split[0]
248 const path = split[1]
249 const fileName = name.substring(
250 name.lastIndexOf("/") + 1)
251 return `file://${path}/${fileName}`
252 }
253 return icon
254 }
255 return ""
256 }
257
258 width: 16
259 height: parent.height
260 anchors.verticalCenter: parent.verticalCenter
261
262 IconImage {
263 anchors.centerIn: parent
264 width: parent.width
265 height: parent.width
266 source: parent.iconSource
267 asynchronous: true
268 smooth: true
269 mipmap: true
270 }
271 68
272 MouseArea { 69 Item {
273 id: trayItemArea
274
275 anchors.fill: parent
276 acceptedButtons: Qt.LeftButton | Qt.RightButton
277 hoverEnabled: true
278 cursorShape: Qt.PointingHandCursor
279 onClicked: mouse => {
280 if (!trayItem)
281 return
282
283 if (mouse.button === Qt.LeftButton
284 && !trayItem.onlyMenu) {
285 trayItem.activate()
286 return
287 }
288
289 if (trayItem.hasMenu) {
290 var globalPos = mapToGlobal(0, 0)
291 var currentScreen = screen || Screen
292 var screenX = currentScreen.x || 0
293 var relativeX = globalPos.x - screenX
294 menuAnchor.menu = trayItem.menu
295 menuAnchor.anchor.window = bar
296 menuAnchor.anchor.rect = Qt.rect(
297 relativeX,
298 21,
299 parent.width, 1)
300 menuAnchor.open()
301 }
302 }
303 }
304 }
305 }
306 }
307 QsMenuAnchor {
308 id: menuAnchor
309 }
310 }
311
312 Rectangle {
313 height: parent.height 70 height: parent.height
314 width: 4 71 width: 4
315 color: "transparent"
316 } 72 }
317 73
318 Rectangle { 74 KeyboardLayout {}
319 id: kbdWidget
320
321 property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" }
322 75
323 width: kbdLabel.contentWidth + 8 76 Item {
324 color: {
325 if (kbdMouseArea.containsMouse) {
326 return "#33808080";
327 }
328 return "transparent";
329 }
330 height: parent.height
331 anchors.verticalCenter: parent.verticalCenter
332
333 MouseArea {
334 id: kbdMouseArea
335
336 anchors.fill: parent
337 hoverEnabled: true
338 cursorShape: Qt.PointingHandCursor
339 enabled: true
340 onClicked: {
341 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {})
342 }
343 }
344
345 Text {
346 id: kbdLabel
347
348 font.pointSize: 10
349 font.family: "Fira Sans"
350 color: {
351 if (NiriService.keyboardLayouts?.current_idx === 0)
352 return "#555";
353 return "white";
354 }
355 anchors.centerIn: parent
356
357 text: {
358 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
359 return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout;
360 }
361 }
362 }
363
364 Rectangle {
365 height: parent.height 77 height: parent.height
366 width: 4 78 width: 4
367 color: "transparent"
368 } 79 }
369 80
370 Text { 81 Clock {}
371 id: clock
372 color: "white"
373
374 anchors.verticalCenter: parent.verticalCenter
375
376 Custom.Chrono {
377 id: chrono
378 format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}"
379 }
380
381 text: chrono.date
382
383 font.pointSize: 10
384 font.family: "Fira Sans"
385 font.features: { "tnum": 1 }
386 }
387 } 82 }
388} \ No newline at end of file 83} \ No newline at end of file
diff --git a/accounts/gkleen@sif/shell/quickshell/Clock.qml b/accounts/gkleen@sif/shell/quickshell/Clock.qml
new file mode 100644
index 00000000..68efb558
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Clock.qml
@@ -0,0 +1,228 @@
1import QtQml
2import QtQuick
3import Quickshell
4import Custom as Custom
5import QtQuick.Controls
6import QtQuick.Layouts
7import Quickshell.Widgets
8
9Item {
10 width: clock.contentWidth
11 height: parent.height
12 anchors.verticalCenter: parent.verticalCenter
13
14 MouseArea {
15 id: clockMouseArea
16
17 anchors.fill: parent
18 hoverEnabled: true
19 enabled: true
20
21 property real angleRem: 0
22 property real sensitivity: 1 / 120
23
24 onWheel: event => {
25 angleRem += event.angleDelta.y;
26 const d = Math.round(angleRem * sensitivity);
27 yearCalendar.year += d;
28 angleRem -= d / sensitivity;
29 }
30 }
31
32 Text {
33 id: clock
34 color: "white"
35
36 anchors.verticalCenter: parent.verticalCenter
37
38 Custom.Chrono {
39 id: chrono
40 format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}"
41 }
42
43 text: chrono.date
44
45 font.pointSize: 10
46 font.family: "Fira Sans"
47 font.features: { "tnum": 1 }
48 }
49
50 PopupWindow {
51 anchor {
52 item: clockMouseArea
53 edges: Edges.Bottom
54 }
55 visible: clockMouseArea.containsMouse
56
57 implicitWidth: clockTooltipContent.width
58 implicitHeight: clockTooltipContent.height
59 color: "black"
60
61 onVisibleChanged: {
62 const d = new Date();
63 yearCalendar.year = d.getFullYear();
64 clockMouseArea.angleRem = 0;
65 }
66
67 WrapperItem {
68 id: clockTooltipContent
69
70 margin: 8
71 leftMargin: 0
72
73 ColumnLayout {
74 Text {
75 id: yearLabel
76
77 horizontalAlignment: Text.AlignHCenter
78
79 font.pointSize: 14
80 font.family: "Fira Sans"
81 font.features: { "tnum": 1 }
82 color: "white"
83
84 text: yearCalendar.year
85
86 Layout.fillWidth: true
87 Layout.bottomMargin: 8
88 }
89
90 GridLayout {
91 property int year: { const d = new Date(); return d.getFullYear(); }
92
93 id: yearCalendar
94
95 columns: 3
96 columnSpacing: 16
97 rowSpacing: 16
98
99 Layout.alignment: Qt.AlignHCenter
100 Layout.fillWidth: false
101
102 Repeater {
103 model: 12
104
105 GridLayout {
106 columns: 2
107
108 required property int index
109 property int month: index
110
111 id: monthCalendar
112
113 Layout.alignment: Qt.AlignTop | Qt.AlignRight
114 Layout.fillWidth: false
115
116 Text {
117 Layout.column: 1
118 Layout.fillWidth: true
119
120 horizontalAlignment: Text.AlignHCenter
121
122 font.pointSize: 10
123 font.family: "Fira Sans"
124
125 text: {
126 const date = Date.fromLocaleDateString(Qt.locale(), `${yearCalendar.year}-${monthCalendar.month + 1}-01`, "yyyy-M-dd");
127 return date.toLocaleString(Qt.locale("en_DK"), "MMMM")
128 }
129
130 color: "#ffead3"
131 }
132
133 DayOfWeekRow {
134 locale: grid.locale
135
136 Layout.row: 1
137 Layout.column: 1
138 Layout.fillWidth: true
139
140 delegate: Text {
141 required property string shortName
142
143 font.pointSize: 10
144 font.family: "Fira Mono"
145
146 text: shortName
147 color: "#ffcc66"
148
149 horizontalAlignment: Text.AlignRight
150 verticalAlignment: Text.AlignVCenter
151 }
152 }
153
154 WeekNumberColumn {
155 month: grid.month
156 year: grid.year
157 locale: grid.locale
158
159 Layout.fillHeight: true
160
161 delegate: Text {
162 required property int weekNumber
163
164 opacity: {
165 const simple = new Date(weekNumber == 1 && monthCalendar.month == 12 ? yearCalendar.year + 1 : yearCalendar.year, 0, 1 + (weekNumber - 1) * 7);
166 const dayOfWeek = simple.getDay();
167 const isoWeekStart = simple;
168
169 isoWeekStart.setDate(simple.getDate() - dayOfWeek + 1);
170 if (dayOfWeek > 4) {
171 isoWeekStart.setDate(isoWeekStart.getDate() + 7);
172 }
173
174 for (let i = 0; i < 7; i++) {
175 const dayInWeek = new Date(isoWeekStart);
176 dayInWeek.setDate(dayInWeek.getDate() + i);
177 if (dayInWeek.getMonth() == monthCalendar.month)
178 return 1;
179 }
180
181 return 0;
182 }
183
184 font.pointSize: 10
185 font.family: "Fira Sans"
186 font.features: { "tnum": 1 }
187
188 text: weekNumber
189 color: "#99ffdd"
190
191 horizontalAlignment: Text.AlignRight
192 verticalAlignment: Text.AlignVCenter
193 }
194 }
195
196 MonthGrid {
197 id: grid
198
199 year: yearCalendar.year
200 month: monthCalendar.month
201 locale: Qt.locale("en_DK")
202
203 Layout.fillWidth: true
204 Layout.fillHeight: true
205
206 delegate: Text {
207 required property var model
208
209 opacity: model.month === monthCalendar.month ? 1 : 0
210
211 font.pointSize: 10
212 font.family: "Fira Sans"
213 font.features: { "tnum": 1 }
214
215 text: model.day
216 color: model.today ? "#ff6699" : "white"
217
218 horizontalAlignment: Text.AlignRight
219 verticalAlignment: Text.AlignVCenter
220 }
221 }
222 }
223 }
224 }
225 }
226 }
227 }
228} \ No newline at end of file
diff --git a/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
new file mode 100644
index 00000000..710ea10c
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml
@@ -0,0 +1,78 @@
1import Quickshell
2import QtQuick
3import qs.Services
4
5Rectangle {
6 id: kbdWidget
7
8 property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" }
9
10 width: kbdLabel.contentWidth + 8
11 color: {
12 if (kbdMouseArea.containsMouse) {
13 return "#33808080";
14 }
15 return "transparent";
16 }
17 height: parent.height
18 anchors.verticalCenter: parent.verticalCenter
19
20 MouseArea {
21 id: kbdMouseArea
22
23 anchors.fill: parent
24 hoverEnabled: true
25 cursorShape: Qt.PointingHandCursor
26 enabled: true
27 onClicked: {
28 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {})
29 }
30 }
31
32 Text {
33 id: kbdLabel
34
35 font.pointSize: 10
36 font.family: "Fira Sans"
37 color: {
38 if (NiriService.keyboardLayouts?.current_idx === 0)
39 return "#555";
40 return "white";
41 }
42 anchors.centerIn: parent
43
44 text: {
45 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
46 if (!currentLayout)
47 return "";
48 return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout;
49 }
50 }
51
52 PopupWindow {
53 anchor {
54 item: kbdMouseArea
55 edges: Edges.Bottom
56 }
57 visible: kbdMouseArea.containsMouse
58
59 implicitWidth: kbdTooltipText.contentWidth + 16
60 implicitHeight: kbdTooltipText.contentHeight + 16
61 color: "black"
62
63 Text {
64 id: kbdTooltipText
65
66 anchors.centerIn: parent
67
68 font.pointSize: 10
69 font.family: "Fira Sans"
70 color: "white"
71
72 text: {
73 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
74 return currentLayout || "";
75 }
76 }
77 }
78}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
index 914152e1..c82caaa6 100644
--- a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml
@@ -13,10 +13,6 @@ Singleton {
13 property var windows: [] 13 property var windows: []
14 readonly property string socketPath: Quickshell.env("NIRI_SOCKET") 14 readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
15 15
16 onKeyboardLayoutsChanged: {
17 console.log(JSON.stringify(keyboardLayouts));
18 }
19
20 function refreshOutputs() { 16 function refreshOutputs() {
21 commandSocket.sendCommand("Outputs", data => { 17 commandSocket.sendCommand("Outputs", data => {
22 outputs = data.Ok.Outputs; 18 outputs = data.Ok.Outputs;
@@ -70,6 +66,8 @@ Singleton {
70 eventWindowUrgencyChanged(event.WindowUrgencyChanged); 66 eventWindowUrgencyChanged(event.WindowUrgencyChanged);
71 else if (event.WindowLayoutsChanged) 67 else if (event.WindowLayoutsChanged)
72 eventWindowLayoutsChanged(event.WindowLayoutsChanged); 68 eventWindowLayoutsChanged(event.WindowLayoutsChanged);
69 else
70 console.log(JSON.stringify(event));
73 } catch (e) { 71 } catch (e) {
74 console.warn("NiriService: Failed to parse event:", line, e) 72 console.warn("NiriService: Failed to parse event:", line, e)
75 } 73 }
@@ -156,11 +154,7 @@ Singleton {
156 }); 154 });
157 } 155 }
158 function eventWindowOpenedOrChanged(data) { 156 function eventWindowOpenedOrChanged(data) {
159 root.windows = Array.from(root.windows).map(win => { 157 root.windows = Array.from(root.windows).filter(win => win.id !== data.window.id).concat([data.window]);
160 if (win.id === data.window.id)
161 return data.window;
162 return win;
163 });
164 } 158 }
165 function eventWindowClosed(data) { 159 function eventWindowClosed(data) {
166 root.windows = Array.from(root.windows).filter(win => win.id !== data.id); 160 root.windows = Array.from(root.windows).filter(win => win.id !== data.id);
diff --git a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
new file mode 100644
index 00000000..afed4bf0
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
@@ -0,0 +1,138 @@
1import QtQuick
2import Quickshell
3import Quickshell.Widgets
4import Quickshell.Services.SystemTray
5
6Item {
7 anchors.verticalCenter: parent.verticalCenter
8 width: systemTrayRow.childrenRect.width
9 height: parent.height
10 clip: true
11
12 Row {
13 id: systemTrayRow
14 anchors.centerIn: parent
15 width: childrenRect.width
16 height: parent.height
17 spacing: 0
18
19 Repeater {
20 model: {
21 var trayItems = Array.from(SystemTray.items.values).filter(item => item.status !== Status.Passive);
22 trayItems.sort((a, b) => a.category !== b.category ? b.category - a.category : a.id.localeCompare(b.id))
23 return trayItems;
24 }
25
26 delegate: Item {
27 property var trayItem: modelData
28 property string iconSource: {
29 let icon = trayItem && trayItem.icon
30 if (typeof icon === 'string' || icon instanceof String) {
31 if (icon.includes("?path=")) {
32 const split = icon.split("?path=")
33 if (split.length !== 2)
34 return icon
35 const name = split[0]
36 const path = split[1]
37 const fileName = name.substring(
38 name.lastIndexOf("/") + 1)
39 return `file://${path}/${fileName}`
40 }
41 return icon
42 }
43 return ""
44 }
45
46 width: 16
47 height: parent.height
48 anchors.verticalCenter: parent.verticalCenter
49
50 IconImage {
51 anchors.centerIn: parent
52 width: parent.width
53 height: parent.width
54 source: parent.iconSource
55 asynchronous: true
56 smooth: true
57 mipmap: true
58 }
59
60 MouseArea {
61 id: trayItemArea
62
63 anchors.fill: parent
64 acceptedButtons: Qt.LeftButton | Qt.RightButton
65 hoverEnabled: true
66 cursorShape: trayItem.onlyMenu ? Qt.ArrowCursor : Qt.PointingHandCursor
67 onClicked: mouse => {
68 if (!trayItem)
69 return
70
71 if (mouse.button === Qt.LeftButton
72 && !trayItem.onlyMenu) {
73 trayItem.activate()
74 return
75 }
76
77 if (trayItem.hasMenu) {
78 var globalPos = mapToGlobal(0, 0)
79 var currentScreen = screen || Screen
80 var screenX = currentScreen.x || 0
81 var relativeX = globalPos.x - screenX
82 menuAnchor.menu = trayItem.menu
83 menuAnchor.anchor.window = bar
84 menuAnchor.anchor.rect = Qt.rect(
85 relativeX,
86 21,
87 parent.width, 1)
88 menuAnchor.open()
89 }
90 }
91 }
92
93 PopupWindow {
94 anchor {
95 item: trayItemArea
96 edges: Edges.Bottom
97 }
98 visible: (trayItem.tooltipTitle || trayItem.tooltipDescription) && trayItemArea.containsMouse && !menuAnchor.visible
99
100 color: "black"
101
102 implicitWidth: Math.max(tooltipTitle.contentWidth, tooltipDescription.contentWidth) + 16
103 implicitHeight: tooltipTitle.contentHeight + tooltipDescription.contentHeight + 16
104
105 WrapperItem {
106 margin: 8
107
108 Column {
109 Text {
110 id: tooltipTitle
111
112 font.pointSize: 10
113 font.family: "Fira Sans"
114 font.bold: true
115 color: "white"
116
117 text: trayItem.tooltipTitle
118 }
119
120 Text {
121 id: tooltipDescription
122
123 font.pointSize: 10
124 font.family: "Fira Sans"
125 color: "white"
126
127 text: trayItem.tooltipDescription
128 }
129 }
130 }
131 }
132 }
133 }
134 }
135 QsMenuAnchor {
136 id: menuAnchor
137 }
138}
diff --git a/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
new file mode 100644
index 00000000..153c56bb
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml
@@ -0,0 +1,71 @@
1import QtQuick
2import qs.Services
3
4Row {
5 id: workspaces
6
7 property var ignoreWorkspaces: @ignore_workspaces@
8
9 height: parent.height
10 anchors.verticalCenter: parent.verticalCenter
11 spacing: 0
12
13 Repeater {
14 model: {
15 let currWorkspaces = NiriService.workspaces;
16 const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces);
17 currWorkspaces = currWorkspaces.filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name));
18 currWorkspaces.sort((a, b) => {
19 if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x)
20 return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x
21 if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y)
22 return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y
23 return a.idx - b.idx;
24 });
25 return currWorkspaces;
26 }
27
28 Rectangle {
29 property var workspaceData: modelData
30
31 width: wsLabel.contentWidth + 8
32 color: {
33 if (mouseArea.containsMouse) {
34 return "#33808080";
35 }
36 return "transparent";
37 }
38 height: parent.height
39 anchors.verticalCenter: parent.verticalCenter
40
41 MouseArea {
42 id: mouseArea
43
44 anchors.fill: parent
45 hoverEnabled: true
46 cursorShape: Qt.PointingHandCursor
47 enabled: true
48 onClicked: {
49 NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {})
50 }
51 }
52
53 Text {
54 id: wsLabel
55
56 font.pointSize: 10
57 font.family: "Fira Sans"
58 color: {
59 if (workspaceData.is_active)
60 return "#23fd00";
61 if (workspaceData.active_window_id === null)
62 return "#555";
63 return "white";
64 }
65 anchors.centerIn: parent
66
67 text: workspaceData.name ? workspaceData.name : workspaceData.idx
68 }
69 }
70 }
71} \ No newline at end of file