140 Commits

Author SHA1 Message Date
Schmarni
e00b487167 fix: destroy prebound parts on model drop
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 23:41:28 +01:00
Schmarni
771a79cd33 fix: don't panic when Model is dropped before parts are generated
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 20:57:36 +01:00
Schmarni
a3afb08664 fix: only re-mesh lines if needed
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 08:22:13 +01:00
Schmarni
4de91ef20b fix: improve xr input accuracy
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 08:07:03 +01:00
Schmarni
94630c451a chore: remove unneeded system
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 07:45:25 +01:00
Schmarni
8ab891edb5 chore: silence unimportant warning
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 07:44:12 +01:00
Schmarni
9f89d1a9ec fix: don't recursivly despawn entities that handles depsawning themselfs
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 07:40:04 +01:00
Schmarni
c8e8ae2506 chore: update bevy-dmabuf, again
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 05:55:01 +01:00
Schmarni
929c061f36 chore: update bevy-dmabuf
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 05:42:20 +01:00
Schmarni
189c09ed79 chore: update bevy-dmabuf
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 04:38:51 +01:00
Schmarni
ccbd773cee fix: gate bevy-dmabuf plugin behind wayland
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 04:00:23 +01:00
Schmarni
87857090b4 fix: compiling without the wayland feature
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 02:23:37 +01:00
Schmarni
5f152df9f7 fix: getting bounding boxes before model is fully loaded now waits until the model and bounding boxes are loaded
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-31 01:25:20 +01:00
Nova
b70a188e67 fix(main): don't init winit on display or wayland_display empty 2025-10-29 20:32:34 -07:00
Schmarni
5a2bb6faed fix: lines not moving when its moved using spatials: bad impl
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-29 16:28:35 +01:00
Schmarni
65c426f981 feat: impl proper entity handles
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-28 23:02:24 +01:00
Schmarni
ec468b6752 chore: cleanup
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-26 00:43:25 +02:00
Schmarni
23f0b5f880 fix: properly implement lines by only transforming points, not the mesh, also make lines and holdout bindless
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-26 00:01:40 +02:00
Schmarni
8bfb01808a fix: fix broken transforms on model spawn
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-24 23:42:15 +02:00
Schmarni
e621f4b60e feat: use entity hashmap for transform sync
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-24 03:10:36 +02:00
Schmarni
7e0b956f49 fix: make the dirty flag work
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-24 02:30:59 +02:00
Nova
4d229b95b6 refactor(spatial): dirty flag 2025-10-24 02:30:59 +02:00
Nova
392eaf4ee5 fix(spatial): proper visibility culling scaling 2025-10-23 15:46:02 -07:00
Nova
6e2de6ac87 fix(spatial): no more zero values in scale 2025-10-23 15:37:19 -07:00
Nova
04e20a3bbf refactor(spatial): local visibility calculation 2025-10-22 15:51:21 -07:00
Schmarni
429a6efcee fix: disable hands when xr is not available
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-22 23:40:29 +02:00
Schmarni
b1ab37c8dc Revert "fix: don't wait for frame"
This reverts commit 2b07345286.
2025-10-22 17:25:09 +02:00
Nova
181224767d fix: make color proper 2025-10-21 16:44:34 -07:00
Nova
2b07345286 fix: don't wait for frame 2025-10-21 15:08:40 -07:00
Nova
d5034b4034 fix: instrumentation 2025-10-21 15:07:21 -07:00
Nova
891d90fc5e upgrade: schemas 2025-10-21 02:15:12 -07:00
Nova
ba97528ed6 feat(objects/mouse_pointer): more key compatibility 2025-10-20 22:51:52 -07:00
Nova
536fafb4cf fix(objects/mouse_pointer): right modifier keys 2025-10-20 22:42:56 -07:00
Nova
59e6a11079 fix: mouse pointer target reliability issues 2025-10-20 22:42:36 -07:00
Nova
0e4f5de529 refactor(objects/mouse_pointer): single task pointer 2025-10-20 15:10:48 -07:00
Schmarni
05d4670609 feat: impl Pipelined Rendering
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-21 00:09:17 +02:00
Schmarni
08cec3c700 chore: fmt
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-20 20:52:58 +02:00
Schmarni
0b29f2f6c9 fix(input/controllers): fix cursor rendering
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-20 20:52:21 +02:00
Nova
38a0520299 feat(objects): async tracked abstraction 2025-10-11 21:46:27 -07:00
Nova
a080560c9c update(schemas): more efficient in tokio tasks 2025-10-11 18:37:00 -07:00
Nova
024ae4ddd7 refactor: rename flatscreen to force flatscreen 2025-10-11 18:36:47 -07:00
Nova
6742caa967 revert: bring back task abstraction 2025-10-11 15:51:26 -07:00
Nova
ba5415653e fix(wayland): make tokio tasks actually close properly 2025-10-11 13:32:22 -07:00
Nova
ddef55879a fix: tokio tracing 2025-10-11 12:19:24 -07:00
Nova
c63416d1f3 fix: tokio tasks 2025-10-11 03:19:56 -07:00
Schmarni
b0ee7e9f54 fix(wayland): fix panic when an app requests presentation_feedback without having bound a display
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-10 21:41:02 +02:00
Nova
ec871f5963 update: stardust xr core 2025-10-05 13:12:29 -07:00
Nova
418e3a2ccb refactor: make flatscreen flag explicit 2025-10-03 02:40:29 -07:00
Schmarni
75bdb44371 chore: bump mesh-text crate version and remove unneeded fd
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-01 10:21:08 +02:00
Schmarni
6678681c2c fix(wayland/shm): don't leak fds on shm pool destruction
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-01 01:19:32 +02:00
Schmarni
3edaaf2dfc fix(wayland/pointer): don't send axis_discrete events when using version 8 or above, as required per spec
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-10-01 00:00:12 +02:00
Schmarni
0ebfc1153e chore(wayland): update waynest
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-30 22:32:09 +02:00
Schmarni
2d6bc06cbe fix(wayland): manually remove objects from connection on destroy
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-29 17:45:03 +02:00
Schmarni
bbf12b9e31 fix(wayland/keyboard): fix modifier key not working properly for some keyboard layouts
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-29 13:18:14 +02:00
Nova
621a9c6d85 upgrade: waynest 2025-09-26 16:32:46 -07:00
Nova
96e910c450 update: stardust-xr core 2025-09-23 14:08:52 -07:00
Nova
25b0760913 refactor(spatial): general improvements and efficiency 2025-09-21 04:17:45 -07:00
Nova
b542dc1b23 fix: cache global transform 2025-09-21 02:47:34 -07:00
Nova
3e4be41d3f fix(lockfile): idk why but it insisted 2025-09-21 00:44:29 -07:00
Nova
76fc1bfab5 cleanup: thingys 2025-09-21 00:40:14 -07:00
Nova
928886563d update: rust version and cargo.toml 2025-09-21 00:40:14 -07:00
Schmarni
e3a3db246e fix(lines): working OIT for lines!
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-18 09:40:40 +02:00
Schmarni
b4dccf6f89 fix(input/controllers): don't apply a scale to the exported Spatial
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-17 05:51:29 +02:00
Nova
0599eece82 update: waynest 2025-09-15 18:32:13 -07:00
Nova
27196e2dda fix(wayland): ignore viewport struct fields 2025-09-15 02:00:26 -07:00
Nova
0a4d6adf74 update(cargo.lock): it insisted 2025-09-14 23:49:32 -07:00
Schmarni
cf1cb90642 fix(wayland/dmabuf): only suggest formats that have srgb variants, fixes blender and vkcube, in the future it might be better to do such a conversion in a shader for more formats
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-15 08:36:07 +02:00
Schmarni
2343bbc974 chore(lines): a bit of cleanup
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-15 08:24:01 +02:00
Nova
fd7cad1ab4 fix(wayland/popup): give real configure geometry 2025-09-14 20:49:54 -07:00
Schmarni
0c3efe9477 feat(bevy/oit): use patched bevy for Premultiplied Alpha in OIT
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-15 05:07:58 +02:00
Nova
065214c873 fix(wayland/core): surface role set properly 2025-09-14 17:08:59 -07:00
Nova
795f111ebc fix(wayland/output): give it a name/description 2025-09-14 01:12:00 -07:00
Nova
f40c6dcbd4 fix(wayland/surface): keep frame callback order 2025-09-14 00:55:26 -07:00
Nova
2632a0c5f3 fix(wayland): proper frame callback handling 2025-09-14 00:40:17 -07:00
Schmarni
1bfd9c95f0 feat(wayland/presentation_time): hook up presentation time to OpenXR
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-12 06:34:14 +02:00
Nova
a753001f15 fix(wayland/core): surface role set properly 2025-09-11 18:01:19 -07:00
Nova
cbd77fe704 cleanup: code thingys 2025-09-11 17:55:55 -07:00
Nova
c5246c9dc8 refactor(wayland/surface): better error formatting 2025-09-11 16:02:39 -07:00
Nova
209171abfc cleanup: clippy 2025-09-11 16:02:25 -07:00
Nova
7e53db3d33 feat(wayland): try set role modularization 2025-09-11 15:53:31 -07:00
Schmarni
4bc71b01cb feat(wayland/shm): impl shm ontop of dmabuf (again), and make the upload async
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-12 00:51:17 +02:00
Nova
c4ccda2118 fix(wayland/xdg/positioner): properly calculate with gravity 2025-09-10 13:33:42 -07:00
Nova
3a55aaa2cf fix(wayland/shm/backing): allow shm pool different size 2025-09-09 22:21:59 -07:00
Nova
0650956ab4 clean(wayland): unnecessary variables 2025-09-09 18:49:55 -07:00
Nova
45ec292b99 fix(wayland/cursor): things 2025-09-09 14:55:56 -07:00
Nova
550087841f feat(wayland): cursor stuff 2025-09-09 03:13:22 -07:00
Nova
bd1b54cf03 fix(wayland/xdg/popup): configure the popup then xdg surface 2025-09-09 00:37:18 -07:00
Nova
707452462d cleanup: wayland 2025-09-09 00:37:00 -07:00
Nova
1c8aa93850 refactor(wayland): many things 2025-09-06 23:56:29 -07:00
Nova
a0b014576e refactor(wayland): remove surface specialization from roles 2025-09-06 21:16:55 -07:00
Nova
50e1921cd9 refactor(wayland): strong reference to all "parent" types 2025-09-06 16:48:23 -07:00
Nova
14c5c355b5 fix(wayland+wgpu): bad recursion limit increase 2025-09-06 15:34:14 -07:00
Nova
4641f4f724 refactor(wayland): weak references to objects in roles 2025-09-06 15:19:37 -07:00
Nova
053d468035 refactor(wayland): naming conventions 2025-09-06 15:09:39 -07:00
Nova
f44abad5b0 fix(wayland): set xdg surface role properly 2025-09-06 14:40:56 -07:00
Schmarni
9e8f09fe97 feat: add spectator camera flag
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-06 23:36:12 +02:00
Schmarni
ae68d0e135 chore(text): update text mesh crate
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-06 04:55:58 +02:00
Nova
7314428ce7 refactor(wayland): move surface id to wl_surface 2025-09-05 17:04:59 -07:00
Nova
c5440bc426 chore: cleanup text 2025-09-05 17:01:01 -07:00
Nova
ad1c97aad6 refactor(wayland): xdg surface sub-roles 2025-09-04 20:15:07 -07:00
Nova
51b0942c49 chore: cargo fmt 2025-09-04 15:37:12 -07:00
Nova
c665f33d25 fix(wayland/dmabuf_backing): always update surface 2025-09-04 14:14:46 -07:00
Nova
a6a1195922 fix: text (partially) 2025-09-03 13:16:57 -07:00
Nova
d6ad00bf53 chore: cleanup clippy 2025-09-02 14:41:15 -07:00
Schmarni
b6524e90e1 fix(spatial): fix model nodes not despawning properly
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 22:47:00 +02:00
Schmarni
6f113a9ec4 fix(lines): add a check to prevenmodel.enh
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 22:40:47 +02:00
Schmarni
bf85140b65 fix(lines): add a check to prevent a crash
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:28:18 +02:00
Schmarni
2b9ba2b957 fix(lines): make lines no longer crash the server on invalid input from clients
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:28:09 +02:00
Schmarni
8df1ba549e chore(logging): disable useless errors from the bevy_mesh_text_3d crate
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:27:34 +02:00
Schmarni
dd0e45cffe chore(entity handles): disable error if the destroy channel was closed, was triggerd on shutdown
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:27:34 +02:00
Schmarni
a14457ecc1 fix(Spatial/Transforms): fix transform propagation by making bevy entities for all spatials
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:27:34 +02:00
Schmarni
eac44ded78 fix(lines): fix the lines material shader
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-02 06:12:31 +02:00
Nova
2357cba5f9 feat: nonfunctional improvements 2025-09-02 06:12:31 +02:00
Nova
e69d85cc56 it heccin borken 2025-09-02 06:12:31 +02:00
Nova
a5b4939b57 fix(wayland/toplevel): don't unwrap 2025-09-01 20:36:19 -07:00
Schmarni
d9a6ca9bef chore: update bevy_mod_openxr to fix transparency rendering issue
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-09-01 09:05:11 +02:00
Nova
2e3e944f99 fix(mouse_pointer): don't overload the d-bus 2025-08-31 14:38:25 -07:00
AnnoyingRains
fefa66060a fix: nix 2025-08-30 22:55:11 -07:00
matthewcroughan
6b93a1a095 nix/gnome-graphical-test: fix pkgs references to updated names 2025-08-30 22:55:11 -07:00
matthewcroughan
5f6b48c9b4 nix/stardust-xr-server: fix build 2025-08-30 22:55:11 -07:00
Nova
bda2d06e81 cleanup(wayland/registry): clippy 2025-08-29 16:20:31 -07:00
Nova
87907984f6 cleanup(wayland/popup): just imports 2025-08-29 16:20:16 -07:00
Nova
83dbde9bc0 refactor(sky): add/remove ambient light dynamically 2025-08-29 16:20:01 -07:00
Nova
c69b2652c8 fix: disable tonemapping and up ambient light 2025-08-29 16:17:35 -07:00
Nova
ec50f38dfd refactor: session state save data as bin file 2025-08-29 11:54:06 -07:00
Nova
8e9ac71ebb feat(codegen): integrated logging 2025-08-29 10:31:04 -07:00
Schmarni
b95ea8e90f fix(sky): actually commit the file...
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-08-26 23:43:07 +02:00
Schmarni
712678a666 fix(sky): update bevy-equirect to fix skytex
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-08-26 18:47:27 +02:00
Schmarni
1c6b42e69a feat: implement SkyTex and SkyLight
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-08-26 15:11:54 +02:00
Nova
51de346f6b fix(lines): add aabb to bevy to fix frustum culling 2025-08-23 04:21:48 -07:00
Nova
71b1792ee2 fix(drawable/lines): make them unlit 2025-08-22 21:21:51 -07:00
Schmarni
30f340fe41 fix(input): don't unwrap on getting the string of a path, fixes crash sometimes experienced with handtracking
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-08-23 02:06:55 +02:00
Nova
040f86d50d refactor: remove as_any function from Aspect since trait upcasting added 2025-08-21 13:47:19 -07:00
Thomas Colliers
f0a494392a Formatting 2025-08-19 03:52:09 +02:00
Thomas Colliers
a7aa609651 fix: profile_tokio broken due to incompatible types 2025-08-19 03:52:09 +02:00
Thomas Colliers
7a3322efad feat(wayland): WIP implementation of wp_importer 2025-08-18 01:49:45 +02:00
Nova
877a32ab09 fix: presentation feedback send unwrap 2025-08-13 12:01:42 -07:00
Nova
4ccee1bf89 fix(lines): properly break line points with same position 2025-08-13 11:57:51 -07:00
Nova
6b78684650 fix: remove out 2025-08-13 11:22:31 -07:00
Nova
4f75aa5edf fix: window opaqueness 2025-08-13 11:21:18 -07:00
72 changed files with 3688 additions and 2161 deletions

View File

@@ -1,2 +1,2 @@
[build] [build]
rustflags = ["--cfg", "tokio_unstable"] rustflags = ["--cfg", "tokio_unstable"]

841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.89"
name = "stardust-xr-server" name = "stardust-xr-server"
version = "0.45.0" version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"] authors = ["Nova King <technobaboo@proton.me>"]
@@ -23,8 +23,9 @@ path = "src/main.rs"
[features] [features]
default = ["wayland"] default = ["wayland"]
wayland = [ wayland = [
"dep:cluFlock",
"dep:waynest", "dep:waynest",
"dep:waynest-protocols",
"dep:waynest-server",
"dep:tokio-stream", "dep:tokio-stream",
"dep:memmap2", "dep:memmap2",
"dep:drm-fourcc", "dep:drm-fourcc",
@@ -34,7 +35,12 @@ wayland = [
"dep:ash", "dep:ash",
] ]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-tracy", "bevy/trace_tracy", "bevy/trace"] profile_app = [
"dep:tracing-tracy",
"bevy/trace_tracy",
"bevy/trace",
"dep:tracy-client",
]
bevy_debugging = ["bevy/bevy_remote", "bevy/track_location"] bevy_debugging = ["bevy/bevy_remote", "bevy/track_location"]
[package.metadata.appimage] [package.metadata.appimage]
@@ -43,16 +49,19 @@ auto_link_exclude_list = ["libc*", "libdl*", "libpthread*", "ld-linux*"]
[profile.release] [profile.release]
opt-level = "s"
lto = true lto = true
strip = true codegen-units = 1
opt-level = 3 panic = "abort"
# strip = "symbols"
debug = true
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
[patch.crates-io] [patch.crates-io]
bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr" } bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr" } bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
bevy_gltf = { git = "https://github.com/Schmarni-Dev/bevy", branch = "gltf_backport" } bevy_gltf = { git = "https://github.com/Schmarni-Dev/bevy", branch = "gltf_backport" }
# TODO: figure out how to not need this horrifing patch # TODO: figure out how to not need this horrifing patch
wgpu = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" } wgpu = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
@@ -60,12 +69,15 @@ wgpu-types = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabu
wgpu-core = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" } wgpu-core = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
naga = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" } naga = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
wgpu-hal = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" } wgpu-hal = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
bevy_pbr = { git = "https://github.com/Schmarni-Dev/bevy", branch = "premul_oit_016" }
bevy_core_pipeline = { git = "https://github.com/Schmarni-Dev/bevy", branch = "premul_oit_016" }
bevy_render = { git = "https://github.com/Schmarni-Dev/bevy", branch = "render_thread_init_callback" }
[dependencies] [dependencies]
# small utility thingys # small utility thingys
nanoid = "0.4.0" nanoid = "0.4.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
rand = "0.8.5" rand = "0.9.2"
rustc-hash = "2.0.0" rustc-hash = "2.0.0"
slotmap = "1.0.7" slotmap = "1.0.7"
global_counter = "=0.2.2" global_counter = "=0.2.2"
@@ -80,12 +92,12 @@ thiserror = "2.0.11"
tracing = { version = "0.1.40", features = ["release_max_level_warn"] } tracing = { version = "0.1.40", features = ["release_max_level_warn"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true } tracing-tracy = { version = "0.11.1", optional = true }
tracy-client = { version = "=0.18.0" } tracy-client = { version = "=0.18.0", optional = true }
# (de)serialization # (de)serialization
serde = { version = "1.0.205", features = ["derive"] } serde = { version = "1.0.205", features = ["derive"] }
serde_repr = "0.1.19" serde_repr = "0.1.19"
toml = "0.8.19" toml = "0.9.7"
# mathy stuffs # mathy stuffs
glam = { version = "0.29.0", features = ["mint", "serde"] } glam = { version = "0.29.0", features = ["mint", "serde"] }
@@ -106,7 +118,6 @@ bevy = { version = "0.16", default-features = false, features = [
"bevy_window", "bevy_window",
"bevy_winit", "bevy_winit",
"std", "std",
"x11",
"wayland", "wayland",
"mp3", "mp3",
"wav", "wav",
@@ -114,7 +125,6 @@ bevy = { version = "0.16", default-features = false, features = [
"png", "png",
"hdr", "hdr",
"jpeg", "jpeg",
"tonemapping_luts",
"multi_threaded", "multi_threaded",
] } ] }
bevy_mod_xr = "0.3" bevy_mod_xr = "0.3"
@@ -123,6 +133,7 @@ bevy_mod_openxr = "0.3"
bevy_sk = { git = "https://github.com/technobaboo/bevy_sk", branch = "stochastic" } bevy_sk = { git = "https://github.com/technobaboo/bevy_sk", branch = "stochastic" }
# bevy-dmabuf = "0.2.0" # bevy-dmabuf = "0.2.0"
bevy-dmabuf.git = "https://github.com/Schmarni-Dev/bevy-dmabuf" bevy-dmabuf.git = "https://github.com/Schmarni-Dev/bevy-dmabuf"
bevy-equirect = "0.1.1"
# bevy-mesh-text-3d.git = "https://github.com/terhechte/bevy-mesh-text-3d" # bevy-mesh-text-3d.git = "https://github.com/terhechte/bevy-mesh-text-3d"
# use my fork until my pr to use minimal bevy features was merged # use my fork until my pr to use minimal bevy features was merged
@@ -132,32 +143,33 @@ openxr = "0.19"
# linux stuffs # linux stuffs
input-event-codes = "6.2.0" input-event-codes = "6.2.0"
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] } zbus = { version = "5.11.0", features = [
directories = "5.0.1" "blocking-api",
"tokio",
], default-features = false }
directories = "6.0.0"
xkbcommon-rs = "0.1.0" xkbcommon-rs = "0.1.0"
cosmic-text = "0.14.2" cosmic-text = "0.14.2"
# Wayland # Wayland
cluFlock = { version = "1.2.7", optional = true } # for the lockfile checking waynest = { git = "https://github.com/verdiwm/waynest.git", default-features = false, optional = true }
# waynest = { git = "https://github.com/verdiwm/waynest.git", features = [ waynest-protocols = { git = "https://github.com/verdiwm/waynest.git", features = [
# "server",
# "stable",
# "tracing",
# ], default-features = false, optional = true }
waynest = { git = "https://github.com/technobaboo/waynest.git", branch = "fix/wayland-drm", features = [
"server", "server",
"stable", "stable",
"external", "mesa",
"tracing", "tracing",
], default-features = false, optional = true } ], default-features = false, optional = true }
waynest-server = { git = "https://github.com/verdiwm/waynest.git", default-features = false, optional = true }
tokio-stream = { version = "0.1.17", optional = true } tokio-stream = { version = "0.1.17", optional = true }
memmap2 = { version = "0.9.5", optional = true } memmap2 = { version = "0.9.5", optional = true }
drm-fourcc = { version = "2.2.0", optional = true } drm-fourcc = { version = "2.2.0", optional = true }
memfd = { version = "0.6.4", optional = true } memfd = { version = "0.6.4", optional = true }
vulkano = { git = "https://github.com/Schmarni-Dev/vulkano", branch = "0_35_dmabuf_fixes", optional = true } vulkano = { git = "https://github.com/Schmarni-Dev/vulkano", branch = "0_35_dmabuf_fixes", default-features = false, optional = true }
wgpu-hal = { version = "24", optional = true, features = ["vulkan"] } wgpu-hal = { version = "24", optional = true, features = ["vulkan"] }
ash = { version = "0.38.0", optional = true, default-features = false } ash = { version = "0.38.0", optional = true, default-features = false }
rustix = { version = "1.0.8", features = ["time"] } rustix = { version = "1.0.8", features = ["time"] }
pin-project-lite = "0.2.16"
futures-sink = "0.3.31"
[dependencies.stardust-xr] [dependencies.stardust-xr]
workspace = true workspace = true

View File

@@ -9,8 +9,6 @@ proc-macro = true
[dependencies] [dependencies]
convert_case = "0.6.0" convert_case = "0.6.0"
quote = "1.0.33" quote = "1.0.33"
mint = "0.5.9"
proc-macro2 = "1.0.71" proc-macro2 = "1.0.71"
split-iter = "0.1.0"
stardust-xr = { workspace = true } stardust-xr = { workspace = true }

View File

@@ -1,7 +1,6 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr::schemas::protocol::*; use stardust_xr::schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream { fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
@@ -64,6 +63,7 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
description: protocol.description.clone(), description: protocol.description.clone(),
inherits: vec![], inherits: vec![],
members: p.members, members: p.members,
inherited_aspects: vec![],
}); });
quote! { quote! {
#node_id #node_id
@@ -105,7 +105,7 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let aspects = protocol let aspects = protocol
.aspects .aspects
.iter() .iter()
.map(generate_aspect) .map(|a| generate_aspect(&a.blocking_read()))
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .unwrap_or_default();
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into() quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into()
@@ -183,14 +183,18 @@ fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
fn generate_aspect(aspect: &Aspect) -> TokenStream { fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description; let description = &aspect.description;
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server); let (client_members, server_members) = aspect
.members
.iter()
.partition::<Vec<_>, _>(|m| m.side == Side::Client);
let client_mod_name = Ident::new( let client_mod_name = Ident::new(
&format!("{}_client", &aspect.name.to_case(Case::Snake)), &format!("{}_client", &aspect.name.to_case(Case::Snake)),
Span::call_site(), Span::call_site(),
); );
let client_side_members = client_members let client_side_members = client_members
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m)) .into_iter()
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.map(|t| { .map(|t| {
// TODO: properly import all dependencies // TODO: properly import all dependencies
@@ -228,6 +232,7 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
let alias_info = generate_alias_info(aspect); let alias_info = generate_alias_info(aspect);
let server_side_members = server_members let server_side_members = server_members
.into_iter()
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m)) .map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .unwrap_or_default();
@@ -281,9 +286,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
} }
macro_rules! #aspect_macro_name { macro_rules! #aspect_macro_name {
() => { () => {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
#[allow(clippy::all)] #[allow(clippy::all)]
fn run_signal( fn run_signal(
&self, &self,
@@ -304,12 +306,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
_node: std::sync::Arc<crate::nodes::Node>, _node: std::sync::Arc<crate::nodes::Node>,
_method: u64, _method: u64,
_message: crate::nodes::Message, _message: crate::nodes::Message,
_method_response: crate::nodes::MethodResponseSender, _method_response: crate::core::scenegraph::MethodResponseSender,
) { ) {
match _method { match _method {
#run_methods #run_methods
_ => { _ => {
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound)); let _ = _method_response.send_err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound);
} }
} }
} }
@@ -381,6 +383,18 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
} }
Side::Client => quote!(_node: &crate::nodes::Node), Side::Client => quote!(_node: &crate::nodes::Node),
}; };
let arguments = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()));
let argument_debug = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()))
.map(|n| quote!(?#n))
.reduce(|a, b| quote!(#a, #b))
.map(|args| quote!(#args,));
let argument_decls = member let argument_decls = member
.arguments .arguments
.iter() .iter()
@@ -405,12 +419,13 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! { quote! {
#[doc = #description] #[doc = #description]
pub fn #name(#argument_decls) -> crate::core::error::Result<()> { pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
let arguments = (#argument_uses);
let (#(#arguments),*) = &arguments;
::tracing::trace!(#argument_debug "sent signal to client: {}::{}", #aspect_name, #name_str);
let result = stardust_xr::schemas::flex::serialize(&arguments).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
let result = stardust_xr::schemas::flex::serialize((#argument_uses)).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
if let Err(err) = result.as_ref() { if let Err(err) = result.as_ref() {
::tracing::warn!("failed to send remote signal: {}::{}, error: {}",#aspect_name,#name_str,err); ::tracing::warn!(#argument_debug "failed to send signal to client : {}::{}, error: {}",#aspect_name,#name_str,err);
} else {
::tracing::trace!("sent remote signal: {}::{}",#aspect_name,#name_str);
} }
result result
} }
@@ -420,11 +435,18 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! { quote! {
#[doc = #description] #[doc = #description]
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> { pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await; let arguments = (#argument_uses);
if let Err(err) = result.as_ref() { let (#(#arguments),*) = &arguments;
::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err); ::tracing::trace!(#argument_debug "called client method: {}::{}",#aspect_name,#name_str);
} else { let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &arguments, vec![]).await;
::tracing::trace!("called remote method: {}::{}",#aspect_name,#name_str);
match result.as_ref() {
Ok(value) => {
::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name,#name_str);
},
Err(err) => {
::tracing::warn!(#argument_debug "client method returned error: {}::{}, error: {}",#aspect_name,#name_str,err);
}
} }
result result
} }
@@ -447,6 +469,8 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream { fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
let opcode = member.opcode; let opcode = member.opcode;
let member_name_ident = Ident::new(&member.name, Span::call_site()); let member_name_ident = Ident::new(&member.name, Span::call_site());
let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
let argument_names = member let argument_names = member
.arguments .arguments
@@ -461,6 +485,13 @@ fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member)
generate_argument_type(&_type, a.optional, true) generate_argument_type(&_type, a.optional, true)
}) })
.reduce(|a, b| quote!(#a, #b)); .reduce(|a, b| quote!(#a, #b));
let argument_debug = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()))
.map(|n| quote!(?#n))
.reduce(|a, b| quote!(#a, #b))
.map(|args| quote!(#args,));
// dbg!(&argument_types); // dbg!(&argument_types);
let deserialize = argument_names let deserialize = argument_names
.clone() .clone()
@@ -483,33 +514,30 @@ fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member)
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional)) .map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b)) .reduce(|a, b| quote!(#a, #b))
.unwrap_or_default(); .unwrap_or_default();
let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
match _type { match _type {
MemberType::Signal => quote! { MemberType::Signal => quote! {
#opcode => { let result = (move || { #opcode => (move || {
#deserialize #deserialize
::tracing::trace!(#argument_debug "received local signal: {}::{}",#aspect_name_str,#member_name);
<Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses) <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() }); })().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() }),
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to receive local signal: {}::{}, error: {}",#aspect_name_str,#member_name,err);
} else {
::tracing::trace!("received local signal: {}::{}",#aspect_name_str,#member_name);
}
result
},
}, },
MemberType::Method => quote! { MemberType::Method => quote! {
#opcode => _method_response.wrap_async(async move { #opcode => _method_response.wrap_async(async move {
#deserialize #deserialize
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await; ::tracing::trace!(#argument_debug "called local method: {}::{}",#aspect_name_str,#member_name);
if let Err(err) = result.as_ref() { let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
::tracing::warn!("failed to call local method: {}::{}, error: {}",#aspect_name_str,#member_name,err);
} else { match result.as_ref() {
::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name); Ok(value) => {
}; ::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name_str,#member_name);
let result = result?; },
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new())) Err(err) => {
::tracing::warn!("client method returned error: {}::{}, error: {}",#aspect_name_str,#member_name,err);
}
}
let result = result?;
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
}), }),
}, },
} }

51
flake.lock generated
View File

@@ -26,11 +26,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1722555600, "lastModified": 1754487366,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", "narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d", "rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -47,11 +47,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1712014858, "lastModified": 1754487366,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", "narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", "rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -65,11 +65,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1713050562, "lastModified": 1725868201,
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=", "narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=",
"owner": "StardustXR", "owner": "StardustXR",
"repo": "flatland", "repo": "flatland",
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e", "rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -84,11 +84,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1719226092, "lastModified": 1755233722,
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=", "narHash": "sha256-AavrbMltJKcC2Fx0lfJoZfmy7g87ebXU0ddVenhajLA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "hercules-ci-effects", "repo": "hercules-ci-effects",
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5", "rev": "99e03e72e3f7e13506f80ef9ebaedccb929d84d0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -115,23 +115,26 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1722555339, "lastModified": 1753579242,
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=", "narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=",
"type": "tarball", "owner": "nix-community",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" "repo": "nixpkgs.lib",
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
"type": "github"
}, },
"original": { "original": {
"type": "tarball", "owner": "nix-community",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" "repo": "nixpkgs.lib",
"type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1713714899, "lastModified": 1755027561,
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=", "narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932", "rev": "005433b926e16227259a1843015b5b2b7f7d1fc3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -143,11 +146,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1723991338, "lastModified": 1755186698,
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=", "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8a3354191c0d7144db9756a74755672387b702ba", "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -61,14 +61,11 @@
}; };
overlayAttrs = config.packages; overlayAttrs = config.packages;
packages = packages =
let
sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
in
{ {
default = self'.packages.${name}; default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test; gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix { "${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
inherit name src sk_gpu; inherit name src;
}; };
}; };
apps.default = { apps.default = {

View File

@@ -20,8 +20,8 @@
# Set a nice desktop background that is pleasing to the eyes :3 # Set a nice desktop background that is pleasing to the eyes :3
extraGSettingsOverrides = '' extraGSettingsOverrides = ''
[org.gnome.desktop.background] [org.gnome.desktop.background]
picture-uri='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg' picture-uri='file://${pkgs.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg' picture-uri-dark='file://${pkgs.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
''; '';
}; };
displayManager = { displayManager = {
@@ -100,7 +100,7 @@
# Eval API is now internal so Shell needs to run in unsafe mode. # Eval API is now internal so Shell needs to run in unsafe mode.
# TODO: improve test driver so that it supports openqa-like manipulation # TODO: improve test driver so that it supports openqa-like manipulation
# that would allow us to drop this mess. # that would allow us to drop this mess.
"${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode" "${pkgs.gnome-shell}/bin/gnome-shell --unsafe-mode"
]; ];
}; };
}; };

View File

