Commit 95eb18d
4
plików
+1914
dodanych
-3
usuniętych
@@ -27,7 +27,7 @@ use smithay::{
27
27
desktop::{
28
28
find_popup_root_surface, layer_map_for_output, LayerSurface as DesktopLayerSurface,
29
29
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Space,
30
- Window,
30
+ Window, WindowSurfaceType,
31
31
},
32
32
input::{
33
33
keyboard::XkbConfig,
@@ -77,8 +77,8 @@ use smithay::{
77
77
},
78
78
shell::{
79
79
wlr_layer::{
80
- Layer as WlrLayer, LayerSurface as WlrLayerSurface, WlrLayerShellHandler,
81
- WlrLayerShellState,
80
+ Layer as WlrLayer, LayerSurface as WlrLayerSurface, LayerSurfaceData,
81
+ WlrLayerShellHandler, WlrLayerShellState,
82
82
},
83
83
xdg::{
84
84
decoration::{XdgDecorationHandler, XdgDecorationState},
@@ -916,6 +916,39 @@ impl CompositorHandler for MyCompositor {
916
916
}
917
917
}
918
918
919
+ // Warstwy (panele/paski): po commicie klienta przeliczamy strefy i — jeśli
920
+ // to pierwszy commit — wysyłamy PIERWSZY `configure`. Smithay celowo nie
921
+ // robi tego w `arrange()` (patrz komentarz w `layer.rs`), więc musi to
922
+ // zrobić kompozytor. Bez tego panel nie wie, jak szeroki ma być.
923
+ if let Some(output) = self
924
+ .space
925
+ .outputs()
926
+ .find(|output| {
927
+ layer_map_for_output(output)
928
+ .layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
929
+ .is_some()
930
+ })
931
+ .cloned()
932
+ {
933
+ let initial_configure_sent = with_states(surface, |states| {
934
+ states
935
+ .data_map
936
+ .get::<LayerSurfaceData>()
937
+ .map(|data| data.lock().unwrap().initial_configure_sent)
938
+ .unwrap_or(false)
939
+ });
940
+
941
+ let mut map = layer_map_for_output(&output);
942
+ // Najpierw przelicz (uwzględnij rozmiar/anchory podane przez klienta),
943
+ // potem wyślij pierwszy configure.
944
+ map.arrange();
945
+ if !initial_configure_sent {
946
+ if let Some(layer) = map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL) {
947
+ layer.layer_surface().send_configure();
948
+ }
949
+ }
950
+ }
951
+
919
952
self.popups.commit(surface);
920
953
}
921
954
}
922
955
new file mode 100644
@@ -0,0 +1,1061 @@
1
+# This file is automatically @generated by Cargo.
2
+# It is not intended for manual editing.
3
+version = 4
4
+
5
+[[package]]
6
+name = "aho-corasick"
7
+version = "1.1.5"
8
+source = "registry+https://github.com/rust-lang/crates.io-index"
9
+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
10
+dependencies = [
11
+ "memchr",
12
+]
13
+
14
+[[package]]
15
+name = "android_system_properties"
16
+version = "0.1.6"
17
+source = "registry+https://github.com/rust-lang/crates.io-index"
18
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
19
+dependencies = [
20
+ "libc",
21
+]
22
+
23
+[[package]]
24
+name = "autocfg"
25
+version = "1.5.1"
26
+source = "registry+https://github.com/rust-lang/crates.io-index"
27
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
28
+
29
+[[package]]
30
+name = "bitflags"
31
+version = "2.13.2"
32
+source = "registry+https://github.com/rust-lang/crates.io-index"
33
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
34
+
35
+[[package]]
36
+name = "bumpalo"
37
+version = "3.20.3"
38
+source = "registry+https://github.com/rust-lang/crates.io-index"
39
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
40
+
41
+[[package]]
42
+name = "cairo-rs"
43
+version = "0.22.9"
44
+source = "registry+https://github.com/rust-lang/crates.io-index"
45
+checksum = "df683f1d30b457964673a5541a4603208ef95ab6873d2a55871895cf310f3b56"
46
+dependencies = [
47
+ "bitflags",
48
+ "cairo-sys-rs",
49
+ "glib",
50
+ "libc",
51
+]
52
+
53
+[[package]]
54
+name = "cairo-sys-rs"
55
+version = "0.22.9"
56
+source = "registry+https://github.com/rust-lang/crates.io-index"
57
+checksum = "ee548131103ad8f698c6725669647b9c757b3b66992dd6a026037596417bee21"
58
+dependencies = [
59
+ "glib-sys",
60
+ "libc",
61
+ "system-deps",
62
+]
63
+
64
+[[package]]
65
+name = "calloop"
66
+version = "0.13.0"
67
+source = "registry+https://github.com/rust-lang/crates.io-index"
68
+checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
69
+dependencies = [
70
+ "bitflags",
71
+ "log",
72
+ "polling",
73
+ "rustix 0.38.44",
74
+ "slab",
75
+ "thiserror",
76
+]
77
+
78
+[[package]]
79
+name = "calloop-wayland-source"
80
+version = "0.3.0"
81
+source = "registry+https://github.com/rust-lang/crates.io-index"
82
+checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
83
+dependencies = [
84
+ "calloop",
85
+ "rustix 0.38.44",
86
+ "wayland-backend",
87
+ "wayland-client",
88
+]
89
+
90
+[[package]]
91
+name = "cc"
92
+version = "1.4.6"
93
+source = "registry+https://github.com/rust-lang/crates.io-index"
94
+checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
95
+dependencies = [
96
+ "find-msvc-tools",
97
+ "shlex",
98
+]
99
+
100
+[[package]]
101
+name = "cfg-expr"
102
+version = "0.20.9"
103
+source = "registry+https://github.com/rust-lang/crates.io-index"
104
+checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917"
105
+dependencies = [
106
+ "smallvec",
107
+ "target-lexicon",
108
+]
109
+
110
+[[package]]
111
+name = "cfg-if"
112
+version = "1.0.4"
113
+source = "registry+https://github.com/rust-lang/crates.io-index"
114
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
115
+
116
+[[package]]
117
+name = "chrono"
118
+version = "0.4.45"
119
+source = "registry+https://github.com/rust-lang/crates.io-index"
120
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
121
+dependencies = [
122
+ "iana-time-zone",
123
+ "num-traits",
124
+ "windows-link",
125
+]
126
+
127
+[[package]]
128
+name = "concurrent-queue"
129
+version = "2.5.0"
130
+source = "registry+https://github.com/rust-lang/crates.io-index"
131
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
132
+dependencies = [
133
+ "crossbeam-utils",
134
+]
135
+
136
+[[package]]
137
+name = "core-foundation-sys"
138
+version = "0.8.7"
139
+source = "registry+https://github.com/rust-lang/crates.io-index"
140
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
141
+
142
+[[package]]
143
+name = "crossbeam-utils"
144
+version = "0.8.23"
145
+source = "registry+https://github.com/rust-lang/crates.io-index"
146
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
147
+
148
+[[package]]
149
+name = "downcast-rs"
150
+version = "1.2.1"
151
+source = "registry+https://github.com/rust-lang/crates.io-index"
152
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
153
+
154
+[[package]]
155
+name = "equivalent"
156
+version = "1.0.2"
157
+source = "registry+https://github.com/rust-lang/crates.io-index"
158
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
159
+
160
+[[package]]
161
+name = "errno"
162
+version = "0.3.14"
163
+source = "registry+https://github.com/rust-lang/crates.io-index"
164
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
165
+dependencies = [
166
+ "libc",
167
+ "windows-sys 0.61.2",
168
+]
169
+
170
+[[package]]
171
+name = "fastrand"
172
+version = "2.5.0"
173
+source = "registry+https://github.com/rust-lang/crates.io-index"
174
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
175
+
176
+[[package]]
177
+name = "find-msvc-tools"
178
+version = "0.1.12"
179
+source = "registry+https://github.com/rust-lang/crates.io-index"
180
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
181
+
182
+[[package]]
183
+name = "futures-core"
184
+version = "0.3.34"
185
+source = "registry+https://github.com/rust-lang/crates.io-index"
186
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
187
+
188
+[[package]]
189
+name = "futures-task"
190
+version = "0.3.34"
191
+source = "registry+https://github.com/rust-lang/crates.io-index"
192
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
193
+
194
+[[package]]
195
+name = "futures-util"
196
+version = "0.3.34"
197
+source = "registry+https://github.com/rust-lang/crates.io-index"
198
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
199
+dependencies = [
200
+ "futures-core",
201
+ "futures-task",
202
+ "pin-project-lite",
203
+ "slab",
204
+]
205
+
206
+[[package]]
207
+name = "getrandom"
208
+version = "0.4.3"
209
+source = "registry+https://github.com/rust-lang/crates.io-index"
210
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
211
+dependencies = [
212
+ "cfg-if",
213
+ "libc",
214
+ "r-efi",
215
+]
216
+
217
+[[package]]
218
+name = "glib"
219
+version = "0.22.9"
220
+source = "registry+https://github.com/rust-lang/crates.io-index"
221
+checksum = "18b9b8d350db41f690ec1b87109f202782748bbd8ab2f2ede17cb40d29e2838c"
222
+dependencies = [
223
+ "bitflags",
224
+ "glib-macros",
225
+ "glib-sys",
226
+ "gobject-sys",
227
+ "libc",
228
+ "memchr",
229
+ "smallvec",
230
+]
231
+
232
+[[package]]
233
+name = "glib-macros"
234
+version = "0.22.9"
235
+source = "registry+https://github.com/rust-lang/crates.io-index"
236
+checksum = "c9597c68fa7bdf4154a3079642cd21c4dccac0b0bdd2897d82861523bf91e7b9"
237
+dependencies = [
238
+ "heck",
239
+ "proc-macro2",
240
+ "quote",
241
+ "syn 3.0.5",
242
+]
243
+
244
+[[package]]
245
+name = "glib-sys"
246
+version = "0.22.9"
247
+source = "registry+https://github.com/rust-lang/crates.io-index"
248
+checksum = "99b38907e67e40dec9b60f858bcada952598719cb512a13eb13d6cdcd16f8295"
249
+dependencies = [
250
+ "libc",
251
+ "system-deps",
252
+]
253
+
254
+[[package]]
255
+name = "gobject-sys"
256
+version = "0.22.9"
257
+source = "registry+https://github.com/rust-lang/crates.io-index"
258
+checksum = "07595c9ba696bd9819cb6a8df39f08078f3dd679508fe052dd558492c2053834"
259
+dependencies = [
260
+ "glib-sys",
261
+ "libc",
262
+ "system-deps",
263
+]
264
+
265
+[[package]]
266
+name = "hashbrown"
267
+version = "0.17.1"
268
+source = "registry+https://github.com/rust-lang/crates.io-index"
269
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
270
+
271
+[[package]]
272
+name = "heck"
273
+version = "0.5.0"
274
+source = "registry+https://github.com/rust-lang/crates.io-index"
275
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
276
+
277
+[[package]]
278
+name = "hermit-abi"
279
+version = "0.5.3"
280
+source = "registry+https://github.com/rust-lang/crates.io-index"
281
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
282
+
283
+[[package]]
284
+name = "iana-time-zone"
285
+version = "0.1.65"
286
+source = "registry+https://github.com/rust-lang/crates.io-index"
287
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
288
+dependencies = [
289
+ "android_system_properties",
290
+ "core-foundation-sys",
291
+ "iana-time-zone-haiku",
292
+ "js-sys",
293
+ "log",
294
+ "wasm-bindgen",
295
+ "windows-core",
296
+]
297
+
298
+[[package]]
299
+name = "iana-time-zone-haiku"
300
+version = "0.1.2"
301
+source = "registry+https://github.com/rust-lang/crates.io-index"
302
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
303
+dependencies = [
304
+ "cc",
305
+]
306
+
307
+[[package]]
308
+name = "indexmap"
309
+version = "2.14.2"
310
+source = "registry+https://github.com/rust-lang/crates.io-index"
311
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
312
+dependencies = [
313
+ "equivalent",
314
+ "hashbrown",
315
+]
316
+
317
+[[package]]
318
+name = "js-sys"
319
+version = "0.3.105"
320
+source = "registry+https://github.com/rust-lang/crates.io-index"
321
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
322
+dependencies = [
323
+ "cfg-if",
324
+ "futures-util",
325
+ "wasm-bindgen",
326
+]
327
+
328
+[[package]]
329
+name = "lazy_static"
330
+version = "1.5.0"
331
+source = "registry+https://github.com/rust-lang/crates.io-index"
332
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
333
+
334
+[[package]]
335
+name = "libc"
336
+version = "0.2.189"
337
+source = "registry+https://github.com/rust-lang/crates.io-index"
338
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
339
+
340
+[[package]]
341
+name = "linux-raw-sys"
342
+version = "0.4.15"
343
+source = "registry+https://github.com/rust-lang/crates.io-index"
344
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
345
+
346
+[[package]]
347
+name = "linux-raw-sys"
348
+version = "0.12.1"
349
+source = "registry+https://github.com/rust-lang/crates.io-index"
350
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
351
+
352
+[[package]]
353
+name = "log"
354
+version = "0.4.34"
355
+source = "registry+https://github.com/rust-lang/crates.io-index"
356
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
357
+
358
+[[package]]
359
+name = "matchers"
360
+version = "0.2.0"
361
+source = "registry+https://github.com/rust-lang/crates.io-index"
362
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
363
+dependencies = [
364
+ "regex-automata",
365
+]
366
+
367
+[[package]]
368
+name = "memchr"
369
+version = "2.8.3"
370
+source = "registry+https://github.com/rust-lang/crates.io-index"
371
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
372
+
373
+[[package]]
374
+name = "memmap2"
375
+version = "0.9.11"
376
+source = "registry+https://github.com/rust-lang/crates.io-index"
377
+checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
378
+dependencies = [
379
+ "libc",
380
+]
381
+
382
+[[package]]
383
+name = "nu-ansi-term"
384
+version = "0.50.3"
385
+source = "registry+https://github.com/rust-lang/crates.io-index"
386
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
387
+dependencies = [
388
+ "windows-sys 0.61.2",
389
+]
390
+
391
+[[package]]
392
+name = "num-traits"
393
+version = "0.2.19"
394
+source = "registry+https://github.com/rust-lang/crates.io-index"
395
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
396
+dependencies = [
397
+ "autocfg",
398
+]
399
+
400
+[[package]]
401
+name = "once_cell"
402
+version = "1.21.4"
403
+source = "registry+https://github.com/rust-lang/crates.io-index"
404
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
405
+
406
+[[package]]
407
+name = "pagan-panel"
408
+version = "0.1.0"
409
+dependencies = [
410
+ "cairo-rs",
411
+ "calloop",
412
+ "calloop-wayland-source",
413
+ "chrono",
414
+ "memmap2",
415
+ "pagan-toolkit",
416
+ "tracing",
417
+ "tracing-subscriber",
418
+ "wayland-client",
419
+ "wayland-protocols-wlr",
420
+]
421
+
422
+[[package]]
423
+name = "pagan-toolkit"
424
+version = "0.1.0"
425
+dependencies = [
426
+ "cairo-rs",
427
+ "memmap2",
428
+ "tempfile",
429
+ "tracing",
430
+ "tracing-subscriber",
431
+ "wayland-client",
432
+ "wayland-protocols",
433
+ "wayland-protocols-wlr",
434
+]
435
+
436
+[[package]]
437
+name = "pin-project-lite"
438
+version = "0.2.17"
439
+source = "registry+https://github.com/rust-lang/crates.io-index"
440
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
441
+
442
+[[package]]
443
+name = "pkg-config"
444
+version = "0.3.34"
445
+source = "registry+https://github.com/rust-lang/crates.io-index"
446
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
447
+
448
+[[package]]
449
+name = "polling"
450
+version = "3.11.0"
451
+source = "registry+https://github.com/rust-lang/crates.io-index"
452
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
453
+dependencies = [
454
+ "cfg-if",
455
+ "concurrent-queue",
456
+ "hermit-abi",
457
+ "pin-project-lite",
458
+ "rustix 1.1.4",
459
+ "windows-sys 0.61.2",
460
+]
461
+
462
+[[package]]
463
+name = "proc-macro2"
464
+version = "1.0.107"
465
+source = "registry+https://github.com/rust-lang/crates.io-index"
466
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
467
+dependencies = [
468
+ "unicode-ident",
469
+]
470
+
471
+[[package]]
472
+name = "quick-xml"
473
+version = "0.41.0"
474
+source = "registry+https://github.com/rust-lang/crates.io-index"
475
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
476
+dependencies = [
477
+ "memchr",
478
+]
479
+
480
+[[package]]
481
+name = "quote"
482
+version = "1.0.47"
483
+source = "registry+https://github.com/rust-lang/crates.io-index"
484
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
485
+dependencies = [
486
+ "proc-macro2",
487
+]
488
+
489
+[[package]]
490
+name = "r-efi"
491
+version = "6.0.0"
492
+source = "registry+https://github.com/rust-lang/crates.io-index"
493
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
494
+
495
+[[package]]
496
+name = "regex-automata"
497
+version = "0.4.18"
498
+source = "registry+https://github.com/rust-lang/crates.io-index"
499
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
500
+dependencies = [
501
+ "aho-corasick",
502
+ "memchr",
503
+ "regex-syntax",
504
+]
505
+
506
+[[package]]
507
+name = "regex-syntax"
508
+version = "0.8.11"
509
+source = "registry+https://github.com/rust-lang/crates.io-index"
510
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
511
+
512
+[[package]]
513
+name = "rustix"
514
+version = "0.38.44"
515
+source = "registry+https://github.com/rust-lang/crates.io-index"
516
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
517
+dependencies = [
518
+ "bitflags",
519
+ "errno",
520
+ "libc",
521
+ "linux-raw-sys 0.4.15",
522
+ "windows-sys 0.59.0",
523
+]
524
+
525
+[[package]]
526
+name = "rustix"
527
+version = "1.1.4"
528
+source = "registry+https://github.com/rust-lang/crates.io-index"
529
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
530
+dependencies = [
531
+ "bitflags",
532
+ "errno",
533
+ "libc",
534
+ "linux-raw-sys 0.12.1",
535
+ "windows-sys 0.61.2",
536
+]
537
+
538
+[[package]]
539
+name = "rustversion"
540
+version = "1.0.23"
541
+source = "registry+https://github.com/rust-lang/crates.io-index"
542
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
543
+
544
+[[package]]
545
+name = "serde_core"
546
+version = "1.0.229"
547
+source = "registry+https://github.com/rust-lang/crates.io-index"
548
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
549
+dependencies = [
550
+ "serde_derive",
551
+]
552
+
553
+[[package]]
554
+name = "serde_derive"
555
+version = "1.0.229"
556
+source = "registry+https://github.com/rust-lang/crates.io-index"
557
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
558
+dependencies = [
559
+ "proc-macro2",
560
+ "quote",
561
+ "syn 3.0.5",
562
+]
563
+
564
+[[package]]
565
+name = "serde_spanned"
566
+version = "1.1.1"
567
+source = "registry+https://github.com/rust-lang/crates.io-index"
568
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
569
+dependencies = [
570
+ "serde_core",
571
+]
572
+
573
+[[package]]
574
+name = "sharded-slab"
575
+version = "0.1.7"
576
+source = "registry+https://github.com/rust-lang/crates.io-index"
577
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
578
+dependencies = [
579
+ "lazy_static",
580
+]
581
+
582
+[[package]]
583
+name = "shlex"
584
+version = "2.0.1"
585
+source = "registry+https://github.com/rust-lang/crates.io-index"
586
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
587
+
588
+[[package]]
589
+name = "slab"
590
+version = "0.4.12"
591
+source = "registry+https://github.com/rust-lang/crates.io-index"
592
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
593
+
594
+[[package]]
595
+name = "smallvec"
596
+version = "1.16.1"
597
+source = "registry+https://github.com/rust-lang/crates.io-index"
598
+checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
599
+
600
+[[package]]
601
+name = "syn"
602
+version = "2.0.119"
603
+source = "registry+https://github.com/rust-lang/crates.io-index"
604
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
605
+dependencies = [
606
+ "proc-macro2",
607
+ "quote",
608
+ "unicode-ident",
609
+]
610
+
611
+[[package]]
612
+name = "syn"
613
+version = "3.0.5"
614
+source = "registry+https://github.com/rust-lang/crates.io-index"
615
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
616
+dependencies = [
617
+ "proc-macro2",
618
+ "quote",
619
+ "unicode-ident",
620
+]
621
+
622
+[[package]]
623
+name = "system-deps"
624
+version = "9.0.0"
625
+source = "registry+https://github.com/rust-lang/crates.io-index"
626
+checksum = "8a0dae2cbca1f0ff93795713cfe0a4c02f760e3c0b8ae2ab4ec4045808ff429f"
627
+dependencies = [
628
+ "cfg-expr",
629
+ "heck",
630
+ "pkg-config",
631
+ "toml",
632
+ "version-compare",
633
+]
634
+
635
+[[package]]
636
+name = "target-lexicon"
637
+version = "0.13.5"
638
+source = "registry+https://github.com/rust-lang/crates.io-index"
639
+checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
640
+
641
+[[package]]
642
+name = "tempfile"
643
+version = "3.27.0"
644
+source = "registry+https://github.com/rust-lang/crates.io-index"
645
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
646
+dependencies = [
647
+ "fastrand",
648
+ "getrandom",
649
+ "once_cell",
650
+ "rustix 1.1.4",
651
+ "windows-sys 0.61.2",
652
+]
653
+
654
+[[package]]
655
+name = "thiserror"
656
+version = "1.0.69"
657
+source = "registry+https://github.com/rust-lang/crates.io-index"
658
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
659
+dependencies = [
660
+ "thiserror-impl",
661
+]
662
+
663
+[[package]]
664
+name = "thiserror-impl"
665
+version = "1.0.69"
666
+source = "registry+https://github.com/rust-lang/crates.io-index"
667
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
668
+dependencies = [
669
+ "proc-macro2",
670
+ "quote",
671
+ "syn 2.0.119",
672
+]
673
+
674
+[[package]]
675
+name = "thread_local"
676
+version = "1.1.10"
677
+source = "registry+https://github.com/rust-lang/crates.io-index"
678
+checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
679
+dependencies = [
680
+ "cfg-if",
681
+]
682
+
683
+[[package]]
684
+name = "toml"
685
+version = "1.1.6+spec-1.1.0"
686
+source = "registry+https://github.com/rust-lang/crates.io-index"
687
+checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
688
+dependencies = [
689
+ "indexmap",
690
+ "serde_core",
691
+ "serde_spanned",
692
+ "toml_datetime",
693
+ "toml_parser",
694
+ "toml_writer",
695
+ "winnow",
696
+]
697
+
698
+[[package]]
699
+name = "toml_datetime"
700
+version = "1.1.1+spec-1.1.0"
701
+source = "registry+https://github.com/rust-lang/crates.io-index"
702
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
703
+dependencies = [
704
+ "serde_core",
705
+]
706
+
707
+[[package]]
708
+name = "toml_parser"
709
+version = "1.1.3+spec-1.1.0"
710
+source = "registry+https://github.com/rust-lang/crates.io-index"
711
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
712
+dependencies = [
713
+ "winnow",
714
+]
715
+
716
+[[package]]
717
+name = "toml_writer"
718
+version = "1.1.2+spec-1.1.0"
719
+source = "registry+https://github.com/rust-lang/crates.io-index"
720
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
721
+
722
+[[package]]
723
+name = "tracing"
724
+version = "0.1.44"
725
+source = "registry+https://github.com/rust-lang/crates.io-index"
726
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
727
+dependencies = [
728
+ "pin-project-lite",
729
+ "tracing-attributes",
730
+ "tracing-core",
731
+]
732
+
733
+[[package]]
734
+name = "tracing-attributes"
735
+version = "0.1.31"
736
+source = "registry+https://github.com/rust-lang/crates.io-index"
737
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
738
+dependencies = [
739
+ "proc-macro2",
740
+ "quote",
741
+ "syn 2.0.119",
742
+]
743
+
744
+[[package]]
745
+name = "tracing-core"
746
+version = "0.1.36"
747
+source = "registry+https://github.com/rust-lang/crates.io-index"
748
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
749
+dependencies = [
750
+ "once_cell",
751
+ "valuable",
752
+]
753
+
754
+[[package]]
755
+name = "tracing-log"
756
+version = "0.2.0"
757
+source = "registry+https://github.com/rust-lang/crates.io-index"
758
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
759
+dependencies = [
760
+ "log",
761
+ "once_cell",
762
+ "tracing-core",
763
+]
764
+
765
+[[package]]
766
+name = "tracing-subscriber"
767
+version = "0.3.23"
768
+source = "registry+https://github.com/rust-lang/crates.io-index"
769
+checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
770
+dependencies = [
771
+ "matchers",
772
+ "nu-ansi-term",
773
+ "once_cell",
774
+ "regex-automata",
775
+ "sharded-slab",
776
+ "smallvec",
777
+ "thread_local",
778
+ "tracing",
779
+ "tracing-core",
780
+ "tracing-log",
781
+]
782
+
783
+[[package]]
784
+name = "unicode-ident"
785
+version = "1.0.24"
786
+source = "registry+https://github.com/rust-lang/crates.io-index"
787
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
788
+
789
+[[package]]
790
+name = "valuable"
791
+version = "0.1.1"
792
+source = "registry+https://github.com/rust-lang/crates.io-index"
793
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
794
+
795
+[[package]]
796
+name = "version-compare"
797
+version = "0.2.1"
798
+source = "registry+https://github.com/rust-lang/crates.io-index"
799
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
800
+
801
+[[package]]
802
+name = "wasm-bindgen"
803
+version = "0.2.128"
804
+source = "registry+https://github.com/rust-lang/crates.io-index"
805
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
806
+dependencies = [
807
+ "cfg-if",
808
+ "once_cell",
809
+ "rustversion",
810
+ "wasm-bindgen-macro",
811
+ "wasm-bindgen-shared",
812
+]
813
+
814
+[[package]]
815
+name = "wasm-bindgen-macro"
816
+version = "0.2.128"
817
+source = "registry+https://github.com/rust-lang/crates.io-index"
818
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
819
+dependencies = [
820
+ "quote",
821
+ "wasm-bindgen-macro-support",
822
+]
823
+
824
+[[package]]
825
+name = "wasm-bindgen-macro-support"
826
+version = "0.2.128"
827
+source = "registry+https://github.com/rust-lang/crates.io-index"
828
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
829
+dependencies = [
830
+ "bumpalo",
831
+ "proc-macro2",
832
+ "quote",
833
+ "syn 3.0.5",
834
+ "wasm-bindgen-shared",
835
+]
836
+
837
+[[package]]
838
+name = "wasm-bindgen-shared"
839
+version = "0.2.128"
840
+source = "registry+https://github.com/rust-lang/crates.io-index"
841
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
842
+dependencies = [
843
+ "unicode-ident",
844
+]
845
+
846
+[[package]]
847
+name = "wayland-backend"
848
+version = "0.3.17"
849
+source = "registry+https://github.com/rust-lang/crates.io-index"
850
+checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
851
+dependencies = [
852
+ "cc",
853
+ "downcast-rs",
854
+ "rustix 1.1.4",
855
+ "smallvec",
856
+ "wayland-sys",
857
+]
858
+
859
+[[package]]
860
+name = "wayland-client"
861
+version = "0.31.15"
862
+source = "registry+https://github.com/rust-lang/crates.io-index"
863
+checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
864
+dependencies = [
865
+ "bitflags",
866
+ "rustix 1.1.4",
867
+ "wayland-backend",
868
+ "wayland-scanner",
869
+]
870
+
871
+[[package]]
872
+name = "wayland-protocols"
873
+version = "0.32.13"
874
+source = "registry+https://github.com/rust-lang/crates.io-index"
875
+checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
876
+dependencies = [
877
+ "bitflags",
878
+ "wayland-backend",
879
+ "wayland-client",
880
+ "wayland-scanner",
881
+]
882
+
883
+[[package]]
884
+name = "wayland-protocols-wlr"
885
+version = "0.3.12"
886
+source = "registry+https://github.com/rust-lang/crates.io-index"
887
+checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
888
+dependencies = [
889
+ "bitflags",
890
+ "wayland-backend",
891
+ "wayland-client",
892
+ "wayland-protocols",
893
+ "wayland-scanner",
894
+]
895
+
896
+[[package]]
897
+name = "wayland-scanner"
898
+version = "0.31.11"
899
+source = "registry+https://github.com/rust-lang/crates.io-index"
900
+checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
901
+dependencies = [
902
+ "proc-macro2",
903
+ "quick-xml",
904
+ "quote",
905
+]
906
+
907
+[[package]]
908
+name = "wayland-sys"
909
+version = "0.31.11"
910
+source = "registry+https://github.com/rust-lang/crates.io-index"
911
+checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
912
+dependencies = [
913
+ "pkg-config",
914
+]
915
+
916
+[[package]]
917
+name = "windows-core"
918
+version = "0.62.2"
919
+source = "registry+https://github.com/rust-lang/crates.io-index"
920
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
921
+dependencies = [
922
+ "windows-implement",
923
+ "windows-interface",
924
+ "windows-link",
925
+ "windows-result",
926
+ "windows-strings",
927
+]
928
+
929
+[[package]]
930
+name = "windows-implement"
931
+version = "0.60.2"
932
+source = "registry+https://github.com/rust-lang/crates.io-index"
933
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
934
+dependencies = [
935
+ "proc-macro2",
936
+ "quote",
937
+ "syn 2.0.119",
938
+]
939
+
940
+[[package]]
941
+name = "windows-interface"
942
+version = "0.59.3"
943
+source = "registry+https://github.com/rust-lang/crates.io-index"
944
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
945
+dependencies = [
946
+ "proc-macro2",
947
+ "quote",
948
+ "syn 2.0.119",
949
+]
950
+
951
+[[package]]
952
+name = "windows-link"
953
+version = "0.2.1"
954
+source = "registry+https://github.com/rust-lang/crates.io-index"
955
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
956
+
957
+[[package]]
958
+name = "windows-result"
959
+version = "0.4.1"
960
+source = "registry+https://github.com/rust-lang/crates.io-index"
961
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
962
+dependencies = [
963
+ "windows-link",
964
+]
965
+
966
+[[package]]
967
+name = "windows-strings"
968
+version = "0.5.1"
969
+source = "registry+https://github.com/rust-lang/crates.io-index"
970
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
971
+dependencies = [
972
+ "windows-link",
973
+]
974
+
975
+[[package]]
976
+name = "windows-sys"
977
+version = "0.59.0"
978
+source = "registry+https://github.com/rust-lang/crates.io-index"
979
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
980
+dependencies = [
981
+ "windows-targets",
982
+]
983
+
984
+[[package]]
985
+name = "windows-sys"
986
+version = "0.61.2"
987
+source = "registry+https://github.com/rust-lang/crates.io-index"
988
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
989
+dependencies = [
990
+ "windows-link",
991
+]
992
+
993
+[[package]]
994
+name = "windows-targets"
995
+version = "0.52.6"
996
+source = "registry+https://github.com/rust-lang/crates.io-index"
997
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
998
+dependencies = [
999
+ "windows_aarch64_gnullvm",
1000
+ "windows_aarch64_msvc",
1001
+ "windows_i686_gnu",
1002
+ "windows_i686_gnullvm",
1003
+ "windows_i686_msvc",
1004
+ "windows_x86_64_gnu",
1005
+ "windows_x86_64_gnullvm",
1006
+ "windows_x86_64_msvc",
1007
+]
1008
+
1009
+[[package]]
1010
+name = "windows_aarch64_gnullvm"
1011
+version = "0.52.6"
1012
+source = "registry+https://github.com/rust-lang/crates.io-index"
1013
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1014
+
1015
+[[package]]
1016
+name = "windows_aarch64_msvc"
1017
+version = "0.52.6"
1018
+source = "registry+https://github.com/rust-lang/crates.io-index"
1019
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1020
+
1021
+[[package]]
1022
+name = "windows_i686_gnu"
1023
+version = "0.52.6"
1024
+source = "registry+https://github.com/rust-lang/crates.io-index"
1025
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1026
+
1027
+[[package]]
1028
+name = "windows_i686_gnullvm"
1029
+version = "0.52.6"
1030
+source = "registry+https://github.com/rust-lang/crates.io-index"
1031
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1032
+
1033
+[[package]]
1034
+name = "windows_i686_msvc"
1035
+version = "0.52.6"
1036
+source = "registry+https://github.com/rust-lang/crates.io-index"
1037
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1038
+
1039
+[[package]]
1040
+name = "windows_x86_64_gnu"
1041
+version = "0.52.6"
1042
+source = "registry+https://github.com/rust-lang/crates.io-index"
1043
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1044
+
1045
+[[package]]
1046
+name = "windows_x86_64_gnullvm"
1047
+version = "0.52.6"
1048
+source = "registry+https://github.com/rust-lang/crates.io-index"
1049
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1050
+
1051
+[[package]]
1052
+name = "windows_x86_64_msvc"
1053
+version = "0.52.6"
1054
+source = "registry+https://github.com/rust-lang/crates.io-index"
1055
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1056
+
1057
+[[package]]
1058
+name = "winnow"
1059
+version = "1.0.4"
1060
+source = "registry+https://github.com/rust-lang/crates.io-index"
1061
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
0
1062
new file mode 100644
@@ -0,0 +1,26 @@
1
+[package]
2
+name = "pagan-panel"
3
+version = "0.1.0"
4
+edition = "2021"
5
+license = "MIT"
6
+description = "PaganDE: panel + pasek zadań (klient wlr-layer-shell) budowany na pagan-toolkit"
7
+publish = false
8
+
9
+[dependencies]
10
+# Rysowanie do SHM i prymitywy Cairo dzielimy z toolkit — jeden spójny wygląd.
11
+pagan-toolkit = { path = "../pagan-toolkit" }
12
+
13
+wayland-client = "0.31"
14
+# Protokoły WLR po stronie klienta: warstwy (panel) i lista okien (pasek zadań).
15
+wayland-protocols-wlr = { version = "0.3", features = ["client"] }
16
+
17
+# Pętla zdarzeń jak w kompozytorze: calloop + źródło Waylanda. Daje też timer
18
+# na zegar, bez pisania własnego polla.
19
+calloop = "0.13"
20
+calloop-wayland-source = "0.3"
21
+
22
+memmap2 = "0.9"
23
+cairo-rs = "0.22"
24
+chrono = { version = "0.4", default-features = false, features = ["clock"] }
25
+tracing = "0.1"
26
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
0
27
new file mode 100644
@@ -0,0 +1,791 @@
1
+//! PaganDE: panel + pasek zadań.
2
+//!
3
+//! PO CO osobny program: panel to zwykły klient Waylanda, ale **nie** okno —
4
+//! to powierzchnia warstwy (`wlr-layer-shell`), która rezerwuje pas ekranu
5
+//! (`exclusive_zone`), więc okna nie wchodzą pod pasek.
6
+//!
7
+//! Co robi:
8
+//! * lista okien z kompozytora (`wlr-foreign-toplevel-management`) → kafelki,
9
+//! * kliknięcie kafelka aktywuje okno (i przywraca zminimalizowane),
10
+//! środkowy przycisk zamyka okno,
11
+//! * przycisk „Pagan” uruchamia terminal (prosty launcher),
12
+//! * zegar po prawej (odświeżany timerem calloop).
13
+//!
14
+//! Wygląd: ciemny, półprzezroczysty pasek z zaokrąglonymi DOLNYMI rogami i akcentem
15
+//! `#4ea1ff` — spójny z motywem kompozytora. Rysujemy Cairo do bufora SHM, dzieląc
16
+//! `ShmBuffer` z `pagan-toolkit`.
17
+
18
+use std::{process::Command, time::Duration};
19
+
20
+use cairo::{Context, FontSlant, FontWeight, Format, ImageSurface};
21
+use calloop::{timer::TimeoutAction, EventLoop, LoopHandle};
22
+use calloop_wayland_source::WaylandSource;
23
+use chrono::Local;
24
+use memmap2::MmapMut;
25
+use tracing::{error, info, warn};
26
+
27
+use wayland_client::{
28
+ globals::{registry_queue_init, GlobalListContents},
29
+ protocol::{
30
+ wl_buffer, wl_callback, wl_compositor, wl_output, wl_pointer, wl_registry, wl_seat, wl_shm,
31
+ wl_shm_pool, wl_surface,
32
+ },
33
+ Connection, Dispatch, Proxy, QueueHandle,
34
+};
35
+use wayland_protocols_wlr::{
36
+ foreign_toplevel::v1::client::{
37
+ zwlr_foreign_toplevel_handle_v1 as ft_handle,
38
+ zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
39
+ zwlr_foreign_toplevel_manager_v1 as ft_manager,
40
+ zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
41
+ },
42
+ layer_shell::v1::client::{
43
+ zwlr_layer_shell_v1::{self as layer_shell, Layer, ZwlrLayerShellV1},
44
+ zwlr_layer_surface_v1::{self as layer_surface, Anchor, ZwlrLayerSurfaceV1},
45
+ },
46
+};
47
+
48
+use pagan_toolkit::shm::ShmBuffer;
49
+
50
+// --- Geometria paska ---------------------------------------------------------
51
+
52
+/// Wysokość panelu (px logiczne) — zarazem `exclusive_zone`.
53
+const BAR_HEIGHT: i32 = 36;
54
+/// Promień zaokrąglenia dolnych rogów.
55
+const BAR_RADIUS: f64 = 12.0;
56
+/// Margines boczny zawartości.
57
+const PAD: f64 = 10.0;
58
+/// Odstęp między elementami.
59
+const GAP: f64 = 8.0;
60
+/// Wysokość „pigułek” (kafelków/przycisków).
61
+const PILL_H: f64 = 24.0;
62
+/// Szerokość przycisku launchera.
63
+const LAUNCHER_W: f64 = 92.0;
64
+/// Maksymalna szerokość kafelka okna.
65
+const TASK_MAX_W: f64 = 190.0;
66
+/// Minimalna szerokość kafelka okna.
67
+const TASK_MIN_W: f64 = 96.0;
68
+
69
+// --- Kolory (spójne z motywem kompozytora) -----------------------------------
70
+
71
+const BG: (f64, f64, f64, f64) = (0.090, 0.098, 0.125, 0.94);
72
+const ACCENT: (f64, f64, f64) = (0.306, 0.631, 1.0);
73
+const TEXT: (f64, f64, f64) = (0.87, 0.89, 0.93);
74
+const TEXT_DIM: (f64, f64, f64) = (0.66, 0.69, 0.75);
75
+const TASK_BG: (f64, f64, f64, f64) = (1.0, 1.0, 1.0, 0.07);
76
+const TASK_HOVER_BG: (f64, f64, f64, f64) = (1.0, 1.0, 1.0, 0.13);
77
+const TASK_ACTIVE_BG: (f64, f64, f64, f64) = (0.306, 0.631, 1.0, 0.22);
78
+
79
+/// Jedno okno widziane przez protokół listy okien.
80
+struct Task {
81
+ handle: ZwlrForeignToplevelHandleV1,
82
+ title: String,
83
+ activated: bool,
84
+ minimized: bool,
85
+ /// Położenie i szerokość kafelka (liczone przy rysowaniu, używane do trafień).
86
+ layout: (f64, f64),
87
+}
88
+
89
+/// Stan panelu.
90
+struct Panel {
91
+ connection: Connection,
92
+ qh: QueueHandle<Panel>,
93
+
94
+ // Trzymane, by obiekty protokołu żyły tak długo jak panel (upuszczenie
95
+ // zniszczyłoby `wl_surface`/warstwę). Nie czytamy ich pól.
96
+ #[allow(dead_code)]
97
+ compositor: wl_compositor::WlCompositor,
98
+ #[allow(dead_code)]
99
+ layer_shell: ZwlrLayerShellV1,
100
+ #[allow(dead_code)]
101
+ manager: ZwlrForeignToplevelManagerV1,
102
+ shm: wl_shm::WlShm,
103
+ #[allow(dead_code)]
104
+ layer_surface: ZwlrLayerSurfaceV1,
105
+ surface: wl_surface::WlSurface,
106
+ seat: wl_seat::WlSeat,
107
+ pointer_handle: Option<wl_pointer::WlPointer>,
108
+ /// Callback klatki — trzymany, by kompozytor nie wysyłał zdarzeń do „martwego” obiektu.
109
+ frame_cb: Option<wl_callback::WlCallback>,
110
+
111
+ /// Rozmiar powierzchni z ostatniego `configure` (px logiczne).
112
+ size: (i32, i32),
113
+ tasks: Vec<Task>,
114
+ /// Indeks kafelka pod kursorem.
115
+ hover: Option<usize>,
116
+ /// Czy kursor jest nad przyciskiem launchera.
117
+ hover_launcher: bool,
118
+ /// Ostatnia pozycja kursora w układzie powierzchni.
119
+ pointer_pos: (f64, f64),
120
+ launcher_rect: (f64, f64, f64, f64),
121
+ clock: String,
122
+ dirty: bool,
123
+ closed: bool,
124
+
125
+ /// Bufory czekające na zwolnienie (podmieniane przy każdej klatce).
126
+ current: Option<ShmBuffer>,
127
+ stale: Vec<ShmBuffer>,
128
+}
129
+
130
+impl Panel {
131
+ fn update_clock(&mut self) {
132
+ self.clock = Local::now().format("%H:%M").to_string();
133
+ }
134
+
135
+ /// Rysuje pasek do bufora SHM i wysyła go kompozytorowi.
136
+ fn redraw(&mut self) {
137
+ let (width, height) = self.size;
138
+ if width <= 0 || height <= 0 || !self.dirty {
139
+ return;
140
+ }
141
+
142
+ let mut buffer = match ShmBuffer::new(&self.shm, &self.qh, width, height) {
143
+ Ok(buffer) => buffer,
144
+ Err(err) => {
145
+ error!(%err, "nie udało się zaalokować bufora panelu");
146
+ return;
147
+ }
148
+ };
149
+
150
+ self.paint(&mut buffer.mmap, width, height);
151
+
152
+ self.surface.attach(Some(&buffer.buffer), 0, 0);
153
+ self.surface.damage(0, 0, width, height);
154
+ self.surface.commit();
155
+ self.frame_cb = Some(self.surface.frame(&self.qh, ()));
156
+ let _ = self.connection.flush();
157
+
158
+ if let Some(previous) = self.current.replace(buffer) {
159
+ self.stale.push(previous);
160
+ }
161
+ self.dirty = false;
162
+ }
163
+
164
+ /// Cała grafika paska (Cairo).
165
+ fn paint(&mut self, mmap: &mut MmapMut, width: i32, height: i32) {
166
+ let stride = width * 4;
167
+ // Powierzchnia Cairo wskazuje wprost na pamięć bufora SHM (jak w toolkicie);
168
+ // `flush()` na końcu wypycha piksele.
169
+ let surface = unsafe {
170
+ ImageSurface::create_for_data_unsafe(
171
+ mmap.as_mut_ptr(),
172
+ Format::ARgb32,
173
+ width,
174
+ height,
175
+ stride,
176
+ )
177
+ }
178
+ .expect("powierzchnia Cairo na buforze SHM");
179
+ let ctx = Context::new(&surface).expect("kontekst Cairo");
180
+
181
+ // Tło z zaokrąglonymi dolnymi rogami.
182
+ ctx.set_source_rgba(BG.0, BG.1, BG.2, BG.3);
183
+ bar_path(&ctx, width as f64, height as f64, BAR_RADIUS);
184
+ ctx.fill().ok();
185
+
186
+ // Akcentowa linia pod paskiem.
187
+ ctx.set_source_rgba(ACCENT.0, ACCENT.1, ACCENT.2, 0.55);
188
+ ctx.set_line_width(1.0);
189
+ ctx.move_to(0.0, height as f64 - 0.5);
190
+ ctx.line_to(width as f64, height as f64 - 0.5);
191
+ ctx.stroke().ok();
192
+
193
+ ctx.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
194
+ ctx.set_font_size(13.0);
195
+
196
+ let pill_y = (height as f64 - PILL_H) / 2.0;
197
+
198
+ // 1) Launcher.
199
+ let launcher_bg = if self.hover_launcher {
200
+ TASK_HOVER_BG
201
+ } else {
202
+ TASK_BG
203
+ };
204
+ draw_pill(&ctx, PAD, pill_y, LAUNCHER_W, PILL_H, launcher_bg);
205
+ ctx.set_source_rgb(ACCENT.0, ACCENT.1, ACCENT.2);
206
+ ctx.set_font_size(14.0);
207
+ draw_centered_text(&ctx, "Pagan", PAD, pill_y, LAUNCHER_W, PILL_H);
208
+ ctx.set_font_size(13.0);
209
+ self.launcher_rect = (PAD, pill_y, LAUNCHER_W, PILL_H);
210
+
211
+ // 2) Kafelki okien.
212
+ let clock_w = 64.0;
213
+ let right_limit = width as f64 - PAD - clock_w - GAP * 2.0;
214
+ let mut x = PAD + LAUNCHER_W + GAP * 2.0;
215
+
216
+ for (index, task) in self.tasks.iter_mut().enumerate() {
217
+ let label = truncate(&ctx, &task.title, TASK_MAX_W - 16.0);
218
+ let pill_w = (text_width(&ctx, &label) + 18.0).clamp(TASK_MIN_W, TASK_MAX_W);
219
+ if x + pill_w > right_limit {
220
+ break; // brak miejsca — dalsze okna nie mieszczą się na pasku
221
+ }
222
+ task.layout = (x, pill_w);
223
+
224
+ let hovered = self.hover == Some(index);
225
+ let background = if task.activated {
226
+ TASK_ACTIVE_BG
227
+ } else if hovered {
228
+ TASK_HOVER_BG
229
+ } else {
230
+ TASK_BG
231
+ };
232
+ draw_pill(&ctx, x, pill_y, pill_w, PILL_H, background);
233
+
234
+ // Kropka stanu.
235
+ let dot = if task.activated {
236
+ (ACCENT.0, ACCENT.1, ACCENT.2, 1.0)
237
+ } else if task.minimized {
238
+ (0.60, 0.60, 0.65, 0.70)
239
+ } else {
240
+ (0.45, 0.47, 0.52, 1.0)
241
+ };
242
+ ctx.set_source_rgba(dot.0, dot.1, dot.2, dot.3);
243
+ ctx.arc(
244
+ x + 9.0,
245
+ pill_y + PILL_H / 2.0,
246
+ 2.6,
247
+ 0.0,
248
+ std::f64::consts::TAU,
249
+ );
250
+ ctx.fill().ok();
251
+
252
+ if task.activated {
253
+ ctx.set_source_rgb(TEXT.0, TEXT.1, TEXT.2);
254
+ } else {
255
+ ctx.set_source_rgb(TEXT_DIM.0, TEXT_DIM.1, TEXT_DIM.2);
256
+ }
257
+ draw_text(&ctx, &label, x + 16.0, pill_y + PILL_H / 2.0);
258
+
259
+ x += pill_w + GAP;
260
+ }
261
+
262
+ // 3) Zegar.
263
+ ctx.set_source_rgb(TEXT.0, TEXT.1, TEXT.2);
264
+ let clock = self.clock.clone();
265
+ let clock_x = width as f64 - PAD - text_width(&ctx, &clock);
266
+ draw_text(&ctx, &clock, clock_x, pill_y + PILL_H / 2.0);
267
+
268
+ surface.flush();
269
+ }
270
+
271
+ fn launcher_contains(&self, (px, py): (f64, f64)) -> bool {
272
+ let (x, y, w, h) = self.launcher_rect;
273
+ px >= x && px <= x + w && py >= y && py <= y + h
274
+ }
275
+
276
+ /// Kafelek pod danym punktem.
277
+ fn task_at(&self, (px, py): (f64, f64)) -> Option<usize> {
278
+ let pill_y = (self.size.1 as f64 - PILL_H) / 2.0;
279
+ self.tasks.iter().position(|task| {
280
+ let (x, w) = task.layout;
281
+ px >= x && px <= x + w && py >= pill_y && py <= pill_y + PILL_H
282
+ })
283
+ }
284
+
285
+ /// Przelicza podświetlenia i — jeśli się zmieniły — przerysowuje pasek.
286
+ fn refresh_hover(&mut self) {
287
+ let point = self.pointer_pos;
288
+ let new_task = self.task_at(point);
289
+ let new_launcher = self.launcher_contains(point);
290
+ if new_task != self.hover || new_launcher != self.hover_launcher {
291
+ self.hover = new_task;
292
+ self.hover_launcher = new_launcher;
293
+ self.dirty = true;
294
+ self.redraw();
295
+ }
296
+ }
297
+
298
+ fn activate_task(&self, index: usize) {
299
+ if let Some(task) = self.tasks.get(index) {
300
+ task.handle.activate(&self.seat);
301
+ }
302
+ }
303
+
304
+ fn close_task(&self, index: usize) {
305
+ if let Some(task) = self.tasks.get(index) {
306
+ task.handle.close();
307
+ }
308
+ }
309
+
310
+ /// Prosty launcher: uruchamia pierwszy dostępny terminal.
311
+ fn launch_terminal(&self) {
312
+ for candidate in [
313
+ "foot",
314
+ "pagan-terminal",
315
+ "xfce4-terminal",
316
+ "kitty",
317
+ "alacritty",
318
+ "xterm",
319
+ ] {
320
+ if which(candidate) {
321
+ match Command::new(candidate).spawn() {
322
+ Ok(_) => {
323
+ info!(terminal = candidate, "uruchomiono terminal");
324
+ return;
325
+ }
326
+ Err(err) => warn!(terminal = candidate, %err, "nie udało się uruchomić"),
327
+ }
328
+ }
329
+ }
330
+ warn!("nie znalazłem terminala (foot/kitty/xterm/…) — launcher nic nie zrobił");
331
+ }
332
+}
333
+
334
+// --- Pomocnicze rysowanie ----------------------------------------------------
335
+
336
+/// Ścieżka paska: pełna szerokość u góry, zaokrąglone DOLNE rogi.
337
+fn bar_path(ctx: &Context, width: f64, height: f64, radius: f64) {
338
+ let r = radius.min(height);
339
+ ctx.new_path();
340
+ ctx.move_to(0.0, 0.0);
341
+ ctx.line_to(width, 0.0);
342
+ ctx.line_to(width, height - r);
343
+ ctx.arc(width - r, height - r, r, 0.0, std::f64::consts::FRAC_PI_2);
344
+ ctx.arc(
345
+ r,
346
+ height - r,
347
+ r,
348
+ std::f64::consts::FRAC_PI_2,
349
+ std::f64::consts::PI,
350
+ );
351
+ ctx.close_path();
352
+}
353
+
354
+/// „Pigułka” (zaokrąglony prostokąt) wypełniona kolorem.
355
+fn draw_pill(ctx: &Context, x: f64, y: f64, w: f64, h: f64, color: (f64, f64, f64, f64)) {
356
+ let r = h / 2.0;
357
+ ctx.set_source_rgba(color.0, color.1, color.2, color.3);
358
+ ctx.new_path();
359
+ ctx.move_to(x + r, y);
360
+ ctx.line_to(x + w - r, y);
361
+ ctx.arc(x + w - r, y + r, r, -std::f64::consts::FRAC_PI_2, 0.0);
362
+ ctx.arc(x + w - r, y + h - r, r, 0.0, std::f64::consts::FRAC_PI_2);
363
+ ctx.line_to(x + r, y + h);
364
+ ctx.arc(
365
+ x + r,
366
+ y + h - r,
367
+ r,
368
+ std::f64::consts::FRAC_PI_2,
369
+ std::f64::consts::PI,
370
+ );
371
+ ctx.arc(
372
+ x + r,
373
+ y + r,
374
+ r,
375
+ std::f64::consts::PI,
376
+ std::f64::consts::PI * 1.5,
377
+ );
378
+ ctx.close_path();
379
+ ctx.fill().ok();
380
+}
381
+
382
+/// Tekst wyrównany do bazowej linii tak, by środek liter był na `center_y`.
383
+fn draw_text(ctx: &Context, text: &str, x: f64, center_y: f64) {
384
+ let Ok(extents) = ctx.text_extents(text) else {
385
+ return;
386
+ };
387
+ ctx.move_to(x, center_y - extents.y_bearing() / 2.0);
388
+ ctx.show_text(text).ok();
389
+}
390
+
391
+/// Tekst wyśrodkowany w prostokącie.
392
+fn draw_centered_text(ctx: &Context, text: &str, x: f64, y: f64, w: f64, h: f64) {
393
+ let Ok(extents) = ctx.text_extents(text) else {
394
+ return;
395
+ };
396
+ ctx.move_to(
397
+ x + (w - extents.x_advance()) / 2.0,
398
+ y + h / 2.0 - extents.y_bearing() / 2.0,
399
+ );
400
+ ctx.show_text(text).ok();
401
+}
402
+
403
+fn text_width(ctx: &Context, text: &str) -> f64 {
404
+ ctx.text_extents(text).map(|e| e.x_advance()).unwrap_or(0.0)
405
+}
406
+
407
+/// Skraca tytuł wielokropkiem, aż zmieści się w `max_width`.
408
+fn truncate(ctx: &Context, text: &str, max_width: f64) -> String {
409
+ if text_width(ctx, text) <= max_width {
410
+ return text.to_string();
411
+ }
412
+ let mut result = String::new();
413
+ for character in text.chars() {
414
+ let candidate = format!("{result}{character}…");
415
+ if text_width(ctx, &candidate) > max_width {
416
+ break;
417
+ }
418
+ result.push(character);
419
+ }
420
+ format!("{result}…")
421
+}
422
+
423
+/// Czy program jest w `PATH` (prosty launcher bez dodatkowych zależności).
424
+fn which(name: &str) -> bool {
425
+ std::env::var_os("PATH")
426
+ .map(|paths| std::env::split_paths(&paths).any(|dir| dir.join(name).is_file()))
427
+ .unwrap_or(false)
428
+}
429
+
430
+// --- Dispatch Waylanda -------------------------------------------------------
431
+
432
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for Panel {
433
+ fn event(
434
+ _state: &mut Self,
435
+ _proxy: &wl_registry::WlRegistry,
436
+ _event: wl_registry::Event,
437
+ _data: &GlobalListContents,
438
+ _conn: &Connection,
439
+ _qh: &QueueHandle<Self>,
440
+ ) {
441
+ }
442
+}
443
+
444
+/// Puste implementacje dla obiektów, których zdarzeń nie potrzebujemy.
445
+macro_rules! ignore_events {
446
+ ($($iface:ty),* $(,)?) => {
447
+ $(
448
+ impl Dispatch<$iface, ()> for Panel {
449
+ fn event(
450
+ _state: &mut Self,
451
+ _proxy: &$iface,
452
+ _event: <$iface as Proxy>::Event,
453
+ _data: &(),
454
+ _conn: &Connection,
455
+ _qh: &QueueHandle<Self>,
456
+ ) {
457
+ }
458
+ }
459
+ )*
460
+ };
461
+}
462
+
463
+ignore_events!(
464
+ wl_compositor::WlCompositor,
465
+ wl_shm::WlShm,
466
+ wl_shm_pool::WlShmPool,
467
+ wl_buffer::WlBuffer,
468
+ wl_output::WlOutput,
469
+);
470
+
471
+impl Dispatch<wl_callback::WlCallback, ()> for Panel {
472
+ fn event(
473
+ state: &mut Self,
474
+ _proxy: &wl_callback::WlCallback,
475
+ event: wl_callback::Event,
476
+ _data: &(),
477
+ _conn: &Connection,
478
+ _qh: &QueueHandle<Self>,
479
+ ) {
480
+ if let wl_callback::Event::Done { .. } = event {
481
+ // Kompozytor oddał klatkę — bufory z poprzednich klatek są wolne.
482
+ state.stale.clear();
483
+ state.frame_cb = None;
484
+ }
485
+ }
486
+}
487
+
488
+impl Dispatch<wl_surface::WlSurface, ()> for Panel {
489
+ fn event(
490
+ _state: &mut Self,
491
+ _proxy: &wl_surface::WlSurface,
492
+ _event: wl_surface::Event,
493
+ _data: &(),
494
+ _conn: &Connection,
495
+ _qh: &QueueHandle<Self>,
496
+ ) {
497
+ }
498
+}
499
+
500
+impl Dispatch<wl_seat::WlSeat, ()> for Panel {
501
+ fn event(
502
+ state: &mut Self,
503
+ seat: &wl_seat::WlSeat,
504
+ event: wl_seat::Event,
505
+ _data: &(),
506
+ _conn: &Connection,
507
+ qh: &QueueHandle<Self>,
508
+ ) {
509
+ if let wl_seat::Event::Capabilities { capabilities: caps } = event {
510
+ // `caps` to `WEnum` (może nieść nieznaną wartość od nowszego serwera).
511
+ let has_pointer = caps
512
+ .into_result()
513
+ .map(|caps| caps.contains(wl_seat::Capability::Pointer))
514
+ .unwrap_or(false);
515
+ if has_pointer && state.pointer_handle.is_none() {
516
+ state.pointer_handle = Some(seat.get_pointer(qh, ()));
517
+ }
518
+ }
519
+ }
520
+}
521
+
522
+impl Dispatch<wl_pointer::WlPointer, ()> for Panel {
523
+ fn event(
524
+ state: &mut Self,
525
+ _pointer: &wl_pointer::WlPointer,
526
+ event: wl_pointer::Event,
527
+ _data: &(),
528
+ _conn: &Connection,
529
+ _qh: &QueueHandle<Self>,
530
+ ) {
531
+ match event {
532
+ wl_pointer::Event::Enter {
533
+ surface_x,
534
+ surface_y,
535
+ ..
536
+ } => {
537
+ state.pointer_pos = (surface_x, surface_y);
538
+ state.refresh_hover();
539
+ }
540
+ wl_pointer::Event::Leave { .. } => {
541
+ if state.hover.take().is_some() || state.hover_launcher {
542
+ state.hover_launcher = false;
543
+ state.dirty = true;
544
+ state.redraw();
545
+ }
546
+ }
547
+ wl_pointer::Event::Motion {
548
+ surface_x,
549
+ surface_y,
550
+ ..
551
+ } => {
552
+ state.pointer_pos = (surface_x, surface_y);
553
+ state.refresh_hover();
554
+ }
555
+ wl_pointer::Event::Button {
556
+ button,
557
+ state: button_state,
558
+ ..
559
+ } => {
560
+ // BTN_LEFT = 0x110, BTN_MIDDLE = 0x112 (linux/input-event-codes.h)
561
+ let pressed = button_state
562
+ .into_result()
563
+ .map(|state| state == wl_pointer::ButtonState::Pressed)
564
+ .unwrap_or(false);
565
+ if pressed {
566
+ let point = state.pointer_pos;
567
+ if state.launcher_contains(point) {
568
+ state.launch_terminal();
569
+ } else if let Some(index) = state.task_at(point) {
570
+ match button {
571
+ 0x112 => state.close_task(index),
572
+ _ => state.activate_task(index),
573
+ }
574
+ }
575
+ }
576
+ }
577
+ _ => {}
578
+ }
579
+ }
580
+}
581
+
582
+impl Dispatch<ZwlrLayerShellV1, ()> for Panel {
583
+ fn event(
584
+ _state: &mut Self,
585
+ _proxy: &ZwlrLayerShellV1,
586
+ _event: layer_shell::Event,
587
+ _data: &(),
588
+ _conn: &Connection,
589
+ _qh: &QueueHandle<Self>,
590
+ ) {
591
+ }
592
+}
593
+
594
+impl Dispatch<ZwlrLayerSurfaceV1, ()> for Panel {
595
+ fn event(
596
+ state: &mut Self,
597
+ layer_surface: &ZwlrLayerSurfaceV1,
598
+ event: layer_surface::Event,
599
+ _data: &(),
600
+ _conn: &Connection,
601
+ _qh: &QueueHandle<Self>,
602
+ ) {
603
+ match event {
604
+ layer_surface::Event::Configure {
605
+ serial,
606
+ width,
607
+ height,
608
+ } => {
609
+ // Kompozytor mówi, jak duża ma być powierzchnia — trzeba potwierdzić.
610
+ layer_surface.ack_configure(serial);
611
+ state.size = (width as i32, height as i32);
612
+ info!(width, height, "panel skonfigurowany");
613
+ state.dirty = true;
614
+ state.redraw();
615
+ }
616
+ layer_surface::Event::Closed => {
617
+ info!("kompozytor zamknął powierzchnię panelu — kończę");
618
+ state.closed = true;
619
+ }
620
+ _ => {}
621
+ }
622
+ }
623
+}
624
+
625
+impl Dispatch<ZwlrForeignToplevelManagerV1, ()> for Panel {
626
+ // Zdarzenie `toplevel` tworzy u klienta nowy obiekt uchwytu — trzeba wskazać
627
+ // jego typ, inaczej `wayland-client` panikuje.
628
+ wayland_client::event_created_child!(Panel, ZwlrForeignToplevelManagerV1, [
629
+ ft_manager::EVT_TOPLEVEL_OPCODE => (ZwlrForeignToplevelHandleV1, ())
630
+ ]);
631
+
632
+ fn event(
633
+ state: &mut Self,
634
+ _manager: &ZwlrForeignToplevelManagerV1,
635
+ event: ft_manager::Event,
636
+ _data: &(),
637
+ _conn: &Connection,
638
+ _qh: &QueueHandle<Self>,
639
+ ) {
640
+ match event {
641
+ ft_manager::Event::Toplevel { toplevel } => {
642
+ state.tasks.push(Task {
643
+ handle: toplevel,
644
+ title: String::new(),
645
+ activated: false,
646
+ minimized: false,
647
+ layout: (0.0, 0.0),
648
+ });
649
+ }
650
+ ft_manager::Event::Finished => info!("kompozytor zakończył listę okien"),
651
+ _ => {}
652
+ }
653
+ }
654
+}
655
+
656
+impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for Panel {
657
+ fn event(
658
+ state: &mut Self,
659
+ handle: &ZwlrForeignToplevelHandleV1,
660
+ event: ft_handle::Event,
661
+ _data: &(),
662
+ _conn: &Connection,
663
+ _qh: &QueueHandle<Self>,
664
+ ) {
665
+ let Some(index) = state
666
+ .tasks
667
+ .iter()
668
+ .position(|task| task.handle.id() == handle.id())
669
+ else {
670
+ return;
671
+ };
672
+
673
+ match event {
674
+ ft_handle::Event::Title { title } => state.tasks[index].title = title,
675
+ ft_handle::Event::State { state: bytes } => {
676
+ let mut activated = false;
677
+ let mut minimized = false;
678
+ for chunk in bytes.chunks_exact(4) {
679
+ match u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) {
680
+ 1 => minimized = true,
681
+ 2 => activated = true,
682
+ _ => {}
683
+ }
684
+ }
685
+ state.tasks[index].activated = activated;
686
+ state.tasks[index].minimized = minimized;
687
+ }
688
+ ft_handle::Event::Done => {
689
+ // `done` zamyka porcję zmian — teraz odświeżamy pasek.
690
+ state.dirty = true;
691
+ state.redraw();
692
+ }
693
+ ft_handle::Event::Closed => {
694
+ state.tasks.retain(|task| task.handle.id() != handle.id());
695
+ state.dirty = true;
696
+ state.redraw();
697
+ }
698
+ _ => {}
699
+ }
700
+ }
701
+}
702
+
703
+// --- Start -------------------------------------------------------------------
704
+
705
+fn main() -> Result<(), Box<dyn std::error::Error>> {
706
+ tracing_subscriber::fmt()
707
+ .with_env_filter(
708
+ tracing_subscriber::EnvFilter::try_from_default_env()
709
+ .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
710
+ )
711
+ .compact()
712
+ .init();
713
+
714
+ let connection = Connection::connect_to_env()?;
715
+ let (globals, queue) = registry_queue_init::<Panel>(&connection)?;
716
+ let qh = queue.handle();
717
+ info!("panel PaganDE startuje");
718
+
719
+ let compositor = globals.bind::<wl_compositor::WlCompositor, Panel, ()>(&qh, 4..=6, ())?;
720
+ let shm = globals.bind::<wl_shm::WlShm, Panel, ()>(&qh, 1..=1, ())?;
721
+ let layer_shell = globals.bind::<ZwlrLayerShellV1, Panel, ()>(&qh, 1..=4, ())?;
722
+ let manager = globals.bind::<ZwlrForeignToplevelManagerV1, Panel, ()>(&qh, 1..=3, ())?;
723
+ let seat = globals.bind::<wl_seat::WlSeat, Panel, ()>(&qh, 1..=7, ())?;
724
+
725
+ let surface = compositor.create_surface(&qh, ());
726
+ let layer_surface = layer_shell.get_layer_surface(
727
+ &surface,
728
+ None, // wyjście wybiera kompozytor
729
+ Layer::Top,
730
+ "pagan-panel".to_string(),
731
+ &qh,
732
+ (),
733
+ );
734
+
735
+ // Rezerwujemy pas u góry ekranu — okna nie będą pod niego wchodzić.
736
+ layer_surface.set_anchor(Anchor::Top | Anchor::Left | Anchor::Right);
737
+ layer_surface.set_exclusive_zone(BAR_HEIGHT);
738
+ // Szerokość 0 = „rozciągnij” (bo zakotwiczone z lewej i prawej), wysokość stała.
739
+ layer_surface.set_size(0, BAR_HEIGHT as u32);
740
+ surface.commit();
741
+
742
+ let mut panel = Panel {
743
+ connection: connection.clone(),
744
+ qh: qh.clone(),
745
+ compositor,
746
+ layer_shell,
747
+ manager,
748
+ shm,
749
+ layer_surface,
750
+ surface,
751
+ seat,
752
+ pointer_handle: None,
753
+ frame_cb: None,
754
+ size: (0, 0),
755
+ tasks: Vec::new(),
756
+ hover: None,
757
+ hover_launcher: false,
758
+ pointer_pos: (0.0, 0.0),
759
+ launcher_rect: (0.0, 0.0, 0.0, 0.0),
760
+ clock: String::new(),
761
+ dirty: false,
762
+ closed: false,
763
+ current: None,
764
+ stale: Vec::new(),
765
+ };
766
+ panel.update_clock();
767
+
768
+ let mut event_loop: EventLoop<Panel> = EventLoop::try_new()?;
769
+ let loop_handle: LoopHandle<'_, Panel> = event_loop.handle();
770
+ WaylandSource::new(connection, queue).insert(loop_handle.clone())?;
771
+
772
+ // Zegar: odświeżamy co 30 s (i tak zmienia się raz na minutę).
773
+ loop_handle.insert_source(
774
+ calloop::timer::Timer::from_duration(Duration::from_secs(30)),
775
+ |_, _, data: &mut Panel| {
776
+ data.update_clock();
777
+ data.dirty = true;
778
+ data.redraw();
779
+ TimeoutAction::ToDuration(Duration::from_secs(30))
780
+ },
781
+ )?;
782
+
783
+ info!("panel gotowy (warstwa górna, {BAR_HEIGHT} px)");
784
+ while !panel.closed {
785
+ if let Err(err) = event_loop.dispatch(Some(Duration::from_millis(250)), &mut panel) {
786
+ error!(%err, "błąd pętli zdarzeń panelu");
787
+ break;
788
+ }
789
+ }
790
+ Ok(())
791
+}