diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2025-09-13 14:56:55 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2025-09-13 14:56:55 +0200 |
commit | 3ce3fe19b11e30fd98d7eee06d56b90ae33b228d (patch) | |
tree | 572e904f871cd05410c21b33ddf9e0ae7e1c5c90 | |
parent | 666464567055a2e4ba9f6bb310e901cdc27977f7 (diff) | |
download | nixos-3ce3fe19b11e30fd98d7eee06d56b90ae33b228d.tar nixos-3ce3fe19b11e30fd98d7eee06d56b90ae33b228d.tar.gz nixos-3ce3fe19b11e30fd98d7eee06d56b90ae33b228d.tar.bz2 nixos-3ce3fe19b11e30fd98d7eee06d56b90ae33b228d.tar.xz nixos-3ce3fe19b11e30fd98d7eee06d56b90ae33b228d.zip |
...
4 files changed, 301 insertions, 7 deletions
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml index dd0feb4b..426ed78c 100644 --- a/accounts/gkleen@sif/shell/quickshell/Bar.qml +++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml | |||
@@ -66,9 +66,9 @@ PanelWindow { | |||
66 | anchors.verticalCenter: parent.verticalCenter | 66 | anchors.verticalCenter: parent.verticalCenter |
67 | spacing: 0 | 67 | spacing: 0 |
68 | 68 | ||
69 | WorktimeWidget { command: "time"; } | 69 | // WorktimeWidget { command: "time"; } |
70 | 70 | ||
71 | WorktimeWidget { command: "today"; } | 71 | // WorktimeWidget { command: "today"; } |
72 | 72 | ||
73 | KeyboardLayout {} | 73 | KeyboardLayout {} |
74 | 74 | ||
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml index 589c36e5..a727b044 100644 --- a/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml +++ b/accounts/gkleen@sif/shell/quickshell/NotificationDisplay.qml | |||
@@ -15,7 +15,7 @@ Scope { | |||
15 | id: notifsRepeater | 15 | id: notifsRepeater |
16 | 16 | ||
17 | model: ScriptModel { | 17 | model: ScriptModel { |
18 | values: NotificationManager.displayInhibited ? [] : [...NotificationManager.groups] | 18 | values: NotificationManager.displayInhibited ? [] : NotificationManager.groups |
19 | } | 19 | } |
20 | 20 | ||
21 | delegate: PanelWindow { | 21 | delegate: PanelWindow { |
@@ -48,6 +48,7 @@ Scope { | |||
48 | readonly property real spaceAbove: { | 48 | readonly property real spaceAbove: { |
49 | var res = 0; | 49 | var res = 0; |
50 | for (let i = 0; i < notifWindow.index; i++) { | 50 | for (let i = 0; i < notifWindow.index; i++) { |
51 | (_ => {})(notifsRepeater.objectAt(i).modelData); | ||
51 | res += notifsRepeater.objectAt(i).height + 8; | 52 | res += notifsRepeater.objectAt(i).height + 8; |
52 | } | 53 | } |
53 | return res; | 54 | return res; |
@@ -60,7 +61,7 @@ Scope { | |||
60 | 61 | ||
61 | color: "transparent" | 62 | color: "transparent" |
62 | 63 | ||
63 | implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + 16 | 64 | implicitHeight: Math.max(notifCount.visible ? notifCount.contentHeight : 0, notifSummary.contentHeight) + (notifBody.visible ? 8 + notifBody.contentHeight : 0) + (notifActions.visible ? 8 + notifActions.height : 0) + (notifTime.visible ? 8 + notifTime.contentHeight : 0) + 16 |
64 | implicitWidth: 400 | 65 | implicitWidth: 400 |
65 | 66 | ||
66 | WrapperMouseArea { | 67 | WrapperMouseArea { |
@@ -108,6 +109,8 @@ Scope { | |||
108 | res += 1; | 109 | res += 1; |
109 | if (notifActions.visible) | 110 | if (notifActions.visible) |
110 | res += 1; | 111 | res += 1; |
112 | if (notifTime.visible) | ||
113 | res += 1; | ||
111 | return res; | 114 | return res; |
112 | } | 115 | } |
113 | 116 | ||
@@ -167,7 +170,7 @@ Scope { | |||
167 | Layout.column: 0 | 170 | Layout.column: 0 |
168 | Layout.row: 0 | 171 | Layout.row: 0 |
169 | Layout.fillHeight: true | 172 | Layout.fillHeight: true |
170 | Layout.rowSpan: notifBody.visible ? 2 : 1 | 173 | Layout.rowSpan: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0) |
171 | } | 174 | } |
172 | 175 | ||
173 | Text { | 176 | Text { |
@@ -176,6 +179,7 @@ Scope { | |||
176 | visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false | 179 | visible: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? false |
177 | text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? "" | 180 | text: notifWindow.modelData?.[notifWindow.activeIx]?.body ?? "" |
178 | textFormat: Text.RichText | 181 | textFormat: Text.RichText |
182 | wrapMode: Text.Wrap | ||
179 | 183 | ||
180 | font.pointSize: 10 | 184 | font.pointSize: 10 |
181 | font.family: "Fira Sans" | 185 | font.family: "Fira Sans" |
@@ -187,6 +191,32 @@ Scope { | |||
187 | Layout.columnSpan: notifCount.visible ? 2 : 1 | 191 | Layout.columnSpan: notifCount.visible ? 2 : 1 |
188 | } | 192 | } |
189 | 193 | ||
194 | Text { | ||
195 | id: notifTime | ||
196 | |||
197 | Connections { | ||
198 | target: NotificationManager.clock | ||
199 | function onDateChanged() { | ||
200 | notifTime.text = NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | visible: notifTime.text && notifTime.text !== "now" | ||
205 | text: NotificationManager.formatTime(notifWindow.modelData?.[notifWindow.activeIx]?.receivedTime) | ||
206 | |||
207 | font.pointSize: 8 | ||
208 | font.family: "Fira Sans" | ||
209 | font.italic: true | ||
210 | color: "#555" | ||
211 | maximumLineCount: 1 | ||
212 | horizontalAlignment: Text.AlignRight | ||
213 | |||
214 | Layout.fillWidth: true | ||
215 | Layout.row: notifBody.visible ? 2 : 1 | ||
216 | Layout.column: notifImage.visible ? 1 : 0 | ||
217 | Layout.columnSpan: 2 | ||
218 | } | ||
219 | |||
190 | RowLayout { | 220 | RowLayout { |
191 | id: notifActions | 221 | id: notifActions |
192 | 222 | ||
@@ -196,9 +226,9 @@ Scope { | |||
196 | uniformCellSizes: true | 226 | uniformCellSizes: true |
197 | 227 | ||
198 | width: 400 - 16 | 228 | width: 400 - 16 |
199 | Layout.row: notifBody.visible ? 2 : 1 | 229 | Layout.row: 1 + (notifBody.visible ? 1 : 0) + (notifTime.visible ? 1 : 0) |
200 | Layout.column: 0 | 230 | Layout.column: 0 |
201 | Layout.columnSpan: notifImage.visible ? 3 : 2 | 231 | Layout.columnSpan: 2 + (notifImage.visible ? 1 : 0) |
202 | 232 | ||
203 | Repeater { | 233 | Repeater { |
204 | model: ScriptModel { | 234 | model: ScriptModel { |
diff --git a/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml index 3dadbc69..1005182a 100644 --- a/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml +++ b/accounts/gkleen@sif/shell/quickshell/NotificationInhibitorWidget.qml | |||
@@ -2,6 +2,9 @@ import Quickshell | |||
2 | import QtQuick | 2 | import QtQuick |
3 | import Quickshell.Widgets | 3 | import Quickshell.Widgets |
4 | import qs.Services | 4 | import qs.Services |
5 | import QtQuick.Controls | ||
6 | import QtQuick.Layouts | ||
7 | import QtQuick.Shapes | ||
5 | 8 | ||
6 | Item { | 9 | Item { |
7 | id: root | 10 | id: root |
@@ -44,4 +47,214 @@ Item { | |||
44 | } | 47 | } |
45 | } | 48 | } |
46 | } | 49 | } |
50 | |||
51 | Loader { | ||
52 | id: tooltipLoader | ||
53 | |||
54 | active: false | ||
55 | |||
56 | Connections { | ||
57 | target: widgetMouseArea | ||
58 | function onContainsMouseChanged() { | ||
59 | if (widgetMouseArea.containsMouse) | ||
60 | tooltipLoader.active = true; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | sourceComponent: PopupWindow { | ||
65 | id: tooltip | ||
66 | |||
67 | property bool nextVisible: !NotificationManager.displayInhibited && (widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse) | ||
68 | |||
69 | anchor { | ||
70 | item: widgetMouseArea | ||
71 | edges: Edges.Bottom | Edges.Left | ||
72 | } | ||
73 | visible: false | ||
74 | |||
75 | onNextVisibleChanged: hangTimer.restart() | ||
76 | |||
77 | Timer { | ||
78 | id: hangTimer | ||
79 | interval: tooltip.visible ? 100 : 500 | ||
80 | onTriggered: { | ||
81 | tooltip.visible = tooltip.nextVisible; | ||
82 | if (!tooltip.visible) | ||
83 | tooltipLoader.active = false; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | implicitWidth: 400 | ||
88 | implicitHeight: Math.min(tooltip.screen.height * 0.66, Math.max(100, scroll.contentHeight + 16)) | ||
89 | color: "black" | ||
90 | |||
91 | WrapperMouseArea { | ||
92 | id: tooltipMouseArea | ||
93 | |||
94 | hoverEnabled: true | ||
95 | enabled: true | ||
96 | |||
97 | anchors.fill: parent | ||
98 | |||
99 | WrapperItem { | ||
100 | margin: 8 | ||
101 | |||
102 | ScrollView { | ||
103 | id: scroll | ||
104 | |||
105 | contentWidth: availableWidth | ||
106 | // ScrollBar.vertical.policy: ScrollBar.AlwaysOn | ||
107 | |||
108 | ColumnLayout { | ||
109 | id: historyLayout | ||
110 | anchors { | ||
111 | left: parent.left | ||
112 | right: parent.right | ||
113 | } | ||
114 | |||
115 | spacing: 8 | ||
116 | |||
117 | Repeater { | ||
118 | model: ScriptModel { | ||
119 | values: [...NotificationManager.history].reverse().map(o => o.notification) | ||
120 | } | ||
121 | |||
122 | delegate: GridLayout { | ||
123 | id: notif | ||
124 | |||
125 | Layout.fillWidth: true | ||
126 | Layout.preferredHeight: notifSummary.contentHeight + (notifBody.visible ? notifBody.contentHeight + 8 : 0) + (notifSep.visible ? notifSep.height + 8 : 0) + notifTime.contentHeight + 8 | ||
127 | |||
128 | required property var modelData | ||
129 | required property int index | ||
130 | |||
131 | columnSpacing: 8 | ||
132 | rowSpacing: 8 | ||
133 | |||
134 | columns: notifImage.visible ? 2 : 1 | ||
135 | rows: { | ||
136 | var res = 2; | ||
137 | if (notifBody.visible) | ||
138 | res += 1; | ||
139 | if (notifSep.visible) | ||
140 | res += 1; | ||
141 | return res; | ||
142 | } | ||
143 | |||
144 | Shape { | ||
145 | id: notifSep | ||
146 | |||
147 | visible: notif.index != 0 | ||
148 | |||
149 | height: 2 | ||
150 | width: 400 - 32 | ||
151 | |||
152 | ShapePath { | ||
153 | strokeWidth: 2 | ||
154 | strokeColor: "#20ffffff" | ||
155 | startX: 0; startY: 0; | ||
156 | PathLine { x: 400 - 32; y: 0; } | ||
157 | } | ||
158 | |||
159 | Layout.row: 0 | ||
160 | Layout.column: 0 | ||
161 | Layout.columnSpan: notifImage.visible ? 2 : 1 | ||
162 | Layout.alignment: Qt.AlignHCenter | ||
163 | } | ||
164 | |||
165 | Text { | ||
166 | id: notifSummary | ||
167 | |||
168 | text: notif.modelData?.summary ?? "" | ||
169 | |||
170 | font.pointSize: 10 | ||
171 | font.family: "Fira Sans" | ||
172 | font.italic: true | ||
173 | color: "white" | ||
174 | maximumLineCount: 1 | ||
175 | elide: Text.ElideRight | ||
176 | |||
177 | Layout.fillWidth: true | ||
178 | Layout.row: notifSep.visible ? 1 : 0 | ||
179 | Layout.column: notifImage.visible ? 1 : 0 | ||
180 | } | ||
181 | |||
182 | Image { | ||
183 | id: notifImage | ||
184 | |||
185 | visible: (notif.modelData?.image || notif.modelData?.appIcon) ?? false | ||
186 | |||
187 | onStatusChanged: { | ||
188 | if (notifImage.status == Image.Error) | ||
189 | notifImage.visible = false; | ||
190 | } | ||
191 | |||
192 | source: (notif.modelData?.image || notif.modelData?.appIcon) ?? "" | ||
193 | fillMode: Image.PreserveAspectFit | ||
194 | asynchronous: true | ||
195 | smooth: true | ||
196 | mipmap: true | ||
197 | |||
198 | Layout.maximumWidth: 50 | ||
199 | Layout.column: 0 | ||
200 | Layout.row: notifSep.visible ? 1 : 0 | ||
201 | Layout.fillHeight: true | ||
202 | Layout.rowSpan: notifBody.visible ? 3 : 2 | ||
203 | } | ||
204 | |||
205 | Text { | ||
206 | id: notifBody | ||
207 | |||
208 | visible: notif.modelData?.body ?? false | ||
209 | text: notif.modelData?.body ?? "" | ||
210 | textFormat: Text.RichText | ||
211 | wrapMode: Text.Wrap | ||
212 | |||
213 | font.pointSize: 10 | ||
214 | font.family: "Fira Sans" | ||
215 | color: "white" | ||
216 | |||
217 | Layout.fillWidth: true | ||
218 | Layout.row: notifSep.visible ? 2 : 1 | ||
219 | Layout.column: notifImage.visible ? 1 : 0 | ||
220 | } | ||
221 | |||
222 | Text { | ||
223 | id: notifTime | ||
224 | |||
225 | Connections { | ||
226 | target: NotificationManager.clock | ||
227 | function onDateChanged() { | ||
228 | notifTime.text = NotificationManager.formatTime(notif.modelData?.receivedTime); | ||
229 | } | ||
230 | } | ||
231 | |||
232 | text: NotificationManager.formatTime(notif.modelData?.receivedTime) | ||
233 | |||
234 | font.pointSize: 8 | ||
235 | font.family: "Fira Sans" | ||
236 | font.italic: true | ||
237 | color: "#555" | ||
238 | maximumLineCount: 1 | ||
239 | horizontalAlignment: Text.AlignRight | ||
240 | |||
241 | Layout.fillWidth: true | ||
242 | Layout.row: { | ||
243 | var res = 1; | ||
244 | if (notifSep.visible) | ||
245 | res += 1; | ||
246 | if (notifBody.visible) | ||
247 | res += 1; | ||
248 | return res; | ||
249 | } | ||
250 | Layout.column: notifImage.visible ? 1 : 0 | ||
251 | } | ||
252 | } | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | } | ||
47 | } | 260 | } |
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml index 778cdc2a..2199ccdf 100644 --- a/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml +++ b/accounts/gkleen@sif/shell/quickshell/Services/NotificationManager.qml | |||
@@ -56,6 +56,8 @@ Singleton { | |||
56 | { "test": { "appName": "Element" }, "group-by": [ "summary" ] } | 56 | { "test": { "appName": "Element" }, "group-by": [ "summary" ] } |
57 | ]; | 57 | ]; |
58 | 58 | ||
59 | property var history: [] | ||
60 | |||
59 | Component { | 61 | Component { |
60 | id: expirationTimer | 62 | id: expirationTimer |
61 | 63 | ||
@@ -74,6 +76,45 @@ Singleton { | |||
74 | } | 76 | } |
75 | } | 77 | } |
76 | 78 | ||
79 | Component { | ||
80 | id: notificationLock | ||
81 | |||
82 | RetainableLock {} | ||
83 | } | ||
84 | |||
85 | readonly property SystemClock clock: SystemClock { | ||
86 | precision: SystemClock.Minutes | ||
87 | } | ||
88 | |||
89 | function formatTime(time) { | ||
90 | const now = root.clock.date; | ||
91 | const diff = now - time; | ||
92 | const minutes = Math.ceil(diff / 60000); | ||
93 | const hours = Math.floor(minutes / 60); | ||
94 | |||
95 | if (hours < 1) { | ||
96 | if (minutes < 1) | ||
97 | return "now"; | ||
98 | if (minutes == 1) | ||
99 | return "1 minute"; | ||
100 | return `${minutes} minutes`; | ||
101 | } | ||
102 | |||
103 | const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | ||
104 | const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate()) | ||
105 | const days = Math.floor((nowDate - timeDate) / (1000 * 86400)) | ||
106 | |||
107 | const timeStr = time.toLocaleTimeString(Qt.locale(), "HH:mm"); | ||
108 | |||
109 | if (days === 0) | ||
110 | return timeStr; | ||
111 | if (days === 1) | ||
112 | return `yesterday ${timeStr}`; | ||
113 | |||
114 | const dateStr = time.toLocaleTimeString(Qt.locale(), "YYYY-MM-DD"); | ||
115 | return `${dateStr} ${timeStr}`; | ||
116 | } | ||
117 | |||
77 | NotificationServer { | 118 | NotificationServer { |
78 | id: server | 119 | id: server |
79 | 120 | ||
@@ -92,7 +133,17 @@ Singleton { | |||
92 | Object.defineProperty(notification, "expirationTimer", { configurable: true, enumerable: true, writable: true }); | 133 | Object.defineProperty(notification, "expirationTimer", { configurable: true, enumerable: true, writable: true }); |
93 | notification.expirationTimer = expirationTimer.createObject(notification, { parent: notification, expirationTime: timeout }); | 134 | notification.expirationTimer = expirationTimer.createObject(notification, { parent: notification, expirationTime: timeout }); |
94 | } | 135 | } |
136 | Object.defineProperty(notification, "receivedTime", { configurable: true, enumerable: true, writable: true }); | ||
137 | notification.receivedTime = root.clock.date; | ||
138 | notification.closed.connect((reason) => server.onNotificationClosed(notification, reason)); | ||
95 | notification.tracked = true; | 139 | notification.tracked = true; |
96 | } | 140 | } |
141 | |||
142 | function onNotificationClosed(notification, reason) { | ||
143 | root.history.push({ | ||
144 | lock: notificationLock.createObject(root, { locked: true, object: notification }), | ||
145 | notification: notification | ||
146 | }); | ||
147 | } | ||
97 | } | 148 | } |
98 | } | 149 | } |