├── .gitignore
├── .vscode
└── launch.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE.md
├── README.md
├── build.rs
├── demo.mp4
├── krustify.png
├── res
└── themes
│ ├── compact
│ ├── notifications.png
│ ├── res.qrc
│ └── template.ui
│ └── default
│ ├── notifications.png
│ ├── res.qrc
│ └── template.ui
├── rust-toolchain.toml
└── src
├── dbus_signal.rs
├── errors.rs
├── image_handler.rs
├── main.rs
├── notification.rs
├── notification_spawner.rs
├── notification_widget.rs
├── settings.rs
└── tray_menu.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.idea
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "lldb",
9 | "request": "launch",
10 | "name": "Debug executable 'krustyfy'",
11 | "cargo": {
12 | "args": [
13 | "build",
14 | "--bin=krustyfy",
15 | "--package=krustyfy"
16 | ],
17 | "filter": {
18 | "name": "krustyfy",
19 | "kind": "bin"
20 | }
21 | },
22 | "args": [],
23 | "cwd": "${workspaceFolder}"
24 | },
25 | {
26 | "type": "lldb",
27 | "request": "launch",
28 | "name": "Debug unit tests in executable 'krustyfy'",
29 | "cargo": {
30 | "args": [
31 | "test",
32 | "--no-run",
33 | "--bin=krustyfy",
34 | "--package=krustyfy"
35 | ],
36 | "filter": {
37 | "name": "krustyfy",
38 | "kind": "bin"
39 | }
40 | },
41 | "args": [],
42 | "cwd": "${workspaceFolder}"
43 | }
44 | ]
45 | }
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.17.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler"
16 | version = "1.0.2"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "0.7.18"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "anyhow"
31 | version = "1.0.63"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a"
34 |
35 | [[package]]
36 | name = "async-broadcast"
37 | version = "0.4.1"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61"
40 | dependencies = [
41 | "event-listener",
42 | "futures-core",
43 | "parking_lot",
44 | ]
45 |
46 | [[package]]
47 | name = "async-recursion"
48 | version = "0.3.2"
49 | source = "registry+https://github.com/rust-lang/crates.io-index"
50 | checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
51 | dependencies = [
52 | "proc-macro2",
53 | "quote",
54 | "syn",
55 | ]
56 |
57 | [[package]]
58 | name = "async-trait"
59 | version = "0.1.57"
60 | source = "registry+https://github.com/rust-lang/crates.io-index"
61 | checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
62 | dependencies = [
63 | "proc-macro2",
64 | "quote",
65 | "syn",
66 | ]
67 |
68 | [[package]]
69 | name = "atty"
70 | version = "0.2.14"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
73 | dependencies = [
74 | "hermit-abi",
75 | "libc",
76 | "winapi 0.3.9",
77 | ]
78 |
79 | [[package]]
80 | name = "autocfg"
81 | version = "1.1.0"
82 | source = "registry+https://github.com/rust-lang/crates.io-index"
83 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
84 |
85 | [[package]]
86 | name = "backtrace"
87 | version = "0.3.66"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
90 | dependencies = [
91 | "addr2line",
92 | "cc",
93 | "cfg-if",
94 | "libc",
95 | "miniz_oxide",
96 | "object",
97 | "rustc-demangle",
98 | ]
99 |
100 | [[package]]
101 | name = "bincode"
102 | version = "1.3.3"
103 | source = "registry+https://github.com/rust-lang/crates.io-index"
104 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
105 | dependencies = [
106 | "serde",
107 | ]
108 |
109 | [[package]]
110 | name = "bitflags"
111 | version = "1.3.2"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
114 |
115 | [[package]]
116 | name = "byteorder"
117 | version = "1.4.3"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
120 |
121 | [[package]]
122 | name = "bytes"
123 | version = "1.2.1"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
126 |
127 | [[package]]
128 | name = "cc"
129 | version = "1.0.73"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
132 |
133 | [[package]]
134 | name = "cfg-if"
135 | version = "1.0.0"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
138 |
139 | [[package]]
140 | name = "copy_to_output"
141 | version = "2.0.0"
142 | source = "registry+https://github.com/rust-lang/crates.io-index"
143 | checksum = "38b0be9c79199e3ea04d8526600ebe0d00378bb0797b828170e4bfde85b26796"
144 | dependencies = [
145 | "anyhow",
146 | "fs_extra",
147 | ]
148 |
149 | [[package]]
150 | name = "core-foundation"
151 | version = "0.9.3"
152 | source = "registry+https://github.com/rust-lang/crates.io-index"
153 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
154 | dependencies = [
155 | "core-foundation-sys",
156 | "libc",
157 | ]
158 |
159 | [[package]]
160 | name = "core-foundation-sys"
161 | version = "0.8.3"
162 | source = "registry+https://github.com/rust-lang/crates.io-index"
163 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
164 |
165 | [[package]]
166 | name = "cpp_core"
167 | version = "0.6.0"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | checksum = "5ebd6ba9742a158232afe2d07ec5d9d5d80d058baf700c5f9aa0e014fe3f24ad"
170 | dependencies = [
171 | "libc",
172 | ]
173 |
174 | [[package]]
175 | name = "derivative"
176 | version = "2.2.0"
177 | source = "registry+https://github.com/rust-lang/crates.io-index"
178 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
179 | dependencies = [
180 | "proc-macro2",
181 | "quote",
182 | "syn",
183 | ]
184 |
185 | [[package]]
186 | name = "device_query"
187 | version = "1.1.1"
188 | source = "registry+https://github.com/rust-lang/crates.io-index"
189 | checksum = "8b54c3f0350a597abbed2c10fff7b233f92744c9422697d5fb58dc5397ab6fae"
190 | dependencies = [
191 | "lazy_static",
192 | "macos-accessibility-client",
193 | "pkg-config",
194 | "readkey",
195 | "readmouse",
196 | "winapi 0.3.9",
197 | "x11",
198 | ]
199 |
200 | [[package]]
201 | name = "dirs"
202 | version = "4.0.0"
203 | source = "registry+https://github.com/rust-lang/crates.io-index"
204 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
205 | dependencies = [
206 | "dirs-sys",
207 | ]
208 |
209 | [[package]]
210 | name = "dirs-sys"
211 | version = "0.3.7"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
214 | dependencies = [
215 | "libc",
216 | "redox_users",
217 | "winapi 0.3.9",
218 | ]
219 |
220 | [[package]]
221 | name = "dunce"
222 | version = "1.0.2"
223 | source = "registry+https://github.com/rust-lang/crates.io-index"
224 | checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
225 |
226 | [[package]]
227 | name = "either"
228 | version = "1.8.0"
229 | source = "registry+https://github.com/rust-lang/crates.io-index"
230 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
231 |
232 | [[package]]
233 | name = "enumflags2"
234 | version = "0.7.5"
235 | source = "registry+https://github.com/rust-lang/crates.io-index"
236 | checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb"
237 | dependencies = [
238 | "enumflags2_derive",
239 | "serde",
240 | ]
241 |
242 | [[package]]
243 | name = "enumflags2_derive"
244 | version = "0.7.4"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae"
247 | dependencies = [
248 | "proc-macro2",
249 | "quote",
250 | "syn",
251 | ]
252 |
253 | [[package]]
254 | name = "env_logger"
255 | version = "0.7.1"
256 | source = "registry+https://github.com/rust-lang/crates.io-index"
257 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
258 | dependencies = [
259 | "atty",
260 | "humantime",
261 | "log",
262 | "regex",
263 | "termcolor",
264 | ]
265 |
266 | [[package]]
267 | name = "event-listener"
268 | version = "2.5.3"
269 | source = "registry+https://github.com/rust-lang/crates.io-index"
270 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
271 |
272 | [[package]]
273 | name = "failure"
274 | version = "0.1.8"
275 | source = "registry+https://github.com/rust-lang/crates.io-index"
276 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
277 | dependencies = [
278 | "backtrace",
279 | "failure_derive",
280 | ]
281 |
282 | [[package]]
283 | name = "failure_derive"
284 | version = "0.1.8"
285 | source = "registry+https://github.com/rust-lang/crates.io-index"
286 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
287 | dependencies = [
288 | "proc-macro2",
289 | "quote",
290 | "syn",
291 | "synstructure",
292 | ]
293 |
294 | [[package]]
295 | name = "fastrand"
296 | version = "1.8.0"
297 | source = "registry+https://github.com/rust-lang/crates.io-index"
298 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
299 | dependencies = [
300 | "instant",
301 | ]
302 |
303 | [[package]]
304 | name = "fs_extra"
305 | version = "1.2.0"
306 | source = "registry+https://github.com/rust-lang/crates.io-index"
307 | checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
308 |
309 | [[package]]
310 | name = "futures-core"
311 | version = "0.3.23"
312 | source = "registry+https://github.com/rust-lang/crates.io-index"
313 | checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
314 |
315 | [[package]]
316 | name = "futures-sink"
317 | version = "0.3.23"
318 | source = "registry+https://github.com/rust-lang/crates.io-index"
319 | checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765"
320 |
321 | [[package]]
322 | name = "futures-task"
323 | version = "0.3.23"
324 | source = "registry+https://github.com/rust-lang/crates.io-index"
325 | checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
326 |
327 | [[package]]
328 | name = "futures-util"
329 | version = "0.3.23"
330 | source = "registry+https://github.com/rust-lang/crates.io-index"
331 | checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
332 | dependencies = [
333 | "futures-core",
334 | "futures-sink",
335 | "futures-task",
336 | "pin-project-lite",
337 | "pin-utils",
338 | "slab",
339 | ]
340 |
341 | [[package]]
342 | name = "getrandom"
343 | version = "0.2.7"
344 | source = "registry+https://github.com/rust-lang/crates.io-index"
345 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
346 | dependencies = [
347 | "cfg-if",
348 | "libc",
349 | "wasi",
350 | ]
351 |
352 | [[package]]
353 | name = "gimli"
354 | version = "0.26.2"
355 | source = "registry+https://github.com/rust-lang/crates.io-index"
356 | checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
357 |
358 | [[package]]
359 | name = "glob"
360 | version = "0.3.0"
361 | source = "registry+https://github.com/rust-lang/crates.io-index"
362 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
363 |
364 | [[package]]
365 | name = "hermit-abi"
366 | version = "0.1.19"
367 | source = "registry+https://github.com/rust-lang/crates.io-index"
368 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
369 | dependencies = [
370 | "libc",
371 | ]
372 |
373 | [[package]]
374 | name = "hex"
375 | version = "0.4.3"
376 | source = "registry+https://github.com/rust-lang/crates.io-index"
377 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
378 |
379 | [[package]]
380 | name = "humantime"
381 | version = "1.3.0"
382 | source = "registry+https://github.com/rust-lang/crates.io-index"
383 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
384 | dependencies = [
385 | "quick-error",
386 | ]
387 |
388 | [[package]]
389 | name = "instant"
390 | version = "0.1.12"
391 | source = "registry+https://github.com/rust-lang/crates.io-index"
392 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
393 | dependencies = [
394 | "cfg-if",
395 | ]
396 |
397 | [[package]]
398 | name = "itertools"
399 | version = "0.8.2"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
402 | dependencies = [
403 | "either",
404 | ]
405 |
406 | [[package]]
407 | name = "itoa"
408 | version = "1.0.3"
409 | source = "registry+https://github.com/rust-lang/crates.io-index"
410 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
411 |
412 | [[package]]
413 | name = "kernel32-sys"
414 | version = "0.2.2"
415 | source = "registry+https://github.com/rust-lang/crates.io-index"
416 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
417 | dependencies = [
418 | "winapi 0.2.8",
419 | "winapi-build",
420 | ]
421 |
422 | [[package]]
423 | name = "krustyfy"
424 | version = "0.1.7"
425 | dependencies = [
426 | "copy_to_output",
427 | "cpp_core",
428 | "device_query",
429 | "glob",
430 | "lazy_static",
431 | "linked-hash-map",
432 | "qt_core",
433 | "qt_gui",
434 | "qt_ui_tools",
435 | "qt_widgets",
436 | "tokio",
437 | "uuid",
438 | "zbus",
439 | "zvariant",
440 | ]
441 |
442 | [[package]]
443 | name = "lazy_static"
444 | version = "1.4.0"
445 | source = "registry+https://github.com/rust-lang/crates.io-index"
446 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
447 |
448 | [[package]]
449 | name = "libc"
450 | version = "0.2.132"
451 | source = "registry+https://github.com/rust-lang/crates.io-index"
452 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
453 |
454 | [[package]]
455 | name = "linked-hash-map"
456 | version = "0.5.6"
457 | source = "registry+https://github.com/rust-lang/crates.io-index"
458 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
459 |
460 | [[package]]
461 | name = "lock_api"
462 | version = "0.4.7"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
465 | dependencies = [
466 | "autocfg",
467 | "scopeguard",
468 | ]
469 |
470 | [[package]]
471 | name = "log"
472 | version = "0.4.17"
473 | source = "registry+https://github.com/rust-lang/crates.io-index"
474 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
475 | dependencies = [
476 | "cfg-if",
477 | ]
478 |
479 | [[package]]
480 | name = "macos-accessibility-client"
481 | version = "0.0.1"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb"
484 | dependencies = [
485 | "core-foundation",
486 | "core-foundation-sys",
487 | ]
488 |
489 | [[package]]
490 | name = "memchr"
491 | version = "2.5.0"
492 | source = "registry+https://github.com/rust-lang/crates.io-index"
493 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
494 |
495 | [[package]]
496 | name = "memoffset"
497 | version = "0.6.5"
498 | source = "registry+https://github.com/rust-lang/crates.io-index"
499 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
500 | dependencies = [
501 | "autocfg",
502 | ]
503 |
504 | [[package]]
505 | name = "miniz_oxide"
506 | version = "0.5.3"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
509 | dependencies = [
510 | "adler",
511 | ]
512 |
513 | [[package]]
514 | name = "mio"
515 | version = "0.8.4"
516 | source = "registry+https://github.com/rust-lang/crates.io-index"
517 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
518 | dependencies = [
519 | "libc",
520 | "log",
521 | "wasi",
522 | "windows-sys",
523 | ]
524 |
525 | [[package]]
526 | name = "nix"
527 | version = "0.24.2"
528 | source = "registry+https://github.com/rust-lang/crates.io-index"
529 | checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
530 | dependencies = [
531 | "bitflags",
532 | "cfg-if",
533 | "libc",
534 | "memoffset",
535 | ]
536 |
537 | [[package]]
538 | name = "num_cpus"
539 | version = "1.13.1"
540 | source = "registry+https://github.com/rust-lang/crates.io-index"
541 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
542 | dependencies = [
543 | "hermit-abi",
544 | "libc",
545 | ]
546 |
547 | [[package]]
548 | name = "object"
549 | version = "0.29.0"
550 | source = "registry+https://github.com/rust-lang/crates.io-index"
551 | checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
552 | dependencies = [
553 | "memchr",
554 | ]
555 |
556 | [[package]]
557 | name = "once_cell"
558 | version = "1.13.1"
559 | source = "registry+https://github.com/rust-lang/crates.io-index"
560 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
561 |
562 | [[package]]
563 | name = "ordered-stream"
564 | version = "0.0.1"
565 | source = "registry+https://github.com/rust-lang/crates.io-index"
566 | checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1"
567 | dependencies = [
568 | "futures-core",
569 | "pin-project-lite",
570 | ]
571 |
572 | [[package]]
573 | name = "parking_lot"
574 | version = "0.12.1"
575 | source = "registry+https://github.com/rust-lang/crates.io-index"
576 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
577 | dependencies = [
578 | "lock_api",
579 | "parking_lot_core",
580 | ]
581 |
582 | [[package]]
583 | name = "parking_lot_core"
584 | version = "0.9.3"
585 | source = "registry+https://github.com/rust-lang/crates.io-index"
586 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
587 | dependencies = [
588 | "cfg-if",
589 | "libc",
590 | "redox_syscall",
591 | "smallvec",
592 | "windows-sys",
593 | ]
594 |
595 | [[package]]
596 | name = "pathdiff"
597 | version = "0.1.0"
598 | source = "registry+https://github.com/rust-lang/crates.io-index"
599 | checksum = "a3bf70094d203e07844da868b634207e71bfab254fe713171fae9a6e751ccf31"
600 |
601 | [[package]]
602 | name = "pin-project-lite"
603 | version = "0.2.9"
604 | source = "registry+https://github.com/rust-lang/crates.io-index"
605 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
606 |
607 | [[package]]
608 | name = "pin-utils"
609 | version = "0.1.0"
610 | source = "registry+https://github.com/rust-lang/crates.io-index"
611 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
612 |
613 | [[package]]
614 | name = "pkg-config"
615 | version = "0.3.25"
616 | source = "registry+https://github.com/rust-lang/crates.io-index"
617 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
618 |
619 | [[package]]
620 | name = "ppv-lite86"
621 | version = "0.2.16"
622 | source = "registry+https://github.com/rust-lang/crates.io-index"
623 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
624 |
625 | [[package]]
626 | name = "proc-macro-crate"
627 | version = "1.2.1"
628 | source = "registry+https://github.com/rust-lang/crates.io-index"
629 | checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
630 | dependencies = [
631 | "once_cell",
632 | "thiserror",
633 | "toml 0.5.9",
634 | ]
635 |
636 | [[package]]
637 | name = "proc-macro-hack"
638 | version = "0.5.19"
639 | source = "registry+https://github.com/rust-lang/crates.io-index"
640 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
641 |
642 | [[package]]
643 | name = "proc-macro2"
644 | version = "1.0.43"
645 | source = "registry+https://github.com/rust-lang/crates.io-index"
646 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
647 | dependencies = [
648 | "unicode-ident",
649 | ]
650 |
651 | [[package]]
652 | name = "qt_core"
653 | version = "0.5.0"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "778fa84e9ee19abcf687ff0544f9bb9cd25059fe1ac479736c04475fd8d83a7a"
656 | dependencies = [
657 | "cpp_core",
658 | "proc-macro-hack",
659 | "qt_macros",
660 | "qt_ritual_build",
661 | ]
662 |
663 | [[package]]
664 | name = "qt_gui"
665 | version = "0.5.0"
666 | source = "registry+https://github.com/rust-lang/crates.io-index"
667 | checksum = "dcc8366c6860b47084283cecc51397045e8401f492a98ac72b6bf1c7bad21837"
668 | dependencies = [
669 | "cpp_core",
670 | "qt_core",
671 | "qt_ritual_build",
672 | ]
673 |
674 | [[package]]
675 | name = "qt_macros"
676 | version = "0.1.1"
677 | source = "registry+https://github.com/rust-lang/crates.io-index"
678 | checksum = "42b6fce195b624df4031efe9499c6500304b6b50e4e8c014709bddbbea2eaae3"
679 | dependencies = [
680 | "proc-macro-hack",
681 | "proc-macro2",
682 | "quote",
683 | "syn",
684 | ]
685 |
686 | [[package]]
687 | name = "qt_ritual_build"
688 | version = "0.5.0"
689 | source = "registry+https://github.com/rust-lang/crates.io-index"
690 | checksum = "2a9c99db64e5bc0ab7404d199b7f2da51d77ea3c1d78d01a9390f01aefbd3979"
691 | dependencies = [
692 | "env_logger",
693 | "itertools",
694 | "qt_ritual_common",
695 | "ritual_build",
696 | "semver",
697 | ]
698 |
699 | [[package]]
700 | name = "qt_ritual_common"
701 | version = "0.4.0"
702 | source = "registry+https://github.com/rust-lang/crates.io-index"
703 | checksum = "7ba1c7e05524bb83823426694e918d635b82d1366f20833887d7d62ea6b12a70"
704 | dependencies = [
705 | "log",
706 | "ritual_common",
707 | "semver",
708 | ]
709 |
710 | [[package]]
711 | name = "qt_ui_tools"
712 | version = "0.5.0"
713 | source = "registry+https://github.com/rust-lang/crates.io-index"
714 | checksum = "8ad3bc61b5903878bb272e60080b1b4eb395ab1c6bf8808dbfb78b9fc63bc10c"
715 | dependencies = [
716 | "cpp_core",
717 | "qt_core",
718 | "qt_gui",
719 | "qt_macros",
720 | "qt_ritual_build",
721 | "qt_widgets",
722 | ]
723 |
724 | [[package]]
725 | name = "qt_widgets"
726 | version = "0.5.0"
727 | source = "registry+https://github.com/rust-lang/crates.io-index"
728 | checksum = "d93cdec198a17bae150564e2da536bf3cb9df91aea63eb48b1b260b216a0b7f6"
729 | dependencies = [
730 | "cpp_core",
731 | "qt_core",
732 | "qt_gui",
733 | "qt_ritual_build",
734 | ]
735 |
736 | [[package]]
737 | name = "quick-error"
738 | version = "1.2.3"
739 | source = "registry+https://github.com/rust-lang/crates.io-index"
740 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
741 |
742 | [[package]]
743 | name = "quote"
744 | version = "1.0.21"
745 | source = "registry+https://github.com/rust-lang/crates.io-index"
746 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
747 | dependencies = [
748 | "proc-macro2",
749 | ]
750 |
751 | [[package]]
752 | name = "rand"
753 | version = "0.8.5"
754 | source = "registry+https://github.com/rust-lang/crates.io-index"
755 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
756 | dependencies = [
757 | "libc",
758 | "rand_chacha",
759 | "rand_core",
760 | ]
761 |
762 | [[package]]
763 | name = "rand_chacha"
764 | version = "0.3.1"
765 | source = "registry+https://github.com/rust-lang/crates.io-index"
766 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
767 | dependencies = [
768 | "ppv-lite86",
769 | "rand_core",
770 | ]
771 |
772 | [[package]]
773 | name = "rand_core"
774 | version = "0.6.3"
775 | source = "registry+https://github.com/rust-lang/crates.io-index"
776 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
777 | dependencies = [
778 | "getrandom",
779 | ]
780 |
781 | [[package]]
782 | name = "readkey"
783 | version = "0.1.7"
784 | source = "registry+https://github.com/rust-lang/crates.io-index"
785 | checksum = "86d401b6d6a1725a59f1b4e813275d289dff3ad09c72b373a10a7a8217ba3146"
786 |
787 | [[package]]
788 | name = "readmouse"
789 | version = "0.2.1"
790 | source = "registry+https://github.com/rust-lang/crates.io-index"
791 | checksum = "be105c72a1e6a5a1198acee3d5b506a15676b74a02ecd78060042a447f408d94"
792 |
793 | [[package]]
794 | name = "redox_syscall"
795 | version = "0.2.16"
796 | source = "registry+https://github.com/rust-lang/crates.io-index"
797 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
798 | dependencies = [
799 | "bitflags",
800 | ]
801 |
802 | [[package]]
803 | name = "redox_users"
804 | version = "0.4.3"
805 | source = "registry+https://github.com/rust-lang/crates.io-index"
806 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
807 | dependencies = [
808 | "getrandom",
809 | "redox_syscall",
810 | "thiserror",
811 | ]
812 |
813 | [[package]]
814 | name = "regex"
815 | version = "1.6.0"
816 | source = "registry+https://github.com/rust-lang/crates.io-index"
817 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
818 | dependencies = [
819 | "aho-corasick",
820 | "memchr",
821 | "regex-syntax",
822 | ]
823 |
824 | [[package]]
825 | name = "regex-syntax"
826 | version = "0.6.27"
827 | source = "registry+https://github.com/rust-lang/crates.io-index"
828 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
829 |
830 | [[package]]
831 | name = "remove_dir_all"
832 | version = "0.5.3"
833 | source = "registry+https://github.com/rust-lang/crates.io-index"
834 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
835 | dependencies = [
836 | "winapi 0.3.9",
837 | ]
838 |
839 | [[package]]
840 | name = "ritual_build"
841 | version = "0.4.0"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "e308b6d715de5f46f5c0980169c2813c5e3fbec42dd4938fdfbf648248fb7ea7"
844 | dependencies = [
845 | "log",
846 | "ritual_common",
847 | ]
848 |
849 | [[package]]
850 | name = "ritual_common"
851 | version = "0.4.0"
852 | source = "registry+https://github.com/rust-lang/crates.io-index"
853 | checksum = "59377d74284596d82c84a994b6abbabd7ae9cc1c3d39fcb3421e0ffcaf112f88"
854 | dependencies = [
855 | "bincode",
856 | "dunce",
857 | "failure",
858 | "itertools",
859 | "lazy_static",
860 | "log",
861 | "num_cpus",
862 | "pathdiff",
863 | "regex",
864 | "semver",
865 | "serde",
866 | "serde_derive",
867 | "serde_json",
868 | "shell-words",
869 | "term-painter",
870 | "toml 0.4.10",
871 | ]
872 |
873 | [[package]]
874 | name = "rustc-demangle"
875 | version = "0.1.21"
876 | source = "registry+https://github.com/rust-lang/crates.io-index"
877 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
878 |
879 | [[package]]
880 | name = "ryu"
881 | version = "1.0.11"
882 | source = "registry+https://github.com/rust-lang/crates.io-index"
883 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
884 |
885 | [[package]]
886 | name = "scopeguard"
887 | version = "1.1.0"
888 | source = "registry+https://github.com/rust-lang/crates.io-index"
889 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
890 |
891 | [[package]]
892 | name = "semver"
893 | version = "0.9.0"
894 | source = "registry+https://github.com/rust-lang/crates.io-index"
895 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
896 | dependencies = [
897 | "semver-parser",
898 | ]
899 |
900 | [[package]]
901 | name = "semver-parser"
902 | version = "0.7.0"
903 | source = "registry+https://github.com/rust-lang/crates.io-index"
904 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
905 |
906 | [[package]]
907 | name = "serde"
908 | version = "1.0.144"
909 | source = "registry+https://github.com/rust-lang/crates.io-index"
910 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
911 | dependencies = [
912 | "serde_derive",
913 | ]
914 |
915 | [[package]]
916 | name = "serde_derive"
917 | version = "1.0.144"
918 | source = "registry+https://github.com/rust-lang/crates.io-index"
919 | checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
920 | dependencies = [
921 | "proc-macro2",
922 | "quote",
923 | "syn",
924 | ]
925 |
926 | [[package]]
927 | name = "serde_json"
928 | version = "1.0.85"
929 | source = "registry+https://github.com/rust-lang/crates.io-index"
930 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
931 | dependencies = [
932 | "itoa",
933 | "ryu",
934 | "serde",
935 | ]
936 |
937 | [[package]]
938 | name = "serde_repr"
939 | version = "0.1.9"
940 | source = "registry+https://github.com/rust-lang/crates.io-index"
941 | checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
942 | dependencies = [
943 | "proc-macro2",
944 | "quote",
945 | "syn",
946 | ]
947 |
948 | [[package]]
949 | name = "sha1"
950 | version = "0.6.1"
951 | source = "registry+https://github.com/rust-lang/crates.io-index"
952 | checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
953 | dependencies = [
954 | "sha1_smol",
955 | ]
956 |
957 | [[package]]
958 | name = "sha1_smol"
959 | version = "1.0.0"
960 | source = "registry+https://github.com/rust-lang/crates.io-index"
961 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
962 |
963 | [[package]]
964 | name = "shell-words"
965 | version = "0.1.0"
966 | source = "registry+https://github.com/rust-lang/crates.io-index"
967 | checksum = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a"
968 |
969 | [[package]]
970 | name = "signal-hook-registry"
971 | version = "1.4.0"
972 | source = "registry+https://github.com/rust-lang/crates.io-index"
973 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
974 | dependencies = [
975 | "libc",
976 | ]
977 |
978 | [[package]]
979 | name = "slab"
980 | version = "0.4.7"
981 | source = "registry+https://github.com/rust-lang/crates.io-index"
982 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
983 | dependencies = [
984 | "autocfg",
985 | ]
986 |
987 | [[package]]
988 | name = "smallvec"
989 | version = "1.9.0"
990 | source = "registry+https://github.com/rust-lang/crates.io-index"
991 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
992 |
993 | [[package]]
994 | name = "socket2"
995 | version = "0.4.4"
996 | source = "registry+https://github.com/rust-lang/crates.io-index"
997 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
998 | dependencies = [
999 | "libc",
1000 | "winapi 0.3.9",
1001 | ]
1002 |
1003 | [[package]]
1004 | name = "static_assertions"
1005 | version = "1.1.0"
1006 | source = "registry+https://github.com/rust-lang/crates.io-index"
1007 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1008 |
1009 | [[package]]
1010 | name = "syn"
1011 | version = "1.0.99"
1012 | source = "registry+https://github.com/rust-lang/crates.io-index"
1013 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
1014 | dependencies = [
1015 | "proc-macro2",
1016 | "quote",
1017 | "unicode-ident",
1018 | ]
1019 |
1020 | [[package]]
1021 | name = "synstructure"
1022 | version = "0.12.6"
1023 | source = "registry+https://github.com/rust-lang/crates.io-index"
1024 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
1025 | dependencies = [
1026 | "proc-macro2",
1027 | "quote",
1028 | "syn",
1029 | "unicode-xid",
1030 | ]
1031 |
1032 | [[package]]
1033 | name = "tempfile"
1034 | version = "3.3.0"
1035 | source = "registry+https://github.com/rust-lang/crates.io-index"
1036 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1037 | dependencies = [
1038 | "cfg-if",
1039 | "fastrand",
1040 | "libc",
1041 | "redox_syscall",
1042 | "remove_dir_all",
1043 | "winapi 0.3.9",
1044 | ]
1045 |
1046 | [[package]]
1047 | name = "term"
1048 | version = "0.4.6"
1049 | source = "registry+https://github.com/rust-lang/crates.io-index"
1050 | checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
1051 | dependencies = [
1052 | "kernel32-sys",
1053 | "winapi 0.2.8",
1054 | ]
1055 |
1056 | [[package]]
1057 | name = "term-painter"
1058 | version = "0.2.4"
1059 | source = "registry+https://github.com/rust-lang/crates.io-index"
1060 | checksum = "dcaa948f0e3e38470cd8dc8dcfe561a75c9e43f28075bb183845be2b9b3c08cf"
1061 | dependencies = [
1062 | "term",
1063 | ]
1064 |
1065 | [[package]]
1066 | name = "termcolor"
1067 | version = "1.1.3"
1068 | source = "registry+https://github.com/rust-lang/crates.io-index"
1069 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1070 | dependencies = [
1071 | "winapi-util",
1072 | ]
1073 |
1074 | [[package]]
1075 | name = "thiserror"
1076 | version = "1.0.32"
1077 | source = "registry+https://github.com/rust-lang/crates.io-index"
1078 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
1079 | dependencies = [
1080 | "thiserror-impl",
1081 | ]
1082 |
1083 | [[package]]
1084 | name = "thiserror-impl"
1085 | version = "1.0.32"
1086 | source = "registry+https://github.com/rust-lang/crates.io-index"
1087 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
1088 | dependencies = [
1089 | "proc-macro2",
1090 | "quote",
1091 | "syn",
1092 | ]
1093 |
1094 | [[package]]
1095 | name = "tokio"
1096 | version = "1.21.0"
1097 | source = "registry+https://github.com/rust-lang/crates.io-index"
1098 | checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
1099 | dependencies = [
1100 | "autocfg",
1101 | "bytes",
1102 | "libc",
1103 | "memchr",
1104 | "mio",
1105 | "num_cpus",
1106 | "once_cell",
1107 | "parking_lot",
1108 | "pin-project-lite",
1109 | "signal-hook-registry",
1110 | "socket2",
1111 | "tokio-macros",
1112 | "winapi 0.3.9",
1113 | ]
1114 |
1115 | [[package]]
1116 | name = "tokio-macros"
1117 | version = "1.8.0"
1118 | source = "registry+https://github.com/rust-lang/crates.io-index"
1119 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
1120 | dependencies = [
1121 | "proc-macro2",
1122 | "quote",
1123 | "syn",
1124 | ]
1125 |
1126 | [[package]]
1127 | name = "toml"
1128 | version = "0.4.10"
1129 | source = "registry+https://github.com/rust-lang/crates.io-index"
1130 | checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
1131 | dependencies = [
1132 | "serde",
1133 | ]
1134 |
1135 | [[package]]
1136 | name = "toml"
1137 | version = "0.5.9"
1138 | source = "registry+https://github.com/rust-lang/crates.io-index"
1139 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
1140 | dependencies = [
1141 | "serde",
1142 | ]
1143 |
1144 | [[package]]
1145 | name = "tracing"
1146 | version = "0.1.36"
1147 | source = "registry+https://github.com/rust-lang/crates.io-index"
1148 | checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
1149 | dependencies = [
1150 | "cfg-if",
1151 | "pin-project-lite",
1152 | "tracing-attributes",
1153 | "tracing-core",
1154 | ]
1155 |
1156 | [[package]]
1157 | name = "tracing-attributes"
1158 | version = "0.1.22"
1159 | source = "registry+https://github.com/rust-lang/crates.io-index"
1160 | checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
1161 | dependencies = [
1162 | "proc-macro2",
1163 | "quote",
1164 | "syn",
1165 | ]
1166 |
1167 | [[package]]
1168 | name = "tracing-core"
1169 | version = "0.1.29"
1170 | source = "registry+https://github.com/rust-lang/crates.io-index"
1171 | checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
1172 | dependencies = [
1173 | "once_cell",
1174 | ]
1175 |
1176 | [[package]]
1177 | name = "uds_windows"
1178 | version = "1.0.2"
1179 | source = "registry+https://github.com/rust-lang/crates.io-index"
1180 | checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
1181 | dependencies = [
1182 | "tempfile",
1183 | "winapi 0.3.9",
1184 | ]
1185 |
1186 | [[package]]
1187 | name = "unicode-ident"
1188 | version = "1.0.3"
1189 | source = "registry+https://github.com/rust-lang/crates.io-index"
1190 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
1191 |
1192 | [[package]]
1193 | name = "unicode-xid"
1194 | version = "0.2.3"
1195 | source = "registry+https://github.com/rust-lang/crates.io-index"
1196 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
1197 |
1198 | [[package]]
1199 | name = "uuid"
1200 | version = "1.1.2"
1201 | source = "registry+https://github.com/rust-lang/crates.io-index"
1202 | checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
1203 | dependencies = [
1204 | "getrandom",
1205 | "rand",
1206 | "uuid-macro-internal",
1207 | ]
1208 |
1209 | [[package]]
1210 | name = "uuid-macro-internal"
1211 | version = "1.1.2"
1212 | source = "registry+https://github.com/rust-lang/crates.io-index"
1213 | checksum = "548f7181a5990efa50237abb7ebca410828b57a8955993334679f8b50b35c97d"
1214 | dependencies = [
1215 | "proc-macro2",
1216 | "quote",
1217 | "syn",
1218 | ]
1219 |
1220 | [[package]]
1221 | name = "wasi"
1222 | version = "0.11.0+wasi-snapshot-preview1"
1223 | source = "registry+https://github.com/rust-lang/crates.io-index"
1224 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1225 |
1226 | [[package]]
1227 | name = "winapi"
1228 | version = "0.2.8"
1229 | source = "registry+https://github.com/rust-lang/crates.io-index"
1230 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
1231 |
1232 | [[package]]
1233 | name = "winapi"
1234 | version = "0.3.9"
1235 | source = "registry+https://github.com/rust-lang/crates.io-index"
1236 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1237 | dependencies = [
1238 | "winapi-i686-pc-windows-gnu",
1239 | "winapi-x86_64-pc-windows-gnu",
1240 | ]
1241 |
1242 | [[package]]
1243 | name = "winapi-build"
1244 | version = "0.1.1"
1245 | source = "registry+https://github.com/rust-lang/crates.io-index"
1246 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
1247 |
1248 | [[package]]
1249 | name = "winapi-i686-pc-windows-gnu"
1250 | version = "0.4.0"
1251 | source = "registry+https://github.com/rust-lang/crates.io-index"
1252 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1253 |
1254 | [[package]]
1255 | name = "winapi-util"
1256 | version = "0.1.5"
1257 | source = "registry+https://github.com/rust-lang/crates.io-index"
1258 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1259 | dependencies = [
1260 | "winapi 0.3.9",
1261 | ]
1262 |
1263 | [[package]]
1264 | name = "winapi-x86_64-pc-windows-gnu"
1265 | version = "0.4.0"
1266 | source = "registry+https://github.com/rust-lang/crates.io-index"
1267 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1268 |
1269 | [[package]]
1270 | name = "windows-sys"
1271 | version = "0.36.1"
1272 | source = "registry+https://github.com/rust-lang/crates.io-index"
1273 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
1274 | dependencies = [
1275 | "windows_aarch64_msvc",
1276 | "windows_i686_gnu",
1277 | "windows_i686_msvc",
1278 | "windows_x86_64_gnu",
1279 | "windows_x86_64_msvc",
1280 | ]
1281 |
1282 | [[package]]
1283 | name = "windows_aarch64_msvc"
1284 | version = "0.36.1"
1285 | source = "registry+https://github.com/rust-lang/crates.io-index"
1286 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
1287 |
1288 | [[package]]
1289 | name = "windows_i686_gnu"
1290 | version = "0.36.1"
1291 | source = "registry+https://github.com/rust-lang/crates.io-index"
1292 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
1293 |
1294 | [[package]]
1295 | name = "windows_i686_msvc"
1296 | version = "0.36.1"
1297 | source = "registry+https://github.com/rust-lang/crates.io-index"
1298 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
1299 |
1300 | [[package]]
1301 | name = "windows_x86_64_gnu"
1302 | version = "0.36.1"
1303 | source = "registry+https://github.com/rust-lang/crates.io-index"
1304 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
1305 |
1306 | [[package]]
1307 | name = "windows_x86_64_msvc"
1308 | version = "0.36.1"
1309 | source = "registry+https://github.com/rust-lang/crates.io-index"
1310 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
1311 |
1312 | [[package]]
1313 | name = "x11"
1314 | version = "2.20.0"
1315 | source = "registry+https://github.com/rust-lang/crates.io-index"
1316 | checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908"
1317 | dependencies = [
1318 | "libc",
1319 | "pkg-config",
1320 | ]
1321 |
1322 | [[package]]
1323 | name = "zbus"
1324 | version = "3.0.0"
1325 | source = "registry+https://github.com/rust-lang/crates.io-index"
1326 | checksum = "1faa83cd7c79d3a669220c634528577b98ff43c35aa7c827ab3e9990692f7868"
1327 | dependencies = [
1328 | "async-broadcast",
1329 | "async-recursion",
1330 | "async-trait",
1331 | "byteorder",
1332 | "derivative",
1333 | "dirs",
1334 | "enumflags2",
1335 | "event-listener",
1336 | "futures-core",
1337 | "futures-sink",
1338 | "futures-util",
1339 | "hex",
1340 | "lazy_static",
1341 | "nix",
1342 | "once_cell",
1343 | "ordered-stream",
1344 | "rand",
1345 | "serde",
1346 | "serde_repr",
1347 | "sha1",
1348 | "static_assertions",
1349 | "tokio",
1350 | "tracing",
1351 | "uds_windows",
1352 | "winapi 0.3.9",
1353 | "zbus_macros",
1354 | "zbus_names",
1355 | "zvariant",
1356 | ]
1357 |
1358 | [[package]]
1359 | name = "zbus_macros"
1360 | version = "3.0.0"
1361 | source = "registry+https://github.com/rust-lang/crates.io-index"
1362 | checksum = "fd5874c328b945cab1865a299e31f855072fa528bafbbfa3249394b352d5742b"
1363 | dependencies = [
1364 | "proc-macro-crate",
1365 | "proc-macro2",
1366 | "quote",
1367 | "regex",
1368 | "syn",
1369 | ]
1370 |
1371 | [[package]]
1372 | name = "zbus_names"
1373 | version = "2.2.0"
1374 | source = "registry+https://github.com/rust-lang/crates.io-index"
1375 | checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5"
1376 | dependencies = [
1377 | "serde",
1378 | "static_assertions",
1379 | "zvariant",
1380 | ]
1381 |
1382 | [[package]]
1383 | name = "zvariant"
1384 | version = "3.6.0"
1385 | source = "registry+https://github.com/rust-lang/crates.io-index"
1386 | checksum = "1bd68e4e6432ef19df47d7e90e2e72b5e7e3d778e0ae3baddf12b951265cc758"
1387 | dependencies = [
1388 | "byteorder",
1389 | "enumflags2",
1390 | "libc",
1391 | "serde",
1392 | "static_assertions",
1393 | "zvariant_derive",
1394 | ]
1395 |
1396 | [[package]]
1397 | name = "zvariant_derive"
1398 | version = "3.6.0"
1399 | source = "registry+https://github.com/rust-lang/crates.io-index"
1400 | checksum = "08e977eaa3af652f63d479ce50d924254ad76722a6289ec1a1eac3231ca30430"
1401 | dependencies = [
1402 | "proc-macro-crate",
1403 | "proc-macro2",
1404 | "quote",
1405 | "syn",
1406 | ]
1407 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "krustyfy"
3 | version = "0.1.7"
4 | edition = "2021"
5 | build = "build.rs"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [dependencies]
10 | cpp_core = "0.6.0"
11 | qt_core = { version = "0.5.0" }
12 | qt_gui = { version = "0.5.0" }
13 | qt_widgets = { version = "0.5.0" }
14 | qt_ui_tools = { version = "0.5.0" }
15 | zbus = {version = "3.0.0", default-features = false, features = ["tokio"]}
16 | zvariant = "3.6.0"
17 | tokio={version="1.21.0", features = ["full"]}
18 | device_query = "1.1.1"
19 | linked-hash-map = "0.5.6"
20 | lazy_static = "1.4.0"
21 |
22 | [dependencies.uuid]
23 | version = "1.1.2"
24 | features = [
25 | "v4", # Lets you generate random UUIDs
26 | "fast-rng", # Use a faster (but still sufficiently random) RNG
27 | "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
28 | ]
29 |
30 | [build-dependencies]
31 | copy_to_output = "2.0.0"
32 | glob = "0.3.0"
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Abigail
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # krustyfy
2 |
3 |
4 |
5 |
6 |
7 |
8 | Unobtrusive notification daemon made in Rust and Qt.
9 |
10 | Notifications **can't be interacted with** (unless you keep Left Alt key pressed) and **mouse input goes right through them** :)
11 |
12 |
13 | https://user-images.githubusercontent.com/112440538/188256590-9793e49d-8265-4d85-a5f7-c2c3f3ed01bd.mp4
14 |
15 | ## Configuration
16 |
17 | Most settings can be changed directly from the **res/themes/{current theme}/template.ui** config file. From the layout of the notification itself to settings like duration, monitor, shadow color, etc. More settings comming soon. :)
18 |
19 |
20 |
21 |
22 |
23 | Take into account that some widgets MUST exist in the template.ui file, otherwise it'll crash. Modify it wisely.
24 |
25 |
26 |
27 | ## Theming
28 |
29 | Themes are located in the **res/themes** subdirectory. Each folder corresponds to a theme, which must have a **template.ui** file. Themes are loaded when the app is launched, and can be changed from the system tray:
30 |
31 | 
32 |
33 | Currently we have two built in themes: **default** and **compact**, and they can be changed, and even modified, while the app is running:
34 |
35 |
36 |
37 | https://user-images.githubusercontent.com/112440538/190928912-c352c2ad-a002-4d9a-aed8-0429989bc1d5.mp4
38 |
39 |
40 |
41 | ## Name
42 |
43 | **K**: Because it's made in Qt, so it works nice with KDE.
44 |
45 | **Rust**: Because it's made in rust.
46 |
47 | **Krusty**: Because it's made by a dumb sad clown.
48 |
49 | **Krustyfy**: Because it just had to have the "ify" suffix, as in "notify".
50 |
51 | ## How to contribute and help the project
52 | I don't know, I never thought I'd get this far. Also since I'm just learning about how to code in rust it's probably full of bad practices and awful code. :)
53 |
54 | ## Building
55 |
56 | Tested with Debian 11 netinst + KDE
57 |
58 | ### Requirements
59 | 1. Install Rustup (https://rustup.rs/)
60 | 2. Install required dev packages: `#apt-get install qt5-qmake qtbase5-dev cmake build-essential pkg-config qttools5-dev`
61 |
62 | ### Clone and build
63 | 1. Clone from git `$git clone https://github.com/abigaliz/krustyfy.git`
64 | 2. `cd krustyfy`
65 | 3. `cargo build --release`
66 |
67 | ### Running it
68 | 1. Disable KDE Notifications from the system tray by going to the System Tray Settings and marking Notifications as Disabled:
69 | 
70 | 2. Log out of the current session.
71 | 3. Run krustyfy from `$HOME/krustyfy/target/release/krustyfy`
72 |
73 | You can also set it to run on startup from KDE System Settings:
74 |
75 | 
76 |
77 | Remember to set up the work path to `$HOME/krustyfy/target/release/`, otherwise it won't be able to access the notification templates folder.
78 |
79 |
80 | ## Usage
81 |
82 | By pressing **Left Alt key** you freeze all notifications (new notifications still come in, but start frozen) and you're able to click on them to interact.
83 |
84 | Otherwise, they are semi-transparent and get blurry and even less opaque when your cursor is over it. Also you click through them, so if a notification spanws just when you were about to click, you don't have to worry; the click will be processed as if the notification was nothing at all, nothing at all, nothing at all.
85 |
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | use copy_to_output::copy_to_output;
2 | use std::env;
3 |
4 | fn main() {
5 | // Re-runs script if any files in res are changed
6 | println!("cargo:rerun-if-changed=res/*");
7 | copy_to_output("res", &env::var("PROFILE").unwrap()).expect("Could not copy");
8 | }
9 |
--------------------------------------------------------------------------------
/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abigaliz/krustyfy/851a673acc8bfd166b2af0bae9127ff12a558ccb/demo.mp4
--------------------------------------------------------------------------------
/krustify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abigaliz/krustyfy/851a673acc8bfd166b2af0bae9127ff12a558ccb/krustify.png
--------------------------------------------------------------------------------
/res/themes/compact/notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abigaliz/krustyfy/851a673acc8bfd166b2af0bae9127ff12a558ccb/res/themes/compact/notifications.png
--------------------------------------------------------------------------------
/res/themes/compact/res.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | notifications.png
4 |
5 |
6 |
--------------------------------------------------------------------------------
/res/themes/compact/template.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | notificationTemplate
4 |
5 |
6 |
7 | 0
8 | 0
9 | 321
10 | 293
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | 0.900000000000000
18 |
19 |
20 | 0.800000000000000
21 |
22 |
23 | 0.200000000000000
24 |
25 |
26 | 1.000000000000000
27 |
28 |
29 | 10.000000000000000
30 |
31 |
32 | -1
33 |
34 |
35 | 15.000000000000000
36 |
37 |
38 | 6500
39 |
40 |
41 | 200
42 |
43 |
44 | 600
45 |
46 |
47 |
48 | 0
49 | 0
50 | 0
51 |
52 |
53 |
54 |
55 | 255
56 | 255
57 | 255
58 |
59 |
60 |
61 |
62 | 0
63 | 0
64 | 0
65 |
66 |
67 |
68 |
69 |
70 | 0
71 | 0
72 | 321
73 | 81
74 |
75 |
76 |
77 |
78 |
79 | 10
80 | 10
81 | 271
82 | 63
83 |
84 |
85 |
86 | border-radius: 10px;
87 | background-color: black;
88 | border-style:none;
89 |
90 |
91 | QFrame::StyledPanel
92 |
93 |
94 | QFrame::Raised
95 |
96 |
97 | 6
98 |
99 |
100 | 3
101 |
102 |
103 |
104 |
105 | 0
106 | 0
107 | 270
108 | 63
109 |
110 |
111 |
112 | background-color:rgba(255, 255, 255, 0);
113 | border-style: none;
114 |
115 |
116 |
117 |
118 | 0
119 | 0
120 | 270
121 | 70
122 |
123 |
124 |
125 | background-color:rgba(255, 255, 255, 0);
126 | border-style: none;
127 |
128 |
129 |
130 | 1
131 |
132 |
133 | QLayout::SetMaximumSize
134 |
135 |
136 | 4
137 |
138 |
139 | 0
140 |
141 |
142 | 1
143 |
144 | -
145 |
146 |
147 |
148 | 0
149 | 0
150 |
151 |
152 |
153 |
154 | 25
155 | 50
156 |
157 |
158 |
159 |
160 | 0
161 | 50
162 |
163 |
164 |
165 | background-color:rgba(255, 255, 255, 0);
166 | border-style: none;
167 |
168 |
169 |
170 |
171 |
172 | true
173 |
174 |
175 | Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
176 |
177 |
178 |
179 | -
180 |
181 |
182 |
183 | 49
184 | 0
185 |
186 |
187 |
188 |
189 | 200
190 | 0
191 |
192 |
193 |
194 |
195 | 290
196 | 90
197 |
198 |
199 |
200 |
201 | 500
202 | 0
203 |
204 |
205 |
206 |
207 | 300
208 | 0
209 |
210 |
211 |
212 | background-color:rgba(255, 255, 255, 0);
213 | border-style: none;
214 |
215 |
216 |
217 | 2
218 |
219 |
220 | QLayout::SetNoConstraint
221 |
222 |
223 | 2
224 |
225 |
-
226 |
227 |
228 |
229 | 0
230 | 0
231 |
232 |
233 |
234 |
235 | 200
236 | 0
237 |
238 |
239 |
240 |
241 | 227
242 | 20
243 |
244 |
245 |
246 |
247 | 75
248 | true
249 |
250 |
251 |
252 | background-color:rgba(255, 255, 255, 0);
253 | border-style: none;
254 |
255 |
256 | Test notification subject
257 |
258 |
259 | Qt::MarkdownText
260 |
261 |
262 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
263 |
264 |
265 |
266 | -
267 |
268 |
269 |
270 | 0
271 | 0
272 |
273 |
274 |
275 |
276 | 0
277 | 30
278 |
279 |
280 |
281 |
282 | 300
283 | 40
284 |
285 |
286 |
287 | background-color:rgba(255, 255, 255, 0);
288 | border-style: none;
289 |
290 |
291 | Test notification body. Everything written here will be overwritten by the actual notification.
292 |
293 |
294 | Qt::MarkdownText
295 |
296 |
297 | false
298 |
299 |
300 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
301 |
302 |
303 | true
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 | 12
318 | 12
319 | 20
320 | 20
321 |
322 |
323 |
324 |
325 | 0
326 | 0
327 |
328 |
329 |
330 |
331 | 20
332 | 20
333 |
334 |
335 |
336 |
337 | 20
338 | 20
339 |
340 |
341 |
342 | false
343 |
344 |
345 | background-color:rgba(255, 255, 255, 0);
346 | border-style: none;
347 |
348 |
349 |
350 |
351 |
352 | :/default_icon/notifications.png
353 |
354 |
355 | false
356 |
357 |
358 |
359 |
360 |
361 |
362 | 10
363 | 160
364 | 301
365 | 121
366 |
367 |
368 |
369 |
370 |
371 | 0
372 | 0
373 | 301
374 | 121
375 |
376 |
377 |
378 | PointingHandCursor
379 |
380 |
381 | The button that will close the notification and
382 | execute the default action.
383 |
384 | This will never be visible (opacity: 0).
385 |
386 |
387 | false
388 |
389 |
390 | false
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
--------------------------------------------------------------------------------
/res/themes/default/notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abigaliz/krustyfy/851a673acc8bfd166b2af0bae9127ff12a558ccb/res/themes/default/notifications.png
--------------------------------------------------------------------------------
/res/themes/default/res.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | notifications.png
4 |
5 |
6 |
--------------------------------------------------------------------------------
/res/themes/default/template.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | notificationTemplate
4 |
5 |
6 |
7 | 0
8 | 0
9 | 321
10 | 293
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | 0.900000000000000
18 |
19 |
20 | 0.800000000000000
21 |
22 |
23 | 0.200000000000000
24 |
25 |
26 | 1.000000000000000
27 |
28 |
29 | 10.000000000000000
30 |
31 |
32 | -1
33 |
34 |
35 | 15.000000000000000
36 |
37 |
38 | 6500
39 |
40 |
41 | 200
42 |
43 |
44 | 600
45 |
46 |
47 |
48 | 0
49 | 0
50 | 0
51 |
52 |
53 |
54 |
55 | 255
56 | 255
57 | 255
58 |
59 |
60 |
61 |
62 | 0
63 | 0
64 | 0
65 |
66 |
67 |
68 |
69 |
70 | 0
71 | 0
72 | 321
73 | 141
74 |
75 |
76 |
77 |
78 |
79 | 10
80 | 10
81 | 301
82 | 121
83 |
84 |
85 |
86 | border-radius: 15px;
87 | background-color: black;
88 | border-style:none;
89 |
90 |
91 | QFrame::StyledPanel
92 |
93 |
94 | QFrame::Raised
95 |
96 |
97 | 6
98 |
99 |
100 | 3
101 |
102 |
103 |
104 |
105 | 0
106 | 0
107 | 301
108 | 29
109 |
110 |
111 |
112 | background-color:rgba(255, 255, 255, 0);
113 | border-style: none;
114 |
115 |
116 |
117 |
118 | 0
119 | 0
120 | 301
121 | 34
122 |
123 |
124 |
125 | background-color:rgba(255, 255, 255, 0);
126 | border-style: none;
127 |
128 |
129 |
130 | 6
131 |
132 |
133 | QLayout::SetFixedSize
134 |
135 |
136 | 5
137 |
138 |
139 | 3
140 |
141 | -
142 |
143 |
144 |
145 | 25
146 | 25
147 |
148 |
149 |
150 |
151 | 25
152 | 25
153 |
154 |
155 |
156 | false
157 |
158 |
159 | background-color:rgba(255, 255, 255, 0);
160 | border-style: none;
161 |
162 |
163 |
164 |
165 |
166 | :/default_icon/notifications.png
167 |
168 |
169 | false
170 |
171 |
172 |
173 | -
174 |
175 |
176 |
177 | 0
178 | 20
179 |
180 |
181 |
182 |
183 | 300
184 | 20
185 |
186 |
187 |
188 | background-color:rgba(255, 255, 255, 0);
189 | border-style: none;
190 |
191 |
192 | Test title
193 |
194 |
195 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 | 0
206 | 30
207 | 301
208 | 91
209 |
210 |
211 |
212 | background-color:rgba(255, 255, 255, 0);
213 | border-style: none;
214 |
215 |
216 |
217 |
218 | 0
219 | 0
220 | 301
221 | 94
222 |
223 |
224 |
225 | background-color:rgba(255, 255, 255, 0);
226 | border-style: none;
227 |
228 |
229 |
230 | 3
231 |
232 |
233 | QLayout::SetMaximumSize
234 |
235 |
236 | 4
237 |
238 |
239 | 0
240 |
241 |
242 | 3
243 |
244 | -
245 |
246 |
247 |
248 | 0
249 | 0
250 |
251 |
252 |
253 |
254 | 0
255 | 85
256 |
257 |
258 |
259 |
260 | 0
261 | 85
262 |
263 |
264 |
265 | background-color:rgba(255, 255, 255, 0);
266 | border-style: none;
267 |
268 |
269 |
270 |
271 |
272 | true
273 |
274 |
275 | Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
276 |
277 |
278 |
279 | -
280 |
281 |
282 |
283 | 49
284 | 0
285 |
286 |
287 |
288 |
289 | 200
290 | 0
291 |
292 |
293 |
294 |
295 | 290
296 | 90
297 |
298 |
299 |
300 |
301 | 500
302 | 0
303 |
304 |
305 |
306 |
307 | 300
308 | 0
309 |
310 |
311 |
312 | background-color:rgba(255, 255, 255, 0);
313 | border-style: none;
314 |
315 |
316 |
317 | 2
318 |
319 |
320 | QLayout::SetNoConstraint
321 |
322 |
323 | 2
324 |
325 |
-
326 |
327 |
328 |
329 | 0
330 | 0
331 |
332 |
333 |
334 |
335 | 200
336 | 0
337 |
338 |
339 |
340 |
341 | 280
342 | 20
343 |
344 |
345 |
346 |
347 | 75
348 | true
349 |
350 |
351 |
352 | background-color:rgba(255, 255, 255, 0);
353 | border-style: none;
354 |
355 |
356 | Test notification subject
357 |
358 |
359 | Qt::MarkdownText
360 |
361 |
362 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
363 |
364 |
365 |
366 | -
367 |
368 |
369 |
370 | 0
371 | 0
372 |
373 |
374 |
375 |
376 | 0
377 | 64
378 |
379 |
380 |
381 |
382 | 300
383 | 64
384 |
385 |
386 |
387 | background-color:rgba(255, 255, 255, 0);
388 | border-style: none;
389 |
390 |
391 | Test notification body. Everything written here will be overwritten by the actual notification.
392 |
393 |
394 | Qt::MarkdownText
395 |
396 |
397 | false
398 |
399 |
400 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
401 |
402 |
403 | true
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 | 10
419 | 160
420 | 301
421 | 121
422 |
423 |
424 |
425 |
426 |
427 | 0
428 | 0
429 | 301
430 | 121
431 |
432 |
433 |
434 | PointingHandCursor
435 |
436 |
437 | The button that will close the notification and
438 | execute the default action.
439 |
440 | This will never be visible (opacity: 0).
441 |
442 |
443 | false
444 |
445 |
446 | false
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
--------------------------------------------------------------------------------
/src/dbus_signal.rs:
--------------------------------------------------------------------------------
1 | use crate::Notification;
2 |
3 | #[derive(Debug)]
4 | pub enum DbusSignal {
5 | ActionInvoked { notification_id: i32 },
6 | NotificationClosed { notification_id: u32, reason: u32 },
7 | }
8 |
9 | #[derive(Debug)]
10 | pub enum DbusMethod {
11 | CloseNotification { notification_id: u32 },
12 | Notify { notification: Notification },
13 | }
14 |
--------------------------------------------------------------------------------
/src/errors.rs:
--------------------------------------------------------------------------------
1 | use std::{ffi::NulError, sync::PoisonError};
2 |
3 | use crate::dbus_signal::{DbusMethod, DbusSignal};
4 |
5 | ///
6 | /// Catchall error type for converting errors from between different libraries as needed
7 | ///
8 | /// <3
9 | ///
10 | #[derive(Debug)]
11 | pub enum KrustifyError {
12 | ZbusFdo(zbus::fdo::Error),
13 | Zvariant(zvariant::Error),
14 | DbusMethodSend(tokio::sync::mpsc::error::SendError),
15 | DbusSignalSend(tokio::sync::mpsc::error::SendError),
16 | CStr(NulError),
17 | FindChild(qt_core::FindChildError),
18 | Other { message: String },
19 | }
20 |
21 | impl From for KrustifyError {
22 | fn from(err: zbus::Error) -> Self {
23 | match err {
24 | zbus::Error::FDO(e) => Self::ZbusFdo(*e),
25 | zbus::Error::Variant(e) => Self::Zvariant(e),
26 | _ => Self::Other {
27 | message: err.to_string(),
28 | },
29 | }
30 | }
31 | }
32 |
33 | impl From for KrustifyError {
34 | fn from(err: zvariant::Error) -> Self {
35 | Self::Zvariant(err)
36 | }
37 | }
38 |
39 | impl From> for KrustifyError {
40 | fn from(err: tokio::sync::mpsc::error::SendError) -> Self {
41 | KrustifyError::DbusMethodSend(err)
42 | }
43 | }
44 |
45 | impl From for KrustifyError {
46 | fn from(err: NulError) -> Self {
47 | KrustifyError::CStr(err)
48 | }
49 | }
50 |
51 | impl From for KrustifyError {
52 | fn from(err: qt_core::FindChildError) -> Self {
53 | KrustifyError::FindChild(err)
54 | }
55 | }
56 |
57 | impl From>> for KrustifyError {
58 | fn from(err: PoisonError>) -> Self {
59 | Self::Other {
60 | message: err.to_string(),
61 | }
62 | }
63 | }
64 |
65 | impl From> for KrustifyError {
66 | fn from(err: tokio::sync::mpsc::error::SendError) -> Self {
67 | Self::DbusSignalSend(err)
68 | }
69 | }
70 |
71 | impl From for zbus::fdo::Error {
72 | fn from(err: KrustifyError) -> Self {
73 | match err {
74 | KrustifyError::ZbusFdo(e) => e,
75 | KrustifyError::Other { message } => zbus::fdo::Error::Failed(message),
76 | KrustifyError::Zvariant(e) => zbus::fdo::Error::Failed(e.to_string()),
77 | KrustifyError::DbusMethodSend(e) => zbus::fdo::Error::Failed(e.to_string()),
78 | KrustifyError::CStr(e) => zbus::fdo::Error::Failed(e.to_string()),
79 | KrustifyError::FindChild(e) => zbus::fdo::Error::Failed(e.to_string()),
80 | KrustifyError::DbusSignalSend(e) => zbus::fdo::Error::Failed(e.to_string()),
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/image_handler.rs:
--------------------------------------------------------------------------------
1 | use cpp_core::{CppBox, Ref};
2 |
3 | use qt_core::{qs, QFileInfo, QString};
4 | use qt_gui::{QIcon, QImage, QPixmap};
5 | use qt_widgets::QFileIconProvider;
6 |
7 | use crate::notification::ImageData;
8 |
9 | const DEFAULT_ICON: &str = "notifications";
10 |
11 | pub unsafe fn find_icon(desktop_entry: &String) -> CppBox {
12 | let desktop_entry_lowercase = desktop_entry.as_str().to_lowercase();
13 |
14 | let qstr = QString::from_std_str(desktop_entry_lowercase.as_str());
15 | let qdesktop_entry = qstr.as_ref();
16 |
17 | let icon_name: Ref;
18 | if QIcon::has_theme_icon(qdesktop_entry) {
19 | icon_name = qdesktop_entry;
20 | } else {
21 | let info = QFileInfo::new();
22 |
23 | let path = format!("/usr/share/applications/{}.desktop", desktop_entry);
24 |
25 | info.set_file_q_string(QString::from_std_str(path).as_ref());
26 |
27 | let icon_provider = QFileIconProvider::new();
28 |
29 | if info.exists_0a() {
30 | let icon = icon_provider.icon_q_file_info(info.as_ref());
31 |
32 | let pixmap = icon.pixmap_int(64);
33 |
34 | return pixmap;
35 | }
36 |
37 | return QIcon::from_theme_1a(QString::from_std_str(DEFAULT_ICON).as_ref()).pixmap_int(64);
38 | }
39 |
40 | QIcon::from_theme_1a(icon_name).pixmap_int(64)
41 | }
42 |
43 | pub unsafe fn parse_image(image_data: ImageData) -> CppBox {
44 | let pixmap = QPixmap::new();
45 |
46 | let image_format = if image_data.has_alpha {
47 | qt_gui::q_image::Format::FormatRGBA8888
48 | } else {
49 | qt_gui::q_image::Format::FormatRGB888
50 | };
51 |
52 | let data = image_data.data.as_ptr();
53 |
54 | let qimage = QImage::from_uchar3_int_format2(
55 | data,
56 | image_data.width,
57 | image_data.height,
58 | image_data.rowstride,
59 | image_format,
60 | );
61 |
62 | pixmap.convert_from_image_1a(qimage.as_ref());
63 |
64 | pixmap
65 | }
66 |
67 | pub unsafe fn load_image(image_path: String) -> CppBox {
68 | let pixmap = QPixmap::new();
69 |
70 | let qimage = QImage::from_q_string(&qs(image_path));
71 |
72 | pixmap.convert_from_image_1a(qimage.as_ref());
73 |
74 | pixmap
75 | }
76 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::convert::TryFrom;
3 | use std::error::Error;
4 | use std::time::Duration;
5 |
6 | use errors::KrustifyError;
7 | use qt_core::{
8 | qs, ConnectionType, QCoreApplication, QString, SignalOfInt, SignalOfQString, WidgetAttribute,
9 | WindowType,
10 | };
11 | use qt_widgets::{QApplication, QFrame, QMainWindow};
12 | use tokio::{
13 | self,
14 | sync::mpsc::{self, Sender},
15 | };
16 | use uuid::Uuid;
17 | use zbus::{dbus_interface, zvariant::Array, ConnectionBuilder};
18 | use zvariant::Value;
19 |
20 | use notification::{ImageData, Notification};
21 | use notification_spawner::NotificationSpawner;
22 |
23 | use crate::dbus_signal::{DbusMethod, DbusSignal};
24 | use crate::settings::{load_settings, SETTINGS};
25 | use crate::tray_menu::generate_tray;
26 |
27 | mod dbus_signal;
28 | mod errors;
29 | mod image_handler;
30 | mod notification;
31 | mod notification_spawner;
32 | mod notification_widget;
33 | mod settings;
34 | mod tray_menu;
35 |
36 | //static
37 | struct NotificationHandler {
38 | count: u32,
39 | dbus_method_sender: Sender,
40 | }
41 |
42 | #[dbus_interface(name = "org.freedesktop.Notifications")]
43 | impl NotificationHandler {
44 | #[dbus_interface(name = "CloseNotification")]
45 | async fn close_notification(&mut self, notification_id: u32) -> zbus::fdo::Result<()> {
46 | self.dbus_method_sender
47 | .send(DbusMethod::CloseNotification { notification_id })
48 | .await
49 | .map_err(KrustifyError::from)?;
50 |
51 | Ok(())
52 | }
53 |
54 | #[dbus_interface(name = "Notify")]
55 | async fn notify(
56 | &mut self,
57 | app_name: String,
58 | replaces_id: u32,
59 | app_icon: String,
60 | summary: String,
61 | body: String,
62 | actions: Vec,
63 | hints: HashMap>,
64 | expire_timeout: i32,
65 | ) -> zbus::fdo::Result {
66 | let desktop_entry = if hints.contains_key("desktop-entry") {
67 | zbus::zvariant::Str::try_from(&hints["desktop-entry"])
68 | .map_err(KrustifyError::from)?
69 | .to_string()
70 | } else {
71 | String::new()
72 | };
73 |
74 | let image_data_property_name = if hints.contains_key("image-data") {
75 | Some("image-data")
76 | } else if hints.contains_key("image_data") {
77 | Some("image_data")
78 | } else if hints.contains_key("icon-data") {
79 | Some("icon-data")
80 | } else if hints.contains_key("icon_data") {
81 | Some("icon_data")
82 | } else {
83 | None
84 | };
85 |
86 | let image_data = if let Some(name) = image_data_property_name {
87 | let image_structure =
88 | zbus::zvariant::Structure::try_from(&hints[name]).map_err(KrustifyError::from)?;
89 |
90 | let fields = image_structure.fields();
91 | let width_value = &fields[0];
92 | let height_value = &fields[1];
93 | let rowstride_value = &fields[2];
94 | let has_alpha_value = &fields[3];
95 | let bits_per_sample_value = &fields[4];
96 | let channels_value = &fields[5];
97 | let data_value = &fields[6];
98 |
99 | let image_raw_bytes_array = Array::try_from(data_value)
100 | .map_err(KrustifyError::from)?
101 | .get()
102 | .to_vec();
103 |
104 | let width = i32::try_from(width_value).map_err(KrustifyError::from)?;
105 | let height = i32::try_from(height_value).map_err(KrustifyError::from)?;
106 | let rowstride = i32::try_from(rowstride_value).map_err(KrustifyError::from)?;
107 | let has_alpha = bool::try_from(has_alpha_value).map_err(KrustifyError::from)?;
108 | let bits_per_sample =
109 | i32::try_from(bits_per_sample_value).map_err(KrustifyError::from)?;
110 | let channels = i32::try_from(channels_value).map_err(KrustifyError::from)?;
111 |
112 | // TODO: this one's tricky
113 | let data = image_raw_bytes_array
114 | .iter()
115 | .map(|value| {
116 | u8::try_from(value)
117 | .ok()
118 | .expect("value in image data was not a u8")
119 | })
120 | .collect::>();
121 |
122 | Some(ImageData::new(
123 | width,
124 | height,
125 | rowstride,
126 | has_alpha,
127 | bits_per_sample,
128 | channels,
129 | data,
130 | ))
131 | } else {
132 | None
133 | };
134 |
135 | let image_path = if hints.contains_key("image-path") {
136 | Some(
137 | zbus::zvariant::Str::try_from(&hints["image-path"])
138 | .map_err(KrustifyError::from)?
139 | .to_string(),
140 | )
141 | } else {
142 | None
143 | };
144 |
145 | let notification_id = if replaces_id == 0 {
146 | self.count += 1;
147 | self.count
148 | } else {
149 | replaces_id
150 | };
151 |
152 | let notification = Notification {
153 | app_name,
154 | replaces_id,
155 | app_icon,
156 | summary,
157 | body,
158 | actions,
159 | image_data,
160 | image_path,
161 | expire_timeout,
162 | notification_id,
163 | desktop_entry,
164 | };
165 |
166 | self.dbus_method_sender
167 | .send(DbusMethod::Notify { notification })
168 | .await
169 | .map_err(KrustifyError::from)?;
170 |
171 | Ok(notification_id)
172 | }
173 |
174 | #[dbus_interface(
175 | out_args("name", "vendor", "version", "spec_version"),
176 | name = "GetServerInformation"
177 | )]
178 | fn get_server_information(&mut self) -> zbus::fdo::Result<(String, String, String, String)> {
179 | let name = String::from("Notification Daemon");
180 | let vendor = String::from(env!("CARGO_PKG_NAME"));
181 | let version = String::from(env!("CARGO_PKG_VERSION"));
182 | let specification_version = String::from("1.2");
183 |
184 | Ok((name, vendor, version, specification_version))
185 | }
186 |
187 | #[dbus_interface(name = "GetCapabilities")]
188 | fn get_capabilities(&mut self) -> zbus::fdo::Result> {
189 | let capabilities = vec![
190 | "action-icons",
191 | "actions",
192 | "body",
193 | "body-hyperlinks",
194 | "body-images",
195 | "body-markup",
196 | "icon-multi",
197 | "icon-static",
198 | "persistence",
199 | "sound",
200 | ];
201 |
202 | Ok(capabilities)
203 | }
204 | }
205 |
206 | #[tokio::main]
207 | async fn main() -> Result<(), Box> {
208 | let (dbus_method_sender, mut dbus_method_receiver) = mpsc::channel(5);
209 | let (dbus_signal_sender, mut dbus_signal_receiver) = mpsc::unbounded_channel();
210 |
211 | let notification_handler = NotificationHandler {
212 | count: 0,
213 | dbus_method_sender,
214 | };
215 | let connection = ConnectionBuilder::session()?
216 | .name("org.freedesktop.Notifications")?
217 | .serve_at("/org/freedesktop/Notifications", notification_handler)?
218 | .build()
219 | .await?;
220 |
221 | tokio::spawn(async move {
222 | while let Some(signal) = dbus_signal_receiver.recv().await {
223 | match signal {
224 | DbusSignal::ActionInvoked { notification_id } => {
225 | connection
226 | .emit_signal(
227 | None::<()>,
228 | "/org/freedesktop/Notifications",
229 | "org.freedesktop.Notifications",
230 | "ActionInvoked",
231 | &(notification_id as u32, "default"),
232 | )
233 | .await
234 | .expect("could not emit ActionInvoked signal");
235 | }
236 | DbusSignal::NotificationClosed {
237 | notification_id,
238 | reason,
239 | } => {
240 | connection
241 | .emit_signal(
242 | None::<()>,
243 | "/org/freedesktop/Notifications",
244 | "org.freedesktop.Notifications",
245 | "NotificationClosed",
246 | &(notification_id, reason),
247 | )
248 | .await
249 | .expect("could not emit NotificationClosed signal");
250 | }
251 | }
252 | }
253 | });
254 |
255 | QApplication::init(|_app| unsafe {
256 | QCoreApplication::set_organization_name(&qs(env!("CARGO_PKG_NAME")));
257 | QCoreApplication::set_application_name(&qs(env!("CARGO_PKG_NAME")));
258 |
259 | load_settings();
260 |
261 | let main_window = QMainWindow::new_0a();
262 |
263 | let desktop = QApplication::desktop();
264 |
265 | let topleft = desktop
266 | .screen_geometry_int(SETTINGS.screen.id.clone())
267 | .top_left();
268 |
269 | main_window.set_window_flags(
270 | WindowType::WindowTransparentForInput
271 | | WindowType::WindowStaysOnTopHint
272 | | WindowType::FramelessWindowHint
273 | | WindowType::BypassWindowManagerHint
274 | | WindowType::X11BypassWindowManagerHint,
275 | );
276 |
277 | main_window.set_attribute_1a(WidgetAttribute::WATranslucentBackground);
278 | main_window.set_attribute_1a(WidgetAttribute::WADeleteOnClose);
279 | main_window.set_attribute_1a(WidgetAttribute::WANoSystemBackground);
280 | main_window.set_style_sheet(&qs("background-color: transparent;"));
281 |
282 | let main_frame = QFrame::new_1a(main_window.as_ptr());
283 |
284 | main_frame.set_attribute_1a(WidgetAttribute::WATranslucentBackground);
285 | main_frame.set_style_sheet(&qs("background-color: transparent;"));
286 |
287 | main_window.set_geometry_4a(topleft.x(), 0, 0, 0);
288 |
289 | main_window.show();
290 |
291 | let spawner = NotificationSpawner::new(dbus_signal_sender, main_frame);
292 |
293 | spawner.init();
294 |
295 | let notitification_signal = SignalOfQString::new();
296 | notitification_signal.connect_with_type(
297 | ConnectionType::QueuedConnection,
298 | &spawner.slot_on_spawn_notification(),
299 | );
300 |
301 | let closed_notification_signal = SignalOfInt::new();
302 | closed_notification_signal.connect_with_type(
303 | ConnectionType::QueuedConnection,
304 | &spawner.slot_on_external_close(),
305 | );
306 |
307 | let ref_notification_signal = notitification_signal
308 | .as_raw_ref()
309 | .expect("could not get a reference to notification_signal");
310 | let ref_closed_notification_signal = closed_notification_signal
311 | .as_raw_ref()
312 | .expect("could not get a reference to notification signal");
313 |
314 | tokio::spawn(async move {
315 | while let Some(method) = dbus_method_receiver.recv().await {
316 | match method {
317 | DbusMethod::CloseNotification { notification_id } => {
318 | tokio::spawn(async move {
319 | tokio::time::sleep(Duration::from_millis(100)).await;
320 | ref_closed_notification_signal.emit(notification_id as i32);
321 | });
322 | }
323 | DbusMethod::Notify { notification } => {
324 | if !SETTINGS.do_not_disturb.value {
325 | let guid = Uuid::new_v4().to_string();
326 | let mut list = notification_spawner::NOTIFICATION_LIST
327 | .lock()
328 | .expect("could not acquire lock to notification list");
329 | list.insert(guid.clone(), notification);
330 | ref_notification_signal.emit(&QString::from_std_str(&guid));
331 | }
332 | }
333 | }
334 | }
335 | });
336 |
337 | let _tray_icon = generate_tray();
338 |
339 | QApplication::exec()
340 | })
341 | }
342 |
--------------------------------------------------------------------------------
/src/notification.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug)]
2 | pub struct ImageData {
3 | pub width: i32,
4 | pub height: i32,
5 | pub rowstride: i32,
6 | pub has_alpha: bool,
7 | pub bits_per_sample: i32,
8 | pub channels: i32,
9 | pub data: Vec,
10 | }
11 |
12 | impl ImageData {
13 | pub fn new(
14 | width: i32,
15 | height: i32,
16 | rowstride: i32,
17 | has_alpha: bool,
18 | bits_per_sample: i32,
19 | channels: i32,
20 | data: Vec,
21 | ) -> ImageData {
22 | ImageData {
23 | width,
24 | height,
25 | rowstride,
26 | has_alpha,
27 | bits_per_sample,
28 | channels,
29 | data,
30 | }
31 | }
32 | }
33 |
34 | #[derive(Debug)]
35 | pub struct Notification {
36 | pub app_name: String,
37 | pub replaces_id: u32,
38 | pub app_icon: String,
39 | pub summary: String,
40 | pub body: String,
41 | pub actions: Vec,
42 | pub image_data: Option,
43 | pub image_path: Option,
44 | pub expire_timeout: i32,
45 | pub notification_id: u32,
46 | pub desktop_entry: String,
47 | }
48 |
--------------------------------------------------------------------------------
/src/notification_spawner.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::rc::Rc;
3 | use std::sync::{Mutex, MutexGuard};
4 |
5 | use cpp_core::{Ptr, Ref, StaticUpcast};
6 |
7 | use linked_hash_map::LinkedHashMap;
8 |
9 | use qt_widgets::QFrame;
10 | use tokio::sync::mpsc::UnboundedSender;
11 |
12 | use lazy_static::lazy_static;
13 |
14 | use qt_core::{
15 | qs, slot, ConnectionType, QBox, QObject, QString, QTimer, SignalNoArgs, SignalOfInt,
16 | SignalOfQString, SlotNoArgs, SlotOfInt, SlotOfQString,
17 | };
18 | use uuid::Uuid;
19 |
20 | use crate::errors::KrustifyError;
21 | use crate::{
22 | dbus_signal::DbusSignal,
23 | image_handler,
24 | notification::{ImageData, Notification},
25 | notification_widget::notifications::NotificationWidget,
26 | };
27 |
28 | lazy_static! {
29 | pub static ref NOTIFICATION_LIST: Mutex> =
30 | Mutex::new(HashMap::new());
31 | }
32 |
33 | pub struct NotificationSpawner {
34 | widget_list: Mutex>>,
35 | check_hover: QBox,
36 | signal_sender: UnboundedSender,
37 | timer: QBox,
38 | reorder_signal: QBox,
39 | action_signal: QBox,
40 | close_signal: QBox,
41 | qobject: QBox,
42 | main_window: QBox,
43 | }
44 |
45 | impl StaticUpcast for NotificationSpawner {
46 | unsafe fn static_upcast(ptr: Ptr) -> Ptr {
47 | ptr.qobject.as_ptr().static_upcast()
48 | }
49 | }
50 |
51 | impl NotificationSpawner {
52 | pub fn new(
53 | signal_sender: UnboundedSender,
54 | main_window: QBox,
55 | ) -> Rc {
56 | unsafe {
57 | let widget_list = Mutex::new(LinkedHashMap::new());
58 |
59 | let timer = QTimer::new_0a();
60 | timer.set_interval(100);
61 |
62 | let check_hover = SignalNoArgs::new();
63 |
64 | timer.timeout().connect(&check_hover);
65 |
66 | let reorder_signal = SignalNoArgs::new();
67 |
68 | let action_signal = SignalOfInt::new();
69 |
70 | let close_signal = SignalOfQString::new();
71 |
72 | let qobject = QObject::new_0a();
73 |
74 | Rc::new(Self {
75 | widget_list,
76 | check_hover,
77 | signal_sender,
78 | timer,
79 | reorder_signal,
80 | action_signal,
81 | close_signal,
82 | qobject,
83 | main_window,
84 | })
85 | }
86 | }
87 |
88 | pub unsafe fn init(self: &Rc) {
89 | self.timer.start_0a();
90 |
91 | self.reorder_signal
92 | .connect_with_type(ConnectionType::QueuedConnection, &self.slot_on_reorder());
93 |
94 | self.close_signal.connect_with_type(
95 | ConnectionType::QueuedConnection,
96 | &self.slot_on_widget_close(),
97 | );
98 |
99 | self.action_signal
100 | .connect_with_type(ConnectionType::QueuedConnection, &self.slot_on_action());
101 | }
102 |
103 | #[slot(SlotOfQString)]
104 | pub unsafe fn on_spawn_notification(self: &Rc, guid: Ref) {
105 | let mut list = NOTIFICATION_LIST.lock().expect("failed to aquire lock");
106 | let notification_option = list.remove(&guid.to_std_string());
107 |
108 | if let Some(notification) = notification_option {
109 | self.spawn_notification(
110 | notification.app_name,
111 | notification.replaces_id,
112 | notification.app_icon,
113 | notification.summary,
114 | notification.body,
115 | notification.actions,
116 | notification.image_data,
117 | notification.image_path,
118 | notification.expire_timeout,
119 | notification.notification_id,
120 | notification.desktop_entry,
121 | )
122 | .expect("failed to spawn notification");
123 | } else {
124 | return;
125 | }
126 | }
127 |
128 | pub unsafe fn get_already_existing_notification<'a>(
129 | self: &Rc,
130 | list: &'a MutexGuard>>,
131 | app_name: &String,
132 | replaces_id: u32,
133 | ) -> Option<&'a Rc> {
134 | for widget in list.values() {
135 | let _replaces_id = widget.notification_id.borrow().to_owned();
136 |
137 | if _replaces_id == replaces_id {
138 | return Some(widget);
139 | }
140 |
141 | if app_name.eq("discord") && _replaces_id == replaces_id - 1
142 | // Fuck you Discord
143 | {
144 | widget.notification_id.replace(replaces_id);
145 | return Some(widget);
146 | }
147 | }
148 |
149 | None
150 | }
151 |
152 | pub unsafe fn spawn_notification(
153 | self: &Rc,
154 | app_name: String,
155 | replaces_id: u32,
156 | _app_icon: String,
157 | summary: String,
158 | body: String,
159 | _actions: Vec,
160 | image_data: Option,
161 | image_path: Option,
162 | _expire_timeout: i32,
163 | notification_id: u32,
164 | desktop_entry: String,
165 | ) -> Result<(), KrustifyError> {
166 | let mut list = self.widget_list.lock()?;
167 |
168 | let already_existing_notification =
169 | self.get_already_existing_notification(&list, &app_name, replaces_id);
170 |
171 | if let Some(notification_widget) = already_existing_notification {
172 | notification_widget.reset_timer();
173 |
174 | self.set_notification_contents(
175 | app_name,
176 | image_data,
177 | image_path,
178 | desktop_entry,
179 | summary,
180 | body,
181 | notification_widget,
182 | );
183 | } else {
184 | let guid = Uuid::new_v4().to_string();
185 |
186 | let _notification_widget = NotificationWidget::new(
187 | &self.main_window,
188 | &self.close_signal,
189 | &self.action_signal,
190 | notification_id,
191 | guid.clone(),
192 | )?;
193 |
194 | self.set_notification_contents(
195 | app_name,
196 | image_data,
197 | image_path,
198 | desktop_entry,
199 | summary,
200 | body,
201 | &_notification_widget,
202 | );
203 |
204 | self.check_hover
205 | .connect(&_notification_widget.slot_check_hover());
206 |
207 | list.insert(guid, _notification_widget);
208 |
209 | self.reorder();
210 | };
211 |
212 | Ok(())
213 | }
214 |
215 | unsafe fn set_notification_contents(
216 | self: &Rc,
217 | app_name: String,
218 | image_data: Option,
219 | image_path: Option,
220 | desktop_entry: String,
221 | summary: String,
222 | body: String,
223 | notification_widget: &Rc,
224 | ) {
225 | let icon = if !desktop_entry.is_empty() {
226 | image_handler::find_icon(&desktop_entry)
227 | } else {
228 | image_handler::find_icon(&app_name)
229 | };
230 |
231 | if image_data.is_none() && image_path.is_none() {
232 | notification_widget.set_content_no_image(qs(app_name), qs(summary), qs(body), icon);
233 | } else {
234 | let pixmap = if image_data.is_some() {
235 | image_handler::parse_image(image_data.expect("literally the imposible"))
236 | } else {
237 | image_handler::load_image(image_path.expect("damn, stupid cosmic rays"))
238 | };
239 |
240 | notification_widget.set_content_with_image(
241 | qs(app_name),
242 | qs(summary),
243 | qs(body),
244 | pixmap,
245 | icon,
246 | );
247 | }
248 | }
249 |
250 | unsafe fn reorder(self: &Rc) {
251 | self.reorder_signal.emit();
252 | }
253 |
254 | #[slot(SlotNoArgs)]
255 | unsafe fn on_reorder(self: &Rc) {
256 | let list = self.widget_list.lock().expect("failed to acquire locks");
257 |
258 | let mut height_accumulator = 0;
259 | let mut biggest_width = 0;
260 | let mut end_height = 0;
261 |
262 | for widget in list.values() {
263 | widget.animate_entry_signal.emit(height_accumulator);
264 | height_accumulator += widget.widget.height();
265 | biggest_width = if biggest_width < widget.widget.width() {
266 | widget.widget.width()
267 | } else {
268 | biggest_width
269 | };
270 |
271 | end_height = if height_accumulator < widget.widget.geometry().bottom() {
272 | widget.widget.geometry().bottom()
273 | } else {
274 | height_accumulator
275 | }
276 | }
277 |
278 | self.main_window
279 | .set_geometry_4a(0, 0, biggest_width, end_height);
280 | let window_geometry = self.main_window.window().geometry();
281 | self.main_window.window().set_geometry_4a(
282 | window_geometry.x(),
283 | window_geometry.y(),
284 | biggest_width,
285 | end_height,
286 | );
287 | }
288 |
289 | #[slot(SlotOfInt)]
290 | unsafe fn on_action(self: &Rc, notifcation_id: i32) {
291 | self.signal_sender
292 | .send(DbusSignal::ActionInvoked {
293 | notification_id: notifcation_id,
294 | })
295 | .expect("failed to send signal");
296 | }
297 |
298 | #[slot(SlotOfQString)]
299 | unsafe fn on_widget_close(self: &Rc, closed_widget: Ref) {
300 | let mut list = self.widget_list.lock().expect("failed to acquire lock");
301 |
302 | let widget = list
303 | .remove(&closed_widget.to_std_string())
304 | .expect("failed to remove widget");
305 | widget.widget.close();
306 | widget.overlay.close();
307 |
308 | self.signal_sender
309 | .send(DbusSignal::NotificationClosed {
310 | notification_id: widget.notification_id.take(),
311 | reason: widget.close_reason.take(),
312 | })
313 | .expect("failed to send signal");
314 |
315 | self.reorder();
316 | }
317 |
318 | #[slot(SlotOfInt)]
319 | pub unsafe fn on_external_close(self: &Rc, notification_id: i32) {
320 | let list = self.widget_list.lock().expect("failed to acquire lock");
321 |
322 | for widget in list.values() {
323 | let _notification_id = widget.notification_id.borrow().to_owned();
324 | if _notification_id as i32 == notification_id {
325 | widget.close_reason.replace(3);
326 | widget.on_close();
327 | break;
328 | }
329 | }
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/src/notification_widget.rs:
--------------------------------------------------------------------------------
1 | pub mod notifications {
2 | use std::ffi::{CStr, CString};
3 | use std::{cell::RefCell, rc::Rc};
4 |
5 | use cpp_core::{CppBox, CppDeletable, Ptr, Ref, StaticUpcast};
6 | use device_query::{DeviceQuery, DeviceState, Keycode};
7 |
8 | use crate::errors::KrustifyError;
9 | use crate::settings::SETTINGS;
10 | use qt_core::{
11 | q_abstract_animation, q_io_device::OpenModeFlag, qs, slot, AspectRatioMode, ConnectionType,
12 | GlobalColor, QBox, QByteArray, QEasingCurve, QFile, QFlags, QObject,
13 | QParallelAnimationGroup, QPropertyAnimation, QPtr, QRect, QSequentialAnimationGroup,
14 | QString, QVariant, SignalNoArgs, SignalOfInt, SignalOfQString, SlotNoArgs, SlotOfInt,
15 | TextElideMode, TransformationMode, WidgetAttribute, WindowType,
16 | };
17 | use qt_gui::{q_painter::RenderHint, QColor, QCursor, QPainter, QPainterPath, QPixmap};
18 | use qt_widgets::{
19 | QDialog, QFrame, QGraphicsBlurEffect, QGraphicsDropShadowEffect, QGraphicsOpacityEffect,
20 | QLabel, QPushButton, QStackedLayout, QWidget,
21 | };
22 |
23 | #[derive(Debug)]
24 | pub struct NotificationWidget {
25 | pub widget: QBox,
26 | // Animations
27 | entry_animation: QBox,
28 | exit_animation: QBox,
29 | blur_animation: QBox,
30 | exit_animation_group: QBox,
31 | parallel_animation: QBox,
32 | // Content
33 | icon_label: QPtr,
34 | app_name_label: QPtr,
35 | image_label: QPtr,
36 | title_label: QPtr,
37 | body_label: QPtr,
38 | close_signal: Ref,
39 | pub animate_entry_signal: QBox,
40 | blur_effect: QBox,
41 | opacity_effect: QBox,
42 | action_button: QPtr,
43 | pub notification_id: RefCell,
44 | pub overlay: QBox,
45 | frame_shadow: QBox,
46 | action_signal: Ref,
47 | guid: String,
48 | parallel_hover_animation: QBox,
49 | default_opacity: CppBox,
50 | default_blur: CppBox,
51 | end_blur: CppBox,
52 | notification_duration: CppBox,
53 | spawn_duration: CppBox,
54 | disappear_duration: CppBox,
55 | default_shadow_color: CppBox,
56 | focused_shadow_color: CppBox,
57 | pub close_reason: RefCell,
58 | }
59 |
60 | impl StaticUpcast for NotificationWidget {
61 | unsafe fn static_upcast(ptr: Ptr) -> Ptr {
62 | ptr.widget.as_ptr().static_upcast()
63 | }
64 | }
65 |
66 | impl NotificationWidget {
67 | pub fn new(
68 | main_window: &QBox,
69 | close_signal: &QBox,
70 | action_signal: &QBox,
71 | _notification_id: u32,
72 | guid: String,
73 | ) -> Result, KrustifyError> {
74 | unsafe {
75 | // Set the notification widget
76 | let widget = QWidget::new_1a(main_window);
77 |
78 | widget.set_object_name(&qs(&guid));
79 |
80 | // Set flags
81 |
82 | widget.set_attribute_1a(WidgetAttribute::WATranslucentBackground);
83 | widget.set_attribute_1a(WidgetAttribute::WADeleteOnClose);
84 | widget.set_attribute_1a(WidgetAttribute::WANoSystemBackground);
85 |
86 | let widget_layout = QStackedLayout::new();
87 |
88 | widget.set_layout(widget_layout.as_ptr());
89 |
90 | let theme = &SETTINGS.theme.name;
91 |
92 | let template_file =
93 | QFile::from_q_string(&qs(format!("./res/themes/{theme}/template.ui")));
94 | template_file.open(QFlags::from(OpenModeFlag::ReadOnly));
95 | let loader = qt_ui_tools::QUiLoader::new_1a(&widget);
96 | let template = loader.load_1a(template_file.as_ptr());
97 | template_file.reset();
98 | template_file.close();
99 | template_file.delete();
100 | loader.delete();
101 |
102 | // Load properties
103 | let default_opacity =
104 | template.property(CStr::as_ptr(&CString::new("defaultOpacity")?));
105 | let hovered_opacity =
106 | template.property(CStr::as_ptr(&CString::new("hoveredOpacity")?));
107 | let default_blur = template.property(CStr::as_ptr(&CString::new("defaultBlur")?));
108 | let hovered_blur = template.property(CStr::as_ptr(&CString::new("hoveredBlur")?));
109 | let end_blur = template.property(CStr::as_ptr(&CString::new("endBlur")?));
110 | let notification_duration =
111 | template.property(CStr::as_ptr(&CString::new("notificationDuration")?));
112 | let spawn_duration =
113 | template.property(CStr::as_ptr(&CString::new("spawnDuration")?));
114 | let disappear_duration =
115 | template.property(CStr::as_ptr(&CString::new("disappearDuration")?));
116 | let default_shadow_color =
117 | template.property(CStr::as_ptr(&CString::new("defaultShadowColor")?));
118 | let focused_shadow_color =
119 | template.property(CStr::as_ptr(&CString::new("focusedShadowColor")?));
120 | let text_shadow_color =
121 | template.property(CStr::as_ptr(&CString::new("textShadowColor")?));
122 |
123 | let notification: QPtr = template.find_child("notification")?;
124 |
125 | widget.layout().add_widget(¬ification);
126 |
127 | let overlay_widget: QPtr = template.find_child("overlay")?;
128 | // Set the default action overlay
129 | let overlay = QDialog::new_1a(&widget);
130 | overlay.set_object_name(&qs("overlay"));
131 |
132 | overlay.set_window_flags(
133 | WindowType::WindowStaysOnTopHint
134 | | WindowType::Tool
135 | | WindowType::FramelessWindowHint
136 | | WindowType::BypassWindowManagerHint,
137 | );
138 |
139 | overlay.set_attribute_1a(WidgetAttribute::WADeleteOnClose);
140 |
141 | overlay.set_window_opacity(0.0);
142 |
143 | let overlay_layout = QStackedLayout::new();
144 | overlay_layout.set_object_name(&qs("overlay_layout"));
145 | overlay.set_layout(overlay_layout.as_ptr());
146 | overlay_layout.add_widget(&overlay_widget);
147 |
148 | let action_button: QPtr = overlay_widget.find_child("pushButton")?;
149 |
150 | let blur_effect = QGraphicsBlurEffect::new_1a(&widget);
151 | blur_effect.set_object_name(&qs("blur_effect"));
152 |
153 | let opacity_effect = QGraphicsOpacityEffect::new_1a(¬ification);
154 | opacity_effect.set_object_name(&qs("opacity_effect"));
155 |
156 | widget.set_graphics_effect(&blur_effect);
157 | notification.set_graphics_effect(&opacity_effect);
158 |
159 | opacity_effect.set_opacity(default_opacity.to_double_0a());
160 | blur_effect.set_blur_radius(default_blur.to_double_0a());
161 |
162 | widget.set_geometry_4a(
163 | 0,
164 | 0 - notification.geometry().height(),
165 | notification.geometry().width(),
166 | notification.geometry().height(),
167 | );
168 |
169 | widget.set_window_opacity(default_opacity.to_double_0a());
170 |
171 | // Set animations
172 | let y_property = QByteArray::new();
173 | y_property.add_assign_q_string(&qs("geometry"));
174 |
175 | let blur_radius_property = QByteArray::new();
176 | blur_radius_property.add_assign_q_string(&qs("blurRadius"));
177 |
178 | let opacity_property = QByteArray::new();
179 | opacity_property.add_assign_q_string(&qs("opacity"));
180 |
181 | let entry_animation = QPropertyAnimation::new_2a(&widget, &y_property);
182 | entry_animation.set_object_name(&qs("entry_animation"));
183 | let exit_animation = QPropertyAnimation::new_2a(&opacity_effect, &opacity_property);
184 | exit_animation.set_object_name(&qs("exit_animation"));
185 | let blur_animation =
186 | QPropertyAnimation::new_2a(&blur_effect, &blur_radius_property);
187 | blur_animation.set_object_name(&qs("blur_animation"));
188 | let blur_hover_animation =
189 | QPropertyAnimation::new_2a(&blur_effect, &blur_radius_property);
190 | blur_hover_animation.set_object_name(&qs("blur_hover_animation"));
191 | let opacity_hover_animation =
192 | QPropertyAnimation::new_2a(&opacity_effect, &opacity_property);
193 | opacity_hover_animation.set_object_name(&qs("opacity_hover_animation"));
194 | let exit_animation_group = QSequentialAnimationGroup::new_1a(&widget);
195 | exit_animation_group.set_object_name(&qs("exit_animation_group"));
196 | let parallel_animation = QParallelAnimationGroup::new_1a(&widget);
197 | parallel_animation.set_object_name(&qs("parallel_animation"));
198 | let parallel_hover_animation = QParallelAnimationGroup::new_1a(&widget);
199 | parallel_hover_animation.set_object_name(&qs("parallel_hover_animation"));
200 |
201 | blur_hover_animation.set_start_value(&default_blur);
202 | blur_hover_animation.set_end_value(&hovered_blur);
203 | blur_hover_animation.set_duration(100);
204 | opacity_hover_animation.set_start_value(&default_opacity);
205 | opacity_hover_animation.set_end_value(&hovered_opacity);
206 | opacity_hover_animation.set_duration(100);
207 |
208 | parallel_hover_animation.add_animation(&blur_hover_animation);
209 | parallel_hover_animation.add_animation(&opacity_hover_animation);
210 |
211 | let frame: QPtr = widget.find_child("notificationFrame")?;
212 |
213 | let frame_shadow = QGraphicsDropShadowEffect::new_1a(&frame);
214 | frame_shadow.set_object_name(&qs("frame_shadow"));
215 |
216 | frame_shadow.set_blur_radius(10.0);
217 | frame_shadow.set_x_offset(1.0);
218 | frame_shadow.set_y_offset(1.0);
219 |
220 | frame.set_graphics_effect(&frame_shadow);
221 |
222 | // Set up content
223 | let icon_label: QPtr =
224 | widget.find_child("iconLabel").unwrap_or(QPtr::null());
225 | let app_name_label: QPtr =
226 | widget.find_child("appNameLabel").unwrap_or(QPtr::null());
227 |
228 | if !app_name_label.is_null() {
229 | let app_name_label_shadow = QGraphicsDropShadowEffect::new_1a(&app_name_label);
230 | app_name_label_shadow.set_object_name(&qs("app_name_label_shadow"));
231 |
232 | app_name_label_shadow.set_blur_radius(1.0);
233 | app_name_label_shadow.set_x_offset(0.0);
234 | app_name_label_shadow.set_y_offset(0.0);
235 | app_name_label_shadow
236 | .set_color(&QColor::from_q_string(&text_shadow_color.to_string()));
237 |
238 | app_name_label.set_graphics_effect(&app_name_label_shadow);
239 | }
240 |
241 | let image_label: QPtr =
242 | widget.find_child("imageLabel").unwrap_or(QPtr::null());
243 |
244 | let title_label: QPtr =
245 | widget.find_child("titleLabel").unwrap_or(QPtr::null());
246 |
247 | if !title_label.is_null() {
248 | let title_label_shadow = QGraphicsDropShadowEffect::new_1a(&title_label);
249 |
250 | title_label_shadow.set_object_name(&qs("title_label_shadow"));
251 |
252 | title_label_shadow.set_blur_radius(1.0);
253 | title_label_shadow.set_x_offset(0.0);
254 | title_label_shadow.set_y_offset(0.0);
255 | title_label_shadow
256 | .set_color(&QColor::from_q_string(&text_shadow_color.to_string()));
257 |
258 | title_label.set_graphics_effect(&title_label_shadow);
259 | }
260 |
261 | let body_label: QPtr =
262 | widget.find_child("bodyLabel").unwrap_or(QPtr::null());
263 |
264 | if !body_label.is_null() {
265 | let body_label_shadow = QGraphicsDropShadowEffect::new_1a(&body_label);
266 | body_label_shadow.set_object_name(&qs("body_label_shadow"));
267 |
268 | body_label_shadow.set_blur_radius(1.0);
269 | body_label_shadow.set_x_offset(0.0);
270 | body_label_shadow.set_y_offset(0.0);
271 | body_label_shadow
272 | .set_color(&QColor::from_q_string(&text_shadow_color.to_string()));
273 |
274 | body_label.set_graphics_effect(&body_label_shadow);
275 | }
276 |
277 | let animate_entry_signal = SignalOfInt::new();
278 |
279 | widget.show();
280 | overlay.show();
281 | overlay.hide();
282 |
283 | // not sure about these ones
284 | let close = close_signal
285 | .as_ref()
286 | .expect("couldn't get reference to close signal");
287 | let action = action_signal
288 | .as_ref()
289 | .expect("coudln't get reference to action signal");
290 |
291 | let notification_id = RefCell::new(_notification_id);
292 |
293 | template.close();
294 | template.delete();
295 |
296 | let this = Rc::new(Self {
297 | widget,
298 | entry_animation,
299 | exit_animation,
300 | blur_animation,
301 | exit_animation_group,
302 | parallel_animation,
303 | icon_label,
304 | app_name_label,
305 | image_label,
306 | title_label,
307 | body_label,
308 | close_signal: close,
309 | animate_entry_signal,
310 | blur_effect,
311 | opacity_effect,
312 | action_signal: action,
313 | action_button,
314 | notification_id,
315 | overlay,
316 | frame_shadow,
317 | guid,
318 | parallel_hover_animation,
319 | default_opacity,
320 | default_blur,
321 | end_blur,
322 | notification_duration,
323 | spawn_duration,
324 | disappear_duration,
325 | default_shadow_color,
326 | focused_shadow_color,
327 | close_reason: RefCell::new(1),
328 | });
329 | this.init();
330 | this.animate_exit();
331 | Ok(this)
332 | }
333 | }
334 |
335 | #[slot(SlotNoArgs)]
336 | unsafe fn ellide(self: &Rc) {
337 | if !self.title_label.is_null() {
338 | let ellided_title = self.title_label.font_metrics().elided_text_3a(
339 | &self.title_label.text(),
340 | TextElideMode::ElideRight,
341 | self.title_label.width(),
342 | );
343 |
344 | self.title_label.set_text(&ellided_title);
345 | }
346 | }
347 |
348 | unsafe fn set_content(
349 | self: &Rc,
350 | app_name: CppBox,
351 | title: CppBox,
352 | body: CppBox,
353 | icon: CppBox,
354 | ) {
355 | if !self.app_name_label.is_null() {
356 | self.app_name_label.set_text(&app_name);
357 | }
358 |
359 | if !self.body_label.is_null() {
360 | self.body_label.set_text(&body);
361 | }
362 |
363 | if !self.title_label.is_null() {
364 | self.title_label.set_text(&title);
365 | }
366 |
367 | if !self.icon_label.is_null() {
368 | let scaled_icon = icon.scaled_2_int_aspect_ratio_mode_transformation_mode(
369 | self.icon_label.width(),
370 | self.icon_label.height(),
371 | AspectRatioMode::IgnoreAspectRatio,
372 | TransformationMode::SmoothTransformation,
373 | );
374 |
375 | self.icon_label.set_pixmap(&scaled_icon);
376 | }
377 |
378 | let signal = SignalNoArgs::new();
379 | signal.connect_with_type(ConnectionType::QueuedConnection, &self.slot_ellide());
380 | signal.emit();
381 | }
382 |
383 | pub unsafe fn set_content_no_image(
384 | self: &Rc,
385 | app_name: CppBox,
386 | title: CppBox,
387 | body: CppBox,
388 | icon: CppBox,
389 | ) {
390 | self.set_content(app_name, title, body, icon);
391 | }
392 |
393 | pub unsafe fn set_content_with_image(
394 | self: &Rc,
395 | app_name: CppBox,
396 | title: CppBox,
397 | body: CppBox,
398 | image: CppBox,
399 | icon: CppBox,
400 | ) {
401 | if !self.image_label.is_null() {
402 | let scaled_image = self.resize_image(image);
403 |
404 | self.image_label.set_pixmap(&scaled_image);
405 |
406 | self.image_label.set_maximum_size_2a(
407 | self.image_label.maximum_height(),
408 | self.image_label.maximum_height(),
409 | );
410 | self.image_label.set_minimum_size_2a(
411 | self.image_label.maximum_height(),
412 | self.image_label.maximum_height(),
413 | );
414 | }
415 |
416 | self.set_content(app_name, title, body, icon);
417 | }
418 |
419 | unsafe fn resize_image(self: &Rc, pixmap: CppBox) -> CppBox {
420 | let target = QPixmap::from_2_int(
421 | self.image_label.maximum_height(),
422 | self.image_label.maximum_height(),
423 | );
424 |
425 | target.fill_1a(&QColor::from_global_color(GlobalColor::Transparent));
426 |
427 | let painter = QPainter::new_1a(&target);
428 |
429 | painter.set_render_hints_2a(
430 | RenderHint::HighQualityAntialiasing
431 | | RenderHint::SmoothPixmapTransform
432 | | RenderHint::Antialiasing,
433 | true,
434 | );
435 |
436 | let path = QPainterPath::new_0a();
437 | path.add_round_rect_6a(
438 | 0.0,
439 | 0.0,
440 | self.image_label.maximum_height() as f64,
441 | self.image_label.maximum_height() as f64,
442 | 25,
443 | 25,
444 | );
445 |
446 | painter.set_clip_path_1a(&path);
447 |
448 | let scaled_pixmap = pixmap.scaled_2_int_aspect_ratio_mode_transformation_mode(
449 | self.image_label.maximum_height(),
450 | self.image_label.maximum_height(),
451 | AspectRatioMode::IgnoreAspectRatio,
452 | TransformationMode::SmoothTransformation,
453 | );
454 |
455 | painter.draw_pixmap_q_rect_q_pixmap(&target.rect(), &scaled_pixmap);
456 |
457 | target
458 | }
459 |
460 | pub unsafe fn reset_timer(self: &Rc) {
461 | self.exit_animation_group.set_current_time(0);
462 | self.exit_animation_group.start_0a();
463 | }
464 |
465 | #[slot(SlotNoArgs)]
466 | pub unsafe fn check_hover(self: &Rc) {
467 | let device_state = DeviceState::new();
468 |
469 | let keys: Vec = device_state.get_keys();
470 |
471 | if keys.contains(&Keycode::LAlt) {
472 | self.freeze();
473 | } else {
474 | self.unfreeze();
475 | }
476 |
477 | let rect = QRect::new();
478 | rect.set_x(self.widget.x() + self.widget.window().x());
479 | rect.set_y(self.widget.y() + self.widget.window().y());
480 | rect.set_width(self.widget.geometry().width());
481 | rect.set_height(self.widget.geometry().height());
482 |
483 | let pos = QCursor::pos_0a();
484 |
485 | if rect.contains_q_point(pos.as_ref()) {
486 | self.hover();
487 | } else {
488 | self.unhover();
489 | }
490 | }
491 |
492 | pub unsafe fn hover(self: &Rc) {
493 | if self.overlay.is_visible() {
494 | self.blur_effect
495 | .set_blur_radius(self.default_blur.to_double_0a());
496 | self.opacity_effect.set_opacity(0.99); // For some reason setting it to 1.0 shifts the widget slightly to the bottom-right
497 | self.frame_shadow.set_blur_radius(15.0);
498 |
499 | let color = QColor::from_q_string(&self.focused_shadow_color.to_string());
500 |
501 | self.frame_shadow.set_color(&color);
502 | self.frame_shadow.set_offset_2_double(0.0, 0.0);
503 | } else if self.exit_animation.state() != q_abstract_animation::State::Running {
504 | self.parallel_hover_animation
505 | .set_direction(q_abstract_animation::Direction::Forward);
506 |
507 | if self.parallel_hover_animation.state() == q_abstract_animation::State::Stopped
508 | && self.parallel_hover_animation.current_time() == 0
509 | {
510 | self.parallel_hover_animation.start_0a();
511 | }
512 | }
513 | }
514 |
515 | pub unsafe fn unhover(self: &Rc) {
516 | if self.overlay.is_visible() {
517 | self.blur_effect
518 | .set_blur_radius(self.default_blur.to_double_0a());
519 | self.opacity_effect
520 | .set_opacity(self.default_opacity.to_double_0a());
521 | } else if self.exit_animation.state() != q_abstract_animation::State::Running {
522 | if self.parallel_hover_animation.state() == q_abstract_animation::State::Stopped
523 | && self.parallel_hover_animation.current_time() > 0
524 | {
525 | self.parallel_hover_animation
526 | .set_direction(q_abstract_animation::Direction::Backward);
527 | self.parallel_hover_animation.start_0a();
528 | }
529 | }
530 |
531 | self.frame_shadow.set_blur_radius(10.0);
532 |
533 | let color = QColor::from_q_string(&self.default_shadow_color.to_string());
534 |
535 | self.frame_shadow.set_color(&color);
536 | self.frame_shadow.set_offset_2_double(1.0, 1.0);
537 | }
538 |
539 | #[slot(SlotOfInt)]
540 | pub unsafe fn animate_entry(self: &Rc, height: i32) {
541 | self.entry_animation
542 | .set_duration(self.spawn_duration.to_int_0a());
543 |
544 | let start_value = self.widget.geometry();
545 | let end_value = QRect::from_4_int(
546 | start_value.left(),
547 | height,
548 | start_value.width(),
549 | start_value.height(),
550 | );
551 |
552 | self.entry_animation
553 | .set_start_value(&QVariant::from_q_rect(start_value));
554 | self.entry_animation
555 | .set_end_value(&QVariant::from_q_rect(&end_value));
556 | self.entry_animation.start_0a();
557 | }
558 |
559 | #[slot(SlotNoArgs)]
560 | unsafe fn animate_exit(self: &Rc) {
561 | self.exit_animation
562 | .set_duration(self.disappear_duration.to_int_0a());
563 | self.exit_animation.set_start_value(&self.default_opacity);
564 | self.exit_animation
565 | .set_end_value(&QVariant::from_float(0.0));
566 | self.exit_animation.set_easing_curve(&QEasingCurve::new_1a(
567 | qt_core::q_easing_curve::Type::OutCurve,
568 | ));
569 |
570 | self.blur_animation
571 | .set_duration(self.disappear_duration.to_int_0a());
572 | self.blur_animation.set_start_value(&self.default_blur);
573 | self.blur_animation.set_end_value(&self.end_blur);
574 |
575 | self.parallel_animation.add_animation(&self.blur_animation);
576 | self.parallel_animation.add_animation(&self.exit_animation);
577 |
578 | self.exit_animation_group
579 | .add_pause(self.notification_duration.to_int_0a())
580 | .finished()
581 | .connect(&self.slot_on_init_exit());
582 | self.exit_animation_group
583 | .add_animation(&self.parallel_animation);
584 |
585 | self.exit_animation_group.start_0a();
586 |
587 | self.exit_animation_group
588 | .finished()
589 | .connect(&self.slot_on_close());
590 | }
591 |
592 | unsafe fn init(self: &Rc) {
593 | self.animate_entry_signal
594 | .connect(&self.slot_animate_entry());
595 | self.action_button
596 | .clicked()
597 | .connect(&self.slot_on_button_clicked());
598 | }
599 |
600 | #[slot(SlotNoArgs)]
601 | pub unsafe fn on_close(self: &Rc) {
602 | self.close_signal.emit(&qs(&self.guid));
603 | }
604 |
605 | #[slot(SlotNoArgs)]
606 | unsafe fn on_init_exit(self: &Rc) {
607 | self.parallel_hover_animation.stop();
608 | self.exit_animation
609 | .set_start_value(&qt_core::QVariant::from_double(
610 | self.opacity_effect.opacity(),
611 | ));
612 | self.blur_animation
613 | .set_start_value(&qt_core::QVariant::from_double(
614 | self.blur_effect.blur_radius(),
615 | ));
616 | }
617 |
618 | unsafe fn freeze(self: &Rc) {
619 | let rect = QRect::new();
620 | rect.set_x(self.widget.x() + self.widget.window().x());
621 | rect.set_y(self.widget.y() + self.widget.window().y());
622 | rect.set_width(self.widget.geometry().width());
623 | rect.set_height(self.widget.geometry().height());
624 |
625 | self.overlay.set_geometry_1a(rect.as_ref());
626 | self.action_button.set_geometry_4a(
627 | 0,
628 | 0,
629 | self.widget.geometry().width(),
630 | self.widget.geometry().height(),
631 | );
632 | if self.overlay.is_visible() {
633 | return;
634 | }
635 | self.overlay.set_visible(true);
636 | if self.exit_animation_group.state() == q_abstract_animation::State::Paused {
637 | return;
638 | }
639 | self.exit_animation_group.pause();
640 | self.blur_effect
641 | .set_blur_radius(self.default_blur.to_double_0a());
642 | self.opacity_effect
643 | .set_opacity(self.default_opacity.to_double_0a());
644 | }
645 |
646 | unsafe fn unfreeze(self: &Rc) {
647 | if !self.overlay.is_visible() {
648 | return;
649 | }
650 | self.overlay.set_visible(false);
651 | self.frame_shadow.set_blur_radius(10.0);
652 |
653 | let color = QColor::from_q_string(&self.default_shadow_color.to_string());
654 |
655 | self.frame_shadow.set_color(&color);
656 | self.frame_shadow.set_offset_2_double(1.0, 1.0);
657 | self.parallel_hover_animation.set_current_time(0);
658 | if self.exit_animation_group.state() != q_abstract_animation::State::Paused {
659 | return;
660 | }
661 | self.exit_animation_group.resume();
662 | }
663 |
664 | #[slot(SlotNoArgs)]
665 | unsafe fn on_button_clicked(self: &Rc) {
666 | let notification_id = self.notification_id.borrow().to_owned();
667 | self.action_signal.emit(notification_id as i32);
668 | self.on_close();
669 | }
670 | }
671 | }
672 |
--------------------------------------------------------------------------------
/src/settings.rs:
--------------------------------------------------------------------------------
1 | use std::sync::atomic::{AtomicBool, Ordering};
2 | use std::sync::{Arc, Mutex};
3 |
4 | use cpp_core::CppBox;
5 | use lazy_static::lazy_static;
6 | use qt_core::{qs, QBox, QPtr, QSettings, QVariant};
7 | use qt_gui::{QGuiApplication, QScreen};
8 |
9 | lazy_static! {
10 | static ref THEME: Mutex = Mutex::new("default".to_string());
11 | static ref SCREEN: Mutex = Mutex::new(-1);
12 | static ref DO_NOT_DISTURB: Arc = Arc::new(AtomicBool::new(false));
13 | }
14 |
15 | static mut QSETTINGS: Option> = None;
16 |
17 | pub static mut SETTINGS: Settings = Settings {
18 | theme: Theme { name: "" },
19 | screen: Screen {
20 | id: -1,
21 | name: "",
22 | qscreen: None,
23 | },
24 | do_not_disturb: DoNotDisturb { value: false },
25 | };
26 |
27 | pub trait Setting {
28 | fn load(&mut self);
29 | fn set(&mut self, value: CppBox);
30 | fn save(&mut self);
31 | }
32 |
33 | pub struct Settings {
34 | pub theme: Theme,
35 | pub screen: Screen,
36 | pub do_not_disturb: DoNotDisturb,
37 | }
38 |
39 | pub unsafe fn load_settings() {
40 | unsafe {
41 | QSETTINGS = Some(QSettings::new());
42 | }
43 |
44 | let mut theme = Theme { name: "default" };
45 | let mut screen = Screen {
46 | name: "",
47 | id: -1,
48 | qscreen: None,
49 | };
50 |
51 | theme.load();
52 | screen.load();
53 |
54 | let do_not_disturb = DoNotDisturb { value: false };
55 |
56 | let this = Settings {
57 | theme,
58 | screen,
59 | do_not_disturb,
60 | };
61 |
62 | SETTINGS = this;
63 | }
64 |
65 | pub struct Theme {
66 | pub name: &'static str,
67 | }
68 |
69 | impl Setting for Theme {
70 | fn load(&mut self) {
71 | unsafe {
72 | let theme_setting = QSETTINGS.as_ref().unwrap().value_1a(&qs("theme"));
73 |
74 | self.set(theme_setting);
75 | }
76 | }
77 |
78 | fn set(&mut self, value: CppBox) {
79 | unsafe {
80 | if value.is_null() {
81 | self.name = "default";
82 | } else {
83 | self.name = Box::leak(value.to_string().to_std_string().into_boxed_str());
84 | }
85 |
86 | let mut _theme = THEME.lock().expect("Could not lock mutex");
87 |
88 | *_theme = self.name.to_string();
89 | }
90 | }
91 |
92 | fn save(&mut self) {
93 | unsafe {
94 | QSETTINGS.as_ref().unwrap().set_value(
95 | &qs("theme"),
96 | &QVariant::from_q_string(&qs(self.name.clone())),
97 | );
98 | }
99 | }
100 | }
101 |
102 | pub struct Screen {
103 | pub id: i32,
104 | pub name: &'static str,
105 | pub qscreen: Option>,
106 | }
107 |
108 | impl Setting for Screen {
109 | fn load(&mut self) {
110 | unsafe {
111 | let screen_setting = QSETTINGS.as_ref().unwrap().value_1a(&qs("screen"));
112 |
113 | self.set(screen_setting);
114 | }
115 | }
116 |
117 | fn set(&mut self, value: CppBox) {
118 | unsafe {
119 | let screens = QGuiApplication::screens();
120 | let mut screen_id = -1;
121 | let mut screen_name = String::new();
122 | let mut qscreen = Some(screens.value_1a(0));
123 |
124 | if !value.is_null() {
125 | for i in 0..screens.length() {
126 | let screen = screens.value_1a(i);
127 |
128 | if screen.name().compare_q_string(&value.to_string()) == 0 {
129 | screen_id = i;
130 | screen_name = screen.name().to_std_string();
131 | qscreen = Some(screen);
132 | }
133 | }
134 | }
135 |
136 | self.name = Box::leak(screen_name.into_boxed_str());
137 | self.id = screen_id;
138 | self.qscreen = qscreen;
139 |
140 | let mut _screen = SCREEN.lock().expect("Could not lock screen mutex");
141 |
142 | *_screen = self.id.clone();
143 | }
144 | }
145 |
146 | fn save(&mut self) {
147 | unsafe {
148 | QSETTINGS.as_ref().unwrap().set_value(
149 | &qs("screen"),
150 | &QVariant::from_q_string(&qs(self.name.clone())),
151 | );
152 | }
153 | }
154 | }
155 |
156 | pub struct DoNotDisturb {
157 | pub value: bool,
158 | }
159 |
160 | impl Setting for DoNotDisturb {
161 | fn load(&mut self) {
162 | self.value = false;
163 | }
164 |
165 | fn set(&mut self, value: CppBox) {
166 | unsafe {
167 | self.value = value.to_bool();
168 | DO_NOT_DISTURB.store(value.to_bool(), Ordering::Relaxed);
169 | }
170 | }
171 |
172 | fn save(&mut self) {
173 | // Should I actually save the Do Not Disturb?
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/tray_menu.rs:
--------------------------------------------------------------------------------
1 | use cpp_core::CppBox;
2 | use qt_core::q_dir::Filter;
3 | use qt_core::{qs, QBox, QDir, QDirIterator, QString, QVariant};
4 | use qt_gui::{QGuiApplication, QIcon};
5 | use qt_widgets::{QActionGroup, QApplication, QMenu, QSystemTrayIcon, SlotOfQAction};
6 |
7 | use crate::settings::Setting;
8 | use crate::SETTINGS;
9 |
10 | pub struct MenuItem {
11 | label: CppBox,
12 | value: CppBox,
13 | }
14 |
15 | pub fn get_available_themes() -> Vec