Skip to main content

max / shop

First pixel: SCTK window + wgpu clear color crates/shop-wayland: renderer-agnostic Window over SCTK 0.21 (compositor, xdg-shell, output, seat, registry via delegate_dispatch2). Blocking-dispatch API drains a Pending queue of Resized/CloseRequested events. crates/shop: wgpu 30 init (Vulkan backend, high-perf adapter), surface created from wayland raw handles, clear-color draw on each configure. Verified on fw13/Wayland: AMD Radeon 840M via RADV GFX1152, window opens, event loop reached. Reference: SCTK 0.21's examples/wgpu.rs (MIT).
Co-Authored-By
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-23 18:56 UTC
Signed with PGP, not checked
Commit: 05ac03eb5f64f1b338e8544d6ab9255b57ad7c16
Parent: 79b868b
6 files changed, +906 insertions, -5 deletions
M Cargo.lock +493
@@ -2,10 +2,1341 @@
2 2 # It is not intended for manual editing.
3 3 version = 4
4 4
5 + [[package]]
6 + name = "aho-corasick"
7 + version = "1.1.4"
8 + source = "registry+https://github.com/rust-lang/crates.io-index"
9 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10 + dependencies = [
11 + "memchr",
12 + ]
13 +
14 + [[package]]
15 + name = "allocator-api2"
16 + version = "0.2.21"
17 + source = "registry+https://github.com/rust-lang/crates.io-index"
18 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
19 +
20 + [[package]]
21 + name = "android_system_properties"
22 + version = "0.1.5"
23 + source = "registry+https://github.com/rust-lang/crates.io-index"
24 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
25 + dependencies = [
26 + "libc",
27 + ]
28 +
29 + [[package]]
30 + name = "anyhow"
31 + version = "1.0.104"
32 + source = "registry+https://github.com/rust-lang/crates.io-index"
33 + checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
34 +
35 + [[package]]
36 + name = "arrayvec"
37 + version = "0.7.8"
38 + source = "registry+https://github.com/rust-lang/crates.io-index"
39 + checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
40 +
41 + [[package]]
42 + name = "ash"
43 + version = "0.38.0+1.3.281"
44 + source = "registry+https://github.com/rust-lang/crates.io-index"
45 + checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
46 + dependencies = [
47 + "libloading",
48 + ]
49 +
50 + [[package]]
51 + name = "autocfg"
52 + version = "1.5.1"
53 + source = "registry+https://github.com/rust-lang/crates.io-index"
54 + checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
55 +
56 + [[package]]
57 + name = "bit-set"
58 + version = "0.10.0"
59 + source = "registry+https://github.com/rust-lang/crates.io-index"
60 + checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d"
61 + dependencies = [
62 + "bit-vec",
63 + ]
64 +
65 + [[package]]
66 + name = "bit-vec"
67 + version = "0.9.1"
68 + source = "registry+https://github.com/rust-lang/crates.io-index"
69 + checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
70 +
71 + [[package]]
72 + name = "bitflags"
73 + version = "2.13.1"
74 + source = "registry+https://github.com/rust-lang/crates.io-index"
75 + checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
76 +
77 + [[package]]
78 + name = "bytemuck"
79 + version = "1.25.2"
80 + source = "registry+https://github.com/rust-lang/crates.io-index"
81 + checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
82 + dependencies = [
83 + "bytemuck_derive",
84 + ]
85 +
86 + [[package]]
87 + name = "bytemuck_derive"
88 + version = "1.11.0"
89 + source = "registry+https://github.com/rust-lang/crates.io-index"
90 + checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5"
91 + dependencies = [
92 + "proc-macro2",
93 + "quote",
94 + "syn 2.0.119",
95 + ]
96 +
97 + [[package]]
98 + name = "calloop"
99 + version = "0.14.4"
100 + source = "registry+https://github.com/rust-lang/crates.io-index"
101 + checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
102 + dependencies = [
103 + "bitflags",
104 + "polling",
105 + "rustix",
106 + "slab",
107 + "tracing",
108 + ]
109 +
110 + [[package]]
111 + name = "calloop-wayland-source"
112 + version = "0.4.1"
113 + source = "registry+https://github.com/rust-lang/crates.io-index"
114 + checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
115 + dependencies = [
116 + "calloop",
117 + "rustix",
118 + "wayland-backend",
119 + "wayland-client",
120 + ]
121 +
122 + [[package]]
123 + name = "cc"
124 + version = "1.3.0"
125 + source = "registry+https://github.com/rust-lang/crates.io-index"
126 + checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
127 + dependencies = [
128 + "find-msvc-tools",
129 + "shlex",
130 + ]
131 +
132 + [[package]]
133 + name = "cfg-if"
134 + version = "1.0.4"
135 + source = "registry+https://github.com/rust-lang/crates.io-index"
136 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
137 +
138 + [[package]]
139 + name = "cfg_aliases"
140 + version = "0.2.2"
141 + source = "registry+https://github.com/rust-lang/crates.io-index"
142 + checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
143 +
144 + [[package]]
145 + name = "codespan-reporting"
146 + version = "0.13.1"
147 + source = "registry+https://github.com/rust-lang/crates.io-index"
148 + checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
149 + dependencies = [
150 + "unicode-width",
151 + ]
152 +
153 + [[package]]
154 + name = "concurrent-queue"
155 + version = "2.5.0"
156 + source = "registry+https://github.com/rust-lang/crates.io-index"
157 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
158 + dependencies = [
159 + "crossbeam-utils",
160 + ]
161 +
162 + [[package]]
163 + name = "crossbeam-utils"
164 + version = "0.8.22"
165 + source = "registry+https://github.com/rust-lang/crates.io-index"
166 + checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
167 +
168 + [[package]]
169 + name = "crunchy"
170 + version = "0.2.4"
171 + source = "registry+https://github.com/rust-lang/crates.io-index"
172 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
173 +
174 + [[package]]
175 + name = "cursor-icon"
176 + version = "1.2.0"
177 + source = "registry+https://github.com/rust-lang/crates.io-index"
178 + checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
179 +
180 + [[package]]
181 + name = "dispatch2"
182 + version = "0.3.1"
183 + source = "registry+https://github.com/rust-lang/crates.io-index"
184 + checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
185 + dependencies = [
186 + "bitflags",
187 + "objc2",
188 + ]
189 +
190 + [[package]]
191 + name = "dlib"
192 + version = "0.5.3"
193 + source = "registry+https://github.com/rust-lang/crates.io-index"
194 + checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
195 + dependencies = [
196 + "libloading",
197 + ]
198 +
199 + [[package]]
200 + name = "document-features"
201 + version = "0.2.12"
202 + source = "registry+https://github.com/rust-lang/crates.io-index"
203 + checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
204 + dependencies = [
205 + "litrs",
206 + ]
207 +
208 + [[package]]
209 + name = "downcast-rs"
210 + version = "1.2.1"
211 + source = "registry+https://github.com/rust-lang/crates.io-index"
212 + checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
213 +
214 + [[package]]
215 + name = "equivalent"
216 + version = "1.0.2"
217 + source = "registry+https://github.com/rust-lang/crates.io-index"
218 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
219 +
220 + [[package]]
221 + name = "errno"
222 + version = "0.3.14"
223 + source = "registry+https://github.com/rust-lang/crates.io-index"
224 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
225 + dependencies = [
226 + "libc",
227 + "windows-sys",
228 + ]
229 +
230 + [[package]]
231 + name = "find-msvc-tools"
232 + version = "0.1.9"
233 + source = "registry+https://github.com/rust-lang/crates.io-index"
234 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
235 +
236 + [[package]]
237 + name = "foldhash"
238 + version = "0.2.0"
239 + source = "registry+https://github.com/rust-lang/crates.io-index"
240 + checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
241 +
242 + [[package]]
243 + name = "gpu-allocator"
244 + version = "0.28.0"
245 + source = "registry+https://github.com/rust-lang/crates.io-index"
246 + checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
247 + dependencies = [
248 + "ash",
249 + "hashbrown 0.16.1",
250 + "log",
251 + "presser",
252 + "thiserror",
253 + ]
254 +
255 + [[package]]
256 + name = "half"
257 + version = "2.7.1"
258 + source = "registry+https://github.com/rust-lang/crates.io-index"
259 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
260 + dependencies = [
261 + "cfg-if",
262 + "crunchy",
263 + "num-traits",
264 + "zerocopy",
265 + ]
266 +
267 + [[package]]
268 + name = "hashbrown"
269 + version = "0.16.1"
270 + source = "registry+https://github.com/rust-lang/crates.io-index"
271 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
272 + dependencies = [
273 + "allocator-api2",
274 + "equivalent",
275 + "foldhash",
276 + ]
277 +
278 + [[package]]
279 + name = "hashbrown"
280 + version = "0.17.1"
281 + source = "registry+https://github.com/rust-lang/crates.io-index"
282 + checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
283 + dependencies = [
284 + "foldhash",
285 + ]
286 +
287 + [[package]]
288 + name = "hermit-abi"
289 + version = "0.5.2"
290 + source = "registry+https://github.com/rust-lang/crates.io-index"
291 + checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
292 +
293 + [[package]]
294 + name = "indexmap"
295 + version = "2.14.0"
296 + source = "registry+https://github.com/rust-lang/crates.io-index"
297 + checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
298 + dependencies = [
299 + "equivalent",
300 + "hashbrown 0.17.1",
301 + ]
302 +
5 303 [[package]]
6 304 name = "kitty-graphics"
7 305 version = "0.0.0"
8 306
307 + [[package]]
308 + name = "lazy_static"
309 + version = "1.5.0"
310 + source = "registry+https://github.com/rust-lang/crates.io-index"
311 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
312 +
313 + [[package]]
314 + name = "libc"
315 + version = "0.2.189"
316 + source = "registry+https://github.com/rust-lang/crates.io-index"
317 + checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
318 +
319 + [[package]]
320 + name = "libloading"
321 + version = "0.8.9"
322 + source = "registry+https://github.com/rust-lang/crates.io-index"
323 + checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
324 + dependencies = [
325 + "cfg-if",
326 + "windows-link",
327 + ]
328 +
329 + [[package]]
330 + name = "libm"
331 + version = "0.2.16"
332 + source = "registry+https://github.com/rust-lang/crates.io-index"
333 + checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
334 +
335 + [[package]]
336 + name = "linux-raw-sys"
337 + version = "0.12.1"
338 + source = "registry+https://github.com/rust-lang/crates.io-index"
339 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
340 +
341 + [[package]]
342 + name = "litrs"
343 + version = "1.0.0"
344 + source = "registry+https://github.com/rust-lang/crates.io-index"
345 + checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
346 +
347 + [[package]]
348 + name = "lock_api"
349 + version = "0.4.14"
350 + source = "registry+https://github.com/rust-lang/crates.io-index"
351 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
352 + dependencies = [
353 + "scopeguard",
354 + ]
355 +
356 + [[package]]
357 + name = "log"
358 + version = "0.4.33"
359 + source = "registry+https://github.com/rust-lang/crates.io-index"
360 + checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
361 +
362 + [[package]]
363 + name = "matchers"
364 + version = "0.2.0"
365 + source = "registry+https://github.com/rust-lang/crates.io-index"
366 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
367 + dependencies = [
368 + "regex-automata",
369 + ]
370 +
371 + [[package]]
372 + name = "memchr"
373 + version = "2.8.3"
374 + source = "registry+https://github.com/rust-lang/crates.io-index"
375 + checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
376 +
377 + [[package]]
378 + name = "memmap2"
379 + version = "0.9.11"
380 + source = "registry+https://github.com/rust-lang/crates.io-index"
381 + checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
382 + dependencies = [
383 + "libc",
384 + ]
385 +
386 + [[package]]
387 + name = "naga"
388 + version = "30.0.0"
389 + source = "registry+https://github.com/rust-lang/crates.io-index"
390 + checksum = "23bf0a141a9ab6f07dbb492db53245e464bc9db42f407772d9ae03d83a2c1033"
391 + dependencies = [
392 + "arrayvec",
393 + "bit-set",
394 + "bitflags",
395 + "cfg-if",
396 + "cfg_aliases",
397 + "codespan-reporting",
398 + "half",
399 + "hashbrown 0.17.1",
400 + "indexmap",
401 + "libm",
402 + "log",
403 + "naga-types",
404 + "num-traits",
405 + "once_cell",
406 + "rustc-hash",
407 + "spirv",
408 + "thiserror",
409 + "unicode-ident",
410 + ]
411 +
412 + [[package]]
413 + name = "naga-types"
414 + version = "30.0.0"
415 + source = "registry+https://github.com/rust-lang/crates.io-index"
416 + checksum = "658200ddc25c6c7b860747516d132d1b284c0fafb7a380233acee9a72fb30e11"
417 + dependencies = [
418 + "hashbrown 0.17.1",
419 + "indexmap",
420 + "rustc-hash",
421 + "thiserror",
422 + ]
423 +
424 + [[package]]
425 + name = "nu-ansi-term"
426 + version = "0.50.3"
427 + source = "registry+https://github.com/rust-lang/crates.io-index"
428 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
429 + dependencies = [
430 + "windows-sys",
431 + ]
432 +
433 + [[package]]
434 + name = "num-traits"
435 + version = "0.2.19"
436 + source = "registry+https://github.com/rust-lang/crates.io-index"
437 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
438 + dependencies = [
439 + "autocfg",
440 + "libm",
441 + ]
442 +
443 + [[package]]
444 + name = "objc2"
445 + version = "0.6.4"
446 + source = "registry+https://github.com/rust-lang/crates.io-index"
447 + checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
448 + dependencies = [
449 + "objc2-encode",
450 + ]
451 +
452 + [[package]]
453 + name = "objc2-core-foundation"
454 + version = "0.3.2"
455 + source = "registry+https://github.com/rust-lang/crates.io-index"
456 + checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
457 + dependencies = [
458 + "bitflags",
459 + "dispatch2",
460 + "objc2",
461 + ]
462 +
463 + [[package]]
464 + name = "objc2-encode"
465 + version = "4.1.0"
466 + source = "registry+https://github.com/rust-lang/crates.io-index"
467 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
468 +
469 + [[package]]
470 + name = "objc2-foundation"
471 + version = "0.3.2"
472 + source = "registry+https://github.com/rust-lang/crates.io-index"
473 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
474 + dependencies = [
475 + "bitflags",
476 + "objc2",
477 + "objc2-core-foundation",
478 + ]
479 +
480 + [[package]]
481 + name = "objc2-metal"
482 + version = "0.3.2"
483 + source = "registry+https://github.com/rust-lang/crates.io-index"
484 + checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
485 + dependencies = [
486 + "bitflags",
487 + "objc2",
488 + "objc2-foundation",
489 + ]
490 +
491 + [[package]]
492 + name = "objc2-quartz-core"
493 + version = "0.3.2"
494 + source = "registry+https://github.com/rust-lang/crates.io-index"
495 + checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
496 + dependencies = [
497 + "bitflags",
498 + "objc2",
499 + "objc2-core-foundation",
500 + "objc2-foundation",
501 + "objc2-metal",
Lines truncated
M Cargo.toml +13 -1
@@ -3,8 +3,21 @@
3 3 members = [
4 4 "crates/shop",
5 5 "crates/kitty-graphics",
6 + "crates/shop-wayland",
6 7 ]
7 8
9 + [workspace.dependencies]
10 + smithay-client-toolkit = { version = "0.21", features = ["system"] }
11 + wayland-client = { version = "0.31", features = ["system"] }
12 + calloop = "0.14"
13 + calloop-wayland-source = "0.4"
14 + wgpu = { version = "30", default-features = false, features = ["wgsl", "vulkan"] }
15 + raw-window-handle = "0.6"
16 + pollster = "1"
17 + anyhow = "1"
18 + tracing = "0.1"
19 + tracing-subscriber = { version = "0.3", features = ["env-filter"] }
20 +
8 21 [workspace.package]
9 22 edition = "2024"
10 23 rust-version = "1.85"
@@ -14,7 +27,6 @@
14 27
15 28 [workspace.lints.rust]
16 29 unsafe_code = "warn"
17 - missing_debug_implementations = "warn"
18 30
19 31 [workspace.lints.clippy]
20 32 pedantic = { level = "warn", priority = -1 }
@@ -16,3 +16,9 @@
16 16 path = "src/main.rs"
17 17
18 18 [dependencies]
19 + shop-wayland = { path = "../shop-wayland" }
20 + wgpu.workspace = true
21 + pollster.workspace = true
22 + anyhow.workspace = true
23 + tracing.workspace = true
24 + tracing-subscriber.workspace = true
@@ -1,8 +1,130 @@
1 1 //! shop — Wayland terminal emulator.
2 2 //!
3 - //! <!-- wiki: shop -->
4 - //! Design hub: `~/Wiki/shop-overview.md`.
3 + //! First-pixel milestone: SCTK window + wgpu clear color driven by the
4 + //! shop-wayland event queue. No PTY, no grid, no text yet.
5 + //!
6 + //! Run on a Wayland compositor (Sway on fw13):
7 + //! ```text
8 + //! RUST_LOG=info cargo run -p shop
9 + //! ```
5 10
6 - fn main() {
7 - println!("shop: bootstrap");
11 + use shop_wayland::{Pending, Window, WindowSpec};
12 + use tracing::{info, warn};
13 +
14 + const INITIAL: (u32, u32) = (960, 540);
15 + const CLEAR: wgpu::Color = wgpu::Color {
16 + // Alloy Akari palette — placeholder mid-value blue-grey until TOKENS is wired in.
17 + r: 0.043,
18 + g: 0.055,
19 + b: 0.086,
20 + a: 1.0,
21 + };
22 +
23 + fn main() -> anyhow::Result<()> {
24 + tracing_subscriber::fmt()
25 + .with_env_filter(
26 + tracing_subscriber::EnvFilter::try_from_default_env()
27 + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
28 + )
29 + .init();
30 +
31 + let mut window = Window::new(WindowSpec {
32 + title: "shop",
33 + app_id: "dev.makecreative.shop",
34 + min_size: (320, 240),
35 + initial_size: INITIAL,
36 + })?;
37 +
38 + let (raw_display, raw_window) = window.raw_handles();
39 + let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle();
40 + instance_desc.backends = wgpu::Backends::VULKAN;
41 + let instance = wgpu::Instance::new(instance_desc);
42 +
43 + // SAFETY: raw_display / raw_window reference wayland pointers owned by
44 + // `window`; both stay alive until `window` is dropped at the end of main.
45 + #[allow(unsafe_code)]
46 + let surface = unsafe {
47 + instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
48 + raw_display_handle: Some(raw_display),
49 + raw_window_handle: raw_window,
50 + })
51 + }?;
52 +
53 + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
54 + power_preference: wgpu::PowerPreference::HighPerformance,
55 + compatible_surface: Some(&surface),
56 + ..Default::default()
57 + }))?;
58 + info!(adapter = %adapter.get_info().name, "wgpu adapter picked");
59 +
60 + let (device, queue) =
61 + pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default()))?;
62 +
63 + let caps = surface.get_capabilities(&adapter);
64 + let format = caps.formats[0];
65 + let mut config = wgpu::SurfaceConfiguration {
66 + usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
67 + format,
68 + color_space: wgpu::SurfaceColorSpace::Auto,
69 + view_formats: vec![format],
70 + alpha_mode: wgpu::CompositeAlphaMode::Auto,
71 + width: INITIAL.0,
72 + height: INITIAL.1,
73 + desired_maximum_frame_latency: 2,
74 + present_mode: wgpu::PresentMode::Mailbox,
75 + };
76 +
77 + info!("entering event loop");
78 + while !window.should_exit() {
79 + for change in window.dispatch()? {
80 + match change {
81 + Pending::Resized { width, height } => {
82 + config.width = width;
83 + config.height = height;
84 + surface.configure(&device, &config);
85 + if let Err(e) = draw(&surface, &device, &queue) {
86 + warn!("draw failed: {e:?}");
87 + }
88 + }
89 + Pending::CloseRequested => {
90 + info!("close requested");
91 + }
92 + }
93 + }
94 + }
95 +
96 + drop(surface);
97 + drop(window);
98 + Ok(())
99 + }
100 +
101 + fn draw(surface: &wgpu::Surface<'_>, device: &wgpu::Device, queue: &wgpu::Queue) -> anyhow::Result<()> {
102 + let frame = match surface.get_current_texture() {
103 + wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
104 + other => anyhow::bail!("surface acquire: {other:?}"),
105 + };
106 + let view = frame
107 + .texture
108 + .create_view(&wgpu::TextureViewDescriptor::default());
109 + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
110 + label: Some("shop.clear"),
111 + });
112 + {
113 + let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
114 + label: Some("shop.clear"),
115 + color_attachments: &[Some(wgpu::RenderPassColorAttachment {
116 + view: &view,
117 + resolve_target: None,
118 + ops: wgpu::Operations {
119 + load: wgpu::LoadOp::Clear(CLEAR),
120 + store: wgpu::StoreOp::Store,
121 + },
122 + depth_slice: None,
123 + })],
124 + ..Default::default()
125 + });
126 + }
127 + queue.submit(std::iter::once(encoder.finish()));
128 + queue.present(frame);
129 + Ok(())
8 130 }
@@ -1,0 +1,20 @@
1 + [package]
2 + name = "shop-wayland"
3 + version = "0.0.0"
4 + description = "Wayland window + event queue for shop (renderer-agnostic)"
5 + edition.workspace = true
6 + rust-version.workspace = true
7 + authors.workspace = true
8 + repository.workspace = true
9 + license.workspace = true
10 + publish = false
11 +
12 + [lints]
13 + workspace = true
14 +
15 + [dependencies]
16 + smithay-client-toolkit.workspace = true
17 + wayland-client.workspace = true
18 + raw-window-handle.workspace = true
19 + anyhow.workspace = true
20 + tracing.workspace = true
@@ -1,0 +1,248 @@
1 + //! Wayland window + event queue for shop.
2 + //!
3 + //! Owns the SCTK client-side state (compositor, xdg-shell, output, seat,
4 + //! registry) and exposes a [`Window`] that drives the event queue and reports
5 + //! pending changes via a drain-on-dispatch queue. Renderer-agnostic — no wgpu,
6 + //! no image decoding, no fonts. The binary owns the render loop.
7 + //!
8 + //! Bootstrapped against SCTK 0.21's `examples/wgpu.rs` structure. See
9 + //! `~/Wiki/shop-overview.md` for the design.
10 +
11 + use std::{collections::VecDeque, ptr::NonNull};
12 +
13 + use raw_window_handle::{
14 + RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
15 + };
16 + use smithay_client_toolkit::{
17 + compositor::{CompositorHandler, CompositorState},
18 + delegate_dispatch2, delegate_registry,
19 + output::{OutputHandler, OutputState},
20 + registry::{ProvidesRegistryState, RegistryState},
21 + registry_handlers,
22 + seat::{Capability, SeatHandler, SeatState},
23 + shell::{
24 + WaylandSurface,
25 + xdg::{
26 + XdgShell,
27 + window::{Window as XdgWindow, WindowConfigure, WindowDecorations, WindowHandler},
28 + },
29 + },
30 + };
31 + use wayland_client::{
32 + Connection, EventQueue, Proxy, QueueHandle,
33 + globals::registry_queue_init,
34 + protocol::{wl_output, wl_seat, wl_surface},
35 + };
36 +
37 + /// Change reported by the compositor since the last dispatch.
38 + #[derive(Debug, Clone)]
39 + pub enum Pending {
40 + /// The compositor gave us a new size (may equal the previous size — treat
41 + /// the first Resized after startup as "now safe to draw").
42 + Resized { width: u32, height: u32 },
43 + /// User asked to close (xdg-toplevel close).
44 + CloseRequested,
45 + }
46 +
47 + /// Spec for creating the top-level window.
48 + pub struct WindowSpec<'a> {
49 + pub title: &'a str,
50 + pub app_id: &'a str,
51 + pub min_size: (u32, u32),
52 + pub initial_size: (u32, u32),
53 + }
54 +
55 + /// Owns the SCTK state and the wayland event queue.
56 + pub struct Window {
57 + conn: Connection,
58 + queue: EventQueue<State>,
59 + state: State,
60 + }
61 +
62 + struct State {
63 + registry: RegistryState,
64 + seat: SeatState,
65 + output: OutputState,
66 + _compositor: CompositorState,
67 + _xdg_shell: XdgShell,
68 + xdg_window: XdgWindow,
69 + pending: VecDeque<Pending>,
70 + width: u32,
71 + height: u32,
72 + exit: bool,
73 + }
74 +
75 + impl Window {
76 + /// Connect to the compositor and create the top-level window. The first
77 + /// `dispatch()` call after this will typically yield a `Pending::Resized`
78 + /// carrying the compositor's chosen initial size.
79 + pub fn new(spec: WindowSpec<'_>) -> anyhow::Result<Self> {
80 + let conn = Connection::connect_to_env()?;
81 + let (globals, queue) = registry_queue_init(&conn)?;
82 + let qh = queue.handle();
83 +
84 + let compositor = CompositorState::bind(&globals, &qh)?;
85 + let xdg_shell = XdgShell::bind(&globals, &qh)?;
86 +
87 + let surface = compositor.create_surface(&qh);
88 + let xdg_window =
89 + xdg_shell.create_window(surface, WindowDecorations::ServerDefault, &qh);
90 + xdg_window.set_title(spec.title.to_string());
91 + xdg_window.set_app_id(spec.app_id.to_string());
92 + xdg_window.set_min_size(Some(spec.min_size));
93 + xdg_window.commit();
94 +
95 + let state = State {
96 + registry: RegistryState::new(&globals),
97 + seat: SeatState::new(&globals, &qh),
98 + output: OutputState::new(&globals, &qh),
99 + _compositor: compositor,
100 + _xdg_shell: xdg_shell,
101 + xdg_window,
102 + pending: VecDeque::new(),
103 + width: spec.initial_size.0,
104 + height: spec.initial_size.1,
105 + exit: false,
106 + };
107 +
108 + Ok(Self { conn, queue, state })
109 + }
110 +
111 + /// Raw display + window handles for a graphics API (wgpu, GL).
112 + ///
113 + /// Returned handles reference wayland pointers owned by this window; do
114 + /// not use them after the `Window` is dropped.
115 + pub fn raw_handles(&self) -> (RawDisplayHandle, RawWindowHandle) {
116 + let display_ptr =
117 + NonNull::new(self.conn.backend().display_ptr().cast()).expect("wayland display ptr");
118 + let surface_ptr = NonNull::new(self.state.xdg_window.wl_surface().id().as_ptr().cast())
119 + .expect("wl_surface ptr");
120 + let display = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(display_ptr));
121 + let window = RawWindowHandle::Wayland(WaylandWindowHandle::new(surface_ptr));
122 + (display, window)
123 + }
124 +
125 + /// Blocking-dispatch one round of wayland events and drain accumulated
126 + /// pending changes.
127 + pub fn dispatch(&mut self) -> anyhow::Result<Vec<Pending>> {
128 + self.queue.blocking_dispatch(&mut self.state)?;
129 + Ok(self.state.pending.drain(..).collect())
130 + }
131 +
132 + pub fn should_exit(&self) -> bool {
133 + self.state.exit
134 + }
135 +
136 + pub fn size(&self) -> (u32, u32) {
137 + (self.state.width, self.state.height)
138 + }
139 + }
140 +
141 + impl CompositorHandler for State {
142 + fn scale_factor_changed(
143 + &mut self,
144 + _: &Connection,
145 + _: &QueueHandle<Self>,
146 + _: &wl_surface::WlSurface,
147 + _: i32,
148 + ) {
149 + }
150 +
151 + fn transform_changed(
152 + &mut self,
153 + _: &Connection,
154 + _: &QueueHandle<Self>,
155 + _: &wl_surface::WlSurface,
156 + _: wl_output::Transform,
157 + ) {
158 + }
159 +
160 + fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &wl_surface::WlSurface, _: u32) {
161 + }
162 +
163 + fn surface_enter(
164 + &mut self,
165 + _: &Connection,
166 + _: &QueueHandle<Self>,
167 + _: &wl_surface::WlSurface,
168 + _: &wl_output::WlOutput,
169 + ) {
170 + }
171 +
172 + fn surface_leave(
173 + &mut self,
174 + _: &Connection,
175 + _: &QueueHandle<Self>,
176 + _: &wl_surface::WlSurface,
177 + _: &wl_output::WlOutput,
178 + ) {
179 + }
180 + }
181 +
182 + impl OutputHandler for State {
183 + fn output_state(&mut self) -> &mut OutputState {
184 + &mut self.output
185 + }
186 +
187 + fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
188 + fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
189 + fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
190 + }
191 +
192 + impl SeatHandler for State {
193 + fn seat_state(&mut self) -> &mut SeatState {
194 + &mut self.seat
195 + }
196 +
197 + fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
198 + fn new_capability(
199 + &mut self,
200 + _: &Connection,
201 + _: &QueueHandle<Self>,
202 + _: wl_seat::WlSeat,
203 + _: Capability,
204 + ) {
205 + }
206 + fn remove_capability(
207 + &mut self,
208 + _: &Connection,
209 + _: &QueueHandle<Self>,
210 + _: wl_seat::WlSeat,
211 + _: Capability,
212 + ) {
213 + }
214 + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
215 + }
216 +
217 + impl WindowHandler for State {
218 + fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &XdgWindow) {
219 + self.exit = true;
220 + self.pending.push_back(Pending::CloseRequested);
221 + }
222 +
223 + fn configure(
224 + &mut self,
225 + _: &Connection,
226 + _: &QueueHandle<Self>,
227 + _: &XdgWindow,
228 + configure: WindowConfigure,
229 + _serial: u32,
230 + ) {
231 + let (new_w, new_h) = configure.new_size;
232 + let width = new_w.map_or(self.width, std::num::NonZeroU32::get);
233 + let height = new_h.map_or(self.height, std::num::NonZeroU32::get);
234 + self.width = width;
235 + self.height = height;
236 + self.pending.push_back(Pending::Resized { width, height });
237 + }
238 + }
239 +
240 + impl ProvidesRegistryState for State {
241 + fn registry(&mut self) -> &mut RegistryState {
242 + &mut self.registry
243 + }
244 + registry_handlers![OutputState];
245 + }
246 +
247 + delegate_registry!(State);
248 + delegate_dispatch2!(State);