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.qml507
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Clock.qml186
-rw-r--r--accounts/gkleen@sif/shell/quickshell/KeyboardLayout.qml78
-rw-r--r--accounts/gkleen@sif/shell/quickshell/SystemTray.qml94
-rw-r--r--accounts/gkleen@sif/shell/quickshell/WorkspaceSwitcher.qml71
6 files changed, 528 insertions, 501 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 9cb58b93..2f323858 100644
--- a/accounts/gkleen@sif/shell/quickshell/Bar.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -1,13 +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
8import QtQuick.Controls
9import QtQuick.Layouts
10import QtQml
11 3
12 4
13PanelWindow { 5PanelWindow {
@@ -46,74 +38,7 @@ PanelWindow {
46 anchors.verticalCenter: parent.verticalCenter 38 anchors.verticalCenter: parent.verticalCenter
47 spacing: 8 39 spacing: 8
48 40
49 Row { 41 WorkspaceSwitcher {}
50 id: workspaces
51
52 property var ignoreWorkspaces: @ignore_workspaces@
53
54 height: parent.height
55 anchors.verticalCenter: parent.verticalCenter
56 spacing: 0
57
58 Repeater {
59 model: {
60 let currWorkspaces = NiriService.workspaces;
61 const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces);
62 currWorkspaces = currWorkspaces.filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name));
63 currWorkspaces.sort((a, b) => {
64 if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x)
65 return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x
66 if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y)
67 return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y
68 return a.idx - b.idx;
69 });
70 return currWorkspaces;
71 }
72
73 Rectangle {
74 property var workspaceData: modelData
75
76 width: wsLabel.contentWidth + 8
77 color: {
78 if (mouseArea.containsMouse) {
79 return "#33808080";
80 }
81 return "transparent";
82 }
83 height: parent.height
84 anchors.verticalCenter: parent.verticalCenter
85
86 MouseArea {
87 id: mouseArea
88
89 anchors.fill: parent
90 hoverEnabled: true
91 cursorShape: Qt.PointingHandCursor
92 enabled: true
93 onClicked: {
94 NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {})
95 }
96 }
97
98 Text {
99 id: wsLabel
100
101 font.pointSize: 10
102 font.family: "Fira Sans"
103 color: {
104 if (workspaceData.is_active)
105 return "#23fd00";
106 if (workspaceData.active_window_id === null)
107 return "#555";
108 return "white";
109 }
110 anchors.centerIn: parent
111
112 text: workspaceData.name ? workspaceData.name : workspaceData.idx
113 }
114 }
115 }
116 }
117 } 42 }
118 43
119 Row { 44 Row {
@@ -124,91 +49,8 @@ PanelWindow {
124 anchors.centerIn: parent 49 anchors.centerIn: parent
125 spacing: 5 50 spacing: 5
126 51
127 Item { 52 ActiveWindowDisplay {
128 id: activeWindowDisplay 53 maxWidth: bar.width - 2*Math.max(left.width, right.width) - 2*8
129
130 property var activeWindow: {
131 let currWindowId = Array.from(NiriService.workspaces).find(ws => {
132 return ws.output === bar.screen.name && ws.is_active;
133 })?.active_window_id;
134
135 return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null;
136 }
137 property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null
138
139 anchors.verticalCenter: parent.verticalCenter
140 width: activeWindowDisplayContent.width
141 height: parent.height
142
143 Row {
144 id: activeWindowDisplayContent
145
146 width: childrenRect.width
147 height: parent.height
148 anchors.verticalCenter: parent.verticalCenter
149 spacing: 8
150
151 IconImage {
152 id: activeWindowIcon
153
154 height: 14
155 width: 14
156
157 anchors.verticalCenter: parent.verticalCenter
158
159 source: {
160 let icon = activeWindowDisplay.windowEntry?.icon
161 if (typeof icon === 'string' || icon instanceof String) {
162 if (icon.includes("?path=")) {
163 const split = icon.split("?path=")
164 if (split.length !== 2)
165 return icon
166 const name = split[0]
167 const path = split[1]
168 const fileName = name.substring(
169 name.lastIndexOf("/") + 1)
170 return `file://${path}/${fileName}`
171 } else
172 icon = Quickshell.iconPath(icon);
173 return icon
174 }
175 return ""
176 }
177 asynchronous: true
178 smooth: true
179 mipmap: true
180 }
181
182 Text {
183 id: windowTitle
184
185 width: Math.min(implicitWidth, bar.width - 2*Math.max(left.width, right.width) - 2*8 - activeWindowIcon.width - activeWindowDisplayContent.spacing)
186
187 property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" }
188
189 elide: Text.ElideRight
190 maximumLineCount: 1
191 color: "white"
192 anchors.verticalCenter: parent.verticalCenter
193 text: {
194 if (!activeWindowDisplay.activeWindow)
195 return "";
196
197 var title = activeWindowDisplay.activeWindow.title;
198 var appName = activeWindowDisplay.windowEntry?.name;
199 if (appAliases[appName])
200 appName = appAliases[appName];
201 if (appName && title.endsWith(appName)) {
202 const oldTitle = title;
203 title = title.substring(0, title.length - appName.length);
204 title = title.replace(/\s*(—|-)\s*$/, "");
205 if (!title)
206 title = oldTitle;
207 }
208 return title;
209 }
210 }
211 }
212 } 54 }
213 } 55 }
214 56
@@ -222,357 +64,20 @@ PanelWindow {
222 anchors.verticalCenter: parent.verticalCenter 64 anchors.verticalCenter: parent.verticalCenter
223 spacing: 0 65 spacing: 0
224 66
225 Item { 67 SystemTray {}
226 anchors.verticalCenter: parent.verticalCenter
227 width: systemTrayRow.childrenRect.width
228 height: parent.height
229 clip: true
230
231 Row {
232 id: systemTrayRow
233 anchors.centerIn: parent
234 width: childrenRect.width
235 height: parent.height
236 spacing: 0
237
238 Repeater {
239 model: SystemTray.items.values
240
241 delegate: Item {
242 property var trayItem: modelData
243 property string iconSource: {
244 let icon = trayItem && trayItem.icon
245 if (typeof icon === 'string' || icon instanceof String) {
246 if (icon.includes("?path=")) {
247 const split = icon.split("?path=")
248 if (split.length !== 2)
249 return icon
250 const name = split[0]
251 const path = split[1]
252 const fileName = name.substring(
253 name.lastIndexOf("/") + 1)
254 return `file://${path}/${fileName}`
255 }
256 return icon
257 }
258 return ""
259 }
260
261 width: 16
262 height: parent.height
263 anchors.verticalCenter: parent.verticalCenter
264
265 IconImage {
266 anchors.centerIn: parent
267 width: parent.width
268 height: parent.width
269 source: parent.iconSource
270 asynchronous: true
271 smooth: true
272 mipmap: true
273 }
274
275 MouseArea {
276 id: trayItemArea
277
278 anchors.fill: parent
279 acceptedButtons: Qt.LeftButton | Qt.RightButton
280 hoverEnabled: true
281 cursorShape: Qt.PointingHandCursor
282 onClicked: mouse => {
283 if (!trayItem)
284 return
285
286 if (mouse.button === Qt.LeftButton
287 && !trayItem.onlyMenu) {
288 trayItem.activate()
289 return
290 }
291
292 if (trayItem.hasMenu) {
293 var globalPos = mapToGlobal(0, 0)
294 var currentScreen = screen || Screen
295 var screenX = currentScreen.x || 0
296 var relativeX = globalPos.x - screenX
297 menuAnchor.menu = trayItem.menu
298 menuAnchor.anchor.window = bar
299 menuAnchor.anchor.rect = Qt.rect(
300 relativeX,
301 21,
302 parent.width, 1)
303 menuAnchor.open()
304 }
305 }
306 }
307 }
308 }
309 }
310 QsMenuAnchor {
311 id: menuAnchor
312 }
313 }
314 68
315 Item { 69 Item {
316 height: parent.height 70 height: parent.height
317 width: 4 71 width: 4
318 } 72 }
319 73
320 Rectangle { 74 KeyboardLayout {}
321 id: kbdWidget
322
323 property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" }
324
325 width: kbdLabel.contentWidth + 8
326 color: {
327 if (kbdMouseArea.containsMouse) {
328 return "#33808080";
329 }
330 return "transparent";
331 }
332 height: parent.height
333 anchors.verticalCenter: parent.verticalCenter
334
335 MouseArea {
336 id: kbdMouseArea
337
338 anchors.fill: parent
339 hoverEnabled: true
340 cursorShape: Qt.PointingHandCursor
341 enabled: true
342 onClicked: {
343 NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {})
344 }
345 }
346
347 Text {
348 id: kbdLabel
349
350 font.pointSize: 10
351 font.family: "Fira Sans"
352 color: {
353 if (NiriService.keyboardLayouts?.current_idx === 0)
354 return "#555";
355 return "white";
356 }
357 anchors.centerIn: parent
358
359 text: {
360 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
361 if (!currentLayout)
362 return "";
363 return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout;
364 }
365 }
366
367 PopupWindow {
368 anchor {
369 item: kbdMouseArea
370 edges: Edges.Bottom
371 }
372 visible: kbdMouseArea.containsMouse
373
374 implicitWidth: kbdTooltipText.contentWidth + 4
375 implicitHeight: kbdTooltipText.contentHeight + 4
376 color: "black"
377
378 Text {
379 id: kbdTooltipText
380
381 anchors.centerIn: parent
382
383 font.pointSize: 10
384 font.family: "Fira Sans"
385 color: "white"
386
387 text: {
388 const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx];
389 return currentLayout || "";
390 }
391 }
392 }
393 }
394 75
395 Item { 76 Item {
396 height: parent.height 77 height: parent.height
397 width: 4 78 width: 4
398 } 79 }
399 80
400 Item { 81 Clock {}
401 width: clock.contentWidth
402 height: parent.height
403 anchors.verticalCenter: parent.verticalCenter
404
405 MouseArea {
406 id: clockMouseArea
407
408 anchors.fill: parent
409 hoverEnabled: true
410 enabled: true
411 }
412
413 Text {
414 id: clock
415 color: "white"
416
417 anchors.verticalCenter: parent.verticalCenter
418
419 Custom.Chrono {
420 id: chrono
421 format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}"
422 }
423
424 text: chrono.date
425
426 font.pointSize: 10
427 font.family: "Fira Sans"
428 font.features: { "tnum": 1 }
429 }
430
431 PopupWindow {
432 anchor {
433 item: clockMouseArea
434 edges: Edges.Bottom
435 }
436 visible: clockMouseArea.containsMouse
437
438 implicitWidth: yearCalendar.implicitWidth + 16
439 implicitHeight: yearCalendar.implicitHeight + 16
440 color: "black"
441
442 GridLayout {
443 property int year: { const d = new Date(); return d.getFullYear(); }
444
445 id: yearCalendar
446
447 columns: 3
448 columnSpacing: 16
449 rowSpacing: 16
450
451 anchors.centerIn: parent
452
453 Repeater {
454 model: 12
455
456 Column {
457 required property int index
458 property int month: index
459
460 id: monthCalendar
461
462 width: parent.width
463
464 Text {
465 width: parent.width
466
467 horizontalAlignment: Text.AlignHCenter
468
469 font.pointSize: 10
470 font.family: "Fira Sans"
471
472 text: {
473 const date = Date.fromLocaleDateString(Qt.locale(), `${yearCalendar.year}-${monthCalendar.month + 1}-01`, "yyyy-M-dd");
474 return date.toLocaleString(Qt.locale("en_DK"), "MMMM")
475 }
476
477 color: "#ffead3"
478 }
479
480 GridLayout {
481 columns: 2
482
483 DayOfWeekRow {
484 locale: grid.locale
485
486 Layout.column: 1
487 Layout.fillWidth: true
488
489 delegate: Text {
490 required property string shortName
491
492 font.pointSize: 10
493 font.family: "Fira Mono"
494
495 text: shortName
496 color: "#ffcc66"
497
498 horizontalAlignment: Text.AlignRight
499 verticalAlignment: Text.AlignVCenter
500 }
501 }
502
503 WeekNumberColumn {
504 month: grid.month
505 year: grid.year
506 locale: grid.locale
507
508 Layout.fillHeight: true
509
510 delegate: Text {
511 required property int weekNumber
512
513 opacity: {
514 const simple = new Date(weekNumber == 1 && monthCalendar.month == 12 ? yearCalendar.year + 1 : yearCalendar.year, 0, 1 + (weekNumber - 1) * 7);
515 const dayOfWeek = simple.getDay();
516 const isoWeekStart = simple;
517
518 isoWeekStart.setDate(simple.getDate() - dayOfWeek + 1);
519 if (dayOfWeek > 4) {
520 isoWeekStart.setDate(isoWeekStart.getDate() + 7);
521 }
522
523 for (let i = 0; i < 7; i++) {
524 const dayInWeek = isoWeekStart;
525 dayInWeek.setDate(dayInWeek.getDate() + i);
526 if (dayInWeek.getMonth() == monthCalendar.month)
527 return 1;
528 }
529
530 return 0;
531 }
532
533 font.pointSize: 10
534 font.family: "Fira Sans"
535 font.features: { "tnum": 1 }
536
537 text: weekNumber
538 color: "#99ffdd"
539
540 horizontalAlignment: Text.AlignRight
541 verticalAlignment: Text.AlignVCenter
542 }
543 }
544
545 MonthGrid {
546 id: grid
547
548 year: yearCalendar.year
549 month: monthCalendar.month
550 locale: Qt.locale("en_DK")
551
552 Layout.fillWidth: true
553 Layout.fillHeight: true
554
555 delegate: Text {
556 required property var model
557
558 opacity: model.month === monthCalendar.month ? 1 : 0
559
560 font.pointSize: 10
561 font.family: "Fira Sans"
562 font.features: { "tnum": 1 }
563
564 text: model.day
565 color: model.today ? "#ff6699" : "white"
566
567 horizontalAlignment: Text.AlignRight
568 verticalAlignment: Text.AlignVCenter
569 }
570 }
571 }
572 }
573 }
574 }
575 }
576 }
577 } 82 }
578} \ 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..56f8d41f
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Clock.qml
@@ -0,0 +1,186 @@
1import QtQml
2import QtQuick
3import Quickshell
4import Custom as Custom
5import QtQuick.Controls
6import QtQuick.Layouts
7
8Item {
9 width: clock.contentWidth
10 height: parent.height
11 anchors.verticalCenter: parent.verticalCenter
12
13 MouseArea {
14 id: clockMouseArea
15
16 anchors.fill: parent
17 hoverEnabled: true
18 enabled: true
19 }
20
21 Text {
22 id: clock
23 color: "white"
24
25 anchors.verticalCenter: parent.verticalCenter
26
27 Custom.Chrono {
28 id: chrono
29 format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}"
30 }
31
32 text: chrono.date
33
34 font.pointSize: 10
35 font.family: "Fira Sans"
36 font.features: { "tnum": 1 }
37 }
38
39 PopupWindow {
40 anchor {
41 item: clockMouseArea
42 edges: Edges.Bottom
43 }
44 visible: clockMouseArea.containsMouse
45
46 implicitWidth: yearCalendar.implicitWidth + 16
47 implicitHeight: yearCalendar.implicitHeight + 16
48 color: "black"
49
50 GridLayout {
51 property int year: { const d = new Date(); return d.getFullYear(); }
52
53 id: yearCalendar
54
55 columns: 3
56 columnSpacing: 16
57 rowSpacing: 16
58
59 anchors.centerIn: parent
60
61 Repeater {
62 model: 12
63
64 Column {
65 required property int index
66 property int month: index
67
68 id: monthCalendar
69
70 width: parent.width
71
72 Layout.alignment: Qt.AlignTop | Qt.AlignRight
73
74 Text {
75 width: parent.width
76
77 horizontalAlignment: Text.AlignHCenter
78
79 font.pointSize: 10
80 font.family: "Fira Sans"
81
82 text: {
83 const date = Date.fromLocaleDateString(Qt.locale(), `${yearCalendar.year}-${monthCalendar.month + 1}-01`, "yyyy-M-dd");
84 return date.toLocaleString(Qt.locale("en_DK"), "MMMM")
85 }
86
87 color: "#ffead3"
88 }
89
90 GridLayout {
91 columns: 2
92
93 DayOfWeekRow {
94 locale: grid.locale
95
96 Layout.column: 1
97 Layout.fillWidth: true
98
99 delegate: Text {
100 required property string shortName
101
102 font.pointSize: 10
103 font.family: "Fira Mono"
104
105 text: shortName
106 color: "#ffcc66"
107
108 horizontalAlignment: Text.AlignRight
109 verticalAlignment: Text.AlignVCenter
110 }
111 }
112
113 WeekNumberColumn {
114 month: grid.month
115 year: grid.year
116 locale: grid.locale
117
118 Layout.fillHeight: true
119
120 delegate: Text {
121 required property int weekNumber
122
123 opacity: {
124 const simple = new Date(weekNumber == 1 && monthCalendar.month == 12 ? yearCalendar.year + 1 : yearCalendar.year, 0, 1 + (weekNumber - 1) * 7);
125 const dayOfWeek = simple.getDay();
126 const isoWeekStart = simple;
127
128 isoWeekStart.setDate(simple.getDate() - dayOfWeek + 1);
129 if (dayOfWeek > 4) {
130 isoWeekStart.setDate(isoWeekStart.getDate() + 7);
131 }
132
133 for (let i = 0; i < 7; i++) {
134 const dayInWeek = isoWeekStart;
135 dayInWeek.setDate(dayInWeek.getDate() + i);
136 if (dayInWeek.getMonth() == monthCalendar.month)
137 return 1;
138 }
139
140 return 0;
141 }
142
143 font.pointSize: 10
144 font.family: "Fira Sans"
145 font.features: { "tnum": 1 }
146
147 text: weekNumber
148 color: "#99ffdd"
149
150 horizontalAlignment: Text.AlignRight
151 verticalAlignment: Text.AlignVCenter
152 }
153 }
154
155 MonthGrid {
156 id: grid
157
158 year: yearCalendar.year
159 month: monthCalendar.month
160 locale: Qt.locale("en_DK")
161
162 Layout.fillWidth: true
163 Layout.fillHeight: true
164
165 delegate: Text {
166 required property var model
167
168 opacity: model.month === monthCalendar.month ? 1 : 0
169
170 font.pointSize: 10
171 font.family: "Fira Sans"
172 font.features: { "tnum": 1 }
173
174 text: model.day
175 color: model.today ? "#ff6699" : "white"
176
177 horizontalAlignment: Text.AlignRight
178 verticalAlignment: Text.AlignVCenter
179 }
180 }
181 }
182 }
183 }
184 }
185 }
186} \ 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..4d3bd180
--- /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 + 4
60 implicitHeight: kbdTooltipText.contentHeight + 4
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/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
new file mode 100644
index 00000000..a02a81fd
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml
@@ -0,0 +1,94 @@
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: SystemTray.items.values
21
22 delegate: Item {
23 property var trayItem: modelData
24 property string iconSource: {
25 let icon = trayItem && trayItem.icon
26 if (typeof icon === 'string' || icon instanceof String) {
27 if (icon.includes("?path=")) {
28 const split = icon.split("?path=")
29 if (split.length !== 2)
30 return icon
31 const name = split[0]
32 const path = split[1]
33 const fileName = name.substring(
34 name.lastIndexOf("/") + 1)
35 return `file://${path}/${fileName}`
36 }
37 return icon
38 }
39 return ""
40 }
41
42 width: 16
43 height: parent.height
44 anchors.verticalCenter: parent.verticalCenter
45
46 IconImage {
47 anchors.centerIn: parent
48 width: parent.width
49 height: parent.width
50 source: parent.iconSource
51 asynchronous: true
52 smooth: true
53 mipmap: true
54 }
55
56 MouseArea {
57 id: trayItemArea
58
59 anchors.fill: parent
60 acceptedButtons: Qt.LeftButton | Qt.RightButton
61 hoverEnabled: true
62 cursorShape: Qt.PointingHandCursor
63 onClicked: mouse => {
64 if (!trayItem)
65 return
66
67 if (mouse.button === Qt.LeftButton
68 && !trayItem.onlyMenu) {
69 trayItem.activate()
70 return
71 }
72
73 if (trayItem.hasMenu) {
74 var globalPos = mapToGlobal(0, 0)
75 var currentScreen = screen || Screen
76 var screenX = currentScreen.x || 0
77 var relativeX = globalPos.x - screenX
78 menuAnchor.menu = trayItem.menu
79 menuAnchor.anchor.window = bar
80 menuAnchor.anchor.rect = Qt.rect(
81 relativeX,
82 21,
83 parent.width, 1)
84 menuAnchor.open()
85 }
86 }
87 }
88 }
89 }
90 }
91 QsMenuAnchor {
92 id: menuAnchor
93 }
94}
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