@@ -1,25 +1,27 @@
{ rustPlatform {
, src rustPlatform,
, name src,
, libGL name,
, mesa vulkan-loader,
, xorg vulkan-headers,
, fontconfig mesa,
, libxkbcommon xorg,
, libclang fontconfig,
libxkbcommon,
libclang,
, cmake cmake,
, cpm-cmake pkg-config,
, pkg-config llvmPackages,
, llvmPackages fetchFromGitHub,
, fetchFromGitHub libXau,
, sk_gpu
, libXau
, libXdmcp libXdmcp,
, stdenv stdenv,
, lib lib,
, openxr-loader openxr-loader,
wayland,
alsa-lib,
}: }:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
@@ -28,50 +30,27 @@ rustPlatform.buildRustPackage rec {
lockFile = (src + "/Cargo.lock"); lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true; allowBuiltinFetchGit = true;
}; };
buildFeatures = [ "local_deps" ];
FORCE_LOCAL_DEPS = true;
CPM_LOCAL_PACKAGES_ONLY = true;
CPM_SOURCE_CACHE = "./build";
CPM_USE_LOCAL_PACKAGES = true;
CPM_DOWNLOAD_ALL = false;
meshoptimizer = fetchFromGitHub { preBuild = ''
owner = "zeux"; substituteInPlace /build/cargo-vendor-dir/bevy_gltf-0.16.1/Cargo.toml \
repo = "meshoptimizer"; --replace-fail '[lints]' "" \
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858"; --replace-fail 'workspace = true' ""
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
};
basis_universal = fetchFromGitHub {
owner = "BinomialLLC";
repo = "basis_universal";
rev = "900e40fb5d2502927360fe2f31762bdbb624455f";
sha256 = "sha256-zBRAXgG5Fi6+5uPQCI/RCGatY6O4ELuYBoKrPNn4K+8=";
};
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
DEP_SK_GPU_SOURCE = "${sk_gpu}";
postPatch = let libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
in ''
sk=$(echo $cargoDepsCopy/stereokit-rust-*/StereoKit)
mkdir -p $sk/build/cpm
# This is not ideal, the original approach was to fetch the exact cmake
# file version that was wanted from GitHub directly, but at least this way it comes from Nixpkgs.. so meh
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.38.7.cmake
mkdir -p $sk/sk_gpu
cp -R ${sk_gpu}/* $sk/sk_gpu
chmod -R 755 $sk/sk_gpu/tools/linux_x64/*
export DEP_SK_GPU_SOURCE=$sk/sk_gpu
export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib";
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
--set-rpath "${libPath}" \
$sk/sk_gpu/tools/linux_x64/skshaderc
''; '';
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
postFixup = ''
patchelf $out/bin/stardust-xr-server --add-rpath ${vulkan-loader}/lib
patchelf $out/bin/stardust-xr-server --add-rpath ${openxr-loader}/lib
patchelf $out/bin/stardust-xr-server --add-rpath ${libxkbcommon}/lib
'';
nativeBuildInputs = [
cmake
pkg-config
llvmPackages.libcxxClang
];
buildInputs = [ buildInputs = [
libGL vulkan-loader
vulkan-headers
mesa mesa
xorg.libX11.dev xorg.libX11.dev
xorg.libXft xorg.libXft
@@ -81,6 +60,9 @@ rustPlatform.buildRustPackage rec {
libXau libXau
libXdmcp libXdmcp
openxr-loader openxr-loader
wayland
alsa-lib
]; ];
LIBCLANG_PATH = "${libclang.lib}/lib"; LIBCLANG_PATH = "${libclang.lib}/lib";
} }

View File

@@ -144,12 +144,7 @@ impl Client {
.unwrap_or_else(|| "??".to_string()); .unwrap_or_else(|| "??".to_string());
let _ = client.dispatch_join_handle.get_or_init(|| { let _ = client.dispatch_join_handle.get_or_init(|| {
task::new( task::new(
|| { || format!("Stardust client \"{exe_printable}\" dispatch, pid={pid_printable}"),
format!(
"client dispatch pid={} exe={}",
&pid_printable, &exe_printable,
)
},
{ {
let client = client.clone(); let client = client.clone();
async move { async move {
@@ -166,7 +161,7 @@ impl Client {
}); });
let _ = client.flush_join_handle.get_or_init(|| { let _ = client.flush_join_handle.get_or_init(|| {
task::new( task::new(
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,), || format!("Stardust client \"{exe_printable}\" flush, pid={pid_printable}"),
{ {
let client = client.clone(); let client = client.clone();
async move { async move {

View File

@@ -33,7 +33,8 @@ impl LaunchInfo {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ClientStateParsed { pub struct ClientStateParsed {
pub launch_info: Option<LaunchInfo>, pub launch_info: Option<LaunchInfo>,
pub data: Vec<u8>, #[serde(skip)]
pub data: Option<Vec<u8>>,
pub root: Mat4, pub root: Mat4,
pub spatial_anchors: FxHashMap<String, Mat4>, pub spatial_anchors: FxHashMap<String, Mat4>,
} }
@@ -41,7 +42,7 @@ impl ClientStateParsed {
pub fn from_deserialized(client: &Client, state: ClientState) -> Self { pub fn from_deserialized(client: &Client, state: ClientState) -> Self {
ClientStateParsed { ClientStateParsed {
launch_info: LaunchInfo::from_client(client), launch_info: LaunchInfo::from_client(client),
data: state.data.unwrap_or_default(), data: state.data,
root: Self::spatial_transform(client, state.root).unwrap_or_default(), root: Self::spatial_transform(client, state.root).unwrap_or_default(),
spatial_anchors: state spatial_anchors: state
.spatial_anchors .spatial_anchors
@@ -63,7 +64,9 @@ impl ClientStateParsed {
} }
pub fn from_file(file: &Path) -> Option<Self> { pub fn from_file(file: &Path) -> Option<Self> {
let file_string = std::fs::read_to_string(file).ok()?; let file_string = std::fs::read_to_string(file).ok()?;
toml::from_str(&file_string).ok() let mut client_state: Self = toml::from_str(&file_string).ok()?;
client_state.data = std::fs::read(file.with_extension("bin")).ok();
Some(client_state)
} }
pub fn to_file(&self, directory: &Path) { pub fn to_file(&self, directory: &Path) {
let app_name = self let app_name = self
@@ -71,11 +74,14 @@ impl ClientStateParsed {
.as_ref() .as_ref()
.map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap()) .map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap())
.unwrap_or("unknown"); .unwrap_or("unknown");
let state_file_path = directory let state_file_prefix = directory.join(format!("{app_name}-{}", nanoid::nanoid!()));
.join(format!("{app_name}-{}", nanoid::nanoid!())) let state_metadata_path = state_file_prefix.with_extension("toml");
.with_extension("toml"); let state_data_path = state_file_prefix.with_extension("bin");
std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap(); std::fs::write(state_metadata_path, toml::to_string(&self).unwrap()).unwrap();
if let Some(data) = self.data.as_deref() {
std::fs::write(state_data_path, data).unwrap();
}
} }
pub fn apply_to(&self, client: &Arc<Client>) -> ClientState { pub fn apply_to(&self, client: &Arc<Client>) -> ClientState {
@@ -83,7 +89,7 @@ impl ClientStateParsed {
root.set_transform(self.root) root.set_transform(self.root)
} }
ClientState { ClientState {
data: Some(self.data.clone()), data: self.data.clone(),
root: 0, root: 0,
spatial_anchors: self spatial_anchors: self
.spatial_anchors .spatial_anchors
@@ -111,7 +117,7 @@ impl Default for ClientStateParsed {
fn default() -> Self { fn default() -> Self {
Self { Self {
launch_info: None, launch_info: None,
data: Default::default(), data: None,
root: Mat4::IDENTITY, root: Mat4::IDENTITY,
spatial_anchors: Default::default(), spatial_anchors: Default::default(),
} }

View File

@@ -3,10 +3,8 @@ use stardust_xr::values::color::{AlphaColor, Rgb, color_space::LinearRgb};
pub trait ColorConvert { pub trait ColorConvert {
fn to_bevy(&self) -> bevy::color::Color; fn to_bevy(&self) -> bevy::color::Color;
} }
// even tho its supposed to be linear the values have to be interpreted as Srgba to produce the
// correct result because StereoKit used Srgba while it was assumed that is uses linear rgba
impl ColorConvert for AlphaColor<f32, Rgb<f32, LinearRgb>> { impl ColorConvert for AlphaColor<f32, Rgb<f32, LinearRgb>> {
fn to_bevy(&self) -> bevy::color::Color { fn to_bevy(&self) -> bevy::color::Color {
bevy::color::Color::srgba(self.c.r, self.c.g, self.c.b, self.a) bevy::color::Color::linear_rgba(self.c.r, self.c.g, self.c.b, self.a)
} }
} }

View File

@@ -1,5 +1,10 @@
use std::ops::Deref;
use std::sync::Arc;
use bevy::prelude::*; use bevy::prelude::*;
use crate::nodes::spatial::SpatialNode;
use super::bevy_channel::{BevyChannel, BevyChannelReader}; use super::bevy_channel::{BevyChannel, BevyChannelReader};
pub struct EntityHandlePlugin; pub struct EntityHandlePlugin;
@@ -10,24 +15,46 @@ impl Plugin for EntityHandlePlugin {
} }
} }
fn despawn(mut cmds: Commands, mut reader: ResMut<BevyChannelReader<Entity>>) { fn despawn(
mut cmds: Commands,
mut reader: ResMut<BevyChannelReader<Entity>>,
child_query: Query<&Children>,
has_spatial: Query<Has<SpatialNode>>,
) {
while let Some(e) = reader.read() { while let Some(e) = reader.read() {
cmds.entity(e).try_despawn(); if let Ok(children) = child_query.get(e) {
for e in children {
if has_spatial.get(*e).unwrap_or_default() {
cmds.entity(*e).try_remove::<ChildOf>();
}
}
}
cmds.entity(e).despawn();
} }
} }
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct EntityHandle(Arc<EntityHandleInner>);
impl EntityHandle {
pub fn get(&self) -> Entity {
self.0.0
}
pub fn new(entity: Entity) -> Self {
Self(EntityHandleInner(entity).into())
}
}
impl Deref for EntityHandle {
type Target = Entity;
fn deref(&self) -> &Self::Target {
&self.0.0
}
}
static DESTROY: BevyChannel<Entity> = BevyChannel::new(); static DESTROY: BevyChannel<Entity> = BevyChannel::new();
#[derive(Deref, DerefMut, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EntityHandle(pub Entity); pub struct EntityHandleInner(Entity);
impl Drop for EntityHandle { impl Drop for EntityHandleInner {
fn drop(&mut self) { fn drop(&mut self) {
if DESTROY.send(self.0).is_none() { DESTROY.send(self.0);
error!("Entity Destroy channel not open");
}
}
}
impl From<Entity> for EntityHandle {
fn from(value: Entity) -> Self {
Self(value)
} }
} }

View File

@@ -28,9 +28,6 @@ pub enum ServerError {
DeserializationError(#[from] DeserializationError), DeserializationError(#[from] DeserializationError),
#[error("Reader error: {0}")] #[error("Reader error: {0}")]
ReaderError(#[from] ReaderError), ReaderError(#[from] ReaderError),
#[cfg(feature = "wayland")]
#[error("Wayland error: {0}")]
WaylandError(waynest::server::Error),
#[error("Aspect {} does not exist for node", 0.to_string())] #[error("Aspect {} does not exist for node", 0.to_string())]
NoAspect(TypeId), NoAspect(TypeId),
#[error("{0}")] #[error("{0}")]

View File

@@ -36,18 +36,18 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
for pair in new.0.iter() { for pair in new.0.iter() {
let (id, entry) = pair.pair(); let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade() { if let Some(entry) = entry.upgrade()
if !old.0.contains_key(id) { && !old.0.contains_key(id)
added.push(entry); {
} added.push(entry);
} }
} }
for pair in old.0.iter() { for pair in old.0.iter() {
let (id, entry) = pair.pair(); let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade() { if let Some(entry) = entry.upgrade()
if !new.0.contains_key(id) { && !new.0.contains_key(id)
removed.push(entry); {
} removed.push(entry);
} }
} }
(added, removed) (added, removed)
@@ -143,7 +143,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
pub const fn new() -> Self { pub const fn new() -> Self {
OwnedRegistry(const_mutex(None)) OwnedRegistry(const_mutex(None))
} }
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> { fn lock(&self) -> MappedMutexGuard<'_, FxHashMap<usize, Arc<T>>> {
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default)) MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
} }
pub fn add(&self, t: T) -> Arc<T> pub fn add(&self, t: T) -> Arc<T>

View File

@@ -1,19 +1,80 @@
use crate::core::error::Result; use crate::{
use crate::nodes::Node; core::{
use crate::nodes::alias::get_original; client::Client,
use crate::{core::client::Client, nodes::Message}; error::{Result, ServerError},
},
nodes::{Message, Node, alias::get_original},
};
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Serialize; use serde::Serialize;
use stardust_xr::scenegraph; use stardust_xr::{
use stardust_xr::scenegraph::ScenegraphError; messenger::MethodResponse,
use stardust_xr::schemas::flex::serialize; scenegraph::{self, ScenegraphError},
use std::future::Future; schemas::flex::serialize,
use std::os::fd::OwnedFd; };
use std::sync::{Arc, OnceLock, Weak}; use std::{
use tokio::sync::oneshot; os::fd::OwnedFd,
sync::{Arc, OnceLock, Weak},
};
use tracing::{debug, debug_span}; use tracing::{debug, debug_span};
pub struct MethodResponseSender(pub(crate) MethodResponse);
impl MethodResponseSender {
pub fn send_err(self, error: ScenegraphError) {
self.0.send(Err(error));
}
pub fn send<T: Serialize>(self, result: Result<T, ServerError>) {
let data = match result {
Ok(d) => d,
Err(e) => {
self.0.send(Err(ScenegraphError::MemberError {
error: e.to_string(),
}));
return;
}
};
let Ok(serialized) = stardust_xr::schemas::flex::serialize(data) else {
self.0.send(Err(ScenegraphError::MemberError {
error: "Internal: Failed to serialize".to_string(),
}));
return;
};
self.0.send(Ok((&serialized, Vec::<OwnedFd>::new())));
}
pub fn wrap<T: Serialize, F: FnOnce() -> Result<T>>(self, f: F) {
self.send(f())
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move {
let (value, fds) = match f.await {
Ok(d) => d,
Err(e) => {
self.0.send(Err(ScenegraphError::MemberError {
error: e.to_string(),
}));
return;
}
};
let Ok(serialized) = serialize(value) else {
self.0.send(Err(ScenegraphError::MemberError {
error: "Internal: Failed to serialize".to_string(),
}));
return;
};
self.0.send(Ok((&serialized, fds)));
});
}
}
impl std::fmt::Debug for MethodResponseSender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedMethodResponse").finish()
}
}
#[derive(Default)] #[derive(Default)]
pub struct Scenegraph { pub struct Scenegraph {
pub(super) client: OnceLock<Weak<Client>>, pub(super) client: OnceLock<Weak<Client>>,
@@ -45,43 +106,6 @@ impl Scenegraph {
self.nodes.lock().remove(&node) self.nodes.lock().remove(&node)
} }
} }
pub type MethodResponse = Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>;
pub struct MethodResponseSender(oneshot::Sender<MethodResponse>);
impl MethodResponseSender {
pub fn send(self, t: Result<Message, ScenegraphError>) {
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
}
// pub fn send_method_return<T: Serialize>(
// self,
// result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
// ) {
// let _ = self.0.send(map_method_return(result));
// }
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MemberError {
error: e.to_string(),
}))
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
}
}
fn map_method_return<T: Serialize>(
result: Result<(T, Vec<OwnedFd>)>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
error: e.to_string(),
})?;
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
error: format!("Internal: Serialization failed: {e}"),
})?;
Ok((serialized_value, fds))
}
impl scenegraph::Scenegraph for Scenegraph { impl scenegraph::Scenegraph for Scenegraph {
fn send_signal( fn send_signal(
&self, &self,
@@ -115,15 +139,15 @@ impl scenegraph::Scenegraph for Scenegraph {
method: u64, method: u64,
data: &[u8], data: &[u8],
fds: Vec<OwnedFd>, fds: Vec<OwnedFd>,
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>, response: MethodResponse,
) { ) {
let Some(client) = self.get_client() else { let Some(client) = self.get_client() else {
let _ = response.send(Err(ScenegraphError::NodeNotFound)); response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
debug!(aspect_id, node_id, method, "Handle method"); debug!(aspect_id, node_id, method, "Handle method");
let Some(node) = self.get_node(node_id) else { let Some(node) = self.get_node(node_id) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound)); response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
node.execute_local_method( node.execute_local_method(

View File

@@ -1,4 +1,3 @@
use color_eyre::eyre::Result;
use std::future::Future; use std::future::Future;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -11,7 +10,7 @@ pub fn new<
>( >(
name_fn: F, name_fn: F,
async_future: A, async_future: A,
) -> Result<JoinHandle<O>> { ) -> std::io::Result<JoinHandle<O>> {
#[cfg(not(feature = "profile_tokio"))] #[cfg(not(feature = "profile_tokio"))]
let result = Ok(tokio::task::spawn(async_future)); let result = Ok(tokio::task::spawn(async_future));
#[cfg(feature = "profile_tokio")] #[cfg(feature = "profile_tokio")]

View File

@@ -1,3 +1,4 @@
#![recursion_limit = "256"]
#![allow(clippy::empty_docs)] #![allow(clippy::empty_docs)]
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
@@ -5,11 +6,14 @@ mod core;
mod nodes; mod nodes;
mod objects; mod objects;
mod session; mod session;
mod spectator_cam;
pub mod tracking_offset; pub mod tracking_offset;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
mod wayland; mod wayland;
use crate::nodes::drawable::sky::SkyPlugin;
use crate::nodes::input; use crate::nodes::input;
use crate::spectator_cam::SpectatorCameraPlugin;
use bevy::MinimalPlugins; use bevy::MinimalPlugins;
use bevy::a11y::AccessibilityPlugin; use bevy::a11y::AccessibilityPlugin;
@@ -18,12 +22,16 @@ use bevy::asset::{AssetMetaCheck, UnapprovedPathMode};
use bevy::audio::AudioPlugin; use bevy::audio::AudioPlugin;
use bevy::core_pipeline::CorePipelinePlugin; use bevy::core_pipeline::CorePipelinePlugin;
use bevy::core_pipeline::oit::OrderIndependentTransparencySettings; use bevy::core_pipeline::oit::OrderIndependentTransparencySettings;
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::diagnostic::DiagnosticsPlugin; use bevy::diagnostic::DiagnosticsPlugin;
use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel}; use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel};
use bevy::gizmos::GizmoPlugin; use bevy::gizmos::GizmoPlugin;
use bevy::gltf::GltfPlugin; use bevy::gltf::GltfPlugin;
use bevy::input::InputPlugin; use bevy::input::InputPlugin;
use bevy::pbr::PbrPlugin; use bevy::pbr::PbrPlugin;
use bevy::render::pipelined_rendering::{
PipelinedRenderThreadOnCreateCallback, PipelinedRenderingPlugin,
};
use bevy::render::settings::{Backends, RenderCreation, WgpuSettings}; use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
use bevy::render::{RenderDebugFlags, RenderPlugin}; use bevy::render::{RenderDebugFlags, RenderPlugin};
use bevy::scene::ScenePlugin; use bevy::scene::ScenePlugin;
@@ -62,17 +70,20 @@ use objects::play_space::PlaySpacePlugin;
use openxr::{EnvironmentBlendMode, ReferenceSpaceType}; use openxr::{EnvironmentBlendMode, ReferenceSpaceType};
use session::{launch_start, save_session}; use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server; use stardust_xr::server::LockedSocket;
use std::ops::DerefMut as _; use std::ops::DerefMut as _;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tokio::sync::Notify; use tokio::sync::Notify;
use tokio::task::JoinError; use tokio::task::JoinError;
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
use tracing::{error, info}; use tracing::{error, info};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::{EnvFilter, fmt, prelude::*}; use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use tracking_offset::TrackingOffsetPlugin; use tracking_offset::TrackingOffsetPlugin;
#[cfg(feature = "wayland")]
use wayland::{Wayland, WaylandPlugin}; use wayland::{Wayland, WaylandPlugin};
use zbus::Connection; use zbus::Connection;
use zbus::fdo::ObjectManager; use zbus::fdo::ObjectManager;
@@ -84,7 +95,11 @@ use bevy::prelude::*;
struct CliArgs { struct CliArgs {
/// Force flatscreen mode and use the mouse pointer as a 3D pointer /// Force flatscreen mode and use the mouse pointer as a 3D pointer
#[clap(short, long, action)] #[clap(short, long, action)]
flatscreen: bool, force_flatscreen: bool,
/// Replaces the flatscreen mode with a first person spectator camera
#[clap(short, long, action)]
spectator: bool,
/// Creates a transparent window fot the flatscreen mode /// Creates a transparent window fot the flatscreen mode
#[clap(short, long, action)] #[clap(short, long, action)]
@@ -132,9 +147,7 @@ async fn main() -> Result<AppExit, JoinError> {
); );
#[cfg(feature = "profile_tokio")] #[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build(); let registry = registry.with(console_subscriber::spawn());
#[cfg(feature = "profile_tokio")]
let registry = registry.with(console_layer);
let log_layer = fmt::Layer::new() let log_layer = fmt::Layer::new()
.with_thread_names(true) .with_thread_names(true)
@@ -143,22 +156,23 @@ async fn main() -> Result<AppExit, JoinError> {
.with_filter( .with_filter(
EnvFilter::builder() EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into()) .with_default_directive(LevelFilter::WARN.into())
.from_env_lossy(), .from_env_lossy()
.add_directive(Directive::from_str("bevy_mesh_text_3d::text_glyphs=off").unwrap()),
); );
registry.with(log_layer).init(); registry.with(log_layer).init();
let cli_args = CliArgs::parse(); let cli_args = CliArgs::parse();
let socket_path = let locked_socket =
server::get_free_socket_path().expect("Unable to find a free stardust socket path"); LockedSocket::get_free().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell"); STARDUST_INSTANCE.set(locked_socket.socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
info!( info!(
socket_path = ?socket_path.display(), socket_path = ?locked_socket.socket_path.display(),
"Stardust socket created" "Stardust socket created"
); );
let socket = let socket = UnixListener::bind(locked_socket.socket_path)
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}"); .expect("Couldn't spawn stardust server at {socket_path}");
task::new(|| "client join loop", async move { task::new(|| "Stardust socket accept loop", async move {
loop { loop {
let Ok((stream, _)) = socket.accept().await else { let Ok((stream, _)) = socket.accept().await else {
continue; continue;
@@ -196,10 +210,9 @@ async fn main() -> Result<AppExit, JoinError> {
.await .await
.expect("Couldn't add the object manager"); .expect("Couldn't add the object manager");
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect( let object_registry = ObjectRegistry::new(&dbus_connection).await;
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
);
#[cfg(feature = "wayland")]
let _wayland = Wayland::new().expect("Couldn't create Wayland instance"); let _wayland = Wayland::new().expect("Couldn't create Wayland instance");
let ready_notifier = Arc::new(Notify::new()); let ready_notifier = Arc::new(Notify::new());
@@ -242,7 +255,7 @@ async fn main() -> Result<AppExit, JoinError> {
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)] #[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)]
pub struct PreFrameWait; pub struct PreFrameWait;
#[derive(Resource, Deref)] #[derive(Resource, Deref)]
pub struct ObjectRegistryRes(ObjectRegistry); pub struct ObjectRegistryRes(Arc<ObjectRegistry>);
#[derive(Resource, Deref)] #[derive(Resource, Deref)]
pub struct DbusConnection(Connection); pub struct DbusConnection(Connection);
@@ -251,7 +264,7 @@ fn bevy_loop(
_project_dirs: Option<ProjectDirs>, _project_dirs: Option<ProjectDirs>,
args: CliArgs, args: CliArgs,
dbus_connection: Connection, dbus_connection: Connection,
object_registry: ObjectRegistry, object_registry: Arc<ObjectRegistry>,
) -> AppExit { ) -> AppExit {
let mut app = App::new(); let mut app = App::new();
app.insert_resource(DbusConnection(dbus_connection)); app.insert_resource(DbusConnection(dbus_connection));
@@ -299,8 +312,11 @@ fn bevy_loop(
// .add(AnimationPlugin) // .add(AnimationPlugin)
.add(AudioPlugin::default()) .add(AudioPlugin::default())
.add(GizmoPlugin) .add(GizmoPlugin)
.add(WindowPlugin::default()) .add(WindowPlugin::default());
.add(DmabufImportPlugin); #[cfg(feature = "wayland")]
{
plugins = plugins.add(DmabufImportPlugin);
}
let mut task_pool_plugin = TaskPoolPlugin::default(); let mut task_pool_plugin = TaskPoolPlugin::default();
// make tokio work // make tokio work
let handle = tokio::runtime::Handle::current(); let handle = tokio::runtime::Handle::current();
@@ -316,19 +332,22 @@ fn bevy_loop(
.async_compute .async_compute
.on_thread_spawn = Some(enter_runtime_context.clone()); .on_thread_spawn = Some(enter_runtime_context.clone());
plugins = plugins.set(task_pool_plugin); plugins = plugins.set(task_pool_plugin);
if args.flatscreen if std::env::var("DISPLAY").is_ok_and(|s| !s.is_empty())
|| std::env::var_os("DISPLAY").is_some_and(|s| !s.is_empty()) || std::env::var("WAYLAND_DISPLAY").is_ok_and(|s| !s.is_empty())
|| std::env::var_os("WAYLAND_DISPLAY").is_some_and(|s| !s.is_empty())
{ {
let mut plugin = WinitPlugin::<WakeUp>::default(); let mut plugin = WinitPlugin::<WakeUp>::default();
plugin.run_on_any_thread = true; plugin.run_on_any_thread = true;
plugins = plugins plugins = plugins.add(plugin).disable::<ScheduleRunnerPlugin>();
.add(plugin) plugins = match args.spectator {
.disable::<ScheduleRunnerPlugin>() true => plugins.add(SpectatorCameraPlugin),
.add(FlatscreenInputPlugin); false => plugins.add(FlatscreenInputPlugin),
};
} }
app.insert_resource(PipelinedRenderThreadOnCreateCallback(
enter_runtime_context.clone(),
));
app.add_plugins( app.add_plugins(
if !args.flatscreen { if !args.force_flatscreen {
add_xr_plugins(plugins) add_xr_plugins(plugins)
.set(OxrInitPlugin { .set(OxrInitPlugin {
app_info: AppInfo { app_info: AppInfo {
@@ -370,7 +389,7 @@ fn bevy_loop(
composite_alpha_mode: if args.transparent_flatscreen { composite_alpha_mode: if args.transparent_flatscreen {
CompositeAlphaMode::PreMultiplied CompositeAlphaMode::PreMultiplied
} else { } else {
CompositeAlphaMode::Inherit CompositeAlphaMode::Auto
}, },
title: "StardustXR server flatscreen mode".to_string(), title: "StardustXR server flatscreen mode".to_string(),
..default() ..default()
@@ -378,8 +397,10 @@ fn bevy_loop(
..default() ..default()
}), }),
); );
app.add_plugins(PipelinedRenderingPlugin);
app.add_plugins(bevy_sk::hand::HandPlugin); app.add_plugins(bevy_sk::hand::HandPlugin);
app.add_plugins(bevy_equirect::EquirectangularPlugin);
// app.add_plugins(HandGizmosPlugin); // app.add_plugins(HandGizmosPlugin);
app.world_mut().resource_mut::<AmbientLight>().brightness = 1000.0; app.world_mut().resource_mut::<AmbientLight>().brightness = 1000.0;
if let Some(priority) = args.overlay_priority { if let Some(priority) = args.overlay_priority {
@@ -416,15 +437,19 @@ fn bevy_loop(
TextNodePlugin, TextNodePlugin,
LinesNodePlugin, LinesNodePlugin,
AudioNodePlugin, AudioNodePlugin,
// not really a node ig? at least for now
SkyPlugin,
)); ));
// object plugins // object plugins
app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin, HmdPlugin)); app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin, HmdPlugin));
// feature plugins // feature plugins
app.add_plugins((WaylandPlugin, TrackingOffsetPlugin, FieldDebugGizmoPlugin)); #[cfg(feature = "wayland")]
app.add_plugins(WaylandPlugin);
app.add_plugins((TrackingOffsetPlugin, FieldDebugGizmoPlugin));
app.add_systems(PostStartup, move || { app.add_systems(PostStartup, move || {
ready_notifier.notify_waiters(); ready_notifier.notify_waiters();
}); });
app.add_observer(cam_observer); app.add_observer(cam_settings);
app.add_systems( app.add_systems(
XrFirst, XrFirst,
xr_step xr_step
@@ -435,11 +460,13 @@ fn bevy_loop(
app.run() app.run()
} }
fn cam_observer( fn cam_settings(
trigger: Trigger<OnAdd, Camera3d>, trigger: Trigger<OnAdd, Camera3d>,
mut query: Query<(&mut Projection, &mut Msaa), With<Camera3d>>, mut query: Query<(Entity, &mut Projection, &mut Msaa, &mut Tonemapping), With<Camera3d>>,
mut cmds: Commands,
) { ) {
let Ok((mut projection, mut msaa)) = query.get_mut(trigger.target()) else { let Ok((entity, mut projection, mut msaa, mut tonemapping)) = query.get_mut(trigger.target())
else {
return; return;
}; };
info!("modifying cam"); info!("modifying cam");
@@ -455,26 +482,9 @@ fn cam_observer(
} }
} }
*msaa = Msaa::Off; *msaa = Msaa::Off;
} *tonemapping = Tonemapping::None;
cmds.entity(entity)
fn add_oit( .insert(OrderIndependentTransparencySettings::default());
mut commands: Commands,
cameras: Query<
Entity,
(
With<Camera3d>,
Without<OrderIndependentTransparencySettings>,
),
>,
) {
for entity in &cameras {
commands
.entity(entity)
.insert(OrderIndependentTransparencySettings {
layer_count: 4,
alpha_threshold: 0.00,
});
}
} }
fn xr_step(world: &mut World) { fn xr_step(world: &mut World) {
@@ -484,13 +494,6 @@ fn xr_step(world: &mut World) {
let time = world.resource::<bevy::prelude::Time>().delta_secs_f64(); let time = world.resource::<bevy::prelude::Time>().delta_secs_f64();
nodes::root::Root::send_frame_events(time); nodes::root::Root::send_frame_events(time);
// we are targeting the frame after the wait
if let Some(mut state) = world.get_resource_mut::<OxrFrameState>() {
state.predicted_display_time = openxr::Time::from_nanos(
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
);
}
let should_wait = world let should_wait = world
.run_system_cached(should_run_frame_loop) .run_system_cached(should_run_frame_loop)
.unwrap_or(false); .unwrap_or(false);
@@ -510,3 +513,13 @@ fn xr_step(world: &mut World) {
tick_internal_client(); tick_internal_client();
} }
pub fn get_time(pipelined: bool, state: &OxrFrameState) -> openxr::Time {
if pipelined {
openxr::Time::from_nanos(
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
)
} else {
state.predicted_display_time
}
}

View File

@@ -1,5 +1,7 @@
use super::{Aspect, AspectIdentifier, Node}; use super::{Aspect, AspectIdentifier, Node};
use crate::core::{client::Client, error::Result, registry::Registry}; use crate::core::{
client::Client, error::Result, registry::Registry, scenegraph::MethodResponseSender,
};
use std::{ use std::{
ops::Add, ops::Add,
sync::{Arc, Weak}, sync::{Arc, Weak},
@@ -71,9 +73,6 @@ impl AspectIdentifier for Alias {
const ID: u64 = 0; const ID: u64 = 0;
} }
impl Aspect for Alias { impl Aspect for Alias {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
fn run_signal( fn run_signal(
&self, &self,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
@@ -89,7 +88,7 @@ impl Aspect for Alias {
_node: Arc<Node>, _node: Arc<Node>,
_method: u64, _method: u64,
_message: super::Message, _message: super::Message,
_response: crate::core::scenegraph::MethodResponseSender, _response: MethodResponseSender,
) { ) {
} }
} }
@@ -122,6 +121,7 @@ impl AliasList {
fn add(&self, node: &Arc<Node>) { fn add(&self, node: &Arc<Node>) {
self.0.add_raw(node); self.0.add_raw(node);
} }
#[tracing::instrument(level = "trace", skip_all)]
pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> { pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> {
self.0 self.0
.get_valid_contents() .get_valid_contents()

View File

@@ -57,29 +57,27 @@ fn update_sound_event(
for sound in SOUND_REGISTRY.get_valid_contents() { for sound in SOUND_REGISTRY.get_valid_contents() {
if sound.entity.get().is_none() { if sound.entity.get().is_none() {
let handle = asset_server.load(sound.pending_audio_path.as_path()); let handle = asset_server.load(sound.pending_audio_path.as_path());
sound let entity = cmds
.entity .spawn((
.set( Name::new("Audio Node"),
cmds.spawn(( SpatialNode(Arc::downgrade(&sound.spatial)),
Name::new("Audio Node"), AudioPlayer::new(handle),
SpatialNode(Arc::downgrade(&sound.spatial)), PlaybackSettings {
AudioPlayer::new(handle), mode: PlaybackMode::Once,
PlaybackSettings { volume: Volume::Linear(sound.volume),
mode: PlaybackMode::Once, speed: 1.0,
volume: Volume::Linear(sound.volume), paused: true,
speed: 1.0, muted: false,
paused: true, spatial: true,
muted: false, spatial_scale: None,
spatial: true, },
spatial_scale: None, ))
}, .id();
)) let entity = EntityHandle::new(entity);
.id() sound.spatial.set_entity(entity.clone());
.into(), sound.entity.set(entity).unwrap();
)
.unwrap();
} }
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.0).ok()) { if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.get()).ok()) {
if sound.play.lock().take().is_some() { if sound.play.lock().take().is_some() {
sink.play(); sink.play();
} }

View File

@@ -0,0 +1,56 @@
#import bevy_pbr::{
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::alpha_discard,
}
#ifdef PREPASS_PIPELINE
#import bevy_pbr::{
prepass_io::{VertexOutput, FragmentOutput},
pbr_deferred_functions::deferred_output,
}
#else
#import bevy_pbr::{
forward_io::{VertexOutput, FragmentOutput},
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
}
#endif
#ifdef OIT_ENABLED
#import bevy_core_pipeline::oit::oit_draw
#endif
@fragment
fn fragment(
in: VertexOutput,
@builtin(front_facing) is_front: bool,
) -> FragmentOutput {
// generate a PbrInput struct from the StandardMaterial bindings
var pbr_input = pbr_input_from_standard_material(in, is_front);
#ifdef VERTEX_COLORS
// Multiply emissive color by vertex color
pbr_input.material.emissive *= vec4(in.color.rgb, 1.0);
#endif
// alpha discard
// pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
#ifdef PREPASS_PIPELINE
// in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader.
let out = deferred_output(in, pbr_input);
#else
var out: FragmentOutput;
// apply lighting
out.color = apply_pbr_lighting(pbr_input);
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr)
// note this does not include fullscreen postprocessing effects like bloom.
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
#endif
#ifdef OIT_ENABLED
oit_draw(in.position, out.color, false);
discard;
#else
return out;
#endif
}

View File

@@ -5,17 +5,17 @@ use crate::{
client::Client, color::ColorConvert, entity_handle::EntityHandle, error::Result, client::Client, color::ColorConvert, entity_handle::EntityHandle, error::Result,
registry::Registry, registry::Registry,
}, },
nodes::{ nodes::{Node, drawable::LinePoint, spatial::Spatial},
Node,
spatial::{Spatial, SpatialNode},
},
}; };
use bevy::{ use bevy::{
asset::RenderAssetUsages, asset::{AssetEvents, RenderAssetUsages, weak_handle},
pbr::{ExtendedMaterial, MaterialExtension},
prelude::*, prelude::*,
render::{ render::{
mesh::{Indices, MeshAabb, PrimitiveTopology}, mesh::{Indices, PrimitiveTopology, VertexAttributeValues},
primitives::Aabb, primitives::Aabb,
render_resource::{AsBindGroup, ShaderRef},
view::VisibilitySystems,
}, },
}; };
use glam::Vec3; use glam::Vec3;
@@ -24,25 +24,79 @@ use std::sync::{
Arc, OnceLock, Arc, OnceLock,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
}; };
use tokio::sync::Notify;
type LineMaterial = ExtendedMaterial<BevyMaterial, LineExtension>;
const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("7d28aa5a-3abd-43bb-b0e9-0de8b81b650d");
// No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
#[data(50, u32, binding_array(101))]
#[bindless(index_table(range(50..51), binding(100)))]
pub struct LineExtension {}
impl From<&LineExtension> for u32 {
fn from(_: &LineExtension) -> Self {
0
}
}
impl MaterialExtension for LineExtension {
fn fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn prepass_fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn deferred_fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn alpha_mode() -> Option<AlphaMode> {
Some(AlphaMode::Blend)
}
}
pub struct LinesNodePlugin; pub struct LinesNodePlugin;
impl Plugin for LinesNodePlugin { impl Plugin for LinesNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, build_line_mesh); app.add_systems(
PostUpdate,
build_line_mesh
.after(TransformSystem::TransformPropagate)
.before(AssetEvents)
.after(VisibilitySystems::VisibilityPropagate)
.before(VisibilitySystems::CheckVisibility),
);
app.world_mut().resource_mut::<Assets<Shader>>().insert(
LINE_SHADER_HANDLE.id(),
Shader::from_wgsl(
include_str!("line.wgsl"),
std::path::Path::new(file!())
.parent()
.unwrap()
.join("line.wgsl")
.to_string_lossy(),
),
);
app.add_plugins(MaterialPlugin::<LineMaterial>::default());
} }
} }
fn build_line_mesh( fn build_line_mesh(
mut meshes: ResMut<Assets<Mesh>>,
mut cmds: Commands, mut cmds: Commands,
mut materials: ResMut<Assets<BevyMaterial>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<LineMaterial>>,
query: Query<(Ref<GlobalTransform>, &InheritedVisibility)>,
) { ) {
for lines in LINES_REGISTRY for lines in LINES_REGISTRY.get_valid_contents().into_iter()
.get_valid_contents()
.into_iter()
.filter(|l| l.gen_mesh.load(Ordering::Relaxed))
{ {
let Some((transform, visibil)) = lines.spatial.get_entity().and_then(|e| query.get(e).ok())
else {
continue;
};
if !(lines.gen_mesh.load(Ordering::Relaxed) || transform.is_changed()) {
continue;
}
lines.gen_mesh.store(false, Ordering::Relaxed); lines.gen_mesh.store(false, Ordering::Relaxed);
let mut vertex_positions = Vec::<Vec3>::new(); let mut vertex_positions = Vec::<Vec3>::new();
let mut vertex_normals = Vec::<Vec3>::new(); let mut vertex_normals = Vec::<Vec3>::new();
@@ -50,16 +104,14 @@ fn build_line_mesh(
let mut vertex_indices = Vec::<u32>::new(); let mut vertex_indices = Vec::<u32>::new();
let lines_data = lines.data.lock(); let lines_data = lines.data.lock();
if lines_data.is_empty() { if lines_data.is_empty() {
*lines.bounds.lock() = Aabb::default(); *lines.bounds.lock() = Some(Aabb::default());
lines.setup_complete.notify_waiters();
match lines.entity.get() { match lines.entity.get() {
Some(e) => cmds.entity(**e), Some(e) => cmds.entity(**e),
None => { None => {
let e = cmds.spawn(( // if we couldn't get the lines entity then we need to gen the mesh later
Name::new("LinesNode"), lines.gen_mesh.store(true, Ordering::Relaxed);
SpatialNode(Arc::downgrade(&lines.spatial)), continue;
));
_ = lines.entity.set(e.id().into());
e
} }
} }
.remove::<Mesh3d>(); .remove::<Mesh3d>();
@@ -68,15 +120,35 @@ fn build_line_mesh(
let mut indices_set = 0; let mut indices_set = 0;
for line in lines_data.iter() { for line in lines_data.iter() {
// yes this alloc is suboptimal, but good enough for now
let line_points = line
.points
.iter()
.map(|p: &LinePoint| LinePoint {
// point: transform.transform_point(p.point.into()).into(),
point: transform.transform_point(p.point.into()).into(),
thickness: p.thickness,
color: p.color,
})
.collect::<Vec<_>>();
let start_set = indices_set; let start_set = indices_set;
// Create a sliding window of points to process each segment of the line // Create a sliding window of points to process each segment of the line
// For cyclic lines: wraps around by connecting last point back to first // For cyclic lines: wraps around by connecting last point back to first
// For non-cyclic lines: handles endpoints with None values // For non-cyclic lines: handles endpoints with None values
let point_windows = { let point_windows = {
let mut out = Vec::new(); let mut out = Vec::new();
let mut last = line.cyclic.then(|| line.points.last()).flatten(); let mut last = line.cyclic.then(|| line_points.last()).flatten();
let mut peekable = line.points.iter().peekable(); let mut peekable = line_points.iter().peekable();
while let Some(curr) = peekable.next() { while let Some(curr) = peekable.next() {
// Skip this point if it has the same position as the previous point
if let Some(prev) = last
&& Vec3::from(prev.point) == Vec3::from(curr.point)
{
last = Some(curr);
continue;
}
let mut end = false; let mut end = false;
// Determine the next point - either the next in sequence or // Determine the next point - either the next in sequence or
// for cyclic lines, wrap back to first point at the end // for cyclic lines, wrap back to first point at the end
@@ -84,7 +156,7 @@ fn build_line_mesh(
Some(v) => Some(*v), Some(v) => Some(*v),
None => { None => {
end = true; end = true;
line.cyclic.then(|| line.points.first()).flatten() line.cyclic.then(|| line_points.first()).flatten()
} }
}; };
@@ -93,6 +165,10 @@ fn build_line_mesh(
} }
out out
}; };
// if we can't make a full line, don't bother trying
if point_windows.len() < 2 {
continue;
}
for (last, curr, next, last_point) in point_windows { for (last, curr, next, last_point) in point_windows {
let last_quat = last.map(|v| { let last_quat = last.map(|v| {
Quat::from_rotation_arc( Quat::from_rotation_arc(
@@ -115,6 +191,10 @@ fn build_line_mesh(
(Some(last), None) => last, (Some(last), None) => last,
(Some(last), Some(next)) => last.lerp(next, 0.5), (Some(last), Some(next)) => last.lerp(next, 0.5),
}; };
if !quat.is_finite() {
error!("non finite quat: next: {next:?}, last: {last:?}, curr: {curr:?},");
break;
}
let normals = [ let normals = [
Vec3::X, Vec3::X,
Vec3::new(1., 0., 1.).normalize(), Vec3::new(1., 0., 1.).normalize(),
@@ -126,71 +206,81 @@ fn build_line_mesh(
Vec3::new(1., 0., -1.).normalize(), Vec3::new(1., 0., -1.).normalize(),
] ]
.map(Vec3::normalize) .map(Vec3::normalize)
.map(|v| (quat * v)); .map(|v| quat * v);
let points = normals.map(|v| (v * curr.thickness) + Vec3::from(curr.point)); let points = normals.map(|v| (v * curr.thickness) + Vec3::from(curr.point));
vertex_normals.extend(normals); vertex_normals.extend(normals);
vertex_positions.extend(points); vertex_positions.extend(points);
vertex_colors.extend([curr.color.to_bevy().to_srgba().to_f32_array(); 8]); vertex_colors.extend([curr.color.to_bevy().to_linear().to_f32_array(); 8]);
// Only connect vertices between segments if this isn't the end point // Only connect vertices between segments if this isn't the end point
if !last_point { if !last_point {
vertex_indices.extend(indices(indices_set)); vertex_indices.extend(indices(indices_set));
} }
indices_set += 1; indices_set += 1;
} }
// Handle the connection between start and end points: if indices_set > 0 {
// - For cyclic lines: connect last segment back to first // Handle the connection between start and end points:
// - For non-cyclic lines: add caps at both ends // - For cyclic lines: connect last segment back to first
if line.cyclic { // - For non-cyclic lines: add caps at both ends
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1)); if line.cyclic {
} else { vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
vertex_indices.extend(cap_indices(start_set, false)); } else {
vertex_indices.extend(cap_indices(indices_set - 1, true)); vertex_indices.extend(cap_indices(start_set, false));
vertex_indices.extend(cap_indices(indices_set - 1, true));
}
} }
} }
let mut mesh = Mesh::new( let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList, PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD, RenderAssetUsages::RENDER_WORLD,
); );
if vertex_colors.iter().flatten().any(|v| !v.is_finite()) {
panic!("vertex colors contains non finite float: {vertex_colors:#?}",);
}
if vertex_normals.iter().any(|v| !v.is_finite()) {
panic!("normals contains non finite dir: {vertex_normals:#?}",);
}
if vertex_normals.iter().any(|v| !v.is_normalized()) {
panic!("normals contains non normalized dir: {vertex_normals:#?}",);
}
if vertex_positions.iter().any(|v| !v.is_finite()) {
panic!("vertex positions contains non finite pos: {vertex_positions:#?}",);
}
mesh.insert_indices(Indices::U32(vertex_indices)); mesh.insert_indices(Indices::U32(vertex_indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions.clone()); mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors);
if let Some(aabb) = mesh.compute_aabb() {
info!(?aabb);
*lines.bounds.lock() = aabb;
}
match lines.entity.get() { let mut entity = match lines.entity.get() {
Some(e) => cmds.entity(**e), Some(e) => cmds.entity(**e),
None => { None => {
let e = cmds.spawn(( let e = cmds.spawn((
Name::new("LinesNode"), Name::new("LinesNode"),
SpatialNode(Arc::downgrade(&lines.spatial)), MeshMaterial3d(materials.add(ExtendedMaterial {
MeshMaterial3d(materials.add(BevyMaterial { base: BevyMaterial {
base_color: Color::WHITE, base_color: Color::WHITE,
perceptual_roughness: 1.0, perceptual_roughness: 1.0,
// TODO: this should be Blend alpha_mode: AlphaMode::Premultiplied,
alpha_mode: AlphaMode::Opaque, emissive: Color::linear_rgba(0.25, 0.25, 0.25, 1.0).into(),
..default() ..default()
},
extension: LineExtension {},
})), })),
)); ));
_ = lines.entity.set(e.id().into()); _ = lines.entity.set(EntityHandle::new(e.id()));
e e
} }
};
if let Some(VertexAttributeValues::Float32x3(values)) =
mesh.attribute(Mesh::ATTRIBUTE_POSITION)
{
let global_to_local = transform.affine().inverse();
let local_aabb = Aabb::enclosing(
values
.iter()
.map(|p| global_to_local.transform_point3(Vec3::from_slice(p))),
)
.unwrap_or_default();
let global_aabb =
Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))).unwrap_or_default();
*lines.bounds.lock() = Some(local_aabb);
lines.setup_complete.notify_waiters();
entity.insert(global_aabb);
} }
.insert(Mesh3d(meshes.add(mesh))); entity
.insert(Mesh3d(meshes.add(mesh)))
.insert(*visibil)
.insert(match visibil.get() {
true => Visibility::Visible,
false => Visibility::Hidden,
});
} }
} }
@@ -232,7 +322,8 @@ pub struct Lines {
data: Mutex<Vec<Line>>, data: Mutex<Vec<Line>>,
gen_mesh: AtomicBool, gen_mesh: AtomicBool,
entity: OnceLock<EntityHandle>, entity: OnceLock<EntityHandle>,
bounds: Mutex<Aabb>, bounds: Mutex<Option<Aabb>>,
setup_complete: Notify,
} }
impl Lines { impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> { pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
@@ -241,10 +332,19 @@ impl Lines {
.unwrap() .unwrap()
.bounding_box_calc .bounding_box_calc
.set(|node| { .set(|node| {
node.get_aspect::<Lines>() Box::pin(async {
.ok() let Ok(lines) = node.get_aspect::<Lines>() else {
.map(|v| *v.bounds.lock()) return Default::default();
.unwrap_or_default() };
let bounds = *lines.bounds.lock();
match bounds {
Some(aabb) => aabb,
None => {
lines.setup_complete.notified().await;
lines.bounds.lock().unwrap_or_default()
}
}
})
}); });
let lines = LINES_REGISTRY.add(Lines { let lines = LINES_REGISTRY.add(Lines {
@@ -252,7 +352,8 @@ impl Lines {
data: Mutex::new(lines), data: Mutex::new(lines),
gen_mesh: AtomicBool::new(true), gen_mesh: AtomicBool::new(true),
entity: OnceLock::new(), entity: OnceLock::new(),
bounds: Mutex::new(Aabb::default()), bounds: Mutex::new(Some(Aabb::default())),
setup_complete: Notify::new(),
}); });
node.add_aspect_raw(lines.clone()); node.add_aspect_raw(lines.clone());

View File

@@ -1,5 +1,6 @@
pub mod lines; pub mod lines;
pub mod model; pub mod model;
pub mod sky;
pub mod text; pub mod text;
use self::{lines::Lines, model::Model, text::Text}; use self::{lines::Lines, model::Model, text::Text};

View File

@@ -25,6 +25,7 @@ use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::Notify;
static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new(); static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new();
@@ -54,11 +55,7 @@ impl Plugin for ModelNodePlugin {
app.init_resource::<MaterialRegistry>(); app.init_resource::<MaterialRegistry>();
app.add_systems( app.add_systems(
Update, Update,
( (load_models, gen_model_parts, apply_materials)
load_models,
gen_model_parts.after(TransformSystem::TransformPropagate),
apply_materials,
)
.chain() .chain()
.in_set(ModelNodeSystemSet), .in_set(ModelNodeSystemSet),
); );
@@ -67,7 +64,14 @@ impl Plugin for ModelNodePlugin {
// No extra data needed for a simple holdout // No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)] #[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
#[data(50, u32, binding_array(101))]
#[bindless(index_table(range(50..51), binding(100)))]
pub struct HoldoutExtension {} pub struct HoldoutExtension {}
impl From<&HoldoutExtension> for u32 {
fn from(_: &HoldoutExtension) -> Self {
0
}
}
impl MaterialExtension for HoldoutExtension { impl MaterialExtension for HoldoutExtension {
fn fragment_shader() -> ShaderRef { fn fragment_shader() -> ShaderRef {
HOLDOUT_SHADER_HANDLE.into() HOLDOUT_SHADER_HANDLE.into()
@@ -101,9 +105,13 @@ fn load_models(
SceneRoot(handle), SceneRoot(handle),
ModelNode(Arc::downgrade(&model)), ModelNode(Arc::downgrade(&model)),
SpatialNode(Arc::downgrade(&model.spatial)), SpatialNode(Arc::downgrade(&model.spatial)),
Visibility::Hidden,
)) ))
.id(); .id();
model.bevy_scene_entity.set(entity.into()).unwrap(); model
.bevy_scene_entity
.set(EntityHandle::new(entity))
.unwrap();
} }
} }
@@ -140,7 +148,7 @@ fn apply_materials(
for (param_name, param) in model_part.pending_material_parameters.lock().drain() { for (param_name, param) in model_part.pending_material_parameters.lock().drain() {
let mut new_mat = materials.get(&mesh_mat.0).unwrap().clone(); let mut new_mat = materials.get(&mesh_mat.0).unwrap().clone();
param.apply_to_material( param.apply_to_material(
&model_part.space.node().unwrap().get_client().unwrap(), &model_part.spatial.node().unwrap().get_client().unwrap(),
&mut new_mat, &mut new_mat,
&param_name, &param_name,
&asset_server, &asset_server,
@@ -189,7 +197,7 @@ fn gen_model_parts(
.unwrap_or_else(|| name.to_string()); .unwrap_or_else(|| name.to_string());
let parent_spatial = parent let parent_spatial = parent
.as_ref() .as_ref()
.map(|p| p.space.clone()) .map(|p| p.spatial.clone())
.unwrap_or_else(|| model.spatial.clone()); .unwrap_or_else(|| model.spatial.clone());
let client = model.spatial.node()?.get_client()?; let client = model.spatial.node()?.get_client()?;
let (spatial, model_part) = let (spatial, model_part) =
@@ -199,7 +207,7 @@ fn gen_model_parts(
client.scenegraph.add_node(Node::generate(&client, false)); client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to( let spatial = Spatial::add_to(
&node, &node,
Some(parent_spatial), Some(parent_spatial.clone()),
transform.compute_matrix(), transform.compute_matrix(),
false, false,
); );
@@ -207,8 +215,7 @@ fn gen_model_parts(
entity: OnceLock::new(), entity: OnceLock::new(),
mesh_entity: OnceLock::new(), mesh_entity: OnceLock::new(),
path, path,
space: spatial.clone(), spatial: spatial.clone(),
_model: Arc::downgrade(&model),
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false), holdout: AtomicBool::new(false),
@@ -218,8 +225,8 @@ fn gen_model_parts(
(spatial, model_part) (spatial, model_part)
} }
Some(part) => { Some(part) => {
part.space.set_spatial_parent(&parent_spatial).unwrap(); part.spatial.set_spatial_parent(&parent_spatial).unwrap();
(part.space.clone(), part.clone()) (part.spatial.clone(), part.clone())
} }
}; };
let aabb = Aabb::enclosing( let aabb = Aabb::enclosing(
@@ -236,13 +243,17 @@ fn gen_model_parts(
) )
.unwrap_or_default(); .unwrap_or_default();
_ = spatial.bounding_box_calc.set(move |n| { _ = spatial.bounding_box_calc.set(move |n| {
n.get_aspect::<ModelPart>() Box::pin(async {
.ok() n.get_aspect::<ModelPart>()
.and_then(|v| v.bounds.get().copied()) .ok()
.unwrap_or_default() .and_then(|v| v.bounds.get().copied())
.unwrap_or_default()
})
}); });
let _ = spatial.set_spatial_parent(&parent_spatial);
spatial.set_local_transform(transform.compute_matrix()); spatial.set_local_transform(transform.compute_matrix());
let entity_handle = EntityHandle::new(entity);
spatial.set_entity(entity_handle.clone());
cmds.entity(entity) cmds.entity(entity)
.insert(SpatialNode(Arc::downgrade(&spatial))); .insert(SpatialNode(Arc::downgrade(&spatial)));
let mesh_entity = children_query let mesh_entity = children_query
@@ -251,14 +262,20 @@ fn gen_model_parts(
.flat_map(|v| v.iter()) .flat_map(|v| v.iter())
.find(|e| has_mesh.get(*e).unwrap_or(false))?; .find(|e| has_mesh.get(*e).unwrap_or(false))?;
_ = model_part.bounds.set(aabb); _ = model_part.bounds.set(aabb);
_ = model_part.entity.set(entity.into()); _ = model_part.entity.set(entity_handle);
_ = model_part.mesh_entity.set(mesh_entity.into()); _ = model_part.mesh_entity.set(EntityHandle::new(mesh_entity));
parts.push(model_part.clone()); parts.push(model_part.clone());
Some(model_part) Some(model_part)
}, },
); );
} }
_ = model.parts.set(parts); _ = model.parts.set(parts);
model.pre_bound_parts.lock().clear();
model
.spatial
.set_entity(model.bevy_scene_entity.get().unwrap().clone());
model.setup_complete.store(true, Ordering::Relaxed);
model.setup_complete_notify.notify_waiters();
} }
} }
@@ -315,8 +332,6 @@ impl HashedPbrMaterial {
mat.emissive_texture.hash(state); mat.emissive_texture.hash(state);
mat.metallic_roughness_texture.hash(state); mat.metallic_roughness_texture.hash(state);
mat.occlusion_texture.hash(state); mat.occlusion_texture.hash(state);
// should always be the same, TODO: make the spherical harmonics buffer a per mesh instance thing
// mat.spherical_harmonics.hash(state);
} }
} }
fn hash_color<H: Hasher>(color: Color, state: &mut H) { fn hash_color<H: Hasher>(color: Color, state: &mut H) {
@@ -454,8 +469,7 @@ pub struct ModelPart {
entity: OnceLock<EntityHandle>, entity: OnceLock<EntityHandle>,
mesh_entity: OnceLock<EntityHandle>, mesh_entity: OnceLock<EntityHandle>,
path: String, path: String,
space: Arc<Spatial>, spatial: Arc<Spatial>,
_model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>, pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>,
holdout: AtomicBool, holdout: AtomicBool,
@@ -525,6 +539,8 @@ pub struct Model {
bevy_scene_entity: OnceLock<EntityHandle>, bevy_scene_entity: OnceLock<EntityHandle>,
parts: OnceLock<Vec<Arc<ModelPart>>>, parts: OnceLock<Vec<Arc<ModelPart>>>,
pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>, pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>,
setup_complete: AtomicBool,
setup_complete_notify: Notify,
} }
impl Model { impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> { pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
@@ -541,6 +557,18 @@ impl Model {
bevy_scene_entity: OnceLock::new(), bevy_scene_entity: OnceLock::new(),
pre_bound_parts: Mutex::default(), pre_bound_parts: Mutex::default(),
parts: OnceLock::new(), parts: OnceLock::new(),
setup_complete_notify: Notify::new(),
setup_complete: AtomicBool::new(false),
});
_ = model.spatial.bounding_box_calc.set(|n| {
Box::pin(async {
if let Ok(model) = n.get_aspect::<Model>()
&& !model.setup_complete.load(Ordering::Relaxed)
{
model.setup_complete_notify.notified().await;
}
Aabb::default()
})
}); });
LOAD_MODEL LOAD_MODEL
.send((model.clone(), pending_model_path)) .send((model.clone(), pending_model_path))
@@ -570,7 +598,6 @@ impl Model {
); );
} }
None => { None => {
// TODO: this could be a denail of service vector
let client = self.spatial.node().unwrap().get_client().unwrap(); let client = self.spatial.node().unwrap().get_client().unwrap();
let part_node = client.scenegraph.add_node(Node::generate(&client, false)); let part_node = client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to( let spatial = Spatial::add_to(
@@ -583,8 +610,7 @@ impl Model {
entity: OnceLock::new(), entity: OnceLock::new(),
mesh_entity: OnceLock::new(), mesh_entity: OnceLock::new(),
path: part_path, path: part_path,
space: spatial, spatial,
_model: Arc::downgrade(self),
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false), holdout: AtomicBool::new(false),
@@ -609,7 +635,7 @@ impl ModelAspect for Model {
let model = node.get_aspect::<Model>()?; let model = node.get_aspect::<Model>()?;
let part = model.get_model_part(part_path)?; let part = model.get_model_part(part_path)?;
Alias::create_with_id( Alias::create_with_id(
&part.space.node().unwrap(), &part.spatial.node().unwrap(),
&calling_client, &calling_client,
id, id,
MODEL_PART_ASPECT_ALIAS_INFO.clone(), MODEL_PART_ASPECT_ALIAS_INFO.clone(),
@@ -620,6 +646,16 @@ impl ModelAspect for Model {
} }
impl Drop for Model { impl Drop for Model {
fn drop(&mut self) { fn drop(&mut self) {
for p in self.parts.get().iter().flat_map(|v| v.iter()) {
if let Some(node) = p.spatial.node() {
node.destroy();
}
}
for p in self.pre_bound_parts.lock().iter() {
if let Some(node) = p.spatial.node() {
node.destroy();
}
}
MODEL_REGISTRY.remove(self); MODEL_REGISTRY.remove(self);
} }
} }

72
src/nodes/drawable/sky.rs Normal file
View File

@@ -0,0 +1,72 @@
use bevy::{
app::{Plugin, Update},
color::Color,
core_pipeline::{Skybox, core_3d::Camera3d},
ecs::{
entity::Entity,
query::With,
system::{Commands, Query, ResMut},
},
pbr::{AmbientLight, environment_map::EnvironmentMapLight},
};
use bevy_equirect::EquirectManager;
use glam::Quat;
pub struct SkyPlugin;
impl Plugin for SkyPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_systems(Update, apply_sky);
}
}
// TODO: make this work with cameras spawned after setting the sky texture
fn apply_sky(
mut equirect: ResMut<EquirectManager>,
cameras: Query<Entity, With<Camera3d>>,
mut cmds: Commands,
) {
if let Some(tex) = super::QUEUED_SKYTEX.lock().take() {
if let Some(path) = tex {
let image_handle = equirect.load_equirect_as_cubemap(path, 1024);
for cam in cameras {
cmds.entity(cam).insert(Skybox {
image: image_handle.clone(),
brightness: 1000.0,
rotation: Quat::IDENTITY,
});
}
} else {
for cam in cameras {
cmds.entity(cam).remove::<Skybox>();
}
}
}
if let Some(light) = super::QUEUED_SKYLIGHT.lock().take() {
if let Some(path) = light {
let image_handle = equirect.load_equirect_as_cubemap(path, 1024);
for cam in cameras {
cmds.entity(cam)
.insert(EnvironmentMapLight {
diffuse_map: image_handle.clone(),
// we might want to use the SkyTex for this?
specular_map: image_handle.clone(),
intensity: 1000.0,
rotation: Quat::IDENTITY,
affects_lightmapped_mesh_diffuse: false,
})
.remove::<AmbientLight>();
}
} else {
for cam in cameras {
cmds.entity(cam)
.insert(AmbientLight {
color: Color::WHITE,
brightness: 1000.0,
affects_lightmapped_meshes: true,
})
.remove::<EnvironmentMapLight>();
}
}
}
}

View File

@@ -11,18 +11,17 @@ use crate::{
}, },
nodes::{ nodes::{
Node, Node,
drawable::XAlign, drawable::{TextFit, XAlign},
spatial::{Spatial, SpatialNode}, spatial::{Spatial, SpatialNode},
}, },
}; };
use bevy::{platform::collections::HashMap, prelude::*, render::mesh::MeshAabb}; use bevy::{platform::collections::HashMap, prelude::*};
use bevy_mesh_text_3d::{ use bevy_mesh_text_3d::{
Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes, Align, Attrs, HorizontalAnchorPoint, MeshTextPlugin, Settings as FontSettings, VerticalAlign,
text_glyphs::TextGlyphs, VerticalAnchorPoint, generate_meshes,
}; };
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use core::f32; use core::f32;
use cosmic_text::Metrics;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ffi::OsStr, mem, path::PathBuf, sync::Arc}; use std::{ffi::OsStr, mem, path::PathBuf, sync::Arc};
@@ -33,8 +32,7 @@ pub struct TextNodePlugin;
impl Plugin for TextNodePlugin { impl Plugin for TextNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
// Text init stuff // Text init stuff
// 1.0 for font size in meters app.add_plugins(MeshTextPlugin);
app.add_plugins(MeshTextPlugin::new(1.0));
app.world_mut() app.world_mut()
.resource_mut::<FontSettings>() .resource_mut::<FontSettings>()
.font_system .font_system
@@ -72,21 +70,33 @@ fn spawn_text(
super::XAlign::Center => Align::Center, super::XAlign::Center => Align::Center,
super::XAlign::Right => Align::Left, super::XAlign::Right => Align::Left,
}); });
let vertical_alignment = Some(match style.text_align_y {
super::YAlign::Top => VerticalAlign::Top,
super::YAlign::Center => VerticalAlign::Middle,
super::YAlign::Bottom => VerticalAlign::Bottom,
});
let text_string = text.text.lock().clone(); let text_string = text.text.lock().clone();
let mut text_glyphs = TextGlyphs::new(
Metrics {
font_size: style.character_height,
line_height: style.character_height,
},
[(text_string.as_str(), attrs.clone())],
&attrs,
&mut font_settings.font_system,
alignment,
);
let max_width = style.bounds.as_ref().map(|v| v.bounds.x); let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
let max_height = style.bounds.as_ref().map(|v| v.bounds.x); let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
let (width, _height) = let horizontal_anchor_point = style
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system); .bounds
.as_ref()
.map(|v| match v.anchor_align_x {
XAlign::Left => HorizontalAnchorPoint::Left,
XAlign::Center => HorizontalAnchorPoint::Middle,
XAlign::Right => HorizontalAnchorPoint::Right,
})
.unwrap_or(HorizontalAnchorPoint::Middle);
let vertical_anchor_point = style
.bounds
.as_ref()
.map(|v| match v.anchor_align_y {
YAlign::Top => VerticalAnchorPoint::Top,
YAlign::Center => VerticalAnchorPoint::Middle,
YAlign::Bottom => VerticalAnchorPoint::Bottom,
})
.unwrap_or(VerticalAnchorPoint::Middle);
let wrap = matches!(style.bounds.as_ref().map(|v| v.fit), Some(TextFit::Wrap));
let char_meshes = generate_meshes( let char_meshes = generate_meshes(
bevy_mesh_text_3d::InputText::Simple { bevy_mesh_text_3d::InputText::Simple {
text: text_string, text: text_string,
@@ -96,8 +106,7 @@ fn spawn_text(
emissive: Color::WHITE.to_linear(), emissive: Color::WHITE.to_linear(),
metallic: 0.0, metallic: 0.0,
perceptual_roughness: 1.0, perceptual_roughness: 1.0,
// If alpha is supported on text we need to change this alpha_mode: AlphaMode::Premultiplied,
alpha_mode: AlphaMode::Opaque,
double_sided: false, double_sided: false,
..default() ..default()
}, },
@@ -109,55 +118,30 @@ fn spawn_text(
bevy_mesh_text_3d::Parameters { bevy_mesh_text_3d::Parameters {
extrusion_depth: 0.0, extrusion_depth: 0.0,
font_size: style.character_height, font_size: style.character_height,
line_height: style.character_height, line_height: style.character_height * 1.1,
alignment, alignment,
max_width, max_width: wrap.then_some(0).and(max_width),
max_height, max_height: wrap.then_some(0).and(max_height),
vertical_alignment,
horizontal_anchor_point,
vertical_anchor_point,
}, },
&mut meshes, &mut meshes,
); );
if let Some(db) = old_db { if let Some(db) = old_db {
mem::swap(font_settings.font_system.db_mut(), db); mem::swap(font_settings.font_system.db_mut(), db);
} }
let Ok(char_meshes) = let Ok((char_meshes, _text_size)) =
char_meshes.inspect_err(|err| error!("unable to create text meshes: {err}")) char_meshes.inspect_err(|err| error!("unable to create text meshes: {err}"))
else { else {
continue; continue;
}; };
let dist = char_meshes.iter().fold(f32::MAX, |dist, v| {
dist.min(
v.transform.translation.x
- meshes
.get(&v.mesh)
.unwrap()
.compute_aabb()
.unwrap_or_default()
.half_extents
.x,
)
});
// TODO: text align
let letters = char_meshes let letters = char_meshes
.into_iter() .into_iter()
.map(|v| { .map(|v| {
cmds.spawn(( cmds.spawn((Mesh3d(v.mesh), MeshMaterial3d(v.material), v.transform))
Mesh3d(v.mesh), .id()
MeshMaterial3d(v.material),
// rotation is sus, might be related to the gltf coordinate system
Transform::from_rotation(Quat::from_rotation_y(f32::consts::PI))
* Transform::from_xyz(
-dist
+ match style.bounds.as_ref().map(|v| v.anchor_align_x) {
Some(XAlign::Center) => width * -0.5,
Some(XAlign::Right) => -width,
Some(XAlign::Left) => 0.0,
None => 0.0,
},
0.0,
0.0,
) * v.transform,
))
.id()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let entity = cmds let entity = cmds
@@ -167,7 +151,9 @@ fn spawn_text(
)) ))
.add_children(&letters) .add_children(&letters)
.id(); .id();
text.entity.lock().replace(EntityHandle(entity)); let entity = EntityHandle::new(entity);
text.entity.lock().replace(entity.clone());
text.spatial.set_entity(entity);
} }
} }
@@ -185,7 +171,7 @@ impl FontDatabaseRegistry {
} }
} }
use super::{TextAspect, TextStyle, model::MaterialRegistry}; use super::{TextAspect, TextStyle, YAlign, model::MaterialRegistry};
static TEXT_REGISTRY: Registry<Text> = Registry::new(); static TEXT_REGISTRY: Registry<Text> = Registry::new();

View File

@@ -137,6 +137,7 @@ impl InputMethod {
self.capture_attempts.remove(handler); self.capture_attempts.remove(handler);
} }
#[tracing::instrument(level = "trace", skip(self, handler))]
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData { pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
let mut input = self.data.lock().clone(); let mut input = self.data.lock().clone();
input.transform(self, handler); input.transform(self, handler);

View File

@@ -138,10 +138,10 @@ pub fn process_input() {
if !handler_node.enabled() { if !handler_node.enabled() {
continue; continue;
} }
if let Some(handler_field_node) = handler.field.spatial.node() { if let Some(handler_field_node) = handler.field.spatial.node()
if !handler_field_node.enabled() { && !handler_field_node.enabled()
continue; {
} continue;
}; };
let ser_span = debug_span!("serializing input").entered(); let ser_span = debug_span!("serializing input").entered();

View File

@@ -77,12 +77,12 @@ impl CameraItem {
_message: Message, _message: Message,
response: MethodResponseSender, response: MethodResponseSender,
) { ) {
response.wrap_sync(move || { response.wrap(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else { else {
bail!("Wrong item type?"); bail!("Wrong item type?");
}; };
Ok(serialize(())?.into()) Ok(serialize(())?)
}); });
} }

View File

@@ -1,13 +1,14 @@
use super::camera::CameraItemAcceptor; use super::camera::CameraItemAcceptor;
use super::{create_item_acceptor_flex, register_item_ui_flex}; use super::{create_item_acceptor_flex, register_item_ui_flex};
use crate::bail; use crate::bail;
use crate::core::error::Result; use crate::nodes::{
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO; Aspect, AspectIdentifier,
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO; items::{ITEM_ACCEPTOR_ASPECT_ALIAS_INFO, ITEM_ASPECT_ALIAS_INFO, ITEM_UI_ASPECT_ALIAS_INFO},
use crate::nodes::{Aspect, AspectIdentifier}; };
use crate::{ use crate::{
core::{ core::{
client::{Client, INTERNAL_CLIENT, get_env, state}, client::{Client, INTERNAL_CLIENT, get_env, state},
error::Result,
registry::Registry, registry::Registry,
}, },
nodes::{ nodes::{

View File

@@ -126,18 +126,16 @@ impl Node {
pub fn enabled(&self) -> bool { pub fn enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed) self.enabled.load(Ordering::Relaxed)
&& if let Ok(spatial) = self.get_aspect::<Spatial>() { && if let Ok(spatial) = self.get_aspect::<Spatial>() {
spatial spatial.visible()
.global_transform()
.to_scale_rotation_translation()
.0
.length_squared()
> 0.0
} else { } else {
true true
} }
} }
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Relaxed) self.enabled.store(enabled, Ordering::Relaxed);
if let Ok(spatial) = self.get_aspect::<Spatial>() {
spatial.mark_dirty();
}
} }
pub fn destroy(&self) { pub fn destroy(&self) {
if let Some(client) = self.get_client() { if let Some(client) = self.get_client() {
@@ -209,11 +207,11 @@ impl Node {
) { ) {
if let Ok(alias) = self.get_aspect::<Alias>() { if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_methods.contains(&method) { if !alias.info.server_methods.contains(&method) {
response.send(Err(ScenegraphError::MemberNotFound)); response.send_err(ScenegraphError::MemberNotFound);
return; return;
} }
let Some(alias) = alias.original.upgrade() else { let Some(alias) = alias.original.upgrade() else {
response.send(Err(ScenegraphError::BrokenAlias)); response.send_err(ScenegraphError::BrokenAlias);
return; return;
}; };
alias.execute_local_method( alias.execute_local_method(
@@ -228,7 +226,7 @@ impl Node {
) )
} else { } else {
let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else { let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else {
response.send(Err(ScenegraphError::AspectNotFound)); response.send_err(ScenegraphError::AspectNotFound);
return; return;
}; };
aspect.run_method(calling_client, self.clone(), method, message, response); aspect.run_method(calling_client, self.clone(), method, message, response);
@@ -303,7 +301,6 @@ pub trait AspectIdentifier: Aspect {
const ID: u64; const ID: u64;
} }
pub trait Aspect: Any + Send + Sync + 'static { pub trait Aspect: Any + Send + Sync + 'static {
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
fn run_signal( fn run_signal(
&self, &self,
calling_client: Arc<Client>, calling_client: Arc<Client>,
@@ -337,7 +334,6 @@ impl Aspects {
.get(&A::ID) .get(&A::ID)
// .cloned doesn't work for some reason // .cloned doesn't work for some reason
.map(|v| v.clone()) .map(|v| v.clone())
.map(|a| a.as_any())
.and_then(|a| Arc::downcast(a).ok()) .and_then(|a| Arc::downcast(a).ok())
.ok_or(ServerError::NoAspect(TypeId::of::<A>())) .ok_or(ServerError::NoAspect(TypeId::of::<A>()))
} }

View File

@@ -6,18 +6,21 @@ use super::fields::{Field, FieldTrait};
use super::{Aspect, AspectIdentifier}; use super::{Aspect, AspectIdentifier};
use crate::bail; use crate::bail;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::entity_handle::EntityHandle;
use crate::core::error::Result; use crate::core::error::Result;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use bevy::ecs::entity::EntityHashMap;
use bevy::prelude::Transform as BevyTransform; use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::primitives::Aabb; use bevy::render::primitives::Aabb;
use color_eyre::eyre::OptionExt; use color_eyre::eyre::OptionExt;
use glam::{Mat4, Quat, Vec3, vec3a}; use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3; use mint::Vector3;
use parking_lot::Mutex; use parking_lot::{Mutex, RwLock};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::pin::Pin;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr}; use std::{f32, ptr};
@@ -27,68 +30,60 @@ impl Plugin for SpatialNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
PostUpdate, PostUpdate,
update_spatial_nodes.before(TransformSystem::TransformPropagate), (spawn_spatial_nodes, update_spatial_nodes)
.chain()
.before(TransformSystem::TransformPropagate),
); );
} }
} }
fn update_spatial_nodes( fn spawn_spatial_nodes(mut cmds: Commands) {
mut query: Query<( for spatial in SPATIAL_REGISTRY
&mut BevyTransform, .get_valid_contents()
&SpatialNode, .into_iter()
Option<&ChildOf>, .filter(|v| v.entity.read().is_none())
&mut Visibility, {
)>, let entity = cmds
parent_query: Query<&GlobalTransform>, .spawn((SpatialNode(Arc::downgrade(&spatial)), Name::new("Spatial")))
) { .id();
query spatial.set_entity(EntityHandle::new(entity));
.par_iter_mut() }
.for_each(|(mut transform, spatial_node, child_of, mut vis)| {
let _span = debug_span!("updating spatial node").entered();
let Some(spatial) = spatial_node.0.upgrade() else {
return;
};
if spatial
.node()
.is_some_and(|v| !v.enabled.load(Ordering::Relaxed))
{
if !matches!(*vis, Visibility::Hidden) {
*vis = Visibility::Hidden;
}
return;
}
let mat4 =
debug_span!("getting global transform").in_scope(|| spatial.global_transform());
let (scale, _, _) = mat4.to_scale_rotation_translation();
match (*vis, scale == Vec3::ZERO) {
(Visibility::Inherited | Visibility::Visible, true) => {
*vis = Visibility::Hidden;
}
(Visibility::Hidden, false) => {
*vis = Visibility::Inherited;
}
_ => {}
}
match child_of {
Some(child_of) => {
let Ok(parent) = parent_query.get(child_of.0) else {
warn!("SpatialNode bevy Parent doesn't have global transform");
return;
};
*transform =
BevyTransform::from_matrix(parent.compute_matrix().inverse() * mat4);
}
None => {
*transform = BevyTransform::from_matrix(mat4);
}
}
});
} }
fn update_spatial_nodes(
mut query: Query<(&mut BevyTransform, &mut Visibility, Option<&ChildOf>)>,
mut cmds: Commands,
) {
for (entity, (transform, parent_entity)) in UPDATED_SPATIALS_NODES.lock().drain() {
let _span = debug_span!("updating spatial node").entered();
let Ok((mut bevy_transform, mut vis, parent)) = query.get_mut(entity) else {
continue;
};
// Set visibility based on node enabled state
if let Some(transform) = transform {
*vis = Visibility::Inherited;
*bevy_transform = transform;
} else {
*vis = Visibility::Hidden;
}
if parent.map(|v| v.0) != parent_entity {
match parent_entity {
Some(e) => cmds.entity(entity).insert(ChildOf(e)),
None => cmds.entity(entity).remove::<ChildOf>(),
};
}
}
}
static SPATIAL_REGISTRY: Registry<Spatial> = Registry::new();
#[derive(Clone, Component, Debug)] #[derive(Clone, Component, Debug)]
#[require(BevyTransform, Visibility)] #[require(BevyTransform, Visibility)]
pub struct SpatialNode(pub Weak<Spatial>); pub struct SpatialNode(pub Weak<Spatial>);
const EPSILON: f32 = 0.00001;
stardust_xr_server_codegen::codegen_spatial_protocol!(); stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform { impl Transform {
pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 { pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 {
@@ -100,9 +95,16 @@ impl Transform {
.then_some(self.rotation) .then_some(self.rotation)
.flatten() .flatten()
.unwrap_or_else(|| Quat::IDENTITY.into()); .unwrap_or_else(|| Quat::IDENTITY.into());
// Zero scale values break everything
let scale = scale let scale = scale
.then_some(self.scale) .then_some(self.scale)
.flatten() .flatten()
.map(|s| Vector3 {
x: if s.x == 0.0 { EPSILON } else { s.x },
y: if s.y == 0.0 { EPSILON } else { s.y },
z: if s.z == 0.0 { EPSILON } else { s.z },
})
.unwrap_or_else(|| Vector3::from([1.0; 3])); .unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into()) Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
@@ -123,25 +125,40 @@ static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial { pub struct Spatial {
pub node: Weak<Node>, pub node: Weak<Node>,
parent: Mutex<Option<Arc<Spatial>>>, entity: RwLock<Option<EntityHandle>>,
old_parent: Mutex<Option<Arc<Spatial>>>, parent: RwLock<Option<Arc<Spatial>>>,
transform: Mutex<Mat4>, old_parent: RwLock<Option<Arc<Spatial>>>,
zone: Mutex<Weak<Zone>>, transform: RwLock<Mat4>,
zone: RwLock<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>, pub bounding_box_calc:
OnceLock<for<'a> fn(&'a Node) -> Pin<Box<dyn Future<Output = Aabb> + 'a + Send + Sync>>>,
} }
impl Spatial { impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> { pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new(Spatial { let spatial = SPATIAL_REGISTRY.add(Spatial {
node, node,
parent: Mutex::new(parent), entity: RwLock::new(None),
old_parent: Mutex::new(None), parent: RwLock::new(parent),
transform: Mutex::new(transform), old_parent: RwLock::new(None),
zone: Mutex::new(Weak::new()), transform: RwLock::new(transform),
zone: RwLock::new(Weak::new()),
children: Registry::new(), children: Registry::new(),
bounding_box_calc: OnceLock::default(), bounding_box_calc: OnceLock::default(),
}) });
spatial.mark_dirty();
spatial
}
pub fn set_entity(&self, entity: EntityHandle) {
self.entity.write().replace(entity);
self.mark_dirty();
for child in self.children.get_valid_contents() {
child.mark_dirty();
}
}
pub fn get_entity(&self) -> Option<Entity> {
self.entity.read().as_ref().map(|v| v.get())
} }
pub fn add_to( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
@@ -172,18 +189,17 @@ impl Spatial {
} }
// the output bounds are probably way bigger than they need to be // the output bounds are probably way bigger than they need to be
pub fn get_bounding_box(&self) -> Aabb { pub async fn get_bounding_box(&self) -> Aabb {
let Some(node) = self.node() else { let Some(node) = self.node() else {
return Aabb::default(); return Aabb::default();
}; };
let mut bounds = self let mut bounds = match self.bounding_box_calc.get() {
.bounding_box_calc Some(f) => f(&node).await,
.get() None => Aabb::default(),
.map(|b| (b)(&node)) };
.unwrap_or_default();
for child in self.children.get_valid_contents() { for child in self.children.get_valid_contents() {
let mat = child.local_transform(); let mat = child.local_transform();
let child_aabb = child.get_bounding_box(); let child_aabb = Box::pin(child.get_bounding_box()).await;
bounds = Aabb::enclosing([ bounds = Aabb::enclosing([
bounds.min().into(), bounds.min().into(),
bounds.max().into(), bounds.max().into(),
@@ -194,9 +210,47 @@ impl Spatial {
} }
bounds bounds
} }
pub(super) fn mark_dirty(&self) {
let Some(entity) = self.entity.read().as_ref().map(|v| v.get()) else {
return;
};
let enabled = self
.node()
.is_none_or(|n| n.enabled.load(Ordering::Relaxed))
&& self.local_visible();
let transform = enabled.then(|| BevyTransform::from_matrix(self.local_transform()));
let parent = self
.get_parent()
.and_then(|v| v.entity.read().as_ref().map(|v| v.get()));
UPDATED_SPATIALS_NODES
.lock()
.insert(entity, (transform, parent));
}
pub fn local_transform(&self) -> Mat4 { pub fn local_transform(&self) -> Mat4 {
*self.transform.lock() *self.transform.read()
}
fn local_visible(&self) -> bool {
// Check our own scale by looking at matrix column lengths
let mat = self.local_transform();
let x_scale = mat.x_axis.length_squared();
let y_scale = mat.y_axis.length_squared();
let z_scale = mat.z_axis.length_squared();
x_scale >= EPSILON.powi(2) || y_scale >= EPSILON.powi(2) || z_scale >= EPSILON.powi(2)
}
/// Check if this node or any ancestor has zero scale (for visibility culling)
pub fn visible(&self) -> bool {
// Check parent chain
if let Some(parent) = self.get_parent()
&& !parent.visible()
{
return false;
}
// Check our own scale by looking at matrix column lengths
self.local_visible()
} }
pub fn global_transform(&self) -> Mat4 { pub fn global_transform(&self) -> Mat4 {
let parent_transform = self let parent_transform = self
@@ -207,7 +261,8 @@ impl Spatial {
parent_transform * self.local_transform() parent_transform * self.local_transform()
} }
pub fn set_local_transform(&self, transform: Mat4) { pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.lock() = transform; *self.transform.write() = transform;
self.mark_dirty();
} }
pub fn set_local_transform_components( pub fn set_local_transform_components(
&self, &self,
@@ -215,9 +270,7 @@ impl Spatial {
transform: Transform, transform: Transform,
) { ) {
if reference_space == Some(self) { if reference_space == Some(self) {
self.set_local_transform( self.set_local_transform(transform.to_mat4(true, true, true) * self.local_transform());
parse_transform(transform, true, true, true) * self.local_transform(),
);
return; return;
} }
let reference_to_parent_transform = reference_space let reference_to_parent_transform = reference_space
@@ -268,7 +321,7 @@ impl Spatial {
} }
fn get_parent(&self) -> Option<Arc<Spatial>> { fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.lock().clone() self.parent.read().clone()
} }
fn set_parent(self: &Arc<Self>, new_parent: &Arc<Spatial>) { fn set_parent(self: &Arc<Self>, new_parent: &Arc<Spatial>) {
if let Some(parent) = self.get_parent() { if let Some(parent) = self.get_parent() {
@@ -276,7 +329,8 @@ impl Spatial {
} }
new_parent.children.add_raw(self); new_parent.children.add_raw(self);
*self.parent.lock() = Some(new_parent.clone()); *self.parent.write() = Some(new_parent.clone());
self.mark_dirty();
} }
pub fn set_spatial_parent(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> { pub fn set_spatial_parent(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> {
@@ -300,13 +354,15 @@ impl Spatial {
pub(self) fn zone_distance(&self) -> f32 { pub(self) fn zone_distance(&self) -> f32 {
self.zone self.zone
.lock() .read()
.upgrade() .upgrade()
.map(|zone| zone.field.clone()) .map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0))) .map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::NEG_INFINITY) .unwrap_or(f32::NEG_INFINITY)
} }
} }
static UPDATED_SPATIALS_NODES: Mutex<EntityHashMap<(Option<BevyTransform>, Option<Entity>)>> =
Mutex::new(EntityHashMap::new());
impl AspectIdentifier for Spatial { impl AspectIdentifier for Spatial {
impl_aspect_for_spatial_aspect_id! {} impl_aspect_for_spatial_aspect_id! {}
} }
@@ -396,6 +452,7 @@ impl Drop for Spatial {
fn drop(&mut self) { fn drop(&mut self) {
zone::release(self); zone::release(self);
ZONEABLE_REGISTRY.remove(self); ZONEABLE_REGISTRY.remove(self);
SPATIAL_REGISTRY.remove(self);
} }
} }
@@ -412,7 +469,7 @@ impl SpatialRefAspect for SpatialRef {
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
) -> Result<BoundingBox> { ) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box(); let bounds = this_spatial.get_bounding_box().await;
Ok(BoundingBox { Ok(BoundingBox {
center: Vec3::from(bounds.center).into(), center: Vec3::from(bounds.center).into(),
@@ -428,7 +485,7 @@ impl SpatialRefAspect for SpatialRef {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?; let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)); let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial));
let bb = this_spatial.get_bounding_box(); let bb = this_spatial.get_bounding_box().await;
let bounds = Aabb::enclosing([ let bounds = Aabb::enclosing([
mat.transform_point3(bb.min().into()), mat.transform_point3(bb.min().into()),
mat.transform_point3(bb.max().into()), mat.transform_point3(bb.max().into()),
@@ -463,23 +520,6 @@ impl SpatialRefAspect for SpatialRef {
} }
} }
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(transform.translation)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(transform.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(transform.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
impl InterfaceAspect for Interface { impl InterfaceAspect for Interface {
fn create_spatial( fn create_spatial(
_node: Arc<Node>, _node: Arc<Node>,
@@ -490,7 +530,7 @@ impl InterfaceAspect for Interface {
zoneable: bool, zoneable: bool,
) -> Result<()> { ) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, true); let transform = transform.to_mat4(true, true, true);
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?; let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable); Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(()) Ok(())
@@ -504,7 +544,7 @@ impl InterfaceAspect for Interface {
field: Arc<Node>, field: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?; let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false); let transform = transform.to_mat4(true, true, false);
let field = field.get_aspect::<Field>()?; let field = field.get_aspect::<Field>()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?; let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;

View File

@@ -21,8 +21,8 @@ pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
} }
release(spatial); release(spatial);
*spatial.old_parent.lock() = spatial.get_parent(); *spatial.old_parent.write() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone); *spatial.zone.write() = Arc::downgrade(zone);
let Some(zone_node) = zone.spatial.node.upgrade() else { let Some(zone_node) = zone.spatial.node.upgrade() else {
return; return;
}; };
@@ -45,11 +45,11 @@ pub fn release(spatial: &Spatial) {
}; };
let spatial = spatial_node.get_aspect::<Spatial>().unwrap(); let spatial = spatial_node.get_aspect::<Spatial>().unwrap();
let Some(old_parent) = spatial.old_parent.lock().take() else { let Some(old_parent) = spatial.old_parent.read().clone() else {
return; return;
}; };
let _ = spatial.set_spatial_parent_in_place(&old_parent); let _ = spatial.set_spatial_parent_in_place(&old_parent);
let mut spatial_zone = spatial.zone.lock(); let mut spatial_zone = spatial.zone.write();
if let Some(spatial_zone) = spatial_zone.upgrade() { if let Some(spatial_zone) = spatial_zone.upgrade() {
spatial_zone.captured.remove_aspect(spatial.as_ref()); spatial_zone.captured.remove_aspect(spatial.as_ref());

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_mod_openxr::{ use bevy_mod_openxr::{
helper_traits::{ToQuat as _, ToVec3 as _}, helper_traits::{ToQuat as _, ToVec3 as _},
resources::OxrFrameState, resources::{OxrFrameState, Pipelined},
session::OxrSession, session::OxrSession,
}; };
use bevy_mod_xr::{ use bevy_mod_xr::{
@@ -12,7 +12,7 @@ use bevy_mod_xr::{
}; };
use openxr::SpaceLocationFlags; use openxr::SpaceLocationFlags;
use crate::{DbusConnection, PreFrameWait, nodes::spatial::Spatial}; use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
use super::{ObjectHandle, SpatialRef, input::mouse_pointer::FlatscreenCam}; use super::{ObjectHandle, SpatialRef, input::mouse_pointer::FlatscreenCam};
@@ -68,6 +68,7 @@ fn update_xr(
ref_space: Option<Res<XrPrimaryReferenceSpace>>, ref_space: Option<Res<XrPrimaryReferenceSpace>>,
hmd: Res<Hmd>, hmd: Res<Hmd>,
state: Option<Res<OxrFrameState>>, state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let (Some(session), Some(view), Some(ref_space), Some(state)) = let (Some(session), Some(view), Some(ref_space), Some(state)) =
(session, hmd.space, ref_space, state) (session, hmd.space, ref_space, state)
@@ -80,9 +81,9 @@ fn update_xr(
// }); // });
return; return;
}; };
// this won't be correct with pipelined rendering let time = get_time(pipelined.is_some(), &state);
let location = session let location = session
.locate_space(&view, &ref_space, state.predicted_display_time) .locate_space(&view, &ref_space, time)
.inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}")); .inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}"));
if let Ok(location) = location { if let Ok(location) = location {
let is_tracked = location let is_tracked = location

View File

@@ -20,14 +20,13 @@ pub struct CaptureManager {
} }
impl CaptureManager { impl CaptureManager {
pub fn update_capture(&mut self, method: &InputMethod) { pub fn update_capture(&mut self, method: &InputMethod) {
if let Some(capture) = &self.capture.upgrade() { if let Some(capture) = &self.capture.upgrade()
if !method && !method
.capture_attempts .capture_attempts
.get_valid_contents() .get_valid_contents()
.contains(capture) .contains(capture)
{ {
self.capture = Weak::new(); self.capture = Weak::new();
}
} }
} }
pub fn set_new_capture( pub fn set_new_capture(

View File

@@ -1,7 +1,7 @@
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers}; use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
use crate::{ use crate::{
DbusConnection, ObjectRegistryRes, DbusConnection, ObjectRegistryRes,
core::client::INTERNAL_CLIENT, core::{client::INTERNAL_CLIENT, task},
nodes::{ nodes::{
Node, OwnedNode, Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray}, fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
@@ -9,6 +9,7 @@ use crate::{
items::panel::KEYMAPS, items::panel::KEYMAPS,
spatial::Spatial, spatial::Spatial,
}, },
objects::FieldRef,
}; };
use bevy::{ use bevy::{
input::{ input::{
@@ -20,22 +21,43 @@ use bevy::{
window::PrimaryWindow, window::PrimaryWindow,
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use dashmap::DashMap;
use glam::{Mat4, Vec3, vec3}; use glam::{Mat4, Vec3, vec3};
use mint::Vector2; use mint::Vector2;
use rustc_hash::{FxHashMap, FxHasher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey}; use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::{ use stardust_xr::{
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry}, schemas::dbus::{
ObjectInfo,
interfaces::FieldRefProxy,
list_query::{ListEvent, ObjectListQuery},
object_registry::ObjectRegistry,
query::{ObjectQuery, QueryContext, QueryEvent},
},
values::Datamap, values::Datamap,
}; };
use std::sync::Arc; use std::sync::{Arc, Weak};
use tokio::task::JoinSet; use tokio::sync::{Notify, mpsc, watch};
use tokio::task::{AbortHandle, JoinSet};
use tokio::time::{Duration, timeout}; use tokio::time::{Duration, timeout};
use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags}; use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
use zbus::{Connection, names::OwnedInterfaceName}; use zbus::{Connection, names::OwnedInterfaceName};
pub struct FlatscreenInputPlugin; #[derive(Clone)]
struct HandlerInfo {
handler: ObjectInfo,
field_ref: Arc<Field>,
keyboard_proxy: KeyboardHandlerProxy<'static>,
}
#[derive(Debug, Clone)]
struct InputEvent {
key: u32,
pressed: bool,
}
pub struct FlatscreenInputPlugin;
impl Plugin for FlatscreenInputPlugin { impl Plugin for FlatscreenInputPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
@@ -48,9 +70,9 @@ impl Plugin for FlatscreenInputPlugin {
#[require(Camera3d)] #[require(Camera3d)]
pub struct FlatscreenCam; pub struct FlatscreenCam;
fn setup(mut cmds: Commands) { fn setup(mut cmds: Commands, object_registry: Res<ObjectRegistryRes>) {
let Ok(pointer) = let Ok(pointer) = MousePointer::new(object_registry.0.clone())
MousePointer::new().inspect_err(|err| error!("unable to create mouse pointer: {err}")) .inspect_err(|err| error!("unable to create mouse pointer: {err}"))
else { else {
return; return;
}; };
@@ -159,6 +181,14 @@ trait KeyboardHandler {
async fn reset(&self) -> zbus::Result<()>; async fn reset(&self) -> zbus::Result<()>;
} }
// Make KeyboardHandlerProxy queryable
stardust_xr::schemas::impl_queryable_for_proxy!(KeyboardHandlerProxy);
// Query context for keyboard handlers
#[derive(Debug, Clone)]
struct KeyboardQueryContext;
impl QueryContext for KeyboardQueryContext {}
#[derive(Resource)] #[derive(Resource)]
pub struct MousePointer { pub struct MousePointer {
node: OwnedNode, node: OwnedNode,
@@ -167,9 +197,16 @@ pub struct MousePointer {
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
mouse_datamap: MouseEvent, mouse_datamap: MouseEvent,
// Task management
focus_task_abort_handle: AbortHandle,
input_delivery_task_abort_handle: AbortHandle,
// Channels
input_event_tx: mpsc::UnboundedSender<InputEvent>,
// Notification for focus recalculation
focus_notify: Arc<Notify>,
} }
impl MousePointer { impl MousePointer {
pub fn new() -> Result<Self> { pub fn new(object_registry: Arc<ObjectRegistry>) -> Result<Self> {
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?; let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false); let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let pointer = InputMethod::add_to( let pointer = InputMethod::add_to(
@@ -186,6 +223,39 @@ impl MousePointer {
.unwrap(), .unwrap(),
); );
// Create channels and notification
let (focused_handler_tx, focused_handler_rx) = watch::channel::<Option<HandlerInfo>>(None);
let (input_event_tx, input_event_rx) = mpsc::unbounded_channel::<InputEvent>();
let focus_notify = Arc::new(Notify::new());
// Spawn input delivery task
info!("Creating input delivery task");
let input_delivery_task_abort_handle = task::new(
|| "Mouse pointer input delivery task",
Self::input_delivery_task(
object_registry.get_connection().clone(),
focused_handler_rx,
input_event_rx,
keymap.data().as_ffi(),
),
)?
.abort_handle();
info!("Input delivery task created successfully");
// Spawn focus tracking task
info!("Creating focus tracking task");
let focus_task_abort_handle = task::new(
|| "Mouse pointer focus task",
Self::focus_tracking_task(
object_registry,
focus_notify.clone(),
spatial.clone(),
pointer.clone(),
focused_handler_tx,
),
)?
.abort_handle();
info!("Focus tracking task created successfully");
Ok(MousePointer { Ok(MousePointer {
node, node,
spatial, spatial,
@@ -193,6 +263,10 @@ impl MousePointer {
capture_manager: CaptureManager::default(), capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(), mouse_datamap: Default::default(),
keymap, keymap,
focus_task_abort_handle,
input_delivery_task_abort_handle,
input_event_tx,
focus_notify,
}) })
} }
pub fn update( pub fn update(
@@ -247,7 +321,28 @@ impl MousePointer {
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap(); *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
} }
self.target_pointer_input(); self.target_pointer_input();
self.send_keyboard_input(dbus_connection, object_registry, keyboard_input_events);
// Send keyboard input events via channel
for event in keyboard_input_events.read() {
if let Some(key) = map_key(event.key_code) {
let input_event = InputEvent {
key,
pressed: matches!(event.state, ButtonState::Pressed),
};
info!(
"Sending keyboard input event: key={}, pressed={}",
key, input_event.pressed
);
if let Err(e) = self.input_event_tx.send(input_event) {
error!("Failed to send keyboard input event: {}", e);
}
} else {
warn!("Unable to map key code: {:?}", event.key_code);
}
}
// Notify focus tracking task to recalculate focus
self.focus_notify.notify_waiters();
} }
fn target_pointer_input(&mut self) { fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| { let distance_calculator: DistanceCalculator = |space, data, field| {
@@ -284,96 +379,145 @@ impl MousePointer {
); );
} }
pub fn send_keyboard_input( async fn focus_tracking_task(
&mut self, object_registry: Arc<ObjectRegistry>,
dbus_connection: &Connection, focus_notify: Arc<Notify>,
object_registry: &ObjectRegistry, spatial: Arc<Spatial>,
mut keyboard_input_events: EventReader<KeyboardInput>, pointer: Arc<InputMethod>,
focused_handler_tx: watch::Sender<Option<HandlerInfo>>,
) { ) {
let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1"); info!("Focus tracking task started");
let events = keyboard_input_events
.read()
.filter_map(|e| Some((map_key(e.key_code)?, e.state)))
.collect::<Vec<_>>();
// Spawn async task to handle keyboard input
tokio::spawn({
let keyboard_handlers = keyboard_handlers.clone();
let spatial = self.spatial.clone();
let keymap_id = self.keymap.data().as_ffi();
let dbus_connection = dbus_connection.clone();
async move { // Create keyboard handler query inside the task
let mut closest_handler = None; let mut keyboard_query = ObjectQuery::<
let mut closest_distance = f32::MAX; (FieldRefProxy<'static>, KeyboardHandlerProxy<'static>),
_,
let mut join_set = JoinSet::new(); >::new(object_registry.clone(), ());
for handler in &keyboard_handlers { let (keyboard_handlers, mapper) = keyboard_query.to_list_query();
let handler = handler.clone(); task::new(
let dbus_connection = dbus_connection.clone(); || "Focus tracking mapper",
join_set.spawn(async move { mapper.init(async |ev| match ev {
// TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending ListEvent::NewMatch((field_ref, keyboard_proxy)) => {
timeout(Duration::from_millis(10), async { info!("New keyboard handler found");
let field_ref = handler let uid = timeout(Duration::from_millis(100), field_ref.uid())
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
.await
.ok()?;
let uid = field_ref.uid().await.ok()?;
Some((handler, uid))
})
.await .await
.ok() .ok()?
.flatten() .ok()?;
}); let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
let field = field_node.get_aspect::<Field>();
Some((field, keyboard_proxy))
} }
while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await { ListEvent::Modified((field_ref, keyboard_proxy)) => {
let exported_fields = EXPORTED_FIELDS.lock(); let uid = timeout(Duration::from_millis(100), field_ref.uid())
let Some(field_ref_node) = .await
exported_fields.get(&field_ref_id).and_then(|f| f.upgrade()) .ok()?
else { .ok()?;
println!("didn't find a thing :("); let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
continue; let field = field_node.get_aspect::<Field>();
}; Some((field, keyboard_proxy))
// println!("still sendin stuff :)");
let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
continue;
};
drop(exported_fields);
let result = field_ref.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: spatial.clone(),
});
if result.deepest_point_distance > 0.0
&& result.min_distance < 0.05
&& result.deepest_point_distance < closest_distance
{
closest_distance = result.deepest_point_distance;
closest_handler = Some(handler);
}
} }
_ => None,
}),
);
let Some(handler) = closest_handler else { // Main focus calculation loop
return; loop {
}; let mut closest_handler = None;
let Ok(keyboard_handler) = handler let mut closest_distance = f32::MAX;
.to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
.await // Find closest handler
else { for (handler, (field_ref, keyboard_proxy)) in &*keyboard_handlers.iter().await {
return; let Ok(field_ref) = field_ref else {
continue;
}; };
// Register keymap first let result = field_ref.ray_march(Ray {
let _ = keyboard_handler.keymap(keymap_id).await; origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: spatial.clone(),
});
// Send key states if result.deepest_point_distance > 0.0
for (key, state) in events.iter() { && result.min_distance < 0.05
let pressed = matches!(state, ButtonState::Pressed); && result.deepest_point_distance < closest_distance
let _ = keyboard_handler.key_state(key + 8, pressed).await; {
closest_distance = result.deepest_point_distance;
closest_handler = Some(HandlerInfo {
handler: handler.clone(),
field_ref: field_ref.clone(),
keyboard_proxy: keyboard_proxy.clone(),
});
} }
} }
});
// Update focused handler
if let Some(ref handler_info) = closest_handler {
info!(
"Focus tracking task: Focused on handler at distance {}",
closest_distance
);
} else {
debug!("Focus tracking task: No handler in focus");
}
let _ = focused_handler_tx.send(closest_handler);
// Wait for next frame signal
focus_notify.notified().await;
}
}
async fn input_delivery_task(
dbus_connection: Connection,
mut focused_handler_rx: watch::Receiver<Option<HandlerInfo>>,
mut input_event_rx: mpsc::UnboundedReceiver<InputEvent>,
keymap_id: u64,
) {
info!("Input delivery task started");
loop {
// Handle input events
while let Some(input_event) = input_event_rx.recv().await {
info!(
"Input delivery task: Received input event key={}, pressed={}",
input_event.key, input_event.pressed
);
// Get current focused handler
let current_handler = focused_handler_rx.borrow().clone();
let Some(handler_info) = current_handler else {
continue;
};
// Send input to handler using cached proxy
info!("Input delivery task: Sending to handler");
let keyboard_handler = &handler_info.keyboard_proxy;
// Register keymap first
if let Err(e) = keyboard_handler.keymap(keymap_id).await {
warn!("Input delivery task: Failed to register keymap: {}", e);
}
// Send key state
if let Err(e) = keyboard_handler
.key_state(input_event.key + 8, input_event.pressed)
.await
{
error!("Input delivery task: Failed to send key state: {}", e);
} else {
info!(
"Input delivery task: Successfully sent key {} (pressed={})",
input_event.key + 8,
input_event.pressed
);
}
}
}
}
}
impl Drop for MousePointer {
fn drop(&mut self) {
// Abort the persistent tasks when MousePointer is dropped
self.focus_task_abort_handle.abort();
self.input_delivery_task_abort_handle.abort();
} }
} }
@@ -385,8 +529,11 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::Tab => Some(input_event_codes::KEY_TAB!()), Key::Tab => Some(input_event_codes::KEY_TAB!()),
Key::Enter => Some(input_event_codes::KEY_ENTER!()), Key::Enter => Some(input_event_codes::KEY_ENTER!()),
Key::ShiftLeft => Some(input_event_codes::KEY_LEFTSHIFT!()), Key::ShiftLeft => Some(input_event_codes::KEY_LEFTSHIFT!()),
Key::ShiftRight => Some(input_event_codes::KEY_RIGHTSHIFT!()),
Key::ControlLeft => Some(input_event_codes::KEY_LEFTCTRL!()), Key::ControlLeft => Some(input_event_codes::KEY_LEFTCTRL!()),
Key::ControlRight => Some(input_event_codes::KEY_RIGHTCTRL!()),
Key::AltLeft => Some(input_event_codes::KEY_LEFTALT!()), Key::AltLeft => Some(input_event_codes::KEY_LEFTALT!()),
Key::AltRight => Some(input_event_codes::KEY_RIGHTALT!()),
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()), Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
Key::Escape => Some(input_event_codes::KEY_ESC!()), Key::Escape => Some(input_event_codes::KEY_ESC!()),
Key::Space => Some(input_event_codes::KEY_SPACE!()), Key::Space => Some(input_event_codes::KEY_SPACE!()),
@@ -452,13 +599,25 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::F3 => Some(input_event_codes::KEY_F3!()), Key::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()), Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()), Key::F5 => Some(input_event_codes::KEY_F5!()),
// Key::F6 => Some(input_event_codes::KEY_F6!()), Key::F6 => Some(input_event_codes::KEY_F6!()),
// Key::F7 => Some(input_event_codes::KEY_F7!()), Key::F7 => Some(input_event_codes::KEY_F7!()),
// Key::F8 => Some(input_event_codes::KEY_F8!()), Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()), Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()), Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()), Key::F11 => Some(input_event_codes::KEY_F11!()),
Key::F12 => Some(input_event_codes::KEY_F12!()), Key::F12 => Some(input_event_codes::KEY_F12!()),
Key::F13 => Some(input_event_codes::KEY_F13!()),
Key::F14 => Some(input_event_codes::KEY_F14!()),
Key::F15 => Some(input_event_codes::KEY_F15!()),
Key::F16 => Some(input_event_codes::KEY_F16!()),
Key::F17 => Some(input_event_codes::KEY_F17!()),
Key::F18 => Some(input_event_codes::KEY_F18!()),
Key::F19 => Some(input_event_codes::KEY_F19!()),
Key::F20 => Some(input_event_codes::KEY_F20!()),
Key::F21 => Some(input_event_codes::KEY_F21!()),
Key::F22 => Some(input_event_codes::KEY_F22!()),
Key::F23 => Some(input_event_codes::KEY_F23!()),
Key::F24 => Some(input_event_codes::KEY_F24!()),
Key::Comma => Some(input_event_codes::KEY_COMMA!()), Key::Comma => Some(input_event_codes::KEY_COMMA!()),
Key::Period => Some(input_event_codes::KEY_DOT!()), Key::Period => Some(input_event_codes::KEY_DOT!()),
Key::Slash => Some(input_event_codes::KEY_SLASH!()), Key::Slash => Some(input_event_codes::KEY_SLASH!()),
@@ -477,6 +636,34 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::NumpadSubtract => Some(input_event_codes::KEY_MINUS!()), Key::NumpadSubtract => Some(input_event_codes::KEY_MINUS!()),
Key::NumpadDecimal => Some(input_event_codes::KEY_DOT!()), Key::NumpadDecimal => Some(input_event_codes::KEY_DOT!()),
Key::NumpadDivide => Some(input_event_codes::KEY_SLASH!()), Key::NumpadDivide => Some(input_event_codes::KEY_SLASH!()),
Key::ContextMenu => Some(input_event_codes::KEY_CONTEXT_MENU!()),
Key::Help => Some(input_event_codes::KEY_HELP!()),
Key::NumLock => Some(input_event_codes::KEY_NUMLOCK!()),
Key::NumpadBackspace => Some(input_event_codes::KEY_BACKSPACE!()),
Key::NumpadClear => Some(input_event_codes::KEY_CLEAR!()),
Key::NumpadClearEntry => Some(input_event_codes::KEY_CLEAR!()),
Key::NumpadComma => Some(input_event_codes::KEY_COMMA!()),
Key::NumpadEnter => Some(input_event_codes::KEY_ENTER!()),
Key::NumpadEqual => Some(input_event_codes::KEY_EQUAL!()),
Key::NumpadHash => Some(input_event_codes::KEY_NUMERIC_POUND!()),
Key::NumpadStar => Some(input_event_codes::KEY_KPASTERISK!()),
Key::Fn => Some(input_event_codes::KEY_FN!()),
Key::ScrollLock => Some(input_event_codes::KEY_SCROLLLOCK!()),
Key::Pause => Some(input_event_codes::KEY_PAUSE!()),
Key::Power => Some(input_event_codes::KEY_POWER!()),
Key::Sleep => Some(input_event_codes::KEY_SLEEP!()),
Key::Suspend => Some(input_event_codes::KEY_SUSPEND!()),
Key::Again => Some(input_event_codes::KEY_AGAIN!()),
Key::Copy => Some(input_event_codes::KEY_COPY!()),
Key::Cut => Some(input_event_codes::KEY_CUT!()),
Key::Find => Some(input_event_codes::KEY_FIND!()),
Key::Open => Some(input_event_codes::KEY_OPEN!()),
Key::Paste => Some(input_event_codes::KEY_PASTE!()),
Key::Props => Some(input_event_codes::KEY_PROPS!()),
Key::Select => Some(input_event_codes::KEY_SELECT!()),
Key::Undo => Some(input_event_codes::KEY_UNDO!()),
Key::Hiragana => Some(input_event_codes::KEY_HIRAGANA!()),
Key::Katakana => Some(input_event_codes::KEY_KATAKANA!()),
_ => None, _ => None,
} }
} }

View File

@@ -2,6 +2,7 @@ use super::{CaptureManager, get_sorted_handlers};
use crate::{ use crate::{
DbusConnection, PreFrameWait, DbusConnection, PreFrameWait,
core::client::INTERNAL_CLIENT, core::client::INTERNAL_CLIENT,
get_time,
nodes::{ nodes::{
Node, OwnedNode, Node, OwnedNode,
drawable::{ drawable::{
@@ -12,14 +13,14 @@ use crate::{
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip}, input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
spatial::Spatial, spatial::Spatial,
}, },
objects::{ObjectHandle, SpatialRef, Tracked}, objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked},
}; };
use bevy::{asset::Handle, ecs::resource::Resource}; use bevy::{asset::Handle, ecs::resource::Resource};
use bevy::{math::Affine3, prelude::*}; use bevy::{math::Affine3, prelude::*};
use bevy_mod_openxr::{ use bevy_mod_openxr::{
action_binding::{OxrSendActionBindings, OxrSuggestActionBinding}, action_binding::{OxrSendActionBindings, OxrSuggestActionBinding},
helper_traits::{ToIsometry3d, ToVec2}, helper_traits::{ToIsometry3d, ToVec2},
resources::{OxrFrameState, OxrInstance}, resources::{OxrFrameState, OxrInstance, Pipelined},
session::OxrSession, session::OxrSession,
}; };
use bevy_mod_xr::{ use bevy_mod_xr::{
@@ -147,6 +148,7 @@ fn update(
session: Option<Res<OxrSession>>, session: Option<Res<OxrSession>>,
ref_space: Option<Res<XrPrimaryReferenceSpace>>, ref_space: Option<Res<XrPrimaryReferenceSpace>>,
state: Option<Res<OxrFrameState>>, state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else { let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else {
controllers.left.set_enabled(false); controllers.left.set_enabled(false);
@@ -158,8 +160,7 @@ fn update(
.sync_actions(&[ActiveActionSet::new(&actions.set)]) .sync_actions(&[ActiveActionSet::new(&actions.set)])
.unwrap(); .unwrap();
}); });
let time = state.predicted_display_time; let time = get_time(pipelined.is_some(), &state);
// stupid bevy gltf loading issue (rotated 180 degrees on the y axis)
controllers controllers
.left .left
.update(&session, &actions, time, ref_space.0); .update(&session, &actions, time, ref_space.0);
@@ -268,8 +269,9 @@ pub struct OxrControllerInput {
model_part: Arc<ModelPart>, model_part: Arc<ModelPart>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
datamap: ControllerDatamap, datamap: ControllerDatamap,
tracked: ObjectHandle<Tracked>, tracked: AsyncTracked,
space: Option<XrSpace>, space: Option<XrSpace>,
_model_node: OwnedNode,
} }
impl OxrControllerInput { impl OxrControllerInput {
fn new(connection: &Connection, side: HandSide) -> Result<Self> { fn new(connection: &Connection, side: HandSide) -> Result<Self> {
@@ -279,11 +281,19 @@ impl OxrControllerInput {
HandSide::Right => "right", HandSide::Right => "right",
}; };
let (spatial, object_handle) = SpatialRef::create(connection, &path); let (spatial, object_handle) = SpatialRef::create(connection, &path);
let tracked = Tracked::new(connection, &path); let tracked = AsyncTracked::new(connection, &path);
let tip = InputDataType::Tip(Tip::default()); let tip = InputDataType::Tip(Tip::default());
let node = spatial.node().unwrap(); let node = spatial.node().unwrap();
node.set_enabled(false); node.set_enabled(false);
let model = Model::add_to(&node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap(); let model_node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
let model_spatial = Spatial::add_to(
&model_node,
Some(spatial.clone()),
Mat4::from_scale(Vec3::splat(0.02)),
false,
);
let model =
Model::add_to(&model_node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
let model_part = model.get_model_part("Cursor".to_string()).unwrap(); let model_part = model.get_model_part("Cursor".to_string()).unwrap();
let input = InputMethod::add_to( let input = InputMethod::add_to(
&node, &node,
@@ -300,6 +310,7 @@ impl OxrControllerInput {
datamap: Default::default(), datamap: Default::default(),
tracked, tracked,
space: None, space: None,
_model_node: OwnedNode(model_node),
}) })
} }
#[instrument(level = "debug", skip(self))] #[instrument(level = "debug", skip(self))]
@@ -307,13 +318,7 @@ impl OxrControllerInput {
if let Some(node) = self.input.spatial.node() { if let Some(node) = self.input.spatial.node() {
node.set_enabled(enabled); node.set_enabled(enabled);
} }
tokio::spawn({ self.tracked.set_tracked(enabled);
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
} }
fn update( fn update(
&mut self, &mut self,
@@ -355,9 +360,7 @@ impl OxrControllerInput {
1.0, 1.0,
)), )),
); );
self.input self.input.spatial.set_local_transform(world_transform);
.spatial
.set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(0.02)));
} }
let path = session let path = session
.instance() .instance()
@@ -366,13 +369,13 @@ impl OxrControllerInput {
HandSide::Right => "/user/hand/right", HandSide::Right => "/user/hand/right",
}) })
.unwrap(); .unwrap();
if let Ok(path) = session.current_interaction_profile(path) { if let Ok(path) = session.current_interaction_profile(path)
if session.instance().path_to_string(path).unwrap() && let Ok(path) = session.instance().path_to_string(path)
== "/interaction_profiles/khr/simple_controller" && path == "/interaction_profiles/khr/simple_controller"
{ {
self.set_enabled(false); self.set_enabled(false);
}
} }
fn get<T: openxr::ActionInput + Default>( fn get<T: openxr::ActionInput + Default>(
session: &OxrSession, session: &OxrSession,
path: openxr::Path, path: openxr::Path,

View File

@@ -7,15 +7,15 @@ use crate::nodes::{
input::{Hand, InputMethod, Joint}, input::{Hand, InputMethod, Joint},
spatial::Spatial, spatial::Spatial,
}; };
use crate::objects::{ObjectHandle, SpatialRef, Tracked}; use crate::objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked};
use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait}; use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait, get_time};
use bevy::prelude::Transform as BevyTransform; use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3}; use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
use bevy_mod_openxr::resources::OxrFrameState; use bevy_mod_openxr::resources::{OxrFrameState, Pipelined};
use bevy_mod_openxr::session::OxrSession; use bevy_mod_openxr::session::OxrSession;
use bevy_mod_xr::hands::{HandBone, HandSide, XrHandBoneEntities, XrHandBoneRadius}; use bevy_mod_xr::hands::{HandBone, HandSide, XrHandBoneEntities, XrHandBoneRadius};
use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated}; use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated, session_available};
use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrSpaceLocationFlags}; use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrSpaceLocationFlags};
use bevy_sk::hand::GRADIENT_TEXTURE_HANDLE; use bevy_sk::hand::GRADIENT_TEXTURE_HANDLE;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
@@ -34,8 +34,11 @@ impl Plugin for HandPlugin {
app.add_systems(PreFrameWait, update_hands.run_if(resource_exists::<Hands>)); app.add_systems(PreFrameWait, update_hands.run_if(resource_exists::<Hands>));
app.add_systems(XrSessionCreated, create_trackers); app.add_systems(XrSessionCreated, create_trackers);
app.add_systems(XrPreDestroySession, destroy_trackers); app.add_systems(XrPreDestroySession, destroy_trackers);
app.add_systems(PostUpdate, update_hand_material); app.add_systems(
app.add_systems(Startup, setup); PostUpdate,
update_hand_material.run_if(resource_exists::<Hands>),
);
app.add_systems(Startup, setup.run_if(session_available));
} }
} }
fn update_hands( fn update_hands(
@@ -50,30 +53,22 @@ fn update_hands(
&mut XrHandBoneRadius, &mut XrHandBoneRadius,
)>, )>,
joints_query: Query<&XrHandBoneEntities>, joints_query: Query<&XrHandBoneEntities>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else { let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else {
tokio::task::spawn({ hands.left.tracked.set_tracked(false);
let left = hands.left.tracked.clone(); hands.right.tracked.set_tracked(false);
let right = hands.right.tracked.clone();
async move {
left.set_tracked(false);
right.set_tracked(false);
}
});
return; return;
}; };
let get_joints = |hand: &mut OxrHandInput| -> Option<openxr::HandJointLocations> { let get_joints = |hand: &mut OxrHandInput| -> Option<openxr::HandJointLocations> {
let Some(tracker) = hand.tracker.as_ref() else { let Some(tracker) = hand.tracker.as_ref() else {
hand.input.spatial.node().unwrap().set_enabled(false); hand.input.spatial.node().unwrap().set_enabled(false);
let handle = hand.tracked.clone(); hand.tracked.set_tracked(false);
tokio::task::spawn(async move {
handle.set_tracked(false);
});
return None; return None;
}; };
// this won't be correct with pipelined rendering let time = get_time(pipelined.is_some(), &state);
session session
.locate_hand_joints(tracker, &ref_space, state.predicted_display_time) .locate_hand_joints(tracker, &ref_space, time)
.inspect_err(|err| error!("Error while locating hand joints")) .inspect_err(|err| error!("Error while locating hand joints"))
.ok() .ok()
.flatten() .flatten()
@@ -182,7 +177,7 @@ pub struct OxrHandInput {
input: Arc<InputMethod>, input: Arc<InputMethod>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
datamap: HandDatamap, datamap: HandDatamap,
tracked: ObjectHandle<Tracked>, tracked: AsyncTracked,
tracker: Option<openxr::HandTracker>, tracker: Option<openxr::HandTracker>,
captured: bool, captured: bool,
material: Handle<BevyMaterial>, material: Handle<BevyMaterial>,
@@ -201,7 +196,7 @@ impl OxrHandInput {
HandSide::Right => "right", HandSide::Right => "right",
} + "/palm"), } + "/palm"),
); );
let tracked = Tracked::new( let tracked = AsyncTracked::new(
connection, connection,
&("/org/stardustxr/Hand/".to_string() &("/org/stardustxr/Hand/".to_string()
+ match side { + match side {
@@ -243,13 +238,7 @@ impl OxrHandInput {
if let Some(node) = self.input.spatial.node() { if let Some(node) = self.input.spatial.node() {
node.set_enabled(enabled); node.set_enabled(enabled);
} }
tokio::spawn({ self.tracked.set_tracked(enabled);
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
} }
fn update( fn update(
&mut self, &mut self,

View File

@@ -20,6 +20,7 @@ use std::{
marker::PhantomData, marker::PhantomData,
sync::{Arc, atomic::Ordering}, sync::{Arc, atomic::Ordering},
}; };
use tokio::{sync::mpsc, task::AbortHandle};
use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath}; use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath};
pub mod hmd; pub mod hmd;
@@ -43,6 +44,48 @@ impl<I: Interface> Drop for ObjectHandle<I> {
} }
} }
/// A wrapper around ObjectHandle<Tracked> that batches async updates
/// instead of spawning a tokio task for each state change
pub struct AsyncTracked {
pub sender: mpsc::UnboundedSender<bool>,
pub _handle: ObjectHandle<Tracked>,
pub _abort_handle: AbortHandle,
}
impl AsyncTracked {
pub fn new(connection: &Connection, path: &str) -> Self {
let handle = Tracked::new(connection, path);
let (sender, mut receiver) = mpsc::unbounded_channel::<bool>();
// Spawn a single long-running task that processes state updates
let task = tokio::task::spawn({
let handle = handle.clone();
async move {
while let Some(is_tracked) = receiver.recv().await {
let _ = handle.set_tracked(is_tracked).await;
}
}
});
Self {
sender,
_handle: handle,
_abort_handle: task.abort_handle(),
}
}
pub fn set_tracked(&self, is_tracked: bool) {
// Just send over channel instead of spawning a task
let _ = self.sender.send(is_tracked);
}
}
impl Drop for AsyncTracked {
fn drop(&mut self) {
self._abort_handle.abort();
}
}
pub struct SpatialRef(u64, OwnedNode); pub struct SpatialRef(u64, OwnedNode);
impl SpatialRef { impl SpatialRef {
pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<SpatialRef>) { pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<SpatialRef>) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_mod_openxr::{ use bevy_mod_openxr::{
helper_traits::{ToQuat, ToVec3}, helper_traits::{ToQuat, ToVec3},
resources::OxrFrameState, resources::{OxrFrameState, Pipelined},
session::OxrSession, session::OxrSession,
}; };
use bevy_mod_xr::{ use bevy_mod_xr::{
@@ -14,9 +14,9 @@ use openxr::SpaceLocationFlags;
use parking_lot::RwLock; use parking_lot::RwLock;
use zbus::{Connection, ObjectServer, interface}; use zbus::{Connection, ObjectServer, interface};
use crate::{DbusConnection, PreFrameWait, nodes::spatial::Spatial}; use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
use super::{ObjectHandle, SpatialRef, Tracked}; use super::{AsyncTracked, ObjectHandle, SpatialRef, Tracked};
pub struct PlaySpacePlugin; pub struct PlaySpacePlugin;
impl Plugin for PlaySpacePlugin { impl Plugin for PlaySpacePlugin {
@@ -31,7 +31,7 @@ impl Plugin for PlaySpacePlugin {
fn setup(connection: Res<DbusConnection>, mut cmds: Commands) { fn setup(connection: Res<DbusConnection>, mut cmds: Commands) {
let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"); let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace");
// the OpenXR session might not exist quite yet // the OpenXR session might not exist quite yet
let tracked = Tracked::new(&connection, "/org/stardustxr/PlaySpace"); let tracked = AsyncTracked::new(&connection, "/org/stardustxr/PlaySpace");
let dbus_connection = connection.clone(); let dbus_connection = connection.clone();
let play_space_data = Arc::new(RwLock::default()); let play_space_data = Arc::new(RwLock::default());
tokio::task::spawn({ tokio::task::spawn({
@@ -75,26 +75,22 @@ fn update(
ref_space: Option<Res<XrPrimaryReferenceSpace>>, ref_space: Option<Res<XrPrimaryReferenceSpace>>,
play_space: Res<PlaySpace>, play_space: Res<PlaySpace>,
state: Option<Res<OxrFrameState>>, state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let (Some(session), Some(stage), Some(ref_space), Some(state)) = let (Some(session), Some(stage), Some(ref_space), Some(state)) =
(session, stage, ref_space, state) (session, stage, ref_space, state)
else { else {
play_space.bounds.write().drain(..); play_space.bounds.write().drain(..);
tokio::task::spawn({ play_space.tracked_handle.set_tracked(false);
let handle = play_space.tracked_handle.clone();
async move {
handle.set_tracked(false).await;
}
});
play_space play_space
.spatial .spatial
.set_local_transform(Mat4::from_translation(vec3(0.0, -1.65, 0.0))); .set_local_transform(Mat4::from_translation(vec3(0.0, -1.65, 0.0)));
return; return;
}; };
// this won't be correct with pipelined rendering let time = get_time(pipelined.is_some(), &state);
let location = session let location = session
.locate_space(&stage.0, &ref_space, state.predicted_display_time) .locate_space(&stage.0, &ref_space, time)
.inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}")); .inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}"));
if let Ok(location) = location { if let Ok(location) = location {
let is_tracked = location.location_flags.contains( let is_tracked = location.location_flags.contains(
@@ -103,12 +99,7 @@ fn update(
| SpaceLocationFlags::ORIENTATION_VALID | SpaceLocationFlags::ORIENTATION_VALID
| SpaceLocationFlags::ORIENTATION_TRACKED, | SpaceLocationFlags::ORIENTATION_TRACKED,
); );
tokio::task::spawn({ play_space.tracked_handle.set_tracked(is_tracked);
let handle = play_space.tracked_handle.clone();
async move {
handle.set_tracked(is_tracked).await;
}
});
if is_tracked { if is_tracked {
play_space play_space
.spatial .spatial
@@ -140,7 +131,7 @@ fn update(
pub struct PlaySpace { pub struct PlaySpace {
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
_spatial_handle: ObjectHandle<SpatialRef>, _spatial_handle: ObjectHandle<SpatialRef>,
tracked_handle: ObjectHandle<Tracked>, tracked_handle: AsyncTracked,
bounds: Arc<RwLock<Vec<(f64, f64)>>>, bounds: Arc<RwLock<Vec<(f64, f64)>>>,
} }
pub struct PlaySpaceBounds(Arc<RwLock<Vec<(f64, f64)>>>); pub struct PlaySpaceBounds(Arc<RwLock<Vec<(f64, f64)>>>);

View File

@@ -5,6 +5,7 @@ use crate::wayland::WAYLAND_DISPLAY;
use crate::{CliArgs, STARDUST_INSTANCE}; use crate::{CliArgs, STARDUST_INSTANCE};
use directories::ProjectDirs; use directories::ProjectDirs;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::path::Path; use std::path::Path;
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
@@ -58,6 +59,7 @@ pub fn restore_session(session_dir: &Path, debug_launched_clients: bool) -> Vec<
}; };
clients clients
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|c| c.path().extension() == Some(OsStr::new("toml")))
.filter_map(|c| ClientStateParsed::from_file(&c.path())) .filter_map(|c| ClientStateParsed::from_file(&c.path()))
.filter_map(ClientStateParsed::launch_command) .filter_map(ClientStateParsed::launch_command)
.filter_map(|c| run_client(c, debug_launched_clients)) .filter_map(|c| run_client(c, debug_launched_clients))

39
src/spectator_cam.rs Normal file
View File

@@ -0,0 +1,39 @@
use bevy::{
app::Plugin,
core_pipeline::core_3d::Camera3d,
ecs::{
component::Component,
system::{Commands, Res},
},
render::camera::{PerspectiveProjection, Projection},
transform::components::Transform,
};
use bevy_mod_openxr::session::OxrSession;
use bevy_mod_xr::session::XrSessionCreated;
use openxr::ReferenceSpaceType;
pub struct SpectatorCameraPlugin;
impl Plugin for SpectatorCameraPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_systems(XrSessionCreated, create);
}
}
#[derive(Component)]
#[require(Camera3d)]
struct SpectatorCam;
fn create(session: Res<OxrSession>, mut cmds: Commands) {
cmds.spawn((
SpectatorCam,
session
.create_reference_space(ReferenceSpaceType::VIEW, Transform::IDENTITY)
.unwrap()
.0,
Projection::Perspective(PerspectiveProjection {
fov: 100f32.to_radians(),
..Default::default()
}),
));
}

View File

@@ -1,5 +1,5 @@
use crate::wayland::Message;
use crate::wayland::dmabuf::buffer_backing::DmabufBacking; use crate::wayland::dmabuf::buffer_backing::DmabufBacking;
use crate::wayland::{Client, Message, WaylandResult};
use crate::wayland::{MessageSink, core::shm_buffer_backing::ShmBufferBacking, util::ClientExt}; use crate::wayland::{MessageSink, core::shm_buffer_backing::ShmBufferBacking, util::ClientExt};
use bevy::{ use bevy::{
asset::{Assets, Handle}, asset::{Assets, Handle},
@@ -8,11 +8,9 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
pub use waynest::server::protocol::core::wayland::wl_buffer::*; use waynest::ObjectId;
use waynest::{ pub use waynest_protocols::server::core::wayland::wl_buffer::*;
server::{Client, Dispatcher, Result}, use waynest_server::{Client as _, RequestDispatcher};
wire::ObjectId,
};
#[derive(Debug)] #[derive(Debug)]
pub struct BufferUsage { pub struct BufferUsage {
@@ -41,7 +39,8 @@ pub enum BufferBacking {
Dmabuf(DmabufBacking), Dmabuf(DmabufBacking),
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Buffer { pub struct Buffer {
pub id: ObjectId, pub id: ObjectId,
backing: BufferBacking, backing: BufferBacking,
@@ -49,8 +48,12 @@ pub struct Buffer {
impl Buffer { impl Buffer {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc<Self> { pub fn new(
client.insert(id, Self { id, backing }) client: &mut Client,
id: ObjectId,
backing: BufferBacking,
) -> WaylandResult<Arc<Self>> {
Ok(client.insert(id, Self { id, backing })?)
} }
/// Returns the tex if it was updated /// Returns the tex if it was updated
@@ -62,11 +65,20 @@ impl Buffer {
) -> Option<Handle<Image>> { ) -> Option<Handle<Image>> {
tracing::debug!("Updating texture for buffer {:?}", self.id); tracing::debug!("Updating texture for buffer {:?}", self.id);
match &self.backing { match &self.backing {
BufferBacking::Shm(backing) => backing.update_tex(images), BufferBacking::Shm(backing) => backing.update_tex(dmatexes, images),
BufferBacking::Dmabuf(backing) => backing.update_tex(dmatexes, images), BufferBacking::Dmabuf(backing) => backing.update_tex(dmatexes, images),
} }
} }
#[tracing::instrument(level = "debug", skip_all)]
pub fn on_commit(&self) {
tracing::debug!("running on_commit for buffer {:?}", self.id);
match &self.backing {
BufferBacking::Shm(backing) => backing.on_commit(),
BufferBacking::Dmabuf(_backing) => {}
}
}
pub fn is_transparent(&self) -> bool { pub fn is_transparent(&self) -> bool {
match &self.backing { match &self.backing {
BufferBacking::Shm(backing) => backing.is_transparent(), BufferBacking::Shm(backing) => backing.is_transparent(),
@@ -81,13 +93,19 @@ impl Buffer {
} }
} }
pub fn uses_buffer_usage(&self) -> bool { pub fn uses_buffer_usage(&self) -> bool {
matches!(self.backing, BufferBacking::Dmabuf(_)) matches!(
self.backing,
BufferBacking::Dmabuf(_) | BufferBacking::Shm(_)
)
} }
} }
impl WlBuffer for Buffer { impl WlBuffer for Buffer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_buffer:request:destroy /// https://wayland.app/protocols/wayland#wl_buffer:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(&self, client: &mut Client, _sender_id: ObjectId) -> WaylandResult<()> {
client.remove(self.id);
tracing::info!("Destroying buffer {:?}", self.id); tracing::info!("Destroying buffer {:?}", self.id);
Ok(()) Ok(())
} }

View File

@@ -1,10 +1,11 @@
pub use waynest::server::protocol::core::wayland::wl_callback::*; use waynest::ObjectId;
use waynest::{ pub use waynest_protocols::server::core::wayland::wl_callback::*;
server::{Dispatcher, Result}, use waynest_server::RequestDispatcher;
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Clone)] #[derive(Debug, RequestDispatcher, Clone)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Callback(pub ObjectId); pub struct Callback(pub ObjectId);
/// https://wayland.app/protocols/wayland#wl_callback /// https://wayland.app/protocols/wayland#wl_callback
impl WlCallback for Callback {} impl WlCallback for Callback {
type Connection = crate::wayland::Client;
}

View File

@@ -1,25 +1,25 @@
use super::surface::WL_SURFACE_REGISTRY; use super::surface::WL_SURFACE_REGISTRY;
use crate::wayland::{WaylandError, WaylandResult};
use crate::wayland::{core::surface::Surface, util::ClientExt}; use crate::wayland::{core::surface::Surface, util::ClientExt};
pub use waynest::server::protocol::core::wayland::wl_compositor::*; use waynest::ObjectId;
use waynest::{ use waynest_protocols::server::core::wayland::wl_surface::WlSurface;
server::{ pub use waynest_protocols::server::core::wayland::{wl_compositor::*, wl_region::*};
Client, Dispatcher, Result, use waynest_server::{Client as _, RequestDispatcher};
protocol::core::wayland::{wl_region::WlRegion, wl_surface::WlSurface},
},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)] #[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = WaylandError, connection = crate::wayland::Client)]
pub struct Compositor; pub struct Compositor;
impl WlCompositor for Compositor { impl WlCompositor for Compositor {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface /// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface
async fn create_surface( async fn create_surface(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let surface = client.insert(id, Surface::new(client, id)); let surface = client.insert(id, Surface::new(client, id))?;
if let Some(output) = client.display().output.get() { if let Some(output) = client.display().output.get() {
surface.enter(client, id, output.id).await?; surface.enter(client, id, output.id).await?;
} }
@@ -31,46 +31,56 @@ impl WlCompositor for Compositor {
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_region /// https://wayland.app/protocols/wayland#wl_compositor:request:create_region
async fn create_region( async fn create_region(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
client.insert(id, Region::default()); client.insert(id, Region { id })?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Dispatcher, Default)] #[derive(Debug, RequestDispatcher)]
pub struct Region {} #[waynest(error = WaylandError, connection = crate::wayland::Client)]
pub struct Region {
id: ObjectId,
}
impl WlRegion for Region { impl WlRegion for Region {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_region:request:add /// https://wayland.app/protocols/wayland#wl_region:request:add
async fn add( async fn add(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_region:request:subtract /// https://wayland.app/protocols/wayland#wl_region:request:subtract
async fn subtract( async fn subtract(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_region:request:destroy /// https://wayland.app/protocols/wayland#wl_region:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }

View File

@@ -1,27 +1,26 @@
use crate::wayland::{Client, WaylandResult};
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use waynest::{ use waynest::ObjectId;
server::{ use waynest_protocols::server::core::wayland::{
Client, Dispatcher, Result, wl_data_device::*, wl_data_device_manager::*, wl_data_offer::WlDataOffer, wl_data_source::*,
protocol::core::wayland::{
wl_data_device::*, wl_data_device_manager::*, wl_data_offer::WlDataOffer,
wl_data_source::*,
},
},
wire::ObjectId,
}; };
use waynest_server::Client as _;
// TODO: actually implement this // TODO: actually implement this
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataDeviceManager; pub struct DataDeviceManager;
impl WlDataDeviceManager for DataDeviceManager { impl WlDataDeviceManager for DataDeviceManager {
type Connection = Client;
async fn create_data_source( async fn create_data_source(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
client.insert(id, DataSource); client.insert(id, DataSource { id })?;
Ok(()) Ok(())
} }
@@ -31,116 +30,137 @@ impl WlDataDeviceManager for DataDeviceManager {
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
client.insert(id, DataDevice); client.insert(id, DataDevice)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
pub struct DataSource; #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataSource {
id: ObjectId,
}
impl WlDataSource for DataSource { impl WlDataSource for DataSource {
async fn send( type Connection = Client;
&self,
_client: &mut Client,
_sender_id: ObjectId,
_mime_type: String,
_fd: OwnedFd,
) -> Result<()> {
Ok(())
}
async fn offer( async fn offer(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_mime_type: String, _mime_type: String,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
async fn set_actions( async fn set_actions(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_dnd_actions: DndAction, _dnd_actions: DndAction,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataDevice; pub struct DataDevice;
impl WlDataDevice for DataDevice { impl WlDataDevice for DataDevice {
type Connection = Client;
async fn start_drag( async fn start_drag(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_source: Option<ObjectId>, _source: Option<ObjectId>,
_origin: ObjectId, _origin: ObjectId,
_icon: Option<ObjectId>, _icon: Option<ObjectId>,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_selection( async fn set_selection(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_source: Option<ObjectId>, _source: Option<ObjectId>,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
pub struct DataOffer; #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataOffer {
id: ObjectId,
}
impl WlDataOffer for DataOffer { impl WlDataOffer for DataOffer {
type Connection = Client;
async fn accept( async fn accept(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
_mime_type: Option<String>, _mime_type: Option<String>,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn receive( async fn receive(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_mime_type: String, _mime_type: String,
_fd: OwnedFd, _fd: OwnedFd,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
async fn finish(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn finish(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_actions( async fn set_actions(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_dnd_actions: DndAction, _dnd_actions: DndAction,
_preferred_action: DndAction, _preferred_action: DndAction,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
nodes::items::panel::KEYMAPS, nodes::items::panel::KEYMAPS,
wayland::{core::surface::Surface, util::ClientExt}, wayland::{Client, WaylandResult, core::surface::Surface, util::ClientExt},
}; };
use dashmap::{DashMap, DashSet}; use dashmap::{DashMap, DashSet};
use memfd::MemfdOptions; use memfd::MemfdOptions;
@@ -9,17 +9,14 @@ use std::{
collections::HashSet, collections::HashSet,
io::Write, io::Write,
os::{ os::{
fd::IntoRawFd, fd::{AsFd, IntoRawFd},
unix::io::{FromRawFd, OwnedFd}, unix::io::{FromRawFd, OwnedFd},
}, },
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
use tokio::sync::Mutex; use tokio::sync::Mutex;
pub use waynest::server::protocol::core::wayland::wl_keyboard::*; use waynest::ObjectId;
use waynest::{ pub use waynest_protocols::server::core::wayland::wl_keyboard::*;
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Default)] #[derive(Default)]
struct ModifierState { struct ModifierState {
@@ -56,7 +53,8 @@ impl ModifierState {
input_event_codes::KEY_LEFTCTRL!() | input_event_codes::KEY_RIGHTCTRL!() => { input_event_codes::KEY_LEFTCTRL!() | input_event_codes::KEY_RIGHTCTRL!() => {
mods |= 4 mods |= 4
} }
input_event_codes::KEY_LEFTALT!() | input_event_codes::KEY_RIGHTALT!() => mods |= 8, input_event_codes::KEY_LEFTALT!() => mods |= 8,
input_event_codes::KEY_RIGHTALT!() => mods |= 128,
input_event_codes::KEY_LEFTMETA!() | input_event_codes::KEY_RIGHTMETA!() => { input_event_codes::KEY_LEFTMETA!() | input_event_codes::KEY_RIGHTMETA!() => {
mods |= 64 mods |= 64
} }
@@ -69,7 +67,8 @@ impl ModifierState {
} }
} }
#[derive(Dispatcher)] #[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Keyboard { pub struct Keyboard {
pub id: ObjectId, pub id: ObjectId,
focused_surface: Mutex<Weak<Surface>>, focused_surface: Mutex<Weak<Surface>>,
@@ -89,10 +88,9 @@ impl Keyboard {
} }
} }
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> Result<()> { async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> WaylandResult<()> {
let mut file = MemfdOptions::default() let mut file = MemfdOptions::default()
.create("stardust-keymap") .create("stardust-keymap")?
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?
.into_file(); .into_file();
file.set_len(keymap.len() as u64)?; file.set_len(keymap.len() as u64)?;
file.write_all(keymap)?; file.write_all(keymap)?;
@@ -105,7 +103,7 @@ impl Keyboard {
client, client,
self.id, self.id,
KeymapFormat::XkbV1, KeymapFormat::XkbV1,
fd, fd.as_fd(),
keymap.len() as u32, keymap.len() as u32,
) )
.await?; .await?;
@@ -121,7 +119,7 @@ impl Keyboard {
keymap_id: u64, keymap_id: u64,
key: u32, key: u32,
pressed: bool, pressed: bool,
) -> Result<()> { ) -> WaylandResult<()> {
// KEYMAP UPDATES // KEYMAP UPDATES
{ {
let mut old_keymap_id = self.current_keymap_id.lock().await; let mut old_keymap_id = self.current_keymap_id.lock().await;
@@ -224,7 +222,7 @@ impl Keyboard {
Ok(()) Ok(())
} }
pub async fn reset(&self, client: &mut Client) -> Result<()> { pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
let mut modifier_state = self.modifier_state.lock().await; let mut modifier_state = self.modifier_state.lock().await;
modifier_state.pressed_keys.clear(); modifier_state.pressed_keys.clear();
modifier_state.mods_depressed = 0; modifier_state.mods_depressed = 0;
@@ -247,8 +245,14 @@ impl Keyboard {
} }
impl WlKeyboard for Keyboard { impl WlKeyboard for Keyboard {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_keyboard:request:release /// https://wayland.app/protocols/wayland#wl_keyboard:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,17 +1,15 @@
use waynest::{ use crate::wayland::{Client, WaylandResult};
server::{Client, Dispatcher, Result}, use waynest::ObjectId;
wire::ObjectId, pub use waynest_protocols::server::core::wayland::wl_output::*;
};
pub use waynest::server::protocol::core::wayland::wl_output::*; #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Output { pub struct Output {
pub id: ObjectId, pub id: ObjectId,
pub version: u32, pub version: u32,
} }
impl Output { impl Output {
pub async fn advertise_outputs(&self, client: &mut Client) -> Result<()> { pub async fn advertise_outputs(&self, client: &mut Client) -> WaylandResult<()> {
self.geometry( self.geometry(
client, client,
self.id, self.id,
@@ -26,6 +24,16 @@ impl Output {
) )
.await?; .await?;
if self.version >= 4 {
self.name(client, self.id, "Stardust Virtual Display".to_string())
.await?;
self.description(
client,
self.id,
"I needed this to account for dumb clients".to_string(),
)
.await?;
}
self.mode(client, self.id, Mode::Current, 2048, 2048, i32::MAX) self.mode(client, self.id, Mode::Current, 2048, 2048, i32::MAX)
.await?; .await?;
@@ -36,8 +44,14 @@ impl Output {
} }
} }
impl WlOutput for Output { impl WlOutput for Output {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_output:request:release /// https://wayland.app/protocols/wayland#wl_output:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,20 +1,24 @@
use crate::wayland::core::{seat::fixed_from_f32, surface::Surface}; use super::surface::SurfaceRole;
use crate::nodes::items::panel::Geometry;
use crate::wayland::core::surface::Surface;
use crate::wayland::{Client, WaylandResult};
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Weak; use std::sync::Weak;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing; use tracing;
pub use waynest::server::protocol::core::wayland::wl_pointer::*; use waynest::ObjectId;
use waynest::{ use waynest_server::Client as _;
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Dispatcher)] pub use waynest_protocols::server::core::wayland::wl_pointer::*;
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Pointer { pub struct Pointer {
pub id: ObjectId, pub id: ObjectId,
version: u32, version: u32,
focused_surface: Mutex<Weak<Surface>>, focused_surface: Mutex<Weak<Surface>>,
cursor_surface: Mutex<Option<Arc<Surface>>>,
} }
impl Pointer { impl Pointer {
pub fn new(id: ObjectId, version: u32) -> Self { pub fn new(id: ObjectId, version: u32) -> Self {
@@ -22,6 +26,7 @@ impl Pointer {
id, id,
version, version,
focused_surface: Mutex::new(Weak::new()), focused_surface: Mutex::new(Weak::new()),
cursor_surface: Mutex::new(None),
} }
} }
@@ -30,7 +35,7 @@ impl Pointer {
client: &mut Client, client: &mut Client,
surface: Arc<Surface>, surface: Arc<Surface>,
position: Vector2<f32>, position: Vector2<f32>,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::debug!( tracing::debug!(
"Handling pointer motion at ({}, {})", "Handling pointer motion at ({}, {})",
position.x, position.x,
@@ -60,8 +65,8 @@ impl Pointer {
self.id, self.id,
serial, serial,
surface.id, surface.id,
fixed_from_f32(position.x), (position.x as f64).into(),
fixed_from_f32(position.y), (position.y as f64).into(),
) )
.await?; .await?;
@@ -75,8 +80,8 @@ impl Pointer {
client, client,
self.id, self.id,
0, // time 0, // time
fixed_from_f32(position.x), (position.x as f64).into(),
fixed_from_f32(position.y), (position.y as f64).into(),
) )
.await?; .await?;
if self.version >= 5 { if self.version >= 5 {
@@ -92,7 +97,7 @@ impl Pointer {
surface: Arc<Surface>, surface: Arc<Surface>,
button: u32, button: u32,
pressed: bool, pressed: bool,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::debug!( tracing::debug!(
"Handling pointer button {} {} on surface {:?}", "Handling pointer button {} {} on surface {:?}",
button, button,
@@ -121,7 +126,7 @@ impl Pointer {
_surface: Arc<Surface>, _surface: Arc<Surface>,
scroll_distance: Option<Vector2<f32>>, scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>, scroll_steps: Option<Vector2<f32>>,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::debug!( tracing::debug!(
"Handling pointer scroll: distance={:?}, steps={:?}", "Handling pointer scroll: distance={:?}, steps={:?}",
scroll_distance, scroll_distance,
@@ -133,7 +138,7 @@ impl Pointer {
self.id, self.id,
0, // time 0, // time
Axis::HorizontalScroll, Axis::HorizontalScroll,
fixed_from_f32(distance.x), (distance.x as f64).into(),
) )
.await?; .await?;
self.axis( self.axis(
@@ -141,23 +146,44 @@ impl Pointer {
self.id, self.id,
0, // time 0, // time
Axis::VerticalScroll, Axis::VerticalScroll,
fixed_from_f32(distance.y), (distance.y as f64).into(),
)
.await?;
}
if self.version < 8
&& self.version >= 5
&& let Some(steps) = scroll_steps
{
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
.await?;
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
.await?;
}
if self.version >= 8
&& let Some(steps) = scroll_steps
{
self.axis_value120(
client,
self.id,
Axis::HorizontalScroll,
(steps.x * 120.) as i32,
)
.await?;
self.axis_value120(
client,
self.id,
Axis::VerticalScroll,
(steps.y * 120.) as i32,
) )
.await?; .await?;
} }
if self.version >= 5 { if self.version >= 5 {
if let Some(steps) = scroll_steps {
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
.await?;
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
.await?;
}
self.frame(client, self.id).await?; self.frame(client, self.id).await?;
} }
Ok(()) Ok(())
} }
pub async fn reset(&self, client: &mut Client) -> Result<()> { pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
let mut focused = self.focused_surface.lock().await; let mut focused = self.focused_surface.lock().await;
if let Some(old_surface) = focused.upgrade() { if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
@@ -167,24 +193,60 @@ impl Pointer {
*focused = Weak::new(); *focused = Weak::new();
Ok(()) Ok(())
} }
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.cursor_surface.lock().await.clone()
}
} }
impl WlPointer for Pointer { impl WlPointer for Pointer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor /// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor
async fn set_cursor( async fn set_cursor(
&self, &self,
_client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
_surface: Option<ObjectId>, surface: Option<ObjectId>,
_hotspot_x: i32, hotspot_x: i32,
_hotspot_y: i32, hotspot_y: i32,
) -> Result<()> { ) -> WaylandResult<()> {
if let Some(focused_surface) = self.focused_surface.lock().await.upgrade()
&& let Some(panel_item) = focused_surface.panel_item.lock().upgrade()
{
panel_item.set_cursor(surface.and_then(|s| client.get::<Surface>(s)).map(|s| {
let size = s
.current_state()
.buffer
.map(|b| b.buffer.size())
.unwrap_or([16; 2].into());
Geometry {
origin: [hotspot_x, hotspot_y].into(),
size: [size.x as u32, size.y as u32].into(),
}
}));
}
let Some(surface) = surface else {
return Ok(());
};
let Some(surface) = client.get::<Surface>(surface) else {
return Ok(());
};
surface
.try_set_role(SurfaceRole::Cursor, Error::Role)
.await?;
self.cursor_surface.lock().await.replace(surface);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_pointer:request:release /// https://wayland.app/protocols/wayland#wl_pointer:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,10 +1,12 @@
use crate::wayland::Client;
use crate::wayland::WaylandResult;
use crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch}; use crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch};
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use std::sync::OnceLock; use std::sync::OnceLock;
pub use waynest::server::protocol::core::wayland::wl_seat::*; use waynest::ObjectId;
use waynest::server::{Client, Dispatcher, Result}; pub use waynest_protocols::server::core::wayland::wl_seat::*;
use waynest::wire::{Fixed, ObjectId}; use waynest_server::Client as _;
#[derive(Debug)] #[derive(Debug)]
pub enum SeatMessage { pub enum SeatMessage {
@@ -43,11 +45,8 @@ pub enum SeatMessage {
Reset, Reset,
} }
pub fn fixed_from_f32(f: f32) -> Fixed { #[derive(Default, waynest_server::RequestDispatcher)]
unsafe { Fixed::from_raw((f * 256.0).round() as u32) } #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
}
#[derive(Default, Dispatcher)]
pub struct Seat { pub struct Seat {
version: u32, version: u32,
pointer: OnceLock<Arc<Pointer>>, pointer: OnceLock<Arc<Pointer>>,
@@ -56,7 +55,7 @@ pub struct Seat {
} }
impl Seat { impl Seat {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<Self> { pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
let seat = Self { let seat = Self {
version, version,
pointer: OnceLock::new(), pointer: OnceLock::new(),
@@ -76,7 +75,11 @@ impl Seat {
Ok(seat) Ok(seat)
} }
pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> { pub async fn handle_message(
&self,
client: &mut Client,
message: SeatMessage,
) -> WaylandResult<()> {
match message { match message {
SeatMessage::PointerMotion { surface, position } => { SeatMessage::PointerMotion { surface, position } => {
if let Some(pointer) = self.pointer.get() { if let Some(pointer) = self.pointer.get() {
@@ -154,16 +157,22 @@ impl Seat {
} }
Ok(()) Ok(())
} }
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.pointer.get()?.cursor_surface().await
}
} }
impl WlSeat for Seat { impl WlSeat for Seat {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer /// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer
async fn get_pointer( async fn get_pointer(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let pointer = client.insert(id, Pointer::new(id, self.version)); let pointer = client.insert(id, Pointer::new(id, self.version))?;
let _ = self.pointer.set(pointer); let _ = self.pointer.set(pointer);
Ok(()) Ok(())
} }
@@ -171,12 +180,12 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard /// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard
async fn get_keyboard( async fn get_keyboard(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::info!("Getting keyboard"); tracing::info!("Getting keyboard");
let keyboard = client.insert(id, Keyboard::new(id)); let keyboard = client.insert(id, Keyboard::new(id))?;
let _ = self.keyboard.set(keyboard); let _ = self.keyboard.set(keyboard);
Ok(()) Ok(())
} }
@@ -184,17 +193,21 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_touch /// https://wayland.app/protocols/wayland#wl_seat:request:get_touch
async fn get_touch( async fn get_touch(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let touch = client.insert(id, Touch(id)); let touch = client.insert(id, Touch(id))?;
let _ = self.touch.set(touch); let _ = self.touch.set(touch);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_seat:request:release /// https://wayland.app/protocols/wayland#wl_seat:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,16 +1,18 @@
use crate::wayland::{Client, WaylandResult, core::shm_pool::ShmPool};
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_shm::*;
use waynest_server::Client as _;
use crate::wayland::core::shm_pool::ShmPool; #[derive(Debug, waynest_server::RequestDispatcher, Default)]
pub use waynest::server::protocol::core::wayland::wl_shm::*; #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)]
pub struct Shm; pub struct Shm;
impl Shm { impl Shm {
pub async fn advertise_formats(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { pub async fn advertise_formats(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
self.format(client, sender_id, Format::Argb8888).await?; self.format(client, sender_id, Format::Argb8888).await?;
self.format(client, sender_id, Format::Xrgb8888).await?; self.format(client, sender_id, Format::Xrgb8888).await?;
@@ -18,22 +20,28 @@ impl Shm {
} }
} }
impl WlShm for Shm { impl WlShm for Shm {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_shm:request:create_pool /// https://wayland.app/protocols/wayland#wl_shm:request:create_pool
async fn create_pool( async fn create_pool(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
pool_id: ObjectId, pool_id: ObjectId,
fd: OwnedFd, fd: OwnedFd,
size: i32, size: i32,
) -> Result<()> { ) -> WaylandResult<()> {
client.insert(pool_id, ShmPool::new(fd, size)?); client.insert(pool_id, ShmPool::new(fd, size, pool_id)?)?;
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_shm:request:release /// https://wayland.app/protocols/wayland#wl_shm:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,20 +1,64 @@
use super::shm_pool::ShmPool; use super::shm_pool::ShmPool;
use crate::wayland::{RENDER_DEVICE, vulkano_data::VULKANO_CONTEXT};
use bevy::{ use bevy::{
asset::{Assets, Handle, RenderAssetUsages}, asset::{Assets, Handle},
image::Image, image::Image,
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
}; };
use bevy_dmabuf::{
dmatex::{Dmatex, DmatexPlane, Resolution},
import::{DmatexUsage, DropCallback, ImportedDmatexs, ImportedTexture, import_texture},
};
use drm_fourcc::DrmFourcc;
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use parking_lot::Mutex;
use waynest::server::protocol::core::wayland::wl_shm::Format; use std::{
os::fd::OwnedFd,
sync::{Arc, OnceLock},
};
use tracing::debug_span;
use vulkano::{
buffer::BufferUsage,
command_buffer::{
AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo,
PrimaryCommandBufferAbstract,
},
image::{
ImageAspect, ImageCreateFlags, ImageCreateInfo, ImageMemory, ImageTiling, ImageUsage,
sys::RawImage,
},
memory::{
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, MemoryAllocateInfo,
ResourceMemory,
allocator::{AllocationCreateInfo, MemoryTypeFilter},
},
sync::GpuFuture,
};
use waynest_protocols::server::core::wayland::wl_shm::Format;
/// Parameters for a shared memory buffer /// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct ShmBufferBacking { pub struct ShmBufferBacking {
pool: Arc<ShmPool>, pool: Arc<ShmPool>,
offset: usize, offset: usize,
stride: usize, stride: usize,
size: Vector2<usize>, size: Vector2<usize>,
format: Format, wl_format: Format,
image: Arc<vulkano::image::Image>,
tex: OnceLock<Handle<Image>>,
pending_imported_dmatex: Mutex<Option<ImportedTexture>>,
}
impl std::fmt::Debug for ShmBufferBacking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShmBufferBacking")
.field("pool", &self.pool)
.field("offset", &self.offset)
.field("stride", &self.stride)
.field("size", &self.size)
.field("wl_format", &self.wl_format)
.field("image", &self.image)
.field("tex", &self.tex)
.finish()
}
} }
impl ShmBufferBacking { impl ShmBufferBacking {
@@ -23,70 +67,203 @@ impl ShmBufferBacking {
offset: usize, offset: usize,
stride: usize, stride: usize,
size: Vector2<usize>, size: Vector2<usize>,
format: Format, wl_format: Format,
) -> Self { ) -> Self {
let vk = VULKANO_CONTEXT.wait();
let format = match wl_format {
Format::Argb8888 | Format::Xrgb8888 => vulkano::format::Format::B8G8R8A8_SRGB,
_ => unimplemented!(),
};
let modifiers = vk
.phys_dev
.format_properties(format)
.unwrap()
.drm_format_modifier_properties
.into_iter()
.filter_map(|v| {
(v.drm_format_modifier_plane_count == 1).then_some(v.drm_format_modifier)
})
.collect();
let raw_image = RawImage::new(
vk.dev.clone(),
ImageCreateInfo {
flags: ImageCreateFlags::empty(),
image_type: vulkano::image::ImageType::Dim2d,
format,
extent: [size.x as u32, size.y as u32, 1],
tiling: ImageTiling::DrmFormatModifier,
usage: ImageUsage::TRANSFER_DST,
drm_format_modifiers: modifiers,
external_memory_handle_types: ExternalMemoryHandleType::DmaBuf.into(),
..Default::default()
},
)
.unwrap();
let (modifier, num_planes) = raw_image.drm_format_modifier().unwrap();
let mem_reqs = raw_image.memory_requirements()[0];
let index = vk
.phys_dev
.memory_properties()
.memory_types
.iter()
.enumerate()
.map(|(i, _v)| i as u32)
.find(|i| mem_reqs.memory_type_bits & (1 << i) != 0)
.expect("no valid memory type");
let mem = ResourceMemory::new_dedicated(
DeviceMemory::allocate(
vk.dev.clone(),
MemoryAllocateInfo {
allocation_size: mem_reqs.layout.size(),
memory_type_index: index,
dedicated_allocation: Some(DedicatedAllocation::Image(&raw_image)),
export_handle_types: ExternalMemoryHandleType::DmaBuf.into(),
..Default::default()
},
)
.unwrap(),
);
let Ok(image) = raw_image.bind_memory([mem]) else {
panic!("unable to bind memory")
};
let image = Arc::new(image);
let ImageMemory::Normal(mem) = image.memory() else {
unreachable!()
};
let [mem] = mem.as_slice() else {
unreachable!()
};
let fd = OwnedFd::from(
mem.device_memory()
.export_fd(ExternalMemoryHandleType::DmaBuf)
.unwrap(),
);
let planes = (0..num_planes)
.filter_map(|i| {
Some(match i {
0 => ImageAspect::MemoryPlane0,
1 => ImageAspect::MemoryPlane1,
2 => ImageAspect::MemoryPlane2,
3 => ImageAspect::MemoryPlane3,
_ => return None,
})
})
.map(|aspect| {
let plane_layout = image.subresource_layout(aspect, 0, 0).unwrap();
DmatexPlane {
dmabuf_fd: fd.try_clone().unwrap().into(),
modifier,
offset: plane_layout.offset as u32,
stride: plane_layout.row_pitch as i32,
}
})
.collect::<Vec<_>>();
let dmatex = Dmatex {
planes,
res: Resolution {
x: size.x as u32,
y: size.y as u32,
},
format: DrmFourcc::Argb8888 as u32,
flip_y: false,
srgb: true,
};
let imported_dmatex = import_texture(
RENDER_DEVICE.wait(),
dmatex,
DropCallback(None),
DmatexUsage::Sampling,
)
.unwrap();
Self { Self {
pool, pool,
offset, offset,
stride, stride,
size, size,
format, wl_format,
image,
pending_imported_dmatex: Mutex::new(Some(imported_dmatex)),
tex: OnceLock::new(),
} }
} }
pub fn on_commit(&self) {
let vk = VULKANO_CONTEXT.wait();
let data_len = self.size.x * self.size.y * 4;
let gpu_buffer = vulkano::buffer::Buffer::new_slice::<u8>(
vk.alloc.clone(),
vulkano::buffer::BufferCreateInfo {
usage: BufferUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
data_len as u64,
)
.unwrap();
{
let _span = debug_span!("copy to gpu buffer").entered();
let shm_data_lock = self.pool.data_lock();
let mut gpu_slice = gpu_buffer.write().unwrap();
for (shm_offset, gpu_offset) in
(0..self.size.y).map(|v| (self.offset + (v * self.stride), (v * (self.size.x * 4))))
{
let line_slice = &shm_data_lock[shm_offset..(shm_offset + (self.size.x * 4))];
let gpu_subslice = &mut gpu_slice[gpu_offset..(gpu_offset + (self.size.x * 4))];
gpu_subslice.copy_from_slice(line_slice);
}
}
let mut command_buffer = AutoCommandBufferBuilder::primary(
vk.command_buffer_alloc.clone(),
vk.queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)
.unwrap();
command_buffer
.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
gpu_buffer.clone(),
self.image.clone(),
))
.unwrap();
let command_buffer = command_buffer.build().unwrap();
command_buffer
.execute(vk.queue.clone())
.unwrap()
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
}
#[tracing::instrument("debug", skip_all)] #[tracing::instrument("debug", skip_all)]
pub fn update_tex(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> { pub fn update_tex(
let src_data_lock = self.pool.data_lock(); &self,
let mut src_cursor = self.offset; dmatexes: &ImportedDmatexs,
images: &mut Assets<Image>,
// Calculate maximum cursor position needed - stride is already in bytes ) -> Option<Handle<Image>> {
let max_cursor = self.offset + (self.size.y * self.stride); self.pending_imported_dmatex
.lock()
// Check if we have enough data .take()
if max_cursor > src_data_lock.len() { .map(|tex| dmatexes.insert_imported_dmatex(images, tex))
return None; .inspect(|handle| {
} _ = self.tex.set(handle.clone());
let data_len = self.size.x * self.size.y * 4; });
if src_data_lock.len() != data_len { self.tex.get().cloned()
return None;
}
let mut dst_cursor = 0;
let mut dst_data = vec![0u8; data_len];
for _y in 0..self.size.y {
for _x in 0..self.size.x {
match self.format {
Format::Argb8888 | Format::Xrgb8888 => {
dst_data[dst_cursor] = src_data_lock[src_cursor + 2]; // Red is byte 2
dst_data[dst_cursor + 1] = src_data_lock[src_cursor + 1]; // Green is byte 1
dst_data[dst_cursor + 2] = src_data_lock[src_cursor]; // Blue is byte 0
dst_data[dst_cursor + 3] = src_data_lock[src_cursor + 3]; // Alpha is byte 3
}
_ => panic!("Unsupported format {:?}", self.format),
}
src_cursor += 4;
dst_cursor += 4;
}
src_cursor += self.stride - (self.size.x * 4);
}
let image = Image::new(
Extent3d {
width: self.size().x as u32,
height: self.size().y as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
dst_data,
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
);
Some(images.add(image))
} }
pub fn is_transparent(&self) -> bool { pub fn is_transparent(&self) -> bool {
match self.format { match self.wl_format {
Format::Xrgb8888 => false, Format::Xrgb8888 => false,
Format::Argb8888 => true, Format::Argb8888 => true,
_ => true, _ => true,

View File

@@ -1,48 +1,52 @@
use super::shm_buffer_backing::ShmBufferBacking;
use crate::wayland::{
Client, WaylandResult,
core::buffer::{Buffer, BufferBacking},
};
use memmap2::{MmapOptions, RemapOptions}; use memmap2::{MmapOptions, RemapOptions};
use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard}; use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard};
use std::os::fd::{IntoRawFd, OwnedFd}; use std::os::fd::{AsRawFd, OwnedFd};
use waynest::{ use waynest::ObjectId;
server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format}, use waynest_protocols::server::core::wayland::wl_shm::Format;
wire::ObjectId, pub use waynest_protocols::server::core::wayland::wl_shm_pool::*;
}; use waynest_server::Client as _;
use crate::wayland::core::buffer::{Buffer, BufferBacking}; #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub use waynest::server::protocol::core::wayland::wl_shm_pool::*;
use super::shm_buffer_backing::ShmBufferBacking;
#[derive(Debug, Dispatcher)]
pub struct ShmPool { pub struct ShmPool {
inner: Mutex<memmap2::MmapMut>, inner: Mutex<memmap2::MmapMut>,
id: ObjectId,
} }
impl ShmPool { impl ShmPool {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn new(fd: OwnedFd, size: i32) -> Result<Self> { pub fn new(fd: OwnedFd, size: i32, id: ObjectId) -> WaylandResult<Self> {
let map = unsafe { let map = unsafe {
MmapOptions::new() MmapOptions::new()
.len(size as usize) .len(size as usize)
.map_mut(fd.into_raw_fd())? .map_mut(fd.as_raw_fd())?
}; };
Ok(Self { Ok(Self {
inner: Mutex::new(map), inner: Mutex::new(map),
id,
}) })
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn data_lock(&self) -> MappedMutexGuard<RawMutex, [u8]> { pub fn data_lock(&self) -> MappedMutexGuard<'_, RawMutex, [u8]> {
MutexGuard::map(self.inner.lock(), |i| i.as_mut()) MutexGuard::map(self.inner.lock(), |i| i.as_mut())
} }
} }
impl WlShmPool for ShmPool { impl WlShmPool for ShmPool {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer /// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn create_buffer( async fn create_buffer(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
id: ObjectId, id: ObjectId,
offset: i32, offset: i32,
@@ -50,7 +54,7 @@ impl WlShmPool for ShmPool {
height: i32, height: i32,
stride: i32, stride: i32,
format: Format, format: Format,
) -> Result<()> { ) -> WaylandResult<()> {
let params = ShmBufferBacking::new( let params = ShmBufferBacking::new(
client.get::<ShmPool>(sender_id).unwrap(), client.get::<ShmPool>(sender_id).unwrap(),
offset as usize, offset as usize,
@@ -59,13 +63,18 @@ impl WlShmPool for ShmPool {
format, format,
); );
Buffer::new(client, id, BufferBacking::Shm(params)); Buffer::new(client, id, BufferBacking::Shm(params))?;
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize /// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn resize(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> { async fn resize(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
size: i32,
) -> WaylandResult<()> {
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? }; unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? };
Ok(()) Ok(())
@@ -73,7 +82,12 @@ impl WlShmPool for ShmPool {
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy /// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }

View File

@@ -2,13 +2,16 @@ use super::{buffer::Buffer, callback::Callback};
use crate::{ use crate::{
BevyMaterial, BevyMaterial,
core::registry::Registry, core::registry::Registry,
nodes::{drawable::model::ModelPart, items::panel::Geometry}, nodes::{
drawable::model::ModelPart,
items::panel::{Geometry, PanelItem, SurfaceId},
},
wayland::{ wayland::{
Message, MessageSink, Client, Message, MessageSink, WaylandError, WaylandResult,
core::buffer::BufferUsage, core::buffer::BufferUsage,
presentation::{MonotonicTimestamp, PresentationFeedback}, presentation::{MonotonicTimestamp, PresentationFeedback},
util::{ClientExt, DoubleBuffer}, util::{ClientExt, DoubleBuffer},
xdg::{popup::Popup, toplevel::Toplevel}, xdg::backend::XdgBackend,
}, },
}; };
use bevy::{ use bevy::{
@@ -19,24 +22,35 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::{Arc, OnceLock}; use std::{
use waynest::{ fmt::Display,
server::{ sync::{Arc, OnceLock, Weak},
Client, Dispatcher, Result,
protocol::{
core::wayland::{wl_output::Transform, wl_surface::*},
stable::presentation_time::wp_presentation_feedback::{Kind, WpPresentationFeedback},
},
},
wire::ObjectId,
}; };
use waynest::ObjectId;
use waynest_protocols::server::{
core::wayland::{wl_output::Transform, wl_surface::*},
stable::presentation_time::wp_presentation_feedback::{Kind, WpPresentationFeedback},
};
use waynest_server::Client as _;
pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new(); pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new();
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceRole { pub enum SurfaceRole {
XdgToplevel(Arc<Toplevel>), Cursor,
XDGPopup(Arc<Popup>), Subsurface,
XdgToplevel,
XdgPopup,
}
impl Display for SurfaceRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SurfaceRole::Cursor => f.write_str("SurfaceRole::Cursor"),
SurfaceRole::Subsurface => f.write_str("SurfaceRole::Subsurface"),
SurfaceRole::XdgToplevel => f.write_str("SurfaceRole::XdgToplevel"),
SurfaceRole::XdgPopup => f.write_str("SurfaceRole::XdgPopup"),
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -66,18 +80,27 @@ impl Default for SurfaceState {
} }
} }
} }
impl SurfaceState {
pub fn has_valid_buffer(&self) -> bool {
self.buffer
.as_ref()
.is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0)
}
}
// if returning false, don't run this callback again... just remove it // if returning false, don't run this callback again... just remove it
pub type OnCommitCallback = Box<dyn Fn(&Surface, &SurfaceState) -> bool + Send + Sync>; pub type OnCommitCallback = Box<dyn FnMut(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(waynest_server::RequestDispatcher)]
#[derive(Dispatcher)] #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Surface { pub struct Surface {
pub id: ObjectId, pub id: ObjectId,
pub surface_id: OnceLock<SurfaceId>,
state: Mutex<DoubleBuffer<SurfaceState>>, state: Mutex<DoubleBuffer<SurfaceState>>,
pub message_sink: MessageSink, pub message_sink: MessageSink,
// TODO: This should probably be a OnceLock? wayland doesn't support changing the surface role pub role: OnceLock<SurfaceRole>,
pub role: Mutex<Option<SurfaceRole>>, pub panel_item: Mutex<Weak<PanelItem<XdgBackend>>>,
on_commit_handlers: Mutex<Vec<OnCommitCallback>>, on_commit_handlers: Mutex<Vec<OnCommitCallback>>,
frame_callbacks: Mutex<Vec<Arc<Callback>>>,
material: OnceLock<Handle<BevyMaterial>>, material: OnceLock<Handle<BevyMaterial>>,
pending_material_applications: Registry<ModelPart>, pending_material_applications: Registry<ModelPart>,
presentation_feedback: Mutex<Vec<Arc<PresentationFeedback>>>, presentation_feedback: Mutex<Vec<Arc<PresentationFeedback>>>,
@@ -85,6 +108,8 @@ pub struct Surface {
impl std::fmt::Debug for Surface { impl std::fmt::Debug for Surface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface") f.debug_struct("Surface")
.field("id", &self.id)
.field("surface_id", &self.surface_id)
.field("state", &self.state) .field("state", &self.state)
.field("message_sink", &self.message_sink) .field("message_sink", &self.message_sink)
.field("role", &self.role) .field("role", &self.role)
@@ -92,6 +117,8 @@ impl std::fmt::Debug for Surface {
"on_commit_handlers", "on_commit_handlers",
&format!("<{} handlers>", self.on_commit_handlers.lock().len()), &format!("<{} handlers>", self.on_commit_handlers.lock().len()),
) )
.field("material", &self.material)
.field("presentation_feedback", &self.presentation_feedback)
.finish() .finish()
} }
} }
@@ -100,23 +127,50 @@ impl Surface {
pub fn new(client: &Client, id: ObjectId) -> Self { pub fn new(client: &Client, id: ObjectId) -> Self {
Surface { Surface {
id, id,
surface_id: OnceLock::new(),
state: Default::default(), state: Default::default(),
message_sink: client.message_sink(), message_sink: client.message_sink(),
role: Mutex::new(None), role: OnceLock::new(),
panel_item: Mutex::new(Weak::default()),
on_commit_handlers: Mutex::new(Vec::new()), on_commit_handlers: Mutex::new(Vec::new()),
frame_callbacks: Mutex::new(Vec::new()),
material: OnceLock::new(), material: OnceLock::new(),
pending_material_applications: Registry::new(), pending_material_applications: Registry::new(),
presentation_feedback: Mutex::default(), presentation_feedback: Mutex::default(),
} }
} }
pub async fn try_set_role(
&self,
role: SurfaceRole,
role_error: impl Into<u32>,
) -> WaylandResult<()> {
match self.role.get().cloned() {
Some(current_role) => {
if current_role == role {
Ok(())
} else {
Err(WaylandError::Fatal {
object_id: self.id,
code: role_error.into(),
message: "Surface has an incomparible role",
})
}
}
None => {
let _ = self.role.set(role);
Ok(())
}
}
}
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> { pub fn state_lock(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
self.state.lock() self.state.lock()
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn add_commit_handler<F: Fn(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>( pub fn add_commit_handler<F: FnMut(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
&self, &self,
handler: F, handler: F,
) { ) {
@@ -187,8 +241,9 @@ impl Surface {
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn frame_event(&self) { pub fn frame_event(&self) {
for callback in self.state.lock().current.frame_callbacks.drain(..) { let callbacks = std::mem::take(&mut *self.frame_callbacks.lock());
let _ = self.message_sink.send(Message::Frame(callback)); if !callbacks.is_empty() {
let _ = self.message_sink.send(Message::Frame(callbacks));
} }
} }
// pub fn size(&self) -> Option<Vector2<u32>> { // pub fn size(&self) -> Option<Vector2<u32>> {
@@ -200,7 +255,7 @@ impl Surface {
// .map(|b| [b.size.x as u32, b.size.y as u32].into()) // .map(|b| [b.size.x as u32, b.size.y as u32].into())
// } // }
// pub async fn release_old_buffer(&self, client: &mut Client) -> Result<()> { // pub async fn release_old_buffer(&self, client: &mut Self::Connection) -> Result<()> {
// let (old_buffer, object) = { // let (old_buffer, object) = {
// let lock = self.state.lock(); // let lock = self.state.lock();
@@ -230,13 +285,11 @@ impl Surface {
display_timestamp: MonotonicTimestamp, display_timestamp: MonotonicTimestamp,
refresh_cycle: u64, refresh_cycle: u64,
) { ) {
self.message_sink let _ = self.message_sink.send(Message::SendPresentationFeedback {
.send(Message::SendPresentationFeedback { surface: self.clone(),
surface: self.clone(), display_timestamp,
display_timestamp, refresh_cycle,
refresh_cycle, });
})
.unwrap();
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
@@ -245,20 +298,16 @@ impl Surface {
client: &mut Client, client: &mut Client,
display_timestamp: MonotonicTimestamp, display_timestamp: MonotonicTimestamp,
refresh_cycle: u64, refresh_cycle: u64,
) -> Result<()> { ) -> WaylandResult<()> {
let feedbacks = self let feedbacks = self
.presentation_feedback .presentation_feedback
.lock() .lock()
.drain(..) .drain(..)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for feedback in feedbacks { for feedback in feedbacks {
feedback if let Some(display_id) = client.display().output.get().map(|display| display.id) {
.sync_output( feedback.sync_output(client, feedback.0, display_id).await?;
client, }
feedback.0,
client.display().output.get().unwrap().id,
)
.await?;
let cycle_lo = refresh_cycle as u32; let cycle_lo = refresh_cycle as u32;
let cycle_hi = (refresh_cycle >> 32) as u32; let cycle_hi = (refresh_cycle >> 32) as u32;
feedback feedback
@@ -279,16 +328,18 @@ impl Surface {
} }
} }
impl WlSurface for Surface { impl WlSurface for Surface {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_surface:request:attach /// https://wayland.app/protocols/wayland#wl_surface:request:attach
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn attach( async fn attach(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
buffer: Option<ObjectId>, buffer: Option<ObjectId>,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> Result<()> { ) -> WaylandResult<()> {
self.state.lock().pending.buffer = buffer.and_then(|b| { self.state.lock().pending.buffer = buffer.and_then(|b| {
let buffer = client.get::<Buffer>(b)?; let buffer = client.get::<Buffer>(b)?;
let mut usage = Some(BufferUsage::new(client, &buffer)); let mut usage = Some(BufferUsage::new(client, &buffer));
@@ -304,13 +355,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn damage( async fn damage(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
@@ -318,11 +369,11 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn frame( async fn frame(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
callback_id: ObjectId, callback_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let callback = client.insert(callback_id, Callback(callback_id)); let callback = client.insert(callback_id, Callback(callback_id))?;
self.state.lock().pending.frame_callbacks.push(callback); self.state.lock().pending.frame_callbacks.push(callback);
Ok(()) Ok(())
} }
@@ -331,10 +382,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_opaque_region( async fn set_opaque_region(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_region: Option<ObjectId>, _region: Option<ObjectId>,
) -> Result<()> { ) -> WaylandResult<()> {
// nothing we can really do to repaint behind this so ignore it // nothing we can really do to repaint behind this so ignore it
Ok(()) Ok(())
} }
@@ -343,22 +394,42 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_input_region( async fn set_input_region(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_region: Option<ObjectId>, _region: Option<ObjectId>,
) -> Result<()> { ) -> WaylandResult<()> {
// too complicated to implement this for now so who the hell cares // too complicated to implement this for now so who the hell cares
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_surface:request:commit /// https://wayland.app/protocols/wayland#wl_surface:request:commit
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn commit(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
// we want the upload to complete before we give the image to bevy
let buffer_option = self
.state_lock()
.pending
.buffer
.as_ref()
.map(|b| b.buffer.clone());
if let Some(buffer) = buffer_option {
tokio::task::spawn_blocking(move || buffer.on_commit())
.await
.unwrap();
}
self.state.lock().apply(); self.state.lock().apply();
self.state.lock().pending.frame_callbacks.clear(); self.state.lock().pending.frame_callbacks.clear();
let current_state = self.current_state(); let current_state = self.current_state();
self.frame_callbacks
.lock()
.extend(current_state.frame_callbacks.iter().cloned());
let mut handlers = self.on_commit_handlers.lock(); let mut handlers = self.on_commit_handlers.lock();
handlers.retain(|f| (f)(self, &current_state)); handlers.retain_mut(|f| (f)(self, &current_state));
Ok(()) Ok(())
} }
@@ -366,10 +437,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_transform( async fn set_buffer_transform(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_transform: Transform, _transform: Transform,
) -> Result<()> { ) -> WaylandResult<()> {
// we just don't have the output transform or fullscreen at all so this optimization is never needed // we just don't have the output transform or fullscreen at all so this optimization is never needed
Ok(()) Ok(())
} }
@@ -378,10 +449,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_scale( async fn set_buffer_scale(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
scale: i32, scale: i32,
) -> Result<()> { ) -> WaylandResult<()> {
self.state.lock().pending.density = scale as f32; self.state.lock().pending.density = scale as f32;
Ok(()) Ok(())
} }
@@ -390,13 +461,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn damage_buffer( async fn damage_buffer(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
@@ -404,23 +475,27 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn offset( async fn offset(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_surface:request:destroy /// https://wayland.app/protocols/wayland#wl_surface:request:destroy
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }
impl Drop for Surface { impl Drop for Surface {
fn drop(&mut self) { fn drop(&mut self) {
self.role.lock().take(); self.role.take();
} }
} }

View File

@@ -1,15 +1,11 @@
use crate::wayland::core::surface::Surface; use crate::wayland::{Client, WaylandResult, core::surface::Surface};
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
pub use waynest::server::protocol::core::wayland::wl_touch::*; use waynest::ObjectId;
use waynest::{ pub use waynest_protocols::server::core::wayland::wl_touch::*;
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
use super::seat::fixed_from_f32; #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Touch(pub ObjectId); pub struct Touch(pub ObjectId);
impl Touch { impl Touch {
pub async fn handle_touch_down( pub async fn handle_touch_down(
@@ -18,7 +14,7 @@ impl Touch {
surface: Arc<Surface>, surface: Arc<Surface>,
id: u32, id: u32,
position: Vector2<f32>, position: Vector2<f32>,
) -> Result<()> { ) -> WaylandResult<()> {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
self.down( self.down(
client, client,
@@ -27,8 +23,8 @@ impl Touch {
0, 0,
surface.id, surface.id,
id as i32, id as i32,
fixed_from_f32(position.x), (position.x as f64).into(),
fixed_from_f32(position.y), (position.y as f64).into(),
) )
.await?; .await?;
self.frame(client, self.0).await self.frame(client, self.0).await
@@ -39,37 +35,39 @@ impl Touch {
client: &mut Client, client: &mut Client,
id: u32, id: u32,
position: Vector2<f32>, position: Vector2<f32>,
) -> Result<()> { ) -> WaylandResult<()> {
self.motion( self.motion(
client, client,
self.0, self.0,
0, 0,
id as i32, id as i32,
fixed_from_f32(position.x), (position.x as f64).into(),
fixed_from_f32(position.y), (position.y as f64).into(),
) )
.await?; .await?;
self.frame(client, self.0).await self.frame(client, self.0).await
} }
pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> Result<()> { pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> WaylandResult<()> {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
self.up(client, self.0, serial, 0, id as i32).await?; self.up(client, self.0, serial, 0, id as i32).await?;
self.frame(client, self.0).await self.frame(client, self.0).await
} }
pub async fn reset(&self, client: &mut Client) -> Result<()> { pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
self.frame(client, self.0).await self.frame(client, self.0).await
} }
} }
impl WlTouch for Touch { impl WlTouch for Touch {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_touch:request:release /// https://wayland.app/protocols/wayland#wl_touch:request:release
async fn release( async fn release(
&self, &self,
_client: &mut waynest::server::Client, _client: &mut Self::Connection,
_sender_id: waynest::wire::ObjectId, _sender_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,7 +1,7 @@
#![allow(unused)] #![allow(unused)]
use crate::wayland::{ use crate::wayland::{
MessageSink, MessageSink, WaylandResult,
core::{ core::{
callback::{Callback, WlCallback}, callback::{Callback, WlCallback},
output::Output, output::Output,
@@ -14,13 +14,12 @@ use std::{
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
time::Instant, time::Instant,
}; };
pub use waynest::server::protocol::core::wayland::wl_display::*; use waynest::ObjectId;
use waynest::{ pub use waynest_protocols::server::core::wayland::wl_display::*;
server::{Client, Dispatcher, Result}, use waynest_server::Client as _;
wire::ObjectId,
};
#[derive(Dispatcher)] #[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Display { pub struct Display {
pub message_sink: MessageSink, pub message_sink: MessageSink,
pub pid: Option<i32>, pub pid: Option<i32>,
@@ -45,13 +44,15 @@ impl Display {
} }
} }
impl WlDisplay for Display { impl WlDisplay for Display {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_display:request:sync /// https://wayland.app/protocols/wayland#wl_display:request:sync
async fn sync( async fn sync(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
callback_id: ObjectId, callback_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
Callback(callback_id) Callback(callback_id)
.done(client, callback_id, serial) .done(client, callback_id, serial)
@@ -65,11 +66,11 @@ impl WlDisplay for Display {
/// https://wayland.app/protocols/wayland#wl_display:request:get_registry /// https://wayland.app/protocols/wayland#wl_display:request:get_registry
async fn get_registry( async fn get_registry(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
registry_id: ObjectId, registry_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let registry = client.insert(registry_id, Registry); let registry = client.insert(registry_id, Registry)?;
registry.advertise_globals(client, registry_id).await?; registry.advertise_globals(client, registry_id).await?;

View File

@@ -6,14 +6,15 @@ use bevy::{
}; };
use bevy_dmabuf::{ use bevy_dmabuf::{
dmatex::{Dmatex, Resolution}, dmatex::{Dmatex, Resolution},
import::{DropCallback, ImportError, ImportedDmatexs, ImportedTexture, import_texture}, import::{
DmatexUsage, DropCallback, ImportError, ImportedDmatexs, ImportedTexture, import_texture,
},
}; };
use drm_fourcc::DrmFourcc; use drm_fourcc::DrmFourcc;
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use tracing::info; use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
/// Parameters for a shared memory buffer /// Parameters for a shared memory buffer
pub struct DmabufBacking { pub struct DmabufBacking {
@@ -46,6 +47,7 @@ impl DmabufBacking {
dev, dev,
dmatex, dmatex,
DropCallback(None), DropCallback(None),
DmatexUsage::Sampling,
)?)), )?)),
}) })
} }
@@ -82,14 +84,14 @@ impl DmabufBacking {
dmatexes: &ImportedDmatexs, dmatexes: &ImportedDmatexs,
images: &mut Assets<Image>, images: &mut Assets<Image>,
) -> Option<Handle<Image>> { ) -> Option<Handle<Image>> {
info!("updating dmabuf tex");
self.pending_imported_dmatex self.pending_imported_dmatex
.lock() .lock()
.take() .take()
.map(|tex| dmatexes.insert_imported_dmatex(images, tex)) .map(|tex| dmatexes.insert_imported_dmatex(images, tex))
.inspect(|handle| { .inspect(|handle| {
_ = self.tex.set(handle.clone()); _ = self.tex.set(handle.clone());
}) });
self.tex.get().cloned()
} }
pub fn is_transparent(&self) -> bool { pub fn is_transparent(&self) -> bool {

View File

@@ -1,5 +1,6 @@
use super::buffer_backing::DmabufBacking; use super::buffer_backing::DmabufBacking;
use crate::wayland::{ use crate::wayland::{
Client, WaylandError, WaylandResult,
core::buffer::{Buffer, BufferBacking}, core::buffer::{Buffer, BufferBacking},
util::ClientExt, util::ClientExt,
}; };
@@ -8,22 +9,19 @@ use drm_fourcc::DrmFourcc;
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::os::fd::{AsRawFd, OwnedFd}; use std::os::fd::{AsRawFd, OwnedFd};
use waynest::{ use waynest::ObjectId;
server::{ use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Client, Dispatcher, Result, Error, Flags, ZwpLinuxBufferParamsV1,
protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Error, Flags, ZwpLinuxBufferParamsV1,
},
},
wire::ObjectId,
}; };
use waynest_server::Client as _;
/// Parameters for creating a DMA-BUF-based wl_buffer /// Parameters for creating a DMA-BUF-based wl_buffer
/// ///
/// This is a temporary object that collects dmabufs and other parameters /// This is a temporary object that collects dmabufs and other parameters
/// that together form a single logical buffer. The object may eventually /// that together form a single logical buffer. The object may eventually
/// create one wl_buffer unless cancelled by destroying it. /// create one wl_buffer unless cancelled by destroying it.
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct BufferParams { pub struct BufferParams {
pub id: ObjectId, pub id: ObjectId,
pub(super) planes: Mutex<FxHashMap<u32, DmatexPlane>>, pub(super) planes: Mutex<FxHashMap<u32, DmatexPlane>>,
@@ -41,7 +39,13 @@ impl BufferParams {
} }
impl ZwpLinuxBufferParamsV1 for BufferParams { impl ZwpLinuxBufferParamsV1 for BufferParams {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { type Connection = Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
tracing::info!("Destroying BufferParams {:?}", self.id); tracing::info!("Destroying BufferParams {:?}", self.id);
Ok(()) Ok(())
} }
@@ -49,7 +53,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn add( async fn add(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
fd: OwnedFd, fd: OwnedFd,
plane_idx: u32, plane_idx: u32,
@@ -57,7 +61,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
stride: u32, stride: u32,
modifier_hi: u32, modifier_hi: u32,
modifier_lo: u32, modifier_lo: u32,
) -> Result<()> { ) -> WaylandResult<()> {
let fd_num = fd.as_raw_fd(); let fd_num = fd.as_raw_fd();
tracing::info!( tracing::info!(
"Adding plane {} with fd {} to BufferParams {:?}", "Adding plane {} with fd {} to BufferParams {:?}",
@@ -75,7 +79,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
plane_idx, plane_idx,
self.id self.id
); );
return Err(waynest::server::Error::MissingObject(self.id)); return Err(crate::wayland::WaylandError::MissingObject(self.id));
} }
// Create plane with the provided parameters // Create plane with the provided parameters
@@ -94,13 +98,13 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn create( async fn create(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
width: i32, width: i32,
height: i32, height: i32,
format: u32, format: u32,
flags: Flags, flags: Flags,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::info!("Creating buffer from BufferParams {:?}", self.id); tracing::info!("Creating buffer from BufferParams {:?}", self.id);
// Create the buffer with DMA-BUF backing using self as the backing // Create the buffer with DMA-BUF backing using self as the backing
let size = [width as u32, height as u32].into(); let size = [width as u32, height as u32].into();
@@ -117,7 +121,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
}); });
match buffer { match buffer {
Ok(buffer) => self.created(client, self.id, buffer.id).await, Ok(buffer) => self.created(client, self.id, buffer?.id).await,
Err(_) => { Err(_) => {
client.remove(self.id); client.remove(self.id);
self.failed(client, self.id).await self.failed(client, self.id).await
@@ -128,14 +132,14 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn create_immed( async fn create_immed(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, _sender_id: ObjectId,
buffer_id: ObjectId, buffer_id: ObjectId,
width: i32, width: i32,
height: i32, height: i32,
format: u32, format: u32,
flags: Flags, flags: Flags,
) -> Result<()> { ) -> WaylandResult<()> {
// TODO: terminate client on fail, or send a fail event or something // TODO: terminate client on fail, or send a fail event or something
// Create the buffer with DMA-BUF backing using self as the backing // Create the buffer with DMA-BUF backing using self as the backing
match DmabufBacking::from_params( match DmabufBacking::from_params(
@@ -145,18 +149,15 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
flags, flags,
) { ) {
Ok(backing) => { Ok(backing) => {
Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing)); Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing))?;
} }
Err(e) => { Err(e) => {
client
.protocol_error(
sender_id,
buffer_id,
Error::Incomplete as u32,
format!("Failed to import dmabuf because {e}"),
)
.await?;
tracing::error!("Failed to import dmabuf because {e}"); tracing::error!("Failed to import dmabuf because {e}");
return Err(WaylandError::Fatal {
object_id: buffer_id,
code: Error::Incomplete as u32,
message: "Failed to import dmabuf",
});
} }
} }
Ok(()) Ok(())

View File

@@ -1,26 +1,22 @@
use super::Dmabuf; use super::Dmabuf;
use crate::wayland::vulkano_data::VULKANO_CONTEXT; use crate::wayland::{Client, WaylandResult, vulkano_data::VULKANO_CONTEXT};
use memfd::MemfdOptions; use memfd::MemfdOptions;
use std::{ use std::{
io::Write, io::Write,
os::fd::{FromRawFd, IntoRawFd, OwnedFd}, os::fd::{AsFd as _, FromRawFd, IntoRawFd, OwnedFd},
sync::Arc, sync::Arc,
}; };
use waynest::{ use waynest::ObjectId;
server::{ use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
Client, Dispatcher, Result, TrancheFlags, ZwpLinuxDmabufFeedbackV1,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
},
},
wire::ObjectId,
}; };
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DmabufFeedback(pub Arc<Dmabuf>); pub struct DmabufFeedback(pub Arc<Dmabuf>);
impl DmabufFeedback { impl DmabufFeedback {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> WaylandResult<()> {
let num_formats = self.0.formats.len(); let num_formats = self.0.formats.len();
// Send format table first // Send format table first
self.send_format_table(client, sender_id).await?; self.send_format_table(client, sender_id).await?;
@@ -63,16 +59,18 @@ impl DmabufFeedback {
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { pub async fn send_format_table(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
// Format + modifier pair (16 bytes): // Format + modifier pair (16 bytes):
// - format: u32 // - format: u32
// - padding: 4 bytes // - padding: 4 bytes
// - modifier: u64 // - modifier: u64
let size = self.0.formats.len() as u32 * 16u32; let size = self.0.formats.len() as u32 * 16u32;
// Create a temporary file for the format table // Create a temporary file for the format table
let mfd = MemfdOptions::default() let mfd = MemfdOptions::default().create("stardustxr-format-table")?;
.create("stardustxr-format-table")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?;
mfd.as_file().set_len(size as u64)?; mfd.as_file().set_len(size as u64)?;
@@ -83,20 +81,21 @@ impl DmabufFeedback {
mfd.as_file().write_all(&0_u32.to_ne_bytes())?; mfd.as_file().write_all(&0_u32.to_ne_bytes())?;
mfd.as_file().write_all(&modifier.to_ne_bytes())?; mfd.as_file().write_all(&modifier.to_ne_bytes())?;
} }
let fd = unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) };
self.format_table( self.format_table(client, sender_id, fd.as_fd(), size)
client, .await?;
sender_id,
unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) },
size,
)
.await?;
Ok(()) Ok(())
} }
} }
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback { impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { type Connection = crate::wayland::Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

@@ -2,23 +2,24 @@ pub mod buffer_backing;
pub mod buffer_params; pub mod buffer_params;
pub mod feedback; pub mod feedback;
use std::sync::LazyLock; use super::vulkano_data::VULKANO_CONTEXT;
use crate::{
use super::{util::ClientExt, vulkano_data::VULKANO_CONTEXT}; core::registry::Registry,
use crate::core::registry::Registry; wayland::{Client, WaylandError, WaylandResult},
use bevy_dmabuf::{format_mapping::drm_fourcc_to_vk_format, wgpu_init::vulkan_to_wgpu}; };
use bevy_dmabuf::{
format_mapping::{drm_fourcc_to_vk_format, vk_format_to_srgb},
wgpu_init::vulkan_to_wgpu,
};
use buffer_params::BufferParams; use buffer_params::BufferParams;
use drm_fourcc::DrmFourcc; use drm_fourcc::DrmFourcc;
use feedback::DmabufFeedback; use feedback::DmabufFeedback;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::sync::LazyLock;
use vulkano::format::FormatFeatures; use vulkano::format::FormatFeatures;
use waynest::{ use waynest::ObjectId;
server::{ use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1;
Client, Dispatcher, Error, Result, use waynest_server::Client as _;
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
},
wire::ObjectId,
};
pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| { pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
let vk = VULKANO_CONTEXT.wait(); let vk = VULKANO_CONTEXT.wait();
@@ -28,6 +29,7 @@ pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
.copied() .copied()
.filter_map(|f| Some((f, drm_fourcc_to_vk_format(f)?))) .filter_map(|f| Some((f, drm_fourcc_to_vk_format(f)?)))
.filter(|(_, vk_format)| vulkan_to_wgpu(*vk_format).is_some()) .filter(|(_, vk_format)| vulkan_to_wgpu(*vk_format).is_some())
.filter(|(_, vk_format)| vk_format_to_srgb(*vk_format).is_some())
.filter_map(|(f, vk_format)| { .filter_map(|(f, vk_format)| {
Some(( Some((
f, f,
@@ -72,7 +74,8 @@ pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
/// - Coherency for read access in dmabuf data /// - Coherency for read access in dmabuf data
/// - Proper lifetime management of dmabuf file descriptors /// - Proper lifetime management of dmabuf file descriptors
/// - Safe handling of buffer attachments /// - Safe handling of buffer attachments
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Dmabuf { pub struct Dmabuf {
// Track supported formats and modifiers // Track supported formats and modifiers
// formats: Mutex<FxHashSet<DrmFormat>>, // formats: Mutex<FxHashSet<DrmFormat>>,
@@ -84,7 +87,7 @@ pub struct Dmabuf {
impl Dmabuf { impl Dmabuf {
/// Create a new DMA-BUF interface instance /// Create a new DMA-BUF interface instance
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<Self> { pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
let dmabuf = Self { let dmabuf = Self {
active_params: Registry::new(), active_params: Registry::new(),
version, version,
@@ -118,53 +121,56 @@ impl Dmabuf {
} }
impl ZwpLinuxDmabufV1 for Dmabuf { impl ZwpLinuxDmabufV1 for Dmabuf {
async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> { type Connection = crate::wayland::Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
sender_id: ObjectId,
) -> WaylandResult<()> {
self.remove_params(sender_id); self.remove_params(sender_id);
Ok(()) Ok(())
} }
async fn create_params( async fn create_params(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
params_id: ObjectId, params_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
// Create new buffer parameters object // Create new buffer parameters object
let params = client.insert(params_id, BufferParams::new(params_id)); let params = client.insert(params_id, BufferParams::new(params_id))?;
self.active_params.add_raw(&params); self.active_params.add_raw(&params);
Ok(()) Ok(())
} }
async fn get_default_feedback( async fn get_default_feedback(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
if self.version < 3 { if self.version < 3 {
client return Err(WaylandError::Fatal {
.protocol_error( object_id: id,
sender_id, code: 71,
id, message: "Can't call get_default_feedback on version < 4 of dmabuf",
71, });
"Can't call get_default_feedback on version < 4 of dmabuf".into(),
)
.await?;
return Err(Error::Custom("Protocol error".into()));
} }
// Create feedback object for default (non-surface-specific) settings // Create feedback object for default (non-surface-specific) settings
let feedback = client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap())); let feedback =
client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap()))?;
feedback.send_params(client, id).await?; feedback.send_params(client, id).await?;
Ok(()) Ok(())
} }
async fn get_surface_feedback( async fn get_surface_feedback(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
id: ObjectId, id: ObjectId,
_surface: ObjectId, _surface: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
// Create feedback object for surface-specific settings // Create feedback object for surface-specific settings
// Note: Surface-specific feedback could be optimized based on the surface's // Note: Surface-specific feedback could be optimized based on the surface's
// requirements, but for now we use the same feedback as default // requirements, but for now we use the same feedback as default

View File

@@ -1,4 +1,5 @@
use crate::wayland::{ use crate::wayland::{
Client, WaylandResult,
core::buffer::{Buffer, BufferBacking}, core::buffer::{Buffer, BufferBacking},
dmabuf::{DMABUF_FORMATS, buffer_backing::DmabufBacking}, dmabuf::{DMABUF_FORMATS, buffer_backing::DmabufBacking},
vulkano_data::VULKANO_CONTEXT, vulkano_data::VULKANO_CONTEXT,
@@ -6,17 +7,16 @@ use crate::wayland::{
use bevy_dmabuf::dmatex::{Dmatex, DmatexPlane, Resolution}; use bevy_dmabuf::dmatex::{Dmatex, DmatexPlane, Resolution};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
use waynest::{ use waynest::ObjectId;
server::{Client, Dispatcher, Result, protocol::external::drm::wl_drm::*}, use waynest_protocols::server::mesa::drm::wl_drm::*;
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)] #[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct MesaDrm { pub struct MesaDrm {
version: u32, version: u32,
} }
impl MesaDrm { impl MesaDrm {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<MesaDrm> { pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<MesaDrm> {
let drm = MesaDrm { version }; let drm = MesaDrm { version };
let path = { let path = {
@@ -46,13 +46,20 @@ impl MesaDrm {
} }
} }
impl WlDrm for MesaDrm { impl WlDrm for MesaDrm {
async fn authenticate(&self, client: &mut Client, sender_id: ObjectId, _id: u32) -> Result<()> { type Connection = Client;
async fn authenticate(
&self,
client: &mut Self::Connection,
sender_id: ObjectId,
_id: u32,
) -> WaylandResult<()> {
self.authenticated(client, sender_id).await self.authenticated(client, sender_id).await
} }
async fn create_buffer( async fn create_buffer(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_id: ObjectId, _id: ObjectId,
_name: u32, _name: u32,
@@ -60,14 +67,14 @@ impl WlDrm for MesaDrm {
_height: i32, _height: i32,
_stride: u32, _stride: u32,
_format: u32, _format: u32,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::error!("Tried to create non-prime wl_drm buffer!"); tracing::error!("Tried to create non-prime wl_drm buffer!");
Ok(()) Ok(())
} }
async fn create_planar_buffer( async fn create_planar_buffer(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_id: ObjectId, _id: ObjectId,
_name: u32, _name: u32,
@@ -80,14 +87,14 @@ impl WlDrm for MesaDrm {
_stride1: i32, _stride1: i32,
_offset2: i32, _offset2: i32,
_stride2: i32, _stride2: i32,
) -> Result<()> { ) -> WaylandResult<()> {
tracing::error!("Tried to create non-prime wl_drm buffer!"); tracing::error!("Tried to create non-prime wl_drm buffer!");
Ok(()) Ok(())
} }
async fn create_prime_buffer( async fn create_prime_buffer(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
buffer_id: ObjectId, buffer_id: ObjectId,
name: OwnedFd, name: OwnedFd,
@@ -100,7 +107,7 @@ impl WlDrm for MesaDrm {
_stride1: i32, _stride1: i32,
_offset2: i32, _offset2: i32,
_stride2: i32, _stride2: i32,
) -> Result<()> { ) -> WaylandResult<()> {
// TODO: actual error checking // TODO: actual error checking
let _ = DmabufBacking::new(Dmatex { let _ = DmabufBacking::new(Dmatex {

View File

@@ -5,21 +5,19 @@ mod mesa_drm;
mod presentation; mod presentation;
mod registry; mod registry;
mod util; mod util;
mod viewporter;
mod vulkano_data; mod vulkano_data;
mod xdg; mod xdg;
use crate::core::error::ServerError;
use crate::core::registry::OwnedRegistry; use crate::core::registry::OwnedRegistry;
use crate::get_time;
use crate::nodes::drawable::model::ModelNodeSystemSet; use crate::nodes::drawable::model::ModelNodeSystemSet;
use crate::wayland::core::seat::SeatMessage; use crate::wayland::core::seat::SeatMessage;
use crate::wayland::core::surface::Surface; use crate::wayland::core::surface::Surface;
use crate::wayland::presentation::MonotonicTimestamp; use crate::wayland::presentation::MonotonicTimestamp;
use crate::{ use crate::wayland::util::ClientExt;
BevyMaterial, use crate::{BevyMaterial, core::task};
core::{
error::{Result, ServerError},
task,
},
};
use bevy::app::{App, Plugin, Update}; use bevy::app::{App, Plugin, Update};
use bevy::ecs::schedule::IntoScheduleConfigs; use bevy::ecs::schedule::IntoScheduleConfigs;
use bevy::ecs::system::{Local, Res, ResMut}; use bevy::ecs::system::{Local, Res, ResMut};
@@ -29,46 +27,152 @@ use bevy::render::{Render, RenderApp};
use bevy::{asset::Assets, ecs::resource::Resource, image::Image}; use bevy::{asset::Assets, ecs::resource::Resource, image::Image};
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use bevy_mod_openxr::render::end_frame; use bevy_mod_openxr::render::end_frame;
use bevy_mod_openxr::resources::{OxrFrameState, OxrInstance, Pipelined};
use bevy_mod_xr::session::XrRenderSet; use bevy_mod_xr::session::XrRenderSet;
use cluFlock::{FlockLock, ToFlock};
use core::buffer::BufferUsage; use core::buffer::BufferUsage;
use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY}; use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY};
use display::Display; use display::Display;
use mint::Vector2; use mint::Vector2;
use pin_project_lite::pin_project;
use std::fs::File; use std::fs::File;
use std::io::ErrorKind;
use std::mem::MaybeUninit;
use std::time::Duration; use std::time::Duration;
use std::{ use std::{
fs::{self, OpenOptions}, io,
io::{self, ErrorKind},
os::unix::fs::OpenOptionsExt,
path::PathBuf, path::PathBuf,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle}; use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle};
use tokio_stream::StreamExt; use tokio_stream::{Stream, StreamExt};
use tracing::{debug_span, instrument}; use tracing::{debug_span, instrument};
use vulkano_data::setup_vulkano_context; use vulkano_data::setup_vulkano_context;
use waynest::{ use waynest::{Connection, Socket};
server::{ use waynest::{ObjectId, ProtocolError};
self, use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
protocol::{ use waynest_server::{Client as _, Listener, Store, StoreError};
core::wayland::{wl_buffer::WlBuffer, wl_callback::WlCallback, wl_display::WlDisplay},
stable::xdg_shell::xdg_toplevel::XdgToplevel,
},
},
wire::{DecodeError, ObjectId},
};
use xdg::toplevel::Toplevel; use xdg::toplevel::Toplevel;
pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new(); pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new();
impl From<waynest::server::Error> for ServerError { #[derive(thiserror::Error, Debug)]
fn from(err: waynest::server::Error) -> Self { pub enum WaylandError {
ServerError::WaylandError(err) // #[error("Listener error: {0}")]
// Listener(#[from] waynest_server::ListenerError),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Decode error: {0}")]
DecodeError(#[from] waynest::ProtocolError),
#[error("Client requested unknown global: {0}")]
UnknownGlobal(u32),
#[error("No object found with ID {0}")]
MissingObject(ObjectId),
#[error("Fatal error on object {object_id} with code {code}: {message}")]
Fatal {
object_id: ObjectId,
code: u32,
message: &'static str,
},
#[error("Memfd error: {0}")]
MemfdError(#[from] memfd::Error),
#[error("Dmabuf import error: {0}")]
DmabufImport(#[from] bevy_dmabuf::import::ImportError),
#[error("Server error: {0}")]
Server(#[from] ServerError),
#[error("Failed to Insert Object")]
FailedToInsertObject,
}
impl<T: Clone> From<StoreError<T>> for WaylandError {
fn from(_value: StoreError<T>) -> Self {
Self::FailedToInsertObject
} }
} }
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> { pin_project! {
pub struct Client {
store: Store<Client, WaylandError>,
#[pin]
connection: Socket,
next_event_serial: u32,
}
}
impl Connection for Client {
type Error = WaylandError;
fn fd(&mut self) -> Result<std::os::unix::prelude::OwnedFd, <Self as Connection>::Error> {
Ok(self.connection.fd()?)
}
}
impl Stream for Client {
type Item = <Socket as Stream>::Item;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
// <Socket as Stream>::poll_next(self.project().connection, cx)
self.project().connection.poll_next(cx)
}
}
impl futures_sink::Sink<waynest::Message> for Client {
type Error = ProtocolError;
fn poll_ready(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_ready(cx)
}
fn start_send(
self: std::pin::Pin<&mut Self>,
item: waynest::Message,
) -> Result<(), Self::Error> {
self.project().connection.start_send(item)
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_flush(cx)
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_close(cx)
}
}
impl Client {
fn new(unix_stream: UnixStream) -> tokio::io::Result<Self> {
Ok(Self {
store: Store::new(),
connection: Socket::new(unix_stream.into_std()?)?,
next_event_serial: 0,
})
}
pub fn next_event_serial(&mut self) -> u32 {
let prev = self.next_event_serial;
self.next_event_serial = self.next_event_serial.wrapping_add(1);
prev
}
}
impl waynest_server::Client for Client {
type Store = Store<Client, WaylandError>;
fn store(&self) -> &Self::Store {
&self.store
}
fn store_mut(&mut self) -> &mut Self::Store {
&mut self.store
}
}
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
// Use XDG runtime directory for secure, user-specific sockets // Use XDG runtime directory for secure, user-specific sockets
let base_dirs = directories::BaseDirs::new()?; let base_dirs = directories::BaseDirs::new()?;
let runtime_dir = base_dirs.runtime_dir()?; let runtime_dir = base_dirs.runtime_dir()?;
@@ -78,21 +182,12 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
let socket_path = runtime_dir.join(format!("wayland-{display}")); let socket_path = runtime_dir.join(format!("wayland-{display}"));
let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock")); let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock"));
// Open lock file without truncation to preserve existing locks let Ok(lock) = File::create(&socket_lock_path) else {
let Ok(lock) = OpenOptions::new()
.create(true)
.truncate(false) // Prevent destroying other processes' locks
.read(true)
.write(true)
.mode(0o660) // Match Wayland-compositor permissions
.open(&socket_lock_path)
else {
continue; continue;
}; };
// Atomic mutual exclusion: fail if another process holds the lock\ if lock.try_lock().is_err() {
let Ok(lock) = lock.try_exclusive_lock() else { continue;
continue; // Lock held by active compositor
}; };
// Check for zombie sockets (file exists but nothing listening) // Check for zombie sockets (file exists but nothing listening)
@@ -101,7 +196,7 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
Ok(_) => continue, // Active compositor found - skip Ok(_) => continue, // Active compositor found - skip
Err(e) if e.kind() == ErrorKind::ConnectionRefused => { Err(e) if e.kind() == ErrorKind::ConnectionRefused => {
// Stale socket - safe to remove since we hold the lock // Stale socket - safe to remove since we hold the lock
let _ = fs::remove_file(&socket_path); let _ = std::fs::remove_file(&socket_path);
} }
Err(_) => continue, // Transient error - conservative skip Err(_) => continue, // Transient error - conservative skip
} }
@@ -114,15 +209,17 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
None // Exhausted all conventional display numbers None // Exhausted all conventional display numbers
} }
pub type WaylandResult<T, E = WaylandError> = std::result::Result<T, E>;
pub enum Message { pub enum Message {
Disconnect, Frame(Vec<Arc<Callback>>),
Frame(Arc<Callback>),
ReleaseBuffer(Arc<Buffer>), ReleaseBuffer(Arc<Buffer>),
CloseToplevel(Arc<Toplevel>), CloseToplevel(Arc<Toplevel>),
ResizeToplevel { ResizeToplevel {
toplevel: Arc<Toplevel>, toplevel: Arc<Toplevel>,
size: Option<Vector2<u32>>, size: Option<Vector2<u32>>,
}, },
ReconfigureToplevel(Arc<Toplevel>),
SetToplevelVisualActive { SetToplevelVisualActive {
toplevel: Arc<Toplevel>, toplevel: Arc<Toplevel>,
active: bool, active: bool,
@@ -142,87 +239,92 @@ struct WaylandClient {
abort_handle: AbortHandle, abort_handle: AbortHandle,
} }
impl WaylandClient { impl WaylandClient {
pub fn from_stream(socket: UnixStream) -> Result<Self> { pub fn from_stream(socket: UnixStream) -> WaylandResult<Self> {
let pid = socket.peer_cred().ok().and_then(|c| c.pid()); let pid = socket.peer_cred().ok().and_then(|c| c.pid());
let mut client = server::Client::new(socket)?; let exe = pid.and_then(|pid| std::fs::read_link(format!("/proc/{pid}/exe")).ok());
let mut client = Client::new(socket)?;
let (message_sink, message_source) = mpsc::unbounded_channel(); let (message_sink, message_source) = mpsc::unbounded_channel();
client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid)); client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid))?;
let pid_printable = pid
.map(|pid| pid.to_string())
.unwrap_or_else(|| "??".to_string());
let exe_printable = exe
.and_then(|exe| {
exe.file_name()
.and_then(|exe| exe.to_str())
.map(|exe| exe.to_string())
})
.unwrap_or_else(|| "??".to_string());
let abort_handle = task::new( let abort_handle = task::new(
|| "wayland client", || format!("Wayland client \"{exe_printable}\" dispatch, pid={pid_printable}"),
Self::handle_client_messages(client, message_source), Self::dispatch_loop(client, message_source),
)? )?
.abort_handle(); .abort_handle();
Ok(WaylandClient { abort_handle }) Ok(WaylandClient { abort_handle })
} }
async fn handle_client_messages(
mut client: server::Client, async fn dispatch_loop(
mut client: Client,
mut render_message_rx: mpsc::UnboundedReceiver<Message>, mut render_message_rx: mpsc::UnboundedReceiver<Message>,
) -> Result<()> { ) -> WaylandResult<()> {
loop { loop {
tokio::select! { tokio::select! {
biased;
// send all queued up messages // send all queued up messages
msg = render_message_rx.recv() => { msg = render_message_rx.recv() => {
if let Some(msg) = msg { let Some(msg) = msg else {
Self::handle_render_message(&mut client, msg).await?; // Render message channel closed, end the dispatch loop
} return Ok(());
};
Self::handle_render_message(&mut client, msg).await?;
} }
// handle the next message // handle the next message
msg = client.next_message() => { msg = client.try_next() => {
match msg { let Some(mut msg) = msg? else {
Ok(Some(mut msg)) => { // Client disconnected, end the dispatch loop
if let Err(e) = client.handle_message(&mut msg).await { return Ok(());
tracing::error!("Wayland: Error handling message: {:?}", e); };
break; if let Err(e) = client
} .get_raw(msg.object_id())
} .ok_or(WaylandError::MissingObject(msg.object_id()))?
Err(e) => { .dispatch_request(&mut client, msg.object_id(), &mut msg)
// wayland clients really aren't nice when disconnecting properly, are they? :p .await
if let server::Error::Decode(DecodeError::IoError(e)) = &e && e.kind() == io::ErrorKind::ConnectionReset { {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) { if let WaylandError::Fatal { object_id, code, message } = e {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server"); client.display().error(&mut client, ObjectId::DISPLAY, object_id, code, message.to_string()).await?;
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
break;
}
tracing::error!("Wayland: Error reading message: {:?}", e);
break;
}
Ok(None) => {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
// Message stream ended
break;
} }
tracing::error!("Wayland: {e}");
return Err(e);
} }
} }
} };
} }
Ok(())
} }
async fn handle_render_message( async fn handle_render_message(client: &mut Client, message: Message) -> WaylandResult<()> {
client: &mut server::Client, use waynest_protocols::server::core::wayland::wl_buffer::WlBuffer;
message: Message, use waynest_protocols::server::core::wayland::wl_callback::WlCallback;
) -> Result<bool, waynest::server::Error> { use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::XdgToplevel;
match message { match message {
Message::Disconnect => return Ok(true), Message::Frame(callbacks) => {
Message::Frame(callback) => {
let now = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic); let now = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
let now = Duration::new(now.tv_sec as u64, now.tv_nsec as u32); let now = Duration::new(now.tv_sec as u64, now.tv_nsec as u32);
let ms = (now.as_millis() % (u32::MAX as u128)) as u32; let ms = (now.as_millis() % (u32::MAX as u128)) as u32;
callback.done(client, callback.0, ms).await?; for callback in callbacks {
client callback.done(client, callback.0, ms).await?;
.get::<Display>(ObjectId::DISPLAY) client
.unwrap() .get::<Display>(ObjectId::DISPLAY)
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw()) .unwrap()
.await?; .delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
client.remove(callback.0); .await?;
client.remove(callback.0);
}
} }
Message::ReleaseBuffer(buffer) => { Message::ReleaseBuffer(buffer) => {
buffer.release(client, buffer.id).await?; buffer.release(client, buffer.id).await?;
@@ -234,6 +336,9 @@ impl WaylandClient {
toplevel.set_size(size); toplevel.set_size(size);
toplevel.reconfigure(client).await?; toplevel.reconfigure(client).await?;
} }
Message::ReconfigureToplevel(toplevel) => {
toplevel.reconfigure(client).await?;
}
Message::SetToplevelVisualActive { toplevel, active } => { Message::SetToplevelVisualActive { toplevel, active } => {
toplevel.set_activated(active); toplevel.set_activated(active);
toplevel.reconfigure(client).await?; toplevel.reconfigure(client).await?;
@@ -253,7 +358,7 @@ impl WaylandClient {
.await?; .await?;
} }
} }
Ok(false) Ok(())
} }
} }
impl Drop for WaylandClient { impl Drop for WaylandClient {
@@ -264,30 +369,32 @@ impl Drop for WaylandClient {
#[derive(Debug, Resource)] #[derive(Debug, Resource)]
pub struct Wayland { pub struct Wayland {
_lockfile: FlockLock<File>, _lockfile: File,
abort_handle: AbortHandle, abort_handle: AbortHandle,
} }
impl Wayland { impl Wayland {
pub fn new() -> Result<Self> { pub fn new() -> color_eyre::eyre::Result<Self> {
let (socket_path, _lockfile) = let (socket_path, _lockfile) = get_free_wayland_socket_path().ok_or(WaylandError::Io(
get_free_wayland_socket_path().ok_or(ServerError::WaylandError( std::io::ErrorKind::AddrNotAvailable.into(),
waynest::server::Error::IoError(std::io::ErrorKind::AddrNotAvailable.into()), ))?;
))?;
let _ = WAYLAND_DISPLAY.set(socket_path.clone()); let _ = WAYLAND_DISPLAY.set(socket_path.clone());
let listener = let listener = waynest_server::Listener::new_with_path(&socket_path)?;
server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?; let _ = WAYLAND_DISPLAY.set(listener.socket_path().to_path_buf());
let abort_handle = let abort_handle = task::new(
task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle(); || "Wayland socket accept loop",
Self::handle_wayland_loop(listener),
)?
.abort_handle();
Ok(Self { Ok(Self {
_lockfile, _lockfile,
abort_handle, abort_handle,
}) })
} }
async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> { async fn handle_wayland_loop(mut listener: Listener) -> WaylandResult<()> {
let mut clients = Vec::new(); let mut clients = Vec::new();
loop { loop {
if let Ok(Some(stream)) = listener.try_next().await { if let Ok(Some(stream)) = listener.try_next().await {
@@ -380,11 +487,39 @@ fn update_graphics(
} }
#[instrument(level = "debug", name = "Wayland frame", skip_all)] #[instrument(level = "debug", name = "Wayland frame", skip_all)]
fn submit_frame_timings(mut frame_count: Local<u64>) { fn submit_frame_timings(
mut frame_count: Local<u64>,
instance: Option<Res<OxrInstance>>,
frame_state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
*frame_count += 1; *frame_count += 1;
let display_timestamp = frame_state
.and_then(|state| Some((state, instance?)))
.and_then(|(state, instance)| {
instance
.exts()
.khr_convert_timespec_time
.and_then(|v| unsafe {
let mut out = MaybeUninit::uninit();
let result = (v.convert_time_to_timespec_time)(
instance.as_raw(),
get_time(pipelined.is_some(), &state),
out.as_mut_ptr(),
);
if result != openxr::sys::Result::SUCCESS {
return None;
}
let v = out.assume_init();
Some(rustix::time::Timespec {
tv_sec: v.tv_sec,
tv_nsec: v.tv_nsec,
})
})
})
.unwrap_or_else(|| rustix::time::clock_gettime(rustix::time::ClockId::Monotonic))
.into();
for surface in WL_SURFACE_REGISTRY.get_valid_contents() { for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
let display_timestamp =
rustix::time::clock_gettime(rustix::time::ClockId::Monotonic).into();
surface.submit_presentation_feedback(display_timestamp, *frame_count); surface.submit_presentation_feedback(display_timestamp, *frame_count);
} }
} }

View File

@@ -1,16 +1,13 @@
use rustix::fs::Timespec; use crate::wayland::WaylandResult;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::presentation_time::{
wp_presentation::WpPresentation, wp_presentation_feedback::WpPresentationFeedback,
},
},
wire::ObjectId,
};
use crate::wayland::core::surface::Surface; use crate::wayland::core::surface::Surface;
use rustix::fs::Timespec;
use waynest::ObjectId;
use waynest_protocols::server::stable::presentation_time::{
wp_presentation::*, wp_presentation_feedback::*,
};
use waynest_server::Client as _;
#[derive(Clone, Copy, Debug)]
pub struct MonotonicTimestamp { pub struct MonotonicTimestamp {
secs: u64, secs: u64,
subsec_nanos: u32, subsec_nanos: u32,
@@ -36,31 +33,49 @@ impl From<Timespec> for MonotonicTimestamp {
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
pub struct Presentation; #[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Presentation {
id: ObjectId,
}
impl Presentation {
pub fn new(id: ObjectId) -> Self {
Self { id }
}
}
impl WpPresentation for Presentation { impl WpPresentation for Presentation {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
async fn feedback( async fn feedback(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
surface: ObjectId, surface: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let Some(surface) = client.get::<Surface>(surface) else { let Some(surface) = client.get::<Surface>(surface) else {
tracing::error!("unable to get surface#{surface}"); tracing::error!("unable to get surface#{surface}");
return Ok(()); return Ok(());
}; };
let feedback = client.insert(id, PresentationFeedback(id)); let feedback = client.insert(id, PresentationFeedback(id))?;
surface.add_presentation_feedback(feedback); surface.add_presentation_feedback(feedback);
Ok(()) Ok(())
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct PresentationFeedback(pub ObjectId); pub struct PresentationFeedback(pub ObjectId);
impl WpPresentationFeedback for PresentationFeedback {} impl WpPresentationFeedback for PresentationFeedback {
type Connection = crate::wayland::Client;
}

View File

@@ -1,4 +1,6 @@
use crate::wayland::{Client, WaylandResult};
use crate::wayland::{ use crate::wayland::{
WaylandError,
core::{ core::{
compositor::{Compositor, WlCompositor}, compositor::{Compositor, WlCompositor},
data_device::DataDeviceManager, data_device::DataDeviceManager,
@@ -10,22 +12,20 @@ use crate::wayland::{
mesa_drm::MesaDrm, mesa_drm::MesaDrm,
presentation::Presentation, presentation::Presentation,
util::ClientExt, util::ClientExt,
viewporter::Viewporter,
xdg::wm_base::{WmBase, XdgWmBase}, xdg::wm_base::{WmBase, XdgWmBase},
}; };
use waynest::{ use waynest::{NewId, ObjectId};
server::{ use waynest_protocols::server::{
Client, Dispatcher, Error, Result, core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
protocol::{ mesa::drm::wl_drm::WlDrm,
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*}, stable::{
external::drm::wl_drm::WlDrm, linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
stable::{ presentation_time::wp_presentation::WpPresentation,
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, viewporter::wp_viewporter::WpViewporter,
presentation_time::wp_presentation::WpPresentation,
},
},
}, },
wire::{NewId, ObjectId},
}; };
use waynest_server::Client as _;
struct RegistryGlobals; struct RegistryGlobals;
impl RegistryGlobals { impl RegistryGlobals {
@@ -38,13 +38,19 @@ impl RegistryGlobals {
pub const DMABUF: u32 = 6; pub const DMABUF: u32 = 6;
pub const WL_DRM: u32 = 7; pub const WL_DRM: u32 = 7;
pub const PRESENTATION: u32 = 8; pub const PRESENTATION: u32 = 8;
pub const VIEWPORTER: u32 = 9;
} }
#[derive(Debug, Dispatcher, Default)] #[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Registry; pub struct Registry;
impl Registry { impl Registry {
pub async fn advertise_globals(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { pub async fn advertise_globals(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
self.global( self.global(
client, client,
sender_id, sender_id,
@@ -126,48 +132,57 @@ impl Registry {
) )
.await?; .await?;
self.global(
client,
sender_id,
RegistryGlobals::VIEWPORTER,
Viewporter::INTERFACE.to_string(),
Viewporter::VERSION,
)
.await?;
Ok(()) Ok(())
} }
} }
impl WlRegistry for Registry { impl WlRegistry for Registry {
type Connection = crate::wayland::Client;
async fn bind( async fn bind(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
name: u32, name: u32,
new_id: NewId, new_id: NewId,
) -> Result<()> { ) -> WaylandResult<()> {
match name { match name {
RegistryGlobals::COMPOSITOR => { RegistryGlobals::COMPOSITOR => {
tracing::info!("Binding compositor"); tracing::info!("Binding compositor");
client.insert(new_id.object_id, Compositor); client.insert(new_id.object_id, Compositor)?;
} }
RegistryGlobals::SHM => { RegistryGlobals::SHM => {
tracing::info!("Binding SHM"); tracing::info!("Binding SHM");
let shm = client.insert(new_id.object_id, Shm); let shm = client.insert(new_id.object_id, Shm)?;
shm.advertise_formats(client, new_id.object_id).await?; shm.advertise_formats(client, new_id.object_id).await?;
} }
RegistryGlobals::WM_BASE => { RegistryGlobals::WM_BASE => {
tracing::info!("Binding WM_BASE"); tracing::info!("Binding WM_BASE");
client.insert( client.insert(
new_id.object_id, new_id.object_id,
WmBase { WmBase::new(new_id.object_id, new_id.version),
version: new_id.version, )?;
},
);
} }
RegistryGlobals::SEAT => { RegistryGlobals::SEAT => {
tracing::info!("Binding seat with id {}", new_id.object_id); tracing::info!("Binding seat with id {}", new_id.object_id);
let seat = Seat::new(client, new_id.object_id, new_id.version).await?; let seat = Seat::new(client, new_id.object_id, new_id.version).await?;
let seat = client.insert(new_id.object_id, seat); let seat = client.insert(new_id.object_id, seat)?;
let _ = client.display().seat.set(seat.clone()); let _ = client.display().seat.set(seat.clone());
tracing::info!("Seat capabilities advertised"); tracing::info!("Seat capabilities advertised");
} }
RegistryGlobals::DATA_DEVICE_MANAGER => { RegistryGlobals::DATA_DEVICE_MANAGER => {
tracing::info!("Binding data device manager"); tracing::info!("Binding data device manager");
client.insert(new_id.object_id, DataDeviceManager); client.insert(new_id.object_id, DataDeviceManager)?;
} }
RegistryGlobals::OUTPUT => { RegistryGlobals::OUTPUT => {
tracing::info!("Binding output"); tracing::info!("Binding output");
@@ -177,7 +192,7 @@ impl WlRegistry for Registry {
id: new_id.object_id, id: new_id.object_id,
version: new_id.version, version: new_id.version,
}, },
); )?;
let _ = client.display().output.set(output.clone()); let _ = client.display().output.set(output.clone());
output.advertise_outputs(client).await?; output.advertise_outputs(client).await?;
} }
@@ -185,22 +200,27 @@ impl WlRegistry for Registry {
tracing::info!("Binding dmabuf"); tracing::info!("Binding dmabuf");
let dmabuf = Dmabuf::new(client, new_id.object_id, new_id.version).await?; let dmabuf = Dmabuf::new(client, new_id.object_id, new_id.version).await?;
client.insert(new_id.object_id, dmabuf); client.insert(new_id.object_id, dmabuf)?;
} }
RegistryGlobals::WL_DRM => { RegistryGlobals::WL_DRM => {
tracing::info!("Binding wl_drm"); tracing::info!("Binding wl_drm");
let drm = MesaDrm::new(client, new_id.object_id, new_id.version).await?; let drm = MesaDrm::new(client, new_id.object_id, new_id.version).await?;
client.insert(new_id.object_id, drm); client.insert(new_id.object_id, drm)?;
} }
RegistryGlobals::PRESENTATION => { RegistryGlobals::PRESENTATION => {
tracing::info!("Binding wp_presentation"); tracing::info!("Binding wp_presentation");
client.insert(new_id.object_id, Presentation); client.insert(new_id.object_id, Presentation::new(new_id.object_id))?;
}
RegistryGlobals::VIEWPORTER => {
tracing::info!("Binding wp_viewporter");
client.insert(new_id.object_id, Viewporter::new(new_id.object_id))?;
} }
id => { id => {
tracing::error!(id, "Wayland: failed to bind to registry global"); tracing::error!(id, "Wayland: failed to bind to registry global");
return Err(Error::MissingObject(unsafe { ObjectId::from_raw(name) })); return Err(WaylandError::UnknownGlobal(name));
} }
} }

View File

@@ -1,22 +1,16 @@
#![allow(unused)] #![allow(unused)]
use super::{Message, MessageSink, display::Display}; use super::{Message, MessageSink, display::Display};
use crate::wayland::{Client, WaylandError, WaylandResult};
use std::{fmt::Debug, sync::Arc}; use std::{fmt::Debug, sync::Arc};
use waynest::{ use waynest::ObjectId;
server::{Client, Result, protocol::core::wayland::wl_display::WlDisplay}, use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
wire::ObjectId, use waynest_server::{Client as _, RequestDispatcher};
};
pub trait ClientExt { pub trait ClientExt {
fn message_sink(&self) -> MessageSink; fn message_sink(&self) -> MessageSink;
fn display(&self) -> Arc<Display>; fn display(&self) -> Arc<Display>;
async fn protocol_error( fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>>;
&mut self,
sender_id: ObjectId,
object_id: ObjectId,
code: u32,
message: String,
) -> Result<()>;
} }
impl ClientExt for Client { impl ClientExt for Client {
fn message_sink(&self) -> MessageSink { fn message_sink(&self) -> MessageSink {
@@ -30,19 +24,8 @@ impl ClientExt for Client {
self.get::<Display>(ObjectId::DISPLAY).unwrap() self.get::<Display>(ObjectId::DISPLAY).unwrap()
} }
async fn protocol_error( fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>> {
&mut self, self.get::<D>(id).ok_or(WaylandError::MissingObject(id))
sender_id: ObjectId,
object_id: ObjectId,
code: u32,
message: String,
) -> Result<()> {
self.display()
.error(self, sender_id, object_id, code, message)
.await?;
let _ = self.message_sink().send(Message::Disconnect);
Ok(())
} }
} }

96
src/wayland/viewporter.rs Normal file
View File

@@ -0,0 +1,96 @@
use crate::wayland::WaylandResult;
use waynest::Fixed;
use waynest::ObjectId;
pub use waynest_protocols::server::stable::viewporter::wp_viewport::*;
pub use waynest_protocols::server::stable::viewporter::wp_viewporter::*;
use waynest_server::Client as _;
// This is a barebones/stub no-op implementation of wp_viewporter to make xwayland apps work
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Viewporter {
id: ObjectId,
}
impl Viewporter {
pub fn new(id: ObjectId) -> Self {
Self { id }
}
}
impl WpViewporter for Viewporter {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(())
}
async fn get_viewport(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
id: ObjectId,
surface_id: ObjectId,
) -> WaylandResult<()> {
let viewport = Viewport::new(id, surface_id);
client.insert(id, viewport)?;
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Viewport {
id: ObjectId,
_surface_id: ObjectId,
}
impl Viewport {
pub fn new(id: ObjectId, surface_id: ObjectId) -> Self {
Self {
id,
_surface_id: surface_id,
}
}
}
impl WpViewport for Viewport {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(())
}
async fn set_source(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_x: Fixed,
_y: Fixed,
_width: Fixed,
_height: Fixed,
) -> WaylandResult<()> {
Ok(())
}
async fn set_destination(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
Ok(())
}
}

View File

@@ -1,12 +1,21 @@
use super::toplevel::Toplevel; use super::toplevel::Toplevel;
use crate::{ use crate::{
core::error::Result, core::{error::Result, task},
nodes::{ nodes::{
drawable::model::ModelPart, drawable::model::ModelPart,
items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo}, items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
},
},
wayland::{
Message,
core::{
seat::{Seat, SeatMessage},
surface::Surface,
},
}, },
wayland::{Message, core::surface::Surface},
}; };
use dashmap::DashMap;
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Weak; use std::sync::Weak;
@@ -14,13 +23,17 @@ use tracing;
#[derive(Debug)] #[derive(Debug)]
pub struct XdgBackend { pub struct XdgBackend {
seat: Weak<Seat>,
toplevel: Weak<Toplevel>, toplevel: Weak<Toplevel>,
children: DashMap<u64, (Weak<Surface>, ChildInfo)>,
} }
impl XdgBackend { impl XdgBackend {
pub fn new(toplevel: Arc<Toplevel>) -> Self { pub fn new(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>) -> Self {
Self { Self {
toplevel: Arc::downgrade(&toplevel), seat: Arc::downgrade(seat),
toplevel: Arc::downgrade(toplevel),
children: DashMap::new(),
} }
} }
@@ -32,17 +45,62 @@ impl XdgBackend {
.expect("Toplevel should always be valid while XdgBackend exists") .expect("Toplevel should always be valid while XdgBackend exists")
} }
fn surface_from_id(&self, id: SurfaceId) -> Option<Arc<Surface>> { pub fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
self.toplevel().wl_surface().panel_item.lock().upgrade()
}
fn surface_from_id(&self, id: &SurfaceId) -> Option<Arc<Surface>> {
match id { match id {
SurfaceId::Toplevel(_) => Some(self.toplevel().surface()), SurfaceId::Toplevel(_) => Some(self.toplevel().wl_surface().clone()),
SurfaceId::Child(_) => None, SurfaceId::Child(id) => self.children.get(id).as_deref().and_then(|c| c.0.upgrade()),
} }
} }
}
pub fn add_child(&self, surface: &Arc<Surface>, info: ChildInfo) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get().cloned() else {
return;
};
self.children
.insert(id, (Arc::downgrade(surface), info.clone()));
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in add_child");
return;
};
panel_item.create_child(id, &info);
}
pub fn reposition_child(&self, surface: &Arc<Surface>, geometry: Geometry) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
return;
};
if let Some(mut child) = self.children.get_mut(id) {
child.1.geometry = geometry;
}
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in reposition_child");
return;
};
panel_item.reposition_child(*id, &geometry);
}
pub fn remove_child(&self, surface: &Surface) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
return;
};
self.children.remove(id);
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in remove_child");
return;
};
panel_item.destroy_child(*id);
}
}
impl Backend for XdgBackend { impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> { fn start_data(&self) -> Result<PanelItemInitData> {
let surface_state = self.toplevel().surface().current_state(); let surface_state = self.toplevel().wl_surface().current_state();
let size = surface_state let size = surface_state
.buffer .buffer
@@ -74,9 +132,20 @@ impl Backend for XdgBackend {
}) })
} }
fn apply_cursor_material(&self, _model_part: &Arc<ModelPart>) {} fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) {
let model_part = model_part.clone();
let Some(seat) = self.seat.upgrade() else {
return;
};
let _ = task::new(|| "Apply cursor material", async move {
let Some(cursor) = seat.cursor_surface().await else {
return;
};
cursor.apply_material(&model_part);
});
}
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) { fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(&surface) {
surface.apply_material(model_part); surface.apply_material(model_part);
} }
} }
@@ -84,7 +153,7 @@ impl Backend for XdgBackend {
fn close_toplevel(&self) { fn close_toplevel(&self) {
let _ = self let _ = self
.toplevel() .toplevel()
.surface() .wl_surface()
.message_sink .message_sink
.send(Message::CloseToplevel(self.toplevel().clone())); .send(Message::CloseToplevel(self.toplevel().clone()));
} }
@@ -92,7 +161,7 @@ impl Backend for XdgBackend {
fn auto_size_toplevel(&self) { fn auto_size_toplevel(&self) {
let _ = self let _ = self
.toplevel() .toplevel()
.surface() .wl_surface()
.message_sink .message_sink
.send(Message::ResizeToplevel { .send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -103,7 +172,7 @@ impl Backend for XdgBackend {
fn set_toplevel_size(&self, size: Vector2<u32>) { fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ = self let _ = self
.toplevel() .toplevel()
.surface() .wl_surface()
.message_sink .message_sink
.send(Message::ResizeToplevel { .send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -114,7 +183,7 @@ impl Backend for XdgBackend {
fn set_toplevel_focused_visuals(&self, focused: bool) { fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self let _ = self
.toplevel() .toplevel()
.surface() .wl_surface()
.message_sink .message_sink
.send(Message::SetToplevelVisualActive { .send(Message::SetToplevelVisualActive {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -123,22 +192,29 @@ impl Backend for XdgBackend {
} }
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) { fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
if let Some(surface) = self.surface_from_id(surface.clone()) { if let Some(surface) = self.surface_from_id(surface) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat( let _ = self
crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position }, .toplevel()
)); .wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerMotion {
surface,
position,
}));
} }
} }
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) { fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) {
if let Some(surface) = self.surface_from_id(surface.clone()) { if let Some(surface) = self.surface_from_id(surface) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat( let _ = self
crate::wayland::core::seat::SeatMessage::PointerButton { .toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerButton {
surface, surface,
button, button,
pressed, pressed,
}, }));
));
} }
} }
@@ -148,14 +224,16 @@ impl Backend for XdgBackend {
scroll_distance: Option<Vector2<f32>>, scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>, scroll_steps: Option<Vector2<f32>>,
) { ) {
if let Some(surface) = self.surface_from_id(surface.clone()) { if let Some(surface) = self.surface_from_id(surface) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat( let _ = self
crate::wayland::core::seat::SeatMessage::PointerScroll { .toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerScroll {
surface, surface,
scroll_distance, scroll_distance,
scroll_steps, scroll_steps,
}, }));
));
} }
} }
@@ -165,15 +243,17 @@ impl Backend for XdgBackend {
key, key,
if pressed { "pressed" } else { "released" } if pressed { "pressed" } else { "released" }
); );
if let Some(surface) = self.surface_from_id(surface.clone()) { if let Some(surface) = self.surface_from_id(surface) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat( let _ = self
crate::wayland::core::seat::SeatMessage::KeyboardKey { .toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::KeyboardKey {
surface, surface,
keymap_id, keymap_id,
key, key,
pressed, pressed,
}, }));
));
} }
} }
@@ -184,14 +264,16 @@ impl Backend for XdgBackend {
position.x, position.x,
position.y position.y
); );
if let Some(surface) = self.surface_from_id(surface.clone()) { if let Some(surface) = self.surface_from_id(surface) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat( let _ = self
crate::wayland::core::seat::SeatMessage::TouchDown { .toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::TouchDown {
surface, surface,
id, id,
position, position,
}, }));
));
} }
} }
@@ -202,25 +284,28 @@ impl Backend for XdgBackend {
position.x, position.x,
position.y position.y
); );
let surface = self.toplevel().surface(); let toplevel = self.toplevel();
let _ = surface.message_sink.send(Message::Seat( let _ = toplevel
crate::wayland::core::seat::SeatMessage::TouchMove { id, position }, .wl_surface()
)); .message_sink
.send(Message::Seat(SeatMessage::TouchMove { id, position }));
} }
fn touch_up(&self, id: u32) { fn touch_up(&self, id: u32) {
tracing::debug!("Backend: Touch up {}", id); tracing::debug!("Backend: Touch up {}", id);
let surface = self.toplevel().surface(); let toplevel = self.toplevel();
let _ = surface.message_sink.send(Message::Seat( let _ = toplevel
crate::wayland::core::seat::SeatMessage::TouchUp { id }, .wl_surface()
)); .message_sink
.send(Message::Seat(SeatMessage::TouchUp { id }));
} }
fn reset_input(&self) { fn reset_input(&self) {
tracing::debug!("Backend: Reset input"); tracing::debug!("Backend: Reset input");
let surface = self.toplevel().surface(); let toplevel = self.toplevel();
let _ = surface.message_sink.send(Message::Seat( let _ = toplevel
crate::wayland::core::seat::SeatMessage::Reset, .wl_surface()
)); .message_sink
.send(Message::Seat(SeatMessage::Reset));
} }
} }

View File

@@ -1,78 +1,62 @@
use super::{ use super::{
backend::XdgBackend,
positioner::{Positioner, PositionerData}, positioner::{Positioner, PositionerData},
surface::Surface, surface::Surface,
}; };
use crate::{ use crate::nodes::items::panel::SurfaceId;
nodes::items::panel::{Geometry, PanelItem, SurfaceId}, use crate::wayland::WaylandResult;
wayland::util::DoubleBuffer,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::Rng; use rand::Rng;
use std::{ use std::sync::Arc;
sync::{Arc, Weak, atomic::AtomicBool}, use waynest::ObjectId;
u64, use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
}; use waynest_server::Client as _;
use waynest::{
server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup},
wire::ObjectId,
};
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Popup { pub struct Popup {
id: ObjectId,
version: u32, version: u32,
surface_id: SurfaceId, pub surface: Arc<Surface>,
parent: Arc<Surface>,
surface: Weak<Surface>,
pub panel_item: Weak<PanelItem<XdgBackend>>,
positioner_data: Mutex<PositionerData>, positioner_data: Mutex<PositionerData>,
geometry: DoubleBuffer<Geometry>, id: ObjectId,
mapped: AtomicBool,
} }
impl Popup { impl Popup {
pub fn new( pub fn new(version: u32, surface: Arc<Surface>, positioner: &Positioner, id: ObjectId) -> Self {
id: ObjectId, let _ = surface
version: u32, .wl_surface
parent: Arc<Surface>, .surface_id
panel_item: &Arc<PanelItem<XdgBackend>>, .set(SurfaceId::Child(rand::rng().random()));
xdg_surface: &Arc<Surface>,
positioner: &Positioner,
) -> Self {
let positioner_data = positioner.data(); let positioner_data = positioner.data();
Self { Self {
id,
version, version,
surface_id: SurfaceId::Child(rand::thread_rng().gen_range(0..u64::MAX)), surface,
parent,
surface: Arc::downgrade(xdg_surface),
panel_item: Arc::downgrade(panel_item),
positioner_data: Mutex::new(positioner_data), positioner_data: Mutex::new(positioner_data),
geometry: DoubleBuffer::new(positioner_data.infinite_geometry()), id,
mapped: AtomicBool::new(false),
} }
} }
} }
impl XdgPopup for Popup { impl XdgPopup for Popup {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab
async fn grab( async fn grab(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition
async fn reposition( async fn reposition(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
positioner: ObjectId, positioner: ObjectId,
token: u32, token: u32,
) -> Result<()> { ) -> WaylandResult<()> {
let positioner = client.get::<Positioner>(positioner).unwrap(); let positioner = client.get::<Positioner>(positioner).unwrap();
let positioner_data = positioner.data(); let positioner_data = positioner.data();
*self.positioner_data.lock() = positioner_data; *self.positioner_data.lock() = positioner_data;
@@ -89,12 +73,32 @@ impl XdgPopup for Popup {
geometry.size.y as i32, geometry.size.y as i32,
) )
.await?; .await?;
self.surface.upgrade().unwrap().reconfigure(client).await?; self.surface.reconfigure(client).await?;
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else {
return Ok(());
};
panel_item
.backend
.reposition_child(&self.surface.wl_surface, geometry);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }
impl Drop for Popup {
fn drop(&mut self) {
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else {
return;
};
panel_item.backend.remove_child(&self.surface.wl_surface);
}
}

View File

@@ -1,15 +1,9 @@
use crate::nodes::items::panel::Geometry; use crate::{nodes::items::panel::Geometry, wayland::WaylandResult};
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use waynest::{ use waynest::ObjectId;
server::{ use waynest_protocols::server::stable::xdg_shell::xdg_positioner::*;
Client, Dispatcher, Result, use waynest_server::Client as _;
protocol::stable::xdg_shell::xdg_positioner::{
Anchor, ConstraintAdjustment, Gravity, XdgPositioner,
},
},
wire::ObjectId,
};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PositionerData { pub struct PositionerData {
@@ -17,11 +11,37 @@ pub struct PositionerData {
pub anchor_rect: Geometry, pub anchor_rect: Geometry,
pub offset: Vector2<i32>, pub offset: Vector2<i32>,
pub anchor: Anchor, pub anchor: Anchor,
pub gravity: Gravity,
pub constraint_adjustment: ConstraintAdjustment, pub constraint_adjustment: ConstraintAdjustment,
pub reactive: bool, pub reactive: bool,
pub parent_size: Vector2<u32>, pub parent_size: Vector2<u32>,
} }
impl PositionerData { impl PositionerData {
fn gravity_has_edge(&self, edge: Gravity) -> bool {
match edge {
Gravity::Top => {
self.gravity == Gravity::Top
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::TopRight
}
Gravity::Bottom => {
self.gravity == Gravity::Bottom
|| self.gravity == Gravity::BottomLeft
|| self.gravity == Gravity::BottomRight
}
Gravity::Left => {
self.gravity == Gravity::Left
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::BottomLeft
}
Gravity::Right => {
self.gravity == Gravity::Right
|| self.gravity == Gravity::TopRight
|| self.gravity == Gravity::BottomRight
}
_ => unreachable!(),
}
}
pub fn infinite_geometry(&self) -> Geometry { pub fn infinite_geometry(&self) -> Geometry {
let anchor_point = match self.anchor { let anchor_point = match self.anchor {
Anchor::TopLeft => self.anchor_rect.origin, Anchor::TopLeft => self.anchor_rect.origin,
@@ -67,30 +87,29 @@ impl PositionerData {
.into(), .into(),
}; };
let mut position = anchor_point; let mut geometry = Geometry {
origin: [
// Apply gravity anchor_point.x + self.offset.x,
if self anchor_point.y + self.offset.y,
.constraint_adjustment ]
.contains(ConstraintAdjustment::FlipX) .into(),
{
position.x -= self.size.x as i32;
}
if self
.constraint_adjustment
.contains(ConstraintAdjustment::FlipY)
{
position.y -= self.size.y as i32;
}
// Apply offset
position.x += self.offset.x;
position.y += self.offset.y;
Geometry {
origin: position,
size: self.size, size: self.size,
};
// apply gravity
if self.gravity_has_edge(Gravity::Top) {
geometry.origin.y -= geometry.size.y as i32;
} else if !self.gravity_has_edge(Gravity::Bottom) {
geometry.origin.y -= (geometry.size.y / 2) as i32;
} }
if self.gravity_has_edge(Gravity::Left) {
geometry.origin.x -= geometry.size.x as i32;
} else if !self.gravity_has_edge(Gravity::Right) {
geometry.origin.x -= (geometry.size.x / 2) as i32;
}
geometry
} }
} }
impl Default for PositionerData { impl Default for PositionerData {
@@ -100,6 +119,7 @@ impl Default for PositionerData {
anchor_rect: Default::default(), anchor_rect: Default::default(),
offset: [0, 0].into(), offset: [0, 0].into(),
anchor: Anchor::TopLeft, anchor: Anchor::TopLeft,
gravity: Gravity::TopLeft,
constraint_adjustment: ConstraintAdjustment::empty(), constraint_adjustment: ConstraintAdjustment::empty(),
reactive: false, reactive: false,
parent_size: [0; 2].into(), parent_size: [0; 2].into(),
@@ -107,14 +127,17 @@ impl Default for PositionerData {
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Positioner { pub struct Positioner {
data: Mutex<PositionerData>, data: Mutex<PositionerData>,
id: ObjectId,
} }
impl Default for Positioner { impl Positioner {
fn default() -> Self { pub fn new(id: ObjectId) -> Self {
Self { Self {
data: Mutex::new(PositionerData::default()), data: Mutex::new(PositionerData::default()),
id,
} }
} }
} }
@@ -124,13 +147,15 @@ impl Positioner {
} }
} }
impl XdgPositioner for Positioner { impl XdgPositioner for Positioner {
type Connection = crate::wayland::Client;
async fn set_size( async fn set_size(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.size = [_width.max(0) as u32, _height.max(0) as u32].into(); data.size = [_width.max(0) as u32, _height.max(0) as u32].into();
data.reactive = true; data.reactive = true;
@@ -139,13 +164,13 @@ impl XdgPositioner for Positioner {
async fn set_anchor_rect( async fn set_anchor_rect(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.anchor_rect.origin = [_x, _y].into(); data.anchor_rect.origin = [_x, _y].into();
data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].into(); data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].into();
@@ -155,10 +180,10 @@ impl XdgPositioner for Positioner {
async fn set_anchor( async fn set_anchor(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_anchor: Anchor, _anchor: Anchor,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.anchor = _anchor; data.anchor = _anchor;
Ok(()) Ok(())
@@ -166,19 +191,21 @@ impl XdgPositioner for Positioner {
async fn set_gravity( async fn set_gravity(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_gravity: Gravity, gravity: Gravity,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock();
data.gravity = gravity;
Ok(()) Ok(())
} }
async fn set_constraint_adjustment( async fn set_constraint_adjustment(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_constraint_adjustment: ConstraintAdjustment, _constraint_adjustment: ConstraintAdjustment,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.constraint_adjustment = _constraint_adjustment; data.constraint_adjustment = _constraint_adjustment;
Ok(()) Ok(())
@@ -186,28 +213,32 @@ impl XdgPositioner for Positioner {
async fn set_offset( async fn set_offset(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.offset.x += _x; data.offset.x += _x;
data.offset.y += _y; data.offset.y += _y;
Ok(()) Ok(())
} }
async fn set_reactive(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn set_reactive(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_parent_size( async fn set_parent_size(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_parent_width: i32, _parent_width: i32,
_parent_height: i32, _parent_height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.parent_size.x = _parent_width.max(0) as u32; data.parent_size.x = _parent_width.max(0) as u32;
data.parent_size.y = _parent_height.max(0) as u32; data.parent_size.y = _parent_height.max(0) as u32;
@@ -216,14 +247,19 @@ impl XdgPositioner for Positioner {
async fn set_parent_configure( async fn set_parent_configure(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }

View File

@@ -1,18 +1,22 @@
use super::{popup::Popup, positioner::Positioner, toplevel::Mapped}; use super::{popup::Popup, positioner::Positioner, toplevel::MappedInner};
use crate::wayland::{core::surface::SurfaceRole, display::Display, xdg::toplevel::Toplevel}; use crate::nodes::items::panel::{ChildInfo, SurfaceId};
use std::sync::Arc; use crate::wayland::{Client, WaylandError};
use std::sync::Weak; use crate::wayland::{
pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*; Message, WaylandResult, core::surface::SurfaceRole, display::Display, util::ClientExt,
use waynest::{ xdg::toplevel::Toplevel,
server::{Client, Dispatcher, Result},
wire::ObjectId,
}; };
use std::sync::Arc;
use waynest::ObjectId;
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
pub use waynest_protocols::server::stable::xdg_shell::xdg_surface::*;
use waynest_server::Client as _;
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Surface { pub struct Surface {
id: ObjectId, id: ObjectId,
version: u32, version: u32,
wl_surface: Weak<crate::wayland::core::surface::Surface>, pub wl_surface: Arc<crate::wayland::core::surface::Surface>,
configured: Arc<std::sync::atomic::AtomicBool>, configured: Arc<std::sync::atomic::AtomicBool>,
} }
impl Surface { impl Surface {
@@ -24,84 +28,75 @@ impl Surface {
Self { Self {
id, id,
version, version,
wl_surface: Arc::downgrade(&wl_surface), wl_surface,
configured: Arc::new(std::sync::atomic::AtomicBool::new(false)), configured: Arc::new(std::sync::atomic::AtomicBool::new(false)),
} }
} }
pub fn wl_surface(&self) -> Arc<crate::wayland::core::surface::Surface> { pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
// We can safely unwrap as the surface must exist for the lifetime of the xdg_surface
self.wl_surface
.upgrade()
.expect("Surface was dropped before xdg_surface")
}
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
self.configure(client, self.id, serial).await self.configure(client, self.id, serial).await
} }
} }
impl XdgSurface for Surface { impl XdgSurface for Surface {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel
async fn get_toplevel( async fn get_toplevel(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
toplevel_id: ObjectId, toplevel_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let surface = self.wl_surface();
let toplevel = client.insert( let toplevel = client.insert(
toplevel_id, toplevel_id,
Toplevel::new( Toplevel::new(
toplevel_id, toplevel_id,
surface.clone(), self.wl_surface.clone(),
client.get::<Self>(sender_id).unwrap(), client.get::<Self>(sender_id).unwrap(),
), ),
); )?;
{ self.wl_surface
let mut surface_role = surface.role.lock(); .try_set_role(SurfaceRole::XdgToplevel, Error::AlreadyConstructed)
.await?;
// A surface must not have any existing role when assigning a new one let toplevel_weak = Arc::downgrade(&toplevel);
// "A surface must not have more than one role, and a role must not be assigned to more than one let display = client.get::<Display>(ObjectId::DISPLAY).unwrap();
// surface at a time. However, wl_surface role-specific interfaces may reassign the role, allow let seat = Arc::downgrade(display.seat.get().unwrap());
// a role to be destroyed, or allow multiple role-specific interfaces to share the same role." let pid = display.pid;
// - xdg_surface protocol doc
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XdgToplevel(toplevel.clone()));
}
}
toplevel.reconfigure(client).await?;
let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
let configured = self.configured.clone(); let configured = self.configured.clone();
surface.add_commit_handler(move |surface, state| { let mut first_commit = true;
let Some(SurfaceRole::XdgToplevel(toplevel)) = &mut *surface.role.lock() else { let message_tx = client.message_sink().clone();
self.wl_surface.add_commit_handler(move |surface, state| {
let Some(toplevel) = toplevel_weak.upgrade() else {
return true; return true;
}; };
// Only proceed if configured and has valid buffer if first_commit {
let has_valid_buffer = state let _ = message_tx.send(Message::ReconfigureToplevel(toplevel.clone()));
.buffer first_commit = false;
.as_ref() }
.is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0);
let mut mapped_lock = toplevel.mapped.lock(); let mut mapped_lock = toplevel.mapped.lock();
if mapped_lock.is_none() if mapped_lock.is_none()
&& configured.load(std::sync::atomic::Ordering::SeqCst) && configured.load(std::sync::atomic::Ordering::SeqCst)
&& has_valid_buffer && state.has_valid_buffer()
{ {
mapped_lock.replace(Mapped::create(toplevel.clone(), pid)); let mapped_inner = MappedInner::create(&seat.upgrade().unwrap(), &toplevel, pid);
*surface.panel_item.lock() = Arc::downgrade(&mapped_inner.panel_item);
mapped_lock.replace(mapped_inner);
return false; return false;
} }
true true
@@ -113,74 +108,88 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_popup /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_popup
async fn get_popup( async fn get_popup(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
sender_id: ObjectId, sender_id: ObjectId,
popup_id: ObjectId, popup_id: ObjectId,
parent: Option<ObjectId>, parent: Option<ObjectId>,
positioner: ObjectId, positioner: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let parent = client.get::<Surface>(parent.unwrap()).unwrap(); self.wl_surface
let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() { .try_set_role(SurfaceRole::XdgPopup, Error::AlreadyConstructed)
SurfaceRole::XdgToplevel(toplevel) => { .await?;
let toplevel_lock = toplevel.mapped.lock();
toplevel_lock.as_ref().unwrap()._panel_item.clone() let Some(parent) = parent else {
} return Err(WaylandError::Fatal {
SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(), object_id: popup_id,
code: 3,
message: "Parent surface does not have an XDG role",
});
}; };
let Some(parent) = client.get::<Surface>(parent) else {
return Err(WaylandError::Fatal {
object_id: popup_id,
code: 3,
message: "Parent surface does not exist",
});
};
*self.wl_surface.panel_item.lock() = parent.wl_surface.panel_item.lock().clone();
let positioner = client.get::<Positioner>(positioner).unwrap(); let positioner = client.get::<Positioner>(positioner).unwrap();
let surface = client.get::<Surface>(self.id).unwrap(); let surface = client.get::<Surface>(self.id).unwrap();
let popup = client.insert( let popup = client.insert(
popup_id, popup_id,
Popup::new( Popup::new(self.version, surface, &positioner, popup_id),
)?;
let positioner_geometry = positioner.data().infinite_geometry();
popup
.configure(
client,
popup_id, popup_id,
self.version, positioner_geometry.origin.x,
parent, positioner_geometry.origin.y,
&panel_item, positioner_geometry.size.x as i32,
&surface, positioner_geometry.size.y as i32,
&positioner, )
), .await?;
);
{
let wl_surface = self.wl_surface();
let mut surface_role = wl_surface.role.lock();
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XDGPopup(popup.clone()));
}
}
let serial = client.next_event_serial(); let serial = client.next_event_serial();
self.configure(client, sender_id, serial).await?; self.configure(client, sender_id, serial).await?;
// let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid; let Some(SurfaceId::Child(id)) = self.wl_surface.surface_id.get() else {
// let configured = self.configured.clone(); return Ok(());
// surface.add_commit_handler(move |surface, state| { };
// let Some(SurfaceRole::XDGPopup(popup)) = &mut *surface.role.lock() else { let Some(parent_id) = parent.wl_surface.surface_id.get() else {
// return true; return Ok(());
// }; };
// // Only proceed if configured and has valid buffer let child_info = ChildInfo {
// let has_valid_buffer = state id: *id,
// .buffer parent: parent_id.clone(),
// .as_ref() geometry: positioner.data().infinite_geometry(),
// .is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0); z_order: 1,
receives_input: true,
};
// let mut mapped_lock = popup.mapped.lock(); let popup_weak = Arc::downgrade(&popup);
// if mapped_lock.is_none() let configured = self.configured.clone();
// && configured.load(std::sync::atomic::Ordering::SeqCst) self.wl_surface.add_commit_handler(move |surface, state| {
// && has_valid_buffer let Some(popup) = popup_weak.upgrade() else {
// { return true;
// mapped_lock.replace(Mapped::create(popup.clone(), pid)); };
// return false; let Some(panel_item) = surface.panel_item.lock().upgrade() else {
// } return true;
// true };
// });
if configured.load(std::sync::atomic::Ordering::SeqCst) && state.has_valid_buffer() {
panel_item
.backend
.add_child(&popup.surface.wl_surface, child_info.clone());
return false;
}
true
});
Ok(()) Ok(())
} }
@@ -188,13 +197,13 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:set_window_geometry /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:set_window_geometry
async fn set_window_geometry( async fn set_window_geometry(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
// we're gonna delegate literally all the window management // we're gonna delegate literally all the window management
// to 3D stuff sooo we don't care, maximized is the floating state // to 3D stuff sooo we don't care, maximized is the floating state
Ok(()) Ok(())
@@ -203,10 +212,10 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure
async fn ack_configure( async fn ack_configure(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
self.configured self.configured
.store(true, std::sync::atomic::Ordering::SeqCst); .store(true, std::sync::atomic::Ordering::SeqCst);
Ok(()) Ok(())

View File

@@ -1,31 +1,34 @@
use super::backend::XdgBackend; use super::backend::XdgBackend;
use crate::{ use crate::{
nodes::{Node, items::panel::PanelItem}, nodes::{
wayland::core::surface::Surface, Node,
items::panel::{PanelItem, SurfaceId},
},
wayland::{
Client, WaylandResult,
core::{seat::Seat, surface::Surface},
},
}; };
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Weak; use waynest::ObjectId;
pub use waynest::server::protocol::stable::xdg_shell::xdg_toplevel::*; pub use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::*;
use waynest::{ use waynest_server::Client as _;
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug)] #[derive(Debug)]
pub struct Mapped { pub struct MappedInner {
pub panel_item_node: Arc<Node>, pub panel_item_node: Arc<Node>,
pub _panel_item: Arc<PanelItem<XdgBackend>>, pub panel_item: Arc<PanelItem<XdgBackend>>,
} }
impl Mapped { impl MappedInner {
pub fn create(toplevel: Arc<Toplevel>, pid: Option<i32>) -> Self { pub fn create(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>, pid: Option<i32>) -> Self {
let (panel_item_node, _panel_item) = let (panel_item_node, panel_item) =
PanelItem::create(Box::new(XdgBackend::new(toplevel)), pid); PanelItem::create(Box::new(XdgBackend::new(seat, toplevel)), pid);
Self { Self {
panel_item_node, panel_item_node,
_panel_item, panel_item,
} }
} }
} }
@@ -52,12 +55,12 @@ impl Default for ToplevelData {
} }
} }
#[derive(Debug, Dispatcher)] #[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Toplevel { pub struct Toplevel {
pub id: ObjectId, pub id: ObjectId,
wl_surface: Weak<Surface>, xdg_surface: Arc<super::surface::Surface>,
xdg_surface: Weak<super::surface::Surface>, pub mapped: Mutex<Option<MappedInner>>,
pub mapped: Mutex<Option<Mapped>>,
data: Mutex<ToplevelData>, data: Mutex<ToplevelData>,
} }
impl Toplevel { impl Toplevel {
@@ -66,20 +69,18 @@ impl Toplevel {
wl_surface: Arc<Surface>, wl_surface: Arc<Surface>,
xdg_surface: Arc<super::surface::Surface>, xdg_surface: Arc<super::surface::Surface>,
) -> Self { ) -> Self {
let _ = wl_surface.surface_id.set(SurfaceId::Toplevel(()));
Toplevel { Toplevel {
id: object_id, id: object_id,
wl_surface: Arc::downgrade(&wl_surface), xdg_surface,
xdg_surface: Arc::downgrade(&xdg_surface),
mapped: Mutex::new(None), mapped: Mutex::new(None),
data: Mutex::new(ToplevelData::default()), data: Mutex::new(ToplevelData::default()),
} }
} }
pub fn surface(&self) -> Arc<Surface> { pub fn wl_surface(&self) -> &Arc<Surface> {
// We can safely unwrap as the surface must exist for the lifetime of the toplevel &self.xdg_surface.wl_surface
self.wl_surface
.upgrade()
.expect("Surface was dropped before toplevel")
} }
pub fn title(&self) -> Option<String> { pub fn title(&self) -> Option<String> {
@@ -102,7 +103,7 @@ impl Toplevel {
// Helper to clamp size against constraints // Helper to clamp size against constraints
fn clamp_size(&self, size: Vector2<u32>) -> Vector2<u32> { fn clamp_size(&self, size: Vector2<u32>) -> Vector2<u32> {
let state = self.surface().current_state(); let state = self.wl_surface().current_state();
let mut clamped = size; let mut clamped = size;
if let Some(min_size) = state.min_size { if let Some(min_size) = state.min_size {
@@ -116,7 +117,7 @@ impl Toplevel {
clamped clamped
} }
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> { pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
let data = self.data.lock().clone(); let data = self.data.lock().clone();
// Use the explicitly set size, applying constraints // Use the explicitly set size, applying constraints
@@ -148,20 +149,19 @@ impl Toplevel {
.collect(), .collect(),
) )
.await?; .await?;
self.xdg_surface self.xdg_surface.reconfigure(client).await?;
.upgrade() Ok(())
.unwrap()
.reconfigure(client)
.await
} }
} }
impl XdgToplevel for Toplevel { impl XdgToplevel for Toplevel {
type Connection = crate::wayland::Client;
async fn set_parent( async fn set_parent(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
parent: Option<ObjectId>, parent: Option<ObjectId>,
) -> Result<()> { ) -> WaylandResult<()> {
// Handle case where parent is specified // Handle case where parent is specified
if let Some(parent) = parent { if let Some(parent) = parent {
// Per spec: parent must be another xdg_toplevel surface // Per spec: parent must be another xdg_toplevel surface
@@ -192,65 +192,65 @@ impl XdgToplevel for Toplevel {
async fn set_title( async fn set_title(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
title: String, title: String,
) -> Result<()> { ) -> WaylandResult<()> {
self.data.lock().title.replace(title); self.data.lock().title.replace(title);
Ok(()) Ok(())
} }
async fn set_app_id( async fn set_app_id(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
app_id: String, app_id: String,
) -> Result<()> { ) -> WaylandResult<()> {
self.data.lock().app_id.replace(app_id); self.data.lock().app_id.replace(app_id);
Ok(()) Ok(())
} }
async fn show_window_menu( async fn show_window_menu(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
_serial: u32, _serial: u32,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn r#move( async fn r#move(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
_serial: u32, _serial: u32,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn resize( async fn resize(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
_serial: u32, _serial: u32,
_edges: ResizeEdge, _edges: ResizeEdge,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_max_size( async fn set_max_size(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
width: i32, width: i32,
height: i32, height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
self.surface().pending_state().pending.max_size = if width == 0 && height == 0 { self.wl_surface().state_lock().pending.max_size = if width == 0 && height == 0 {
None None
} else { } else {
Some([width as u32, height as u32].into()) Some([width as u32, height as u32].into())
@@ -260,12 +260,12 @@ impl XdgToplevel for Toplevel {
async fn set_min_size( async fn set_min_size(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
width: i32, width: i32,
height: i32, height: i32,
) -> Result<()> { ) -> WaylandResult<()> {
self.surface().pending_state().pending.min_size = if width == 0 && height == 0 { self.xdg_surface.wl_surface.state_lock().pending.min_size = if width == 0 && height == 0 {
None None
} else { } else {
Some([width as u32, height as u32].into()) Some([width as u32, height as u32].into())
@@ -273,32 +273,53 @@ impl XdgToplevel for Toplevel {
Ok(()) Ok(())
} }
async fn set_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn set_maximized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn unset_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn unset_maximized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_fullscreen( async fn set_fullscreen(
&self, &self,
_client: &mut Client, _client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
_output: Option<ObjectId>, _output: Option<ObjectId>,
) -> Result<()> { ) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn unset_fullscreen(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn unset_fullscreen(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_minimized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn set_minimized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
self.mapped.lock().take(); self.mapped.lock().take();
Ok(()) Ok(())
} }

View File

@@ -1,50 +1,73 @@
use crate::wayland::xdg::surface::Surface;
pub use waynest::server::protocol::stable::xdg_shell::xdg_wm_base::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
use super::positioner::Positioner; use super::positioner::Positioner;
use crate::wayland::{WaylandError, WaylandResult, util::ClientExt, xdg::surface::Surface};
#[derive(Debug, Dispatcher, Default)] use waynest::ObjectId;
pub use waynest_protocols::server::stable::xdg_shell::xdg_wm_base::*;
use waynest_server::Client as _;
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct WmBase { pub struct WmBase {
pub version: u32, version: u32,
id: ObjectId,
}
impl WmBase {
pub fn new(id: ObjectId, version: u32) -> Self {
Self { version, id }
}
} }
impl XdgWmBase for WmBase { impl XdgWmBase for WmBase {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
async fn create_positioner( async fn create_positioner(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
client.insert(id, Positioner::default()); client.insert(id, Positioner::new(id))?;
Ok(()) Ok(())
} }
async fn get_xdg_surface( async fn get_xdg_surface(
&self, &self,
client: &mut Client, client: &mut Self::Connection,
_sender_id: ObjectId, _sender_id: ObjectId,
xdg_surface_id: ObjectId, xdg_surface_id: ObjectId,
wl_surface_id: ObjectId, wl_surface_id: ObjectId,
) -> Result<()> { ) -> WaylandResult<()> {
let wl_surface = client let wl_surface = client.try_get::<crate::wayland::core::surface::Surface>(wl_surface_id)?;
.get::<crate::wayland::core::surface::Surface>(wl_surface_id) match wl_surface.role.get() {
.ok_or(waynest::server::Error::Custom( None => (),
"can't get wayland surface id".to_string(), Some(_) => {
))?; return Err(WaylandError::Fatal {
object_id: wl_surface_id,
code: Error::Role as u32,
message: "Wayland surface has role",
});
}
};
let xdg_surface = Surface::new(xdg_surface_id, self.version, wl_surface); let xdg_surface = Surface::new(xdg_surface_id, self.version, wl_surface);
client.insert(xdg_surface_id, xdg_surface); client.insert(xdg_surface_id, xdg_surface)?;
Ok(()) Ok(())
} }
async fn pong(&self, _client: &mut Client, _sender_id: ObjectId, _serial: u32) -> Result<()> { async fn pong(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }