From f4d08dac9cee5ce252c90cfcfb4e9191f025ed32 Mon Sep 17 00:00:00 2001 From: Nova Date: Sat, 5 Jul 2025 19:51:40 -0700 Subject: [PATCH] feat: wayland --- .github/workflows/build.yml | 8 +- Cargo.lock | 377 +++++------------ Cargo.toml | 40 +- src/core/error.rs | 3 + src/core/graphics_info.rs | 7 + src/core/mod.rs | 1 + src/main.rs | 12 +- src/nodes/drawable/holdout.wgsl | 5 + src/nodes/drawable/model.rs | 125 +++--- src/nodes/items/panel.rs | 10 +- src/objects/input/mouse_pointer.rs | 3 +- src/session.rs | 16 +- src/wayland/compositor.rs | 152 ------- src/wayland/core/buffer.rs | 89 ++++ src/wayland/core/callback.rs | 10 + src/wayland/core/compositor.rs | 70 ++++ src/wayland/core/display.rs | 75 ++++ src/wayland/core/keyboard.rs | 259 ++++++++++++ src/wayland/core/mod.rs | 14 + src/wayland/core/output.rs | 16 + src/wayland/core/pointer.rs | 183 +++++++++ src/wayland/core/registry.rs | 148 +++++++ src/wayland/core/seat.rs | 191 +++++++++ src/wayland/core/shm.rs | 39 ++ src/wayland/core/shm_buffer_backing.rs | 102 +++++ src/wayland/core/shm_pool.rs | 74 ++++ src/wayland/core/surface.rs | 367 +++++++++++++++++ src/wayland/core/touch.rs | 75 ++++ src/wayland/data_device.rs | 100 ----- src/wayland/decoration.rs | 97 ----- src/wayland/dmabuf/buffer_backing.rs | 200 +++++++++ src/wayland/dmabuf/buffer_params.rs | 163 ++++++++ src/wayland/dmabuf/feedback.rs | 92 +++++ src/wayland/dmabuf/mod.rs | 108 +++++ src/wayland/drm.rs | 144 ------- src/wayland/mod.rs | 443 ++++++++++++-------- src/wayland/seat.rs | 313 -------------- src/wayland/state.rs | 230 ----------- src/wayland/surface.rs | 202 --------- src/wayland/util.rs | 42 ++ src/wayland/utils.rs | 121 ------ src/wayland/wayland-drm.xml | 189 --------- src/wayland/xdg/backend.rs | 246 +++++++++++ src/wayland/xdg/mod.rs | 6 + src/wayland/xdg/popup.rs | 89 ++++ src/wayland/xdg/positioner.rs | 229 +++++++++++ src/wayland/xdg/surface.rs | 206 ++++++++++ src/wayland/xdg/toplevel.rs | 310 ++++++++++++++ src/wayland/xdg/wm_base.rs | 48 +++ src/wayland/xdg_activation.rs | 34 -- src/wayland/xdg_shell.rs | 549 ------------------------- 51 files changed, 3941 insertions(+), 2691 deletions(-) create mode 100644 src/core/graphics_info.rs create mode 100644 src/nodes/drawable/holdout.wgsl delete mode 100644 src/wayland/compositor.rs create mode 100644 src/wayland/core/buffer.rs create mode 100644 src/wayland/core/callback.rs create mode 100644 src/wayland/core/compositor.rs create mode 100644 src/wayland/core/display.rs create mode 100644 src/wayland/core/keyboard.rs create mode 100644 src/wayland/core/mod.rs create mode 100644 src/wayland/core/output.rs create mode 100644 src/wayland/core/pointer.rs create mode 100644 src/wayland/core/registry.rs create mode 100644 src/wayland/core/seat.rs create mode 100644 src/wayland/core/shm.rs create mode 100644 src/wayland/core/shm_buffer_backing.rs create mode 100644 src/wayland/core/shm_pool.rs create mode 100644 src/wayland/core/surface.rs create mode 100644 src/wayland/core/touch.rs delete mode 100644 src/wayland/data_device.rs delete mode 100644 src/wayland/decoration.rs create mode 100644 src/wayland/dmabuf/buffer_backing.rs create mode 100644 src/wayland/dmabuf/buffer_params.rs create mode 100644 src/wayland/dmabuf/feedback.rs create mode 100644 src/wayland/dmabuf/mod.rs delete mode 100644 src/wayland/drm.rs delete mode 100644 src/wayland/seat.rs delete mode 100644 src/wayland/state.rs delete mode 100644 src/wayland/surface.rs create mode 100644 src/wayland/util.rs delete mode 100644 src/wayland/utils.rs delete mode 100644 src/wayland/wayland-drm.xml create mode 100644 src/wayland/xdg/backend.rs create mode 100644 src/wayland/xdg/mod.rs create mode 100644 src/wayland/xdg/popup.rs create mode 100644 src/wayland/xdg/positioner.rs create mode 100644 src/wayland/xdg/surface.rs create mode 100644 src/wayland/xdg/toplevel.rs create mode 100644 src/wayland/xdg/wm_base.rs delete mode 100644 src/wayland/xdg_activation.rs delete mode 100644 src/wayland/xdg_shell.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a90d96a..d3fed27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build +name: CI on: push: @@ -6,7 +6,7 @@ on: - "*" jobs: - build_and_package: + check: runs-on: ubuntu-latest steps: @@ -19,7 +19,9 @@ jobs: - name: Set up Rust uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: 1.85.0 + override: true + components: rustfmt, clippy - name: Build server run: cargo build --release diff --git a/Cargo.lock b/Cargo.lock index a0b5cfc..033887b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,21 +249,6 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -[[package]] -name = "appendlist" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2" - -[[package]] -name = "approx" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" -dependencies = [ - "num-traits", -] - [[package]] name = "approx" version = "0.5.1" @@ -334,9 +319,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -498,12 +483,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "atomic_float" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" - [[package]] name = "atomicow" version = "1.1.0" @@ -1062,13 +1041,13 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68553e0090fe9c3ba066c65629f636bd58e4ebd9444fdba097b91af6cd3e243f" dependencies = [ - "approx 0.5.1", + "approx", "bevy_reflect", "derive_more", "glam", "itertools 0.14.0", "libm", - "rand 0.8.5", + "rand", "rand_distr", "serde", "smallvec", @@ -1565,7 +1544,7 @@ checksum = "6a5e7f00c6b3b6823df5ec2a5e9067273607208919bc8c211773ebb9643c87f0" dependencies = [ "accesskit", "accesskit_winit", - "approx 0.5.1", + "approx", "bevy_a11y", "bevy_app", "bevy_derive", @@ -1598,7 +1577,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1618,7 +1597,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -1691,15 +1670,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "block2" version = "0.5.1" @@ -1780,26 +1750,13 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "calloop" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10929724661d1c43856fd87c7a127ae944ec55579134fb485e4136fb6a46fdcb" -dependencies = [ - "bitflags 2.9.1", - "polling", - "rustix 0.38.44", - "slab", - "tracing", -] - [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop 0.13.0", + "calloop", "rustix 0.38.44", "wayland-backend", "wayland-client", @@ -1807,9 +1764,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -1849,16 +1806,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "cgmath" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" -dependencies = [ - "approx 0.4.0", - "num-traits", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -2212,15 +2159,6 @@ dependencies = [ "windows 0.54.0", ] -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -2266,16 +2204,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "ctrlc" version = "3.4.7" @@ -2339,16 +2267,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "directories" version = "5.0.1" @@ -2448,46 +2366,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -[[package]] -name = "drm" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" -dependencies = [ - "bitflags 2.9.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "libc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b" -dependencies = [ - "drm-sys", - "rustix 0.38.44", -] - [[package]] name = "drm-fourcc" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -[[package]] -name = "drm-sys" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - [[package]] name = "either" version = "1.15.0" @@ -2850,6 +2734,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", @@ -2870,16 +2755,6 @@ dependencies = [ "windows 0.61.3", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "gethostname" version = "0.4.3" @@ -2941,7 +2816,7 @@ dependencies = [ "bytemuck", "libm", "mint", - "rand 0.8.5", + "rand", "serde", ] @@ -3087,9 +2962,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -3396,6 +3271,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -3411,6 +3297,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -3595,12 +3490,6 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -3778,6 +3667,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix 0.38.44", +] + [[package]] name = "memmap2" version = "0.9.5" @@ -3925,7 +3823,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -4561,7 +4459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand", ] [[package]] @@ -4852,18 +4750,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", ] [[package]] @@ -4873,17 +4761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -4895,15 +4773,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - [[package]] name = "rand_distr" version = "0.4.3" @@ -4911,7 +4780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -5260,17 +5129,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -5354,41 +5212,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smithay" -version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git#30179679619a317cd0f5a5d6496332fd3adcaba6" -dependencies = [ - "appendlist", - "atomic_float", - "bitflags 2.9.1", - "calloop 0.14.2", - "cgmath", - "cursor-icon", - "downcast-rs 1.2.1", - "drm", - "drm-ffi", - "drm-fourcc", - "errno", - "gl_generator", - "indexmap 2.10.0", - "libc", - "libloading", - "profiling", - "rand 0.9.1", - "rustix 1.0.7", - "sha2", - "smallvec", - "tempfile", - "thiserror 2.0.12", - "tracing", - "wayland-protocols", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-server", - "xkbcommon", -] - [[package]] name = "smithay-client-toolkit" version = "0.19.2" @@ -5396,7 +5219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.9.1", - "calloop 0.13.0", + "calloop", "calloop-wayland-source", "cursor-icon", "libc", @@ -5532,36 +5355,43 @@ dependencies = [ "bevy_mod_xr", "bevy_sk", "clap", + "cluFlock", "color-eyre", "console-subscriber", "cosmic-text 0.14.2", "dashmap", "directories", + "drm-fourcc", "glam", "global_counter", "input-event-codes", "lazy_static", + "libc", + "memfd", + "memmap2", "mint", "nanoid", + "nix 0.30.1", "openxr", "parking_lot 0.12.4", - "rand 0.8.5", + "rand", "rustc-hash 2.1.1", + "rustix 1.0.7", "send_wrapper", "serde", "serde_repr", "slotmap", - "smithay", "stardust-xr", "stardust-xr-server-codegen", "thiserror 2.0.12", "tokio", + "tokio-stream", "toml", "tracing", "tracing-subscriber", "tracing-tracy", - "wayland-backend", "wayland-scanner", + "waynest", "xkbcommon-rs", "zbus", ] @@ -5878,17 +5708,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot 0.12.4", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "tracing", @@ -6023,7 +5855,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand", "slab", "tokio", "tokio-util", @@ -6064,7 +5896,6 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6224,12 +6055,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - [[package]] name = "uds_windows" version = "1.1.0" @@ -6511,20 +6336,6 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-scanner", - "wayland-server", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635cf2968bd88599445b25a2eeef655d463bb04f9aed04e4bf8c2018f3d4fc41" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-protocols", - "wayland-scanner", - "wayland-server", ] [[package]] @@ -6551,7 +6362,6 @@ dependencies = [ "wayland-client", "wayland-protocols", "wayland-scanner", - "wayland-server", ] [[package]] @@ -6565,19 +6375,6 @@ dependencies = [ "quote", ] -[[package]] -name = "wayland-server" -version = "0.31.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "485dfb8ccf0daa0d34625d34e6ac15f99e550a7999b6fd88a0835ccd37655785" -dependencies = [ - "bitflags 2.9.1", - "downcast-rs 1.2.1", - "rustix 0.38.44", - "wayland-backend", - "wayland-scanner", -] - [[package]] name = "wayland-sys" version = "0.31.6" @@ -6589,6 +6386,33 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "waynest" +version = "0.0.25" +source = "git+https://github.com/verdiwm/waynest.git#418768890e3cda975caee6ce54d35284f6f6d627" +dependencies = [ + "async-trait", + "bitflags 2.9.1", + "bytes", + "futures-util", + "pin-project-lite", + "rustix 1.0.7", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "waynest-macros", +] + +[[package]] +name = "waynest-macros" +version = "0.0.25" +source = "git+https://github.com/verdiwm/waynest.git#418768890e3cda975caee6ce54d35284f6f6d627" +dependencies = [ + "quote", + "syn 2.0.104", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -7253,7 +7077,7 @@ dependencies = [ "bitflags 2.9.1", "block2", "bytemuck", - "calloop 0.13.0", + "calloop", "cfg_aliases 0.2.1", "concurrent-queue", "core-foundation", @@ -7358,17 +7182,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" -[[package]] -name = "xkbcommon" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" -dependencies = [ - "libc", - "memmap2", - "xkeysym", -] - [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -7531,9 +7344,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.16" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4a518c0ea2576f4da876349d7f67a7be489297cd77c2cf9e04c2e05fcd3974" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 67a7bbb..d71d20a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2024" -rust-version = "1.85" +rust-version = "1.88" name = "stardust-xr-server" version = "0.45.0" authors = ["Nova King "] @@ -21,8 +21,16 @@ name = "stardust-xr-server" path = "src/main.rs" [features] -default = [] -wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"] +default = ["wayland"] +wayland = [ + "dep:cluFlock", + "dep:waynest", + "dep:tokio-stream", + "dep:memmap2", + "dep:drm-fourcc", + "dep:memfd", +] +dmabuf = [] profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_app = ["dep:tracing-tracy", "bevy/trace_tracy", "bevy/trace"] bevy_debugging = ["bevy/bevy_remote", "bevy/track_location"] @@ -63,7 +71,7 @@ dashmap = "6.1.0" color-eyre = { version = "0.6.3", default-features = false } clap = { version = "4.5.13", features = ["derive"] } console-subscriber = { version = "0.4.0", optional = true } -thiserror = "2.0.9" +thiserror = "2.0.11" tracing = { version = "0.1.40", features = ["release_max_level_warn"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-tracy = { version = "0.11.1", optional = true } @@ -118,21 +126,25 @@ input-event-codes = "6.2.0" zbus = { version = "5.0.0", default-features = false, features = ["tokio"] } directories = "5.0.1" xkbcommon-rs = "0.1.0" - -# wayland -wayland-backend = { version = "0.3.7", optional = true, default-features = false } +rustix = "1.0.7" wayland-scanner = { version = "0.31.4", optional = true } cosmic-text = "0.14.2" - -[dependencies.smithay] -git = "https://github.com/smithay/smithay.git" -default-features = false -features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"] -optional = true +# Wayland +cluFlock = { version = "1.2.7", optional = true } # for the lockfile checking +waynest = { git = "https://github.com/verdiwm/waynest.git", features = [ + "server", + "stable", + "tracing", +], default-features = false, optional = true } +tokio-stream = { version = "0.1.17", optional = true } +memmap2 = { version = "0.9.5", optional = true } +drm-fourcc = { version = "2.2.0", optional = true } +memfd = { version = "0.6.4", optional = true } +libc = "0.2.172" +nix = "0.30.1" [dependencies.stardust-xr] workspace = true - [dependencies.stardust-xr-server-codegen] path = "codegen" diff --git a/src/core/error.rs b/src/core/error.rs index 42a6d50..33071f9 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -28,6 +28,9 @@ pub enum ServerError { DeserializationError(#[from] DeserializationError), #[error("Reader error: {0}")] ReaderError(#[from] ReaderError), + #[cfg(feature = "wayland")] + #[error("Wayland error: {0}")] + WaylandError(waynest::server::Error), #[error("Aspect {} does not exist for node", 0.to_string())] NoAspect(TypeId), #[error("{0}")] diff --git a/src/core/graphics_info.rs b/src/core/graphics_info.rs new file mode 100644 index 0000000..b947d58 --- /dev/null +++ b/src/core/graphics_info.rs @@ -0,0 +1,7 @@ +use bevy::asset::Assets; +use bevy::ecs::world::Mut; +use bevy::image::Image; + +pub struct GraphicsInfo<'w> { + pub _images: Mut<'w, Assets>, +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 92c18f7..b6e4b63 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,6 +6,7 @@ pub mod delta; pub mod destroy_queue; pub mod entity_handle; pub mod error; +pub mod graphics_info; pub mod registry; pub mod resource; pub mod scenegraph; diff --git a/src/main.rs b/src/main.rs index 850f701..ff7768f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,6 +44,7 @@ use bevy_sk::vr_materials::PbrMaterial; use clap::Parser; use core::client::{Client, tick_internal_client}; use core::entity_handle::EntityHandlePlugin; +use core::graphics_info::GraphicsInfo; use core::task; use directories::ProjectDirs; use nodes::audio::AudioNodePlugin; @@ -68,6 +69,7 @@ use tokio::task::JoinError; use tracing::metadata::LevelFilter; use tracing::{error, info}; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; +use wayland::Wayland; use zbus::Connection; use zbus::fdo::ObjectManager; @@ -188,6 +190,8 @@ async fn main() -> Result { "Couldn't make the object registry to find all objects with given interfaces in d-bus", ); + let _wayland = Wayland::new(None).expect("Couldn't create Wayland instance"); + let ready_notifier = Arc::new(Notify::new()); let io_loop = tokio::task::spawn_blocking({ let ready_notifier = ready_notifier.clone(); @@ -422,7 +426,9 @@ fn update_cameras(mut camera: Query<&mut Projection, (With,)>) { fn xr_step(world: &mut World) { // camera::update(token); #[cfg(feature = "wayland")] - wayland.frame_event(); + Wayland::early_frame(&mut GraphicsInfo { + _images: world.resource_mut::>(), + }); destroy_queue::clear(); // update things like the Xr input methods @@ -457,5 +463,7 @@ fn xr_step(world: &mut World) { tick_internal_client(); #[cfg(feature = "wayland")] - wayland.update(); + world.resource_scope::, _>(|world, mut materials| { + Wayland::update_graphics(&mut materials, &mut world.resource_mut::>()); + }); } diff --git a/src/nodes/drawable/holdout.wgsl b/src/nodes/drawable/holdout.wgsl new file mode 100644 index 0000000..2ec3b6f --- /dev/null +++ b/src/nodes/drawable/holdout.wgsl @@ -0,0 +1,5 @@ +fn fragment( + in: VertexOutput +) -> FragmentOutput { + return vec4(0.0); +} diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index 857e7e0..67680ce 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -10,8 +10,11 @@ use crate::nodes::Node; use crate::nodes::alias::{Alias, AliasList}; use crate::nodes::spatial::{Spatial, SpatialNode}; use crate::{BevyMaterial, bail}; +use bevy::asset::{load_internal_asset, weak_handle}; +use bevy::pbr::{ExtendedMaterial, MaterialExtension}; use bevy::prelude::*; use bevy::render::primitives::Aabb; +use bevy::render::render_resource::{AsBindGroup, ShaderRef}; use color_eyre::eyre::eyre; use parking_lot::Mutex; use rustc_hash::{FxHashMap, FxHasher}; @@ -19,24 +22,48 @@ use stardust_xr::values::ResourceID; use std::ffi::OsStr; use std::hash::{Hash, Hasher}; use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, OnceLock, Weak}; static LOAD_MODEL: BevyChannel<(Arc, PathBuf)> = BevyChannel::new(); +type HoldoutMaterial = ExtendedMaterial; +const HOLDOUT_SHADER_HANDLE: Handle = weak_handle!("92b481b7-d3da-4188-b252-2335ec814ee2"); +const HOLDOUT_MATERIAL_HANDLE: Handle = + weak_handle!("d56f1d62-9121-434b-a34f-9f0bbd6b3390"); + pub struct ModelNodePlugin; impl Plugin for ModelNodePlugin { fn build(&self, app: &mut App) { LOAD_MODEL.init(app); + + load_internal_asset!( + app, + HOLDOUT_SHADER_HANDLE, + "holdout.wgsl", + Shader::from_wgsl + ); + app.add_plugins(MaterialPlugin::::default()); + app.world_mut() + .resource_mut::>() + .insert(&HOLDOUT_MATERIAL_HANDLE, HoldoutMaterial::default()); + app.init_resource::(); app.add_systems(Update, load_models); - app.add_systems( - PostUpdate, - ( - gen_model_parts, - apply_materials, - ) - .chain(), - ); + app.add_systems(PostUpdate, (gen_model_parts, apply_materials).chain()); + } +} + +// No extra data needed for a simple holdout +#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)] +pub struct HoldoutExtension {} +impl MaterialExtension for HoldoutExtension { + fn fragment_shader() -> ShaderRef { + HOLDOUT_SHADER_HANDLE.into() + } + + fn alpha_mode() -> Option { + Some(AlphaMode::Opaque) } } @@ -64,6 +91,7 @@ fn load_models( } fn apply_materials( + mut commands: Commands, mut query: Query<&mut MeshMaterial3d>, mut material_registry: ResMut, asset_server: Res, @@ -75,12 +103,21 @@ fn apply_materials( .filter_map(|p| p.parts.get()) .flatten() { - let Ok(mut mesh_mat) = query.get_mut(**model_part.mesh_entity.get().unwrap()) else { + let entity = **model_part.mesh_entity.get().unwrap(); + let Ok(mut mesh_mat) = query.get_mut(entity) else { continue; }; - if let Some(material) = model_part.pending_material_replacement.lock().take() { - let pbr_mat = material.to_pbr_mat(&asset_server); - let handle = material_registry.get_handle(pbr_mat, &mut materials); + if model_part.holdout.load(Ordering::Relaxed) { + commands + .entity(entity) + .remove::>() + .insert(MeshMaterial3d(HOLDOUT_MATERIAL_HANDLE)); + continue; + } + if let Some(material) = model_part.pending_material_replacement.lock().take() + && let Some(material) = materials.get(&material) + { + let handle = material_registry.get_handle(material.clone(), &mut materials); mesh_mat.0 = handle; } for (param_name, param) in model_part.pending_material_parameters.lock().drain() { @@ -157,6 +194,7 @@ fn gen_model_parts( _model: Arc::downgrade(&model), pending_material_parameters: Mutex::default(), pending_material_replacement: Mutex::default(), + holdout: AtomicBool::new(false), aliases: AliasList::default(), bounds: OnceLock::new(), }); @@ -393,50 +431,6 @@ impl MaterialParameter { } } -pub struct Material { - pub color: Color, - pub emission_factor: Color, - pub metallic: f32, - pub roughness: f32, - pub alpha_mode: AlphaMode, - pub double_sided: bool, - - pub diffuse_texture: Option, - pub emission_texture: Option, - pub metal_texture: Option, - pub occlusion_texture: Option, -} - -impl Material { - fn to_pbr_mat(&self, asset_server: &AssetServer) -> BevyMaterial { - BevyMaterial { - color: self.color, - emission_factor: self.emission_factor, - metallic: self.metallic, - roughness: self.roughness, - alpha_mode: self.alpha_mode, - double_sided: self.double_sided, - diffuse_texture: self - .diffuse_texture - .as_ref() - .map(|p| asset_server.load(p.as_path())), - emission_texture: self - .emission_texture - .as_ref() - .map(|p| asset_server.load(p.as_path())), - metal_texture: self - .metal_texture - .as_ref() - .map(|p| asset_server.load(p.as_path())), - occlusion_texture: self - .occlusion_texture - .as_ref() - .map(|p| asset_server.load(p.as_path())), - ..Default::default() // spherical_harmonics: bevy_sk::skytext::SPHERICAL_HARMONICS_HANDLE, - } - } -} - pub struct ModelPart { entity: OnceLock, mesh_entity: OnceLock, @@ -444,12 +438,13 @@ pub struct ModelPart { space: Arc, _model: Weak, pending_material_parameters: Mutex>, - pending_material_replacement: Mutex>, + pending_material_replacement: Mutex>>, + holdout: AtomicBool, aliases: AliasList, bounds: OnceLock, } impl ModelPart { - pub fn replace_material(&self, replacement: Material) { + pub fn replace_material(&self, replacement: Handle) { self.pending_material_replacement .lock() .replace(replacement); @@ -464,18 +459,7 @@ impl ModelPartAspect for ModelPart { #[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."] fn apply_holdout_material(node: Arc, _calling_client: Arc) -> Result<()> { let model_part = node.get_aspect::()?; - model_part.replace_material(Material { - color: Color::BLACK.with_alpha(0.0), - emission_factor: Color::BLACK, - metallic: 0.0, - roughness: 1.0, - alpha_mode: AlphaMode::Opaque, - double_sided: false, - diffuse_texture: None, - emission_texture: None, - metal_texture: None, - occlusion_texture: None, - }); + model_part.holdout.store(true, Ordering::Relaxed); Ok(()) } @@ -584,6 +568,7 @@ impl Model { _model: Arc::downgrade(self), pending_material_parameters: Mutex::default(), pending_material_replacement: Mutex::default(), + holdout: AtomicBool::new(false), aliases: AliasList::default(), bounds: OnceLock::new(), }); diff --git a/src/nodes/items/panel.rs b/src/nodes/items/panel.rs index a1f51ac..42e7ce5 100644 --- a/src/nodes/items/panel.rs +++ b/src/nodes/items/panel.rs @@ -23,7 +23,7 @@ use mint::Vector2; use parking_lot::Mutex; use slotmap::{DefaultKey, Key, KeyData, SlotMap}; use std::sync::{Arc, Weak}; -use tracing::{debug, info}; +use tracing::debug; stardust_xr_server_codegen::codegen_item_panel_protocol!(); impl Default for Geometry { @@ -34,6 +34,7 @@ impl Default for Geometry { } } } +impl Copy for Geometry {} lazy_static! { pub static ref KEYMAPS: Mutex> = Mutex::new(SlotMap::default()); @@ -97,6 +98,7 @@ pub trait PanelItemTrait: Send + Sync + 'static { fn send_acceptor_item_created(&self, node: &Node, item: &Arc); } +#[derive(Debug)] pub struct PanelItem { pub node: Weak, pub backend: Box, @@ -460,12 +462,6 @@ impl PanelItemTrait for PanelItem { let _ = panel_item_acceptor_client::capture_item(node, item, init_data); } } -impl Drop for PanelItem { - fn drop(&mut self) { - // Dropped panel item, basically just a debug breakpoint place - info!("Dropped panel item"); - } -} impl InterfaceAspect for Interface { #[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."] diff --git a/src/objects/input/mouse_pointer.rs b/src/objects/input/mouse_pointer.rs index fe6d4ec..7593055 100644 --- a/src/objects/input/mouse_pointer.rs +++ b/src/objects/input/mouse_pointer.rs @@ -303,7 +303,8 @@ impl MousePointer { let handler = handler.clone(); let dbus_connection = dbus_connection.clone(); join_set.spawn(async move { - timeout(Duration::from_millis(1), async { + // TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending + timeout(Duration::from_millis(10), async { let field_ref = handler .to_typed_proxy::(&dbus_connection) .await diff --git a/src/session.rs b/src/session.rs index 144140d..df82893 100644 --- a/src/session.rs +++ b/src/session.rs @@ -86,14 +86,11 @@ pub fn run_client(mut command: Command, debug_launched_clients: bool) -> Option< } pub fn connection_env() -> FxHashMap { - macro_rules! var_env_insert { - ($env:ident, $name:ident) => { - $env.insert(stringify!($name).to_string(), $name.get().unwrap().clone()); - }; - } - let mut env: FxHashMap = FxHashMap::default(); - var_env_insert!(env, STARDUST_INSTANCE); + env.insert( + stringify!(STARDUST_INSTANCE).to_string(), + STARDUST_INSTANCE.get().unwrap().clone(), + ); if let Some(flat_wayland_display) = std::env::var_os("WAYLAND_DISPLAY") { env.insert( @@ -103,7 +100,10 @@ pub fn connection_env() -> FxHashMap { } #[cfg(feature = "wayland")] { - var_env_insert!(env, WAYLAND_DISPLAY); + env.insert( + stringify!(WAYLAND_DISPLAY).to_string(), + WAYLAND_DISPLAY.get().unwrap().to_string_lossy().to_string(), + ); env.insert("XDG_SESSION_TYPE".to_string(), "wayland".to_string()); } env diff --git a/src/wayland/compositor.rs b/src/wayland/compositor.rs deleted file mode 100644 index c57d12b..0000000 --- a/src/wayland/compositor.rs +++ /dev/null @@ -1,152 +0,0 @@ -use super::{ - state::{ClientState, WaylandState}, - utils::{ChildInfoExt, WlSurfaceExt}, - xdg_shell::surface_panel_item, -}; -use crate::{ - nodes::items::panel::{ChildInfo, Geometry, SurfaceId}, - wayland::surface::CoreSurface, -}; -use parking_lot::Mutex; -use rand::Rng; -use smithay::{ - backend::renderer::utils::{RendererSurfaceStateUserData, on_commit_buffer_handler}, - delegate_compositor, - desktop::PopupKind, - reexports::wayland_server::{Client, protocol::wl_surface::WlSurface}, - wayland::compositor::{ - CompositorClientState, CompositorHandler, CompositorState, add_post_commit_hook, - }, -}; -use std::sync::Arc; -use tracing::{debug, warn}; - -pub struct ConfiguredSurface; - -impl CompositorHandler for WaylandState { - fn compositor_state(&mut self) -> &mut CompositorState { - &mut self.compositor_state - } - - fn commit(&mut self, surface: &WlSurface) { - debug!(?surface, "Surface commit"); - - on_commit_buffer_handler::(surface); - - if let Some(toplevel) = self - .xdg_shell - .toplevel_surfaces() - .iter() - .find(|s| s.wl_surface() == surface) - { - if !toplevel.is_initial_configure_sent() { - debug!("Sending initial configure for toplevel surface"); - toplevel.send_configure(); - surface.insert_data(ConfiguredSurface); - } - } - - self.popup_manager.commit(surface); - if let Some(PopupKind::Xdg(popup)) = self.popup_manager.find_popup(surface) { - if surface.insert_data(ConfiguredSurface) { - debug!("Configuring popup surface"); - let _ = popup.send_configure(); - } - } - } - - fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { - &client.get_data::().unwrap().compositor_state - } - - fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) { - let id = rand::thread_rng().gen_range(0..u64::MAX); - surface.insert_data(SurfaceId::Child(id)); - CoreSurface::add_to(surface); - let Some(parent_surface_id) = parent.get_data::() else { - warn!("Parent surface has no SurfaceId"); - return; - }; - surface.insert_data(Mutex::new(ChildInfo { - id, - parent: parent_surface_id, - geometry: Geometry { - origin: [0; 2].into(), - size: [256; 2].into(), - }, - z_order: 1, - receives_input: false, - })); - - let Some(panel_item) = surface_panel_item(parent) else { - warn!("Parent has no panel item"); - return; - }; - let panel_item_weak = Arc::downgrade(&panel_item); - add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| { - if surface_panel_item(surf).is_some() { - return; - } - debug!("Linking surface to panel item"); - surf.insert_data(panel_item_weak.clone()); - - let Some(panel_item) = surface_panel_item(surf) else { - warn!("Failed to link surface to panel item"); - return; - }; - - surf.with_child_info(|_info| { - panel_item.backend.reposition_child(surf); - }); - - debug!("Adding new child to panel item"); - panel_item.backend.new_child(surf); - }); - - add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| { - let Some(view) = surf - .get_data_raw::(|s| s.lock().ok()?.view()) - .flatten() - else { - debug!("No view data for surface"); - return; - }; - let mut changed = false; - surf.with_child_info(|info| { - if info.geometry.origin.x != view.offset.x - && info.geometry.origin.y != view.offset.y - { - changed = true; - debug!("Surface position changed"); - } - if info.geometry.size.x != view.dst.w as u32 - && info.geometry.size.y != view.dst.h as u32 - { - changed = true; - debug!("Surface size changed"); - } - info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into(); - }); - - let Some(panel_item) = surface_panel_item(surf) else { - return; - }; - if changed { - debug!("Repositioning child due to geometry change"); - panel_item.backend.reposition_child(surf); - } - }); - } - - fn destroyed(&mut self, surface: &WlSurface) { - let Some(panel_item) = surface_panel_item(surface) else { - return; - }; - if surface.get_child_info().is_some() { - debug!("Dropping destroyed child surface"); - panel_item.backend.drop_child(surface); - } - } -} - -delegate_compositor!(WaylandState); diff --git a/src/wayland/core/buffer.rs b/src/wayland/core/buffer.rs new file mode 100644 index 0000000..85ebd63 --- /dev/null +++ b/src/wayland/core/buffer.rs @@ -0,0 +1,89 @@ +use std::sync::Arc; + +#[cfg(feature = "dmabuf")] +use crate::wayland::dmabuf::buffer_backing::DmabufBacking; +use crate::{ + core::registry::Registry, + wayland::{GraphicsInfo, core::shm_buffer_backing::ShmBufferBacking}, +}; +use bevy::{ + asset::{Assets, Handle}, + image::Image, +}; +use mint::Vector2; +pub use waynest::server::protocol::core::wayland::wl_buffer::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +pub static BUFFER_REGISTRY: Registry = Registry::new(); + +#[derive(Debug)] +pub enum BufferBacking { + Shm(ShmBufferBacking), + #[cfg(feature = "dmabuf")] + Dmabuf(DmabufBacking), +} +impl BufferBacking { + // Returns true if the buffer can be released immediately after texture update + pub fn can_release_after_update(&self) -> bool { + matches!(self, BufferBacking::Shm(_)) + } +} + +#[derive(Debug, Dispatcher)] +pub struct Buffer { + pub id: ObjectId, + backing: BufferBacking, +} + +impl Buffer { + pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc { + let buffer = client.insert(id, Self { id, backing }); + BUFFER_REGISTRY.add_raw(&buffer); + buffer + } + + #[allow(unused)] + pub fn init_tex(self: Arc, graphics_info: &mut GraphicsInfo) { + match &self.backing { + BufferBacking::Shm(_) => (), + #[cfg(feature = "dmabuf")] + BufferBacking::Dmabuf(backing) => backing.init_tex(graphics_info, self.clone()), + } + } + + /// Returns the tex if it was updated + pub fn update_tex(&self, images: &mut Assets) -> Option> { + tracing::debug!("Updating texture for buffer {:?}", self.id); + match &self.backing { + BufferBacking::Shm(backing) => backing.update_tex(images), + #[cfg(feature = "dmabuf")] + BufferBacking::Dmabuf(backing) => backing + .get_tex() + .map(|tex| tex.get_id().to_string()) + .and_then(|tex_id| Tex::find(tex_id).ok()), + } + } + + pub fn can_release_after_update(&self) -> bool { + self.backing.can_release_after_update() + } + + pub fn size(&self) -> Vector2 { + match &self.backing { + BufferBacking::Shm(backing) => backing.size(), + #[cfg(feature = "dmabuf")] + BufferBacking::Dmabuf(backing) => backing.size(), + } + } +} + +impl WlBuffer for Buffer { + /// https://wayland.app/protocols/wayland#wl_buffer:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + tracing::info!("Destroying buffer {:?}", self.id); + Ok(()) + } +} diff --git a/src/wayland/core/callback.rs b/src/wayland/core/callback.rs new file mode 100644 index 0000000..195bc34 --- /dev/null +++ b/src/wayland/core/callback.rs @@ -0,0 +1,10 @@ +pub use waynest::server::protocol::core::wayland::wl_callback::*; +use waynest::{ + server::{Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher, Clone)] +pub struct Callback(pub ObjectId); +/// https://wayland.app/protocols/wayland#wl_callback +impl WlCallback for Callback {} diff --git a/src/wayland/core/compositor.rs b/src/wayland/core/compositor.rs new file mode 100644 index 0000000..658d57e --- /dev/null +++ b/src/wayland/core/compositor.rs @@ -0,0 +1,70 @@ +use super::surface::WL_SURFACE_REGISTRY; +use crate::wayland::core::surface::Surface; +pub use waynest::server::protocol::core::wayland::wl_compositor::*; +use waynest::{ + server::{Client, Dispatcher, Result, protocol::core::wayland::wl_region::WlRegion}, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher, Default)] +pub struct Compositor; +impl WlCompositor for Compositor { + /// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface + async fn create_surface( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + let surface = client.insert(id, Surface::new(client, id)); + WL_SURFACE_REGISTRY.add_raw(&surface); + + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_compositor:request:create_region + async fn create_region( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + client.insert(id, Region::default()); + Ok(()) + } +} + +#[derive(Debug, Dispatcher, Default)] +pub struct Region {} +impl WlRegion for Region { + /// https://wayland.app/protocols/wayland#wl_region:request:add + async fn add( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_region:request:subtract + async fn subtract( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_region:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/display.rs b/src/wayland/core/display.rs new file mode 100644 index 0000000..0707783 --- /dev/null +++ b/src/wayland/core/display.rs @@ -0,0 +1,75 @@ +#![allow(unused)] + +use crate::wayland::{ + MessageSink, + core::{ + callback::{Callback, WlCallback}, + registry::Registry, + seat::Seat, + }, +}; +use global_counter::primitive::exact::CounterU32; +use std::{ + sync::{Arc, OnceLock}, + time::Instant, +}; +pub use waynest::server::protocol::core::wayland::wl_display::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Dispatcher)] +pub struct Display { + pub message_sink: MessageSink, + pub pid: Option, + pub seat: OnceLock>, + id_counter: CounterU32, + pub creation_time: Instant, +} +impl Display { + pub fn new(message_sink: MessageSink, pid: Option) -> Self { + Self { + message_sink, + pid, + seat: OnceLock::new(), + id_counter: CounterU32::new(0xff000000), // Start at 0xff000000 to avoid conflicts with client-generated IDs + creation_time: Instant::now(), + } + } + pub fn next_server_id(&self) -> ObjectId { + unsafe { ObjectId::from_raw(self.id_counter.inc()) } + } +} +impl WlDisplay for Display { + /// https://wayland.app/protocols/wayland#wl_display:request:sync + async fn sync( + &self, + client: &mut Client, + sender_id: ObjectId, + callback_id: ObjectId, + ) -> Result<()> { + let serial = client.next_event_serial(); + Callback(callback_id) + .done(client, callback_id, serial) + .await?; + + self.delete_id(client, sender_id, callback_id.as_raw()) + .await?; + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_display:request:get_registry + async fn get_registry( + &self, + client: &mut Client, + _sender_id: ObjectId, + registry_id: ObjectId, + ) -> Result<()> { + let registry = client.insert(registry_id, Registry); + + registry.advertise_globals(client, registry_id).await?; + + Ok(()) + } +} diff --git a/src/wayland/core/keyboard.rs b/src/wayland/core/keyboard.rs new file mode 100644 index 0000000..3683746 --- /dev/null +++ b/src/wayland/core/keyboard.rs @@ -0,0 +1,259 @@ +use crate::{ + nodes::items::panel::KEYMAPS, + wayland::{core::surface::Surface, util::ClientExt}, +}; +use dashmap::{DashMap, DashSet}; +use memfd::MemfdOptions; +use slotmap::{DefaultKey, KeyData}; +use std::{ + collections::HashSet, + os::{ + fd::{AsRawFd, IntoRawFd}, + unix::io::{FromRawFd, OwnedFd}, + }, + sync::{Arc, Weak}, +}; +use tokio::sync::Mutex; +pub use waynest::server::protocol::core::wayland::wl_keyboard::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Default)] +struct ModifierState { + pressed_keys: HashSet, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, +} + +impl ModifierState { + fn update_key(&mut self, key: u32, pressed: bool) -> bool { + let changed = if pressed { + self.pressed_keys.insert(key) + } else { + self.pressed_keys.remove(&key) + }; + + if changed { + self.update_modifiers(); + } + changed + } + + fn update_modifiers(&mut self) { + let mut mods = 0; + + // Update modifier state based on currently pressed keys + for key in &self.pressed_keys { + match *key { + input_event_codes::KEY_LEFTSHIFT!() | input_event_codes::KEY_RIGHTSHIFT!() => { + mods |= 1 + } + input_event_codes::KEY_LEFTCTRL!() | input_event_codes::KEY_RIGHTCTRL!() => { + mods |= 4 + } + input_event_codes::KEY_LEFTALT!() | input_event_codes::KEY_RIGHTALT!() => mods |= 8, + input_event_codes::KEY_LEFTMETA!() | input_event_codes::KEY_RIGHTMETA!() => { + mods |= 64 + } + input_event_codes::KEY_CAPSLOCK!() => self.mods_locked ^= 1, + _ => {} + } + } + + self.mods_depressed = mods; + } +} + +#[derive(Dispatcher)] +pub struct Keyboard { + pub id: ObjectId, + focused_surface: Mutex>, + modifier_state: Mutex, + pressed_keys: DashMap>, + current_keymap_id: Mutex, +} + +impl Keyboard { + pub fn new(id: ObjectId) -> Self { + Self { + id, + focused_surface: Mutex::new(Weak::new()), + modifier_state: Mutex::new(ModifierState::default()), + pressed_keys: DashMap::default(), + current_keymap_id: Mutex::new(0), + } + } + + async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> Result<()> { + let file = MemfdOptions::default() + .create("stardust-keymap") + .map_err(|e| waynest::server::Error::Custom(e.to_string()))? + .into_file(); + file.set_len(keymap.len() as u64)?; + // file.write_all(keymap)?; + // file.flush()?; + + // let map = libc::mmap(addr, len, prot, flags, fd, offset) + + let mut map = unsafe { memmap2::MmapMut::map_mut(file.as_raw_fd()) }?; + map.copy_from_slice(keymap); + + let fd = unsafe { OwnedFd::from_raw_fd(file.into_raw_fd()) }; + + // Send keymap to client + self.keymap( + client, + self.id, + KeymapFormat::XkbV1, + fd, + keymap.len() as u32, + ) + .await?; + + Ok(()) + } + + /// has to be the wayland key, so -8 or whatever + pub async fn handle_keyboard_key( + &self, + client: &mut Client, + surface: Arc, + keymap_id: u64, + key: u32, + pressed: bool, + ) -> Result<()> { + // KEYMAP UPDATES + { + let mut old_keymap_id = self.current_keymap_id.lock().await; + + if *old_keymap_id != keymap_id { + // println!("Updating keymap to {keymap_id}"); + let keymap_key = DefaultKey::from(KeyData::from_ffi(keymap_id)); + + // Get keymap data and drop the lock immediately + let keymap_data = { + let keymap_lock = KEYMAPS.lock(); + keymap_lock + .get(keymap_key) + .map(|s| s.as_bytes().to_vec()) + .unwrap_or_default() + }; + + // Now we can safely await + self.send_keymap(client, &keymap_data).await?; + }; + *old_keymap_id = keymap_id; + drop(old_keymap_id); + } + + // PRESSED KEYS UPDATE + let pressed_keys = self.pressed_keys.entry(surface.id).or_default(); + if pressed { + pressed_keys.insert(key); + } else { + pressed_keys.remove(&key); + } + // println!("pressed keys: {:?}", &*pressed_keys); + + // FOCUS UPDATES + let mut focused = self.focused_surface.lock().await; + let mut modifier_state = self.modifier_state.lock().await; + + let refocus = focused.as_ptr() != Arc::as_ptr(&surface); + // If we're entering a new surface + if refocus { + // Send leave to old surface if it exists and is still alive + if let Some(old_surface) = focused.upgrade() { + let serial = client.next_event_serial(); + self.leave(client, old_surface.id, serial, self.id).await?; + // println!("Left surface {}", old_surface.id); + } + + // Send enter to new surface + let serial = client.next_event_serial(); + self.enter( + client, + self.id, + serial, + surface.id, + pressed_keys.iter().flat_map(|k| k.to_ne_bytes()).collect(), + ) + .await?; + // println!("Entered new surface {}", surface.id); + + // Update focused surface + *focused = Arc::downgrade(&surface); + } + + // KEY EVENT SENDING + let serial = client.next_event_serial(); + // println!( + // "Sent key {key} {}", + // if pressed { "pressed" } else { "released" } + // ); + self.key( + client, + self.id, + serial, + client.display().creation_time.elapsed().as_millis() as u32, // time + key, + if pressed { + KeyState::Pressed + } else { + KeyState::Released + }, + ) + .await?; + + // MODIFIER UPDATES + // Update modifier state and send modifiers event if changed + if refocus || modifier_state.update_key(key, pressed) { + // println!("Update modifiers"); + let serial = client.next_event_serial(); + self.modifiers( + client, + self.id, + serial, + modifier_state.mods_depressed, + modifier_state.mods_latched, + modifier_state.mods_locked, + modifier_state.group, + ) + .await?; + } + + Ok(()) + } + + pub async fn reset(&self, client: &mut Client) -> Result<()> { + let mut modifier_state = self.modifier_state.lock().await; + modifier_state.pressed_keys.clear(); + modifier_state.mods_depressed = 0; + modifier_state.mods_latched = 0; + modifier_state.mods_locked = 0; + modifier_state.group = 0; + + let serial = client.next_event_serial(); + self.modifiers( + client, + self.id, + serial, + modifier_state.mods_depressed, + modifier_state.mods_latched, + modifier_state.mods_locked, + modifier_state.group, + ) + .await + } +} + +impl WlKeyboard for Keyboard { + /// https://wayland.app/protocols/wayland#wl_keyboard:request:release + async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/mod.rs b/src/wayland/core/mod.rs new file mode 100644 index 0000000..e724bce --- /dev/null +++ b/src/wayland/core/mod.rs @@ -0,0 +1,14 @@ +pub mod buffer; +pub mod callback; +pub mod compositor; +pub mod display; +pub mod keyboard; +pub mod output; +pub mod pointer; +pub mod registry; +pub mod seat; +pub mod shm; +pub mod shm_buffer_backing; +pub mod shm_pool; +pub mod surface; +pub mod touch; diff --git a/src/wayland/core/output.rs b/src/wayland/core/output.rs new file mode 100644 index 0000000..e890254 --- /dev/null +++ b/src/wayland/core/output.rs @@ -0,0 +1,16 @@ +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +pub use waynest::server::protocol::core::wayland::wl_output::*; + +#[derive(Debug, Dispatcher, Default)] +pub struct Output; + +impl WlOutput for Output { + /// https://wayland.app/protocols/wayland#wl_output:request:release + async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + todo!() + } +} diff --git a/src/wayland/core/pointer.rs b/src/wayland/core/pointer.rs new file mode 100644 index 0000000..ad34c1e --- /dev/null +++ b/src/wayland/core/pointer.rs @@ -0,0 +1,183 @@ +use crate::wayland::core::{seat::fixed_from_f32, surface::Surface}; +use mint::Vector2; +use std::sync::Arc; +use std::sync::Weak; +use tokio::sync::Mutex; +use tracing; +pub use waynest::server::protocol::core::wayland::wl_pointer::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Dispatcher)] +pub struct Pointer { + pub id: ObjectId, + focused_surface: Mutex>, +} +impl Pointer { + pub fn new(id: ObjectId) -> Self { + Self { + id, + focused_surface: Mutex::new(Weak::new()), + } + } + + pub async fn handle_pointer_motion( + &self, + client: &mut Client, + surface: Arc, + position: Vector2, + ) -> Result<()> { + tracing::debug!( + "Handling pointer motion at ({}, {})", + position.x, + position.y + ); + let mut focused = self.focused_surface.lock().await; + + // If we're entering a new surface + if focused.as_ptr() != Arc::as_ptr(&surface) { + tracing::debug!("Surface transition detected"); + // Send leave to old surface if it exists and is still alive + if let Some(old_surface) = focused.upgrade() { + let serial = client.next_event_serial(); + tracing::debug!("Sending leave event with serial {}", serial); + self.leave(client, self.id, serial, old_surface.id).await?; + } + + // Send enter to new surface + let serial = client.next_event_serial(); + tracing::debug!( + "Sending enter event with serial {} to surface {:?}", + serial, + surface.id + ); + self.enter( + client, + self.id, + serial, + surface.id, + fixed_from_f32(position.x), + fixed_from_f32(position.y), + ) + .await?; + + // Update focused surface + *focused = Arc::downgrade(&surface); + } + + // Send motion event to current surface + tracing::debug!("Sending motion event to surface"); + self.motion( + client, + self.id, + 0, // time + fixed_from_f32(position.x), + fixed_from_f32(position.y), + ) + .await?; + self.frame(client, self.id).await?; + + Ok(()) + } + + pub async fn handle_pointer_button( + &self, + client: &mut Client, + surface: Arc, + button: u32, + pressed: bool, + ) -> Result<()> { + tracing::debug!( + "Handling pointer button {} {} on surface {:?}", + button, + if pressed { "pressed" } else { "released" }, + surface.id + ); + let serial = client.next_event_serial(); + self.button( + client, + self.id, + serial, + 0, // time + button, + if pressed { + ButtonState::Pressed + } else { + ButtonState::Released + }, + ) + .await?; + self.frame(client, self.id).await + } + pub async fn handle_pointer_scroll( + &self, + client: &mut Client, + _surface: Arc, + scroll_distance: Option>, + scroll_steps: Option>, + ) -> Result<()> { + tracing::debug!( + "Handling pointer scroll: distance={:?}, steps={:?}", + scroll_distance, + scroll_steps + ); + if let Some(distance) = scroll_distance { + self.axis( + client, + self.id, + 0, // time + Axis::HorizontalScroll, + fixed_from_f32(distance.x), + ) + .await?; + self.axis( + client, + self.id, + 0, // time + Axis::VerticalScroll, + fixed_from_f32(distance.y), + ) + .await?; + } + 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 + } + + pub async fn reset(&self, client: &mut Client) -> Result<()> { + let mut focused = self.focused_surface.lock().await; + if let Some(old_surface) = focused.upgrade() { + let serial = client.next_event_serial(); + self.leave(client, self.id, serial, old_surface.id).await?; + self.frame(client, self.id).await?; + } + *focused = Weak::new(); + Ok(()) + } +} + +impl WlPointer for Pointer { + /// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor + async fn set_cursor( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _serial: u32, + _surface: Option, + _hotspot_x: i32, + _hotspot_y: i32, + ) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_pointer:request:release + async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/registry.rs b/src/wayland/core/registry.rs new file mode 100644 index 0000000..23fcbe0 --- /dev/null +++ b/src/wayland/core/registry.rs @@ -0,0 +1,148 @@ +use crate::wayland::{ + core::{ + compositor::{Compositor, WlCompositor}, + display::Display, + output::{Output, WlOutput}, + seat::{Seat, WlSeat}, + shm::{Shm, WlShm}, + }, + xdg::wm_base::{WmBase, XdgWmBase}, +}; +pub use waynest::server::protocol::core::wayland::wl_registry::*; +#[cfg(feature = "dmabuf")] +use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1; +use waynest::{ + server::{Client, Dispatcher, Error, Result}, + wire::{NewId, ObjectId}, +}; + +struct RegistryGlobals; +impl RegistryGlobals { + pub const COMPOSITOR: u32 = 0; + pub const SHM: u32 = 1; + pub const WM_BASE: u32 = 2; + pub const SEAT: u32 = 3; + pub const OUTPUT: u32 = 4; + #[cfg(feature = "dmabuf")] + pub const DMABUF: u32 = 5; +} + +#[derive(Debug, Dispatcher, Default)] +pub struct Registry; + +impl Registry { + pub async fn advertise_globals(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { + self.global( + client, + sender_id, + RegistryGlobals::COMPOSITOR, + Compositor::INTERFACE.to_string(), + Compositor::VERSION, + ) + .await?; + + self.global( + client, + sender_id, + RegistryGlobals::SHM, + Shm::INTERFACE.to_string(), + Shm::VERSION, + ) + .await?; + + self.global( + client, + sender_id, + RegistryGlobals::WM_BASE, + WmBase::INTERFACE.to_string(), + WmBase::VERSION, + ) + .await?; + + self.global( + client, + sender_id, + RegistryGlobals::SEAT, + Seat::INTERFACE.to_string(), + Seat::VERSION, + ) + .await?; + + self.global( + client, + sender_id, + RegistryGlobals::OUTPUT, + Output::INTERFACE.to_string(), + Output::VERSION, + ) + .await?; + + #[cfg(feature = "dmabuf")] + self.global( + client, + sender_id, + RegistryGlobals::DMABUF, + crate::wayland::dmabuf::Dmabuf::INTERFACE.to_string(), + Dmabuf::VERSION, + ) + .await?; + + Ok(()) + } +} + +impl WlRegistry for Registry { + async fn bind( + &self, + client: &mut Client, + _sender_id: ObjectId, + name: u32, + new_id: NewId, + ) -> Result<()> { + match name { + RegistryGlobals::COMPOSITOR => { + tracing::info!("Binding compositor"); + client.insert(new_id.object_id, Compositor); + } + RegistryGlobals::SHM => { + tracing::info!("Binding SHM"); + let shm = client.insert(new_id.object_id, Shm); + shm.advertise_formats(client, new_id.object_id).await?; + } + RegistryGlobals::WM_BASE => { + tracing::info!("Binding WM_BASE"); + client.insert(new_id.object_id, WmBase); + } + RegistryGlobals::SEAT => { + tracing::info!("Binding seat with id {}", new_id.object_id); + let seat = client.insert(new_id.object_id, Seat::new()); + if let Some(display) = client.get::(ObjectId::DISPLAY) { + tracing::info!("Setting seat in display"); + let _ = display.seat.set(seat.clone()); + tracing::info!("Seat set successfully"); + } else { + tracing::warn!("No display found to set seat"); + } + seat.advertise_capabilities(client, new_id.object_id) + .await?; + tracing::info!("Seat capabilities advertised"); + } + RegistryGlobals::OUTPUT => { + tracing::info!("Binding output"); + client.insert(new_id.object_id, Output); + } + #[cfg(feature = "dmabuf")] + RegistryGlobals::DMABUF => { + tracing::info!("Binding dmabuf"); + let dmabuf = client.insert(new_id.object_id, Dmabuf::new()); + dmabuf.send_modifiers(client, new_id.object_id).await?; + } + id => { + tracing::error!(id, "Wayland: failed to bind to registry global"); + return Err(Error::MissingObject(unsafe { ObjectId::from_raw(name) })); + } + } + + Ok(()) + } +} diff --git a/src/wayland/core/seat.rs b/src/wayland/core/seat.rs new file mode 100644 index 0000000..5783187 --- /dev/null +++ b/src/wayland/core/seat.rs @@ -0,0 +1,191 @@ +use crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch}; +use mint::Vector2; +use std::sync::Arc; +use std::sync::OnceLock; +pub use waynest::server::protocol::core::wayland::wl_seat::*; +use waynest::server::{Client, Dispatcher, Result}; +use waynest::wire::{Fixed, ObjectId}; + +#[derive(Debug)] +pub enum SeatMessage { + PointerMotion { + surface: Arc, + position: Vector2, + }, + PointerButton { + surface: Arc, + button: u32, + pressed: bool, + }, + PointerScroll { + surface: Arc, + scroll_distance: Option>, + scroll_steps: Option>, + }, + KeyboardKey { + surface: Arc, + keymap_id: u64, + key: u32, + pressed: bool, + }, + TouchDown { + surface: Arc, + id: u32, + position: Vector2, + }, + TouchMove { + id: u32, + position: Vector2, + }, + TouchUp { + id: u32, + }, + Reset, +} + +pub fn fixed_from_f32(f: f32) -> Fixed { + unsafe { Fixed::from_raw((f * 256.0).round() as u32) } +} + +#[derive(Default, Dispatcher)] +pub struct Seat { + pointer: OnceLock>, + keyboard: OnceLock>, + touch: OnceLock>, +} + +impl Seat { + pub fn new() -> Self { + Self::default() + } + + pub async fn advertise_capabilities(&self, client: &mut Client, id: ObjectId) -> Result<()> { + tracing::debug!("Advertising seat capabilities with id {}", id); + let capabilities = Capability::Pointer | Capability::Keyboard | Capability::Touch; + WlSeat::capabilities(self, client, id, capabilities).await?; + tracing::debug!("Capabilities advertised: {:?}", capabilities); + Ok(()) + } + + pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> { + match message { + SeatMessage::PointerMotion { surface, position } => { + if let Some(pointer) = self.pointer.get() { + pointer + .handle_pointer_motion(client, surface, position) + .await?; + } + } + SeatMessage::PointerButton { + surface, + button, + pressed, + } => { + if let Some(pointer) = self.pointer.get() { + pointer + .handle_pointer_button(client, surface, button, pressed) + .await?; + } + } + SeatMessage::PointerScroll { + surface, + scroll_distance, + scroll_steps, + } => { + if let Some(pointer) = self.pointer.get() { + pointer + .handle_pointer_scroll(client, surface, scroll_distance, scroll_steps) + .await?; + } + } + SeatMessage::KeyboardKey { + surface, + keymap_id, + key, + pressed, + } => { + if let Some(keyboard) = self.keyboard.get() { + keyboard + .handle_keyboard_key(client, surface, keymap_id, key - 8, pressed) + .await?; + } + } + SeatMessage::TouchDown { + surface, + id, + position, + } => { + if let Some(touch) = self.touch.get() { + touch + .handle_touch_down(client, surface, id, position) + .await?; + } + } + SeatMessage::TouchMove { id, position } => { + if let Some(touch) = self.touch.get() { + touch.handle_touch_move(client, id, position).await?; + } + } + SeatMessage::TouchUp { id } => { + if let Some(touch) = self.touch.get() { + touch.handle_touch_up(client, id).await?; + } + } + SeatMessage::Reset => { + if let Some(pointer) = self.pointer.get() { + pointer.reset(client).await?; + } + if let Some(keyboard) = self.keyboard.get() { + keyboard.reset(client).await?; + } + if let Some(touch) = self.touch.get() { + touch.reset(client).await?; + } + } + } + Ok(()) + } +} +impl WlSeat for Seat { + /// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer + async fn get_pointer( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + let pointer = client.insert(id, Pointer::new(id)); + let _ = self.pointer.set(pointer); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard + async fn get_keyboard( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + tracing::info!("Getting keyboard"); + let keyboard = client.insert(id, Keyboard::new(id)); + let _ = self.keyboard.set(keyboard); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_seat:request:get_touch + async fn get_touch( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + let touch = client.insert(id, Touch(id)); + let _ = self.touch.set(touch); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_seat:request:release + async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/shm.rs b/src/wayland/core/shm.rs new file mode 100644 index 0000000..602ddbd --- /dev/null +++ b/src/wayland/core/shm.rs @@ -0,0 +1,39 @@ +use std::os::fd::OwnedFd; + +use crate::wayland::core::shm_pool::ShmPool; +pub use waynest::server::protocol::core::wayland::wl_shm::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher, Default)] +pub struct Shm; +impl Shm { + pub async fn advertise_formats(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { + self.format(client, sender_id, Format::Argb8888).await?; + self.format(client, sender_id, Format::Xrgb8888).await?; + + Ok(()) + } +} +impl WlShm for Shm { + /// https://wayland.app/protocols/wayland#wl_shm:request:create_pool + async fn create_pool( + &self, + client: &mut Client, + _sender_id: ObjectId, + pool_id: ObjectId, + fd: OwnedFd, + size: i32, + ) -> Result<()> { + client.insert(pool_id, ShmPool::new(fd, size)?); + + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_shm:request:release + async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/shm_buffer_backing.rs b/src/wayland/core/shm_buffer_backing.rs new file mode 100644 index 0000000..4d31ff3 --- /dev/null +++ b/src/wayland/core/shm_buffer_backing.rs @@ -0,0 +1,102 @@ +use super::shm_pool::ShmPool; +use bevy::{ + asset::{Assets, Handle, RenderAssetUsages}, + image::Image, + render::render_resource::{Extent3d, TextureDimension, TextureFormat}, +}; +use mint::Vector2; +use std::sync::{Arc, OnceLock}; +use waynest::server::protocol::core::wayland::wl_shm::Format; + +/// Parameters for a shared memory buffer +#[derive(Debug)] +pub struct ShmBufferBacking { + pool: Arc, + offset: usize, + stride: usize, + size: Vector2, + format: Format, + image: OnceLock>, +} + +impl ShmBufferBacking { + pub fn new( + pool: Arc, + offset: usize, + stride: usize, + size: Vector2, + format: Format, + ) -> Self { + Self { + pool, + offset, + stride, + size, + format, + image: OnceLock::new(), + } + } + + pub fn update_tex(&self, images: &mut Assets) -> Option> { + let image = self.image.get_or_init(|| { + let image = Image::new_fill( + Extent3d { + width: self.size.x as u32, + height: self.size.y as u32, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + &[255, 0, 255, 255], + TextureFormat::Rgba8Unorm, + RenderAssetUsages::all(), + ); + images.add(image) + }); + + let src_data_lock = self.pool.data_lock(); + let mut src_cursor = self.offset; + + // Calculate maximum cursor position needed - stride is already in bytes + let max_cursor = self.offset + (self.size.y * self.stride); + + // Check if we have enough data + if max_cursor > src_data_lock.len() { + return None; + } + + let dst_data = images.get_mut(image).unwrap().data.get_or_insert_with(|| { + let length = self.size.x as usize * self.size.y as usize * 4; + vec![255; length] + }); + let mut dst_cursor = 0; + + for _y in 0..self.size.y { + for _x in 0..self.size.x { + match self.format { + 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] = 255; // X means ignore alpha, treat as fully opaque + } + Format::Argb8888 => { + 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); + } + + Some(image.clone()) + } + + pub fn size(&self) -> Vector2 { + self.size + } +} diff --git a/src/wayland/core/shm_pool.rs b/src/wayland/core/shm_pool.rs new file mode 100644 index 0000000..eed91f4 --- /dev/null +++ b/src/wayland/core/shm_pool.rs @@ -0,0 +1,74 @@ +use memmap2::{MmapOptions, RemapOptions}; +use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard}; +use std::os::fd::{IntoRawFd, OwnedFd}; +use waynest::{ + server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format}, + wire::ObjectId, +}; + +use crate::wayland::core::buffer::{Buffer, BufferBacking}; + +pub use waynest::server::protocol::core::wayland::wl_shm_pool::*; + +use super::shm_buffer_backing::ShmBufferBacking; + +#[derive(Debug, Dispatcher)] +pub struct ShmPool { + inner: Mutex, +} + +impl ShmPool { + pub fn new(fd: OwnedFd, size: i32) -> Result { + let map = unsafe { + MmapOptions::new() + .len(size as usize) + .map_mut(fd.into_raw_fd())? + }; + + Ok(Self { + inner: Mutex::new(map), + }) + } + + pub fn data_lock(&self) -> MappedMutexGuard { + MutexGuard::map(self.inner.lock(), |i| i.as_mut()) + } +} + +impl WlShmPool for ShmPool { + /// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer + async fn create_buffer( + &self, + client: &mut Client, + sender_id: ObjectId, + id: ObjectId, + offset: i32, + width: i32, + height: i32, + stride: i32, + format: Format, + ) -> Result<()> { + let params = ShmBufferBacking::new( + client.get::(sender_id).unwrap(), + offset as usize, + stride as usize, + [width as usize, height as usize].into(), + format, + ); + + Buffer::new(client, id, BufferBacking::Shm(params)); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize + async fn resize(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> { + let mut inner = self.inner.lock(); + unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? }; + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/core/surface.rs b/src/wayland/core/surface.rs new file mode 100644 index 0000000..9543468 --- /dev/null +++ b/src/wayland/core/surface.rs @@ -0,0 +1,367 @@ +use super::{buffer::Buffer, callback::Callback}; +use crate::{ + BevyMaterial, + core::registry::Registry, + nodes::{drawable::model::ModelPart, items::panel::Geometry}, + wayland::{ + Message, MessageSink, + util::{ClientExt, DoubleBuffer}, + xdg::{popup::Popup, toplevel::Toplevel}, + }, +}; +use bevy::{ + asset::{Assets, Handle}, + image::Image, +}; +use mint::Vector2; +use parking_lot::Mutex; +use std::sync::{Arc, OnceLock}; +use waynest::{ + server::{ + Client, Dispatcher, Result, + protocol::core::wayland::{wl_output::Transform, wl_surface::*}, + }, + wire::ObjectId, +}; + +pub static WL_SURFACE_REGISTRY: Registry = Registry::new(); + +#[derive(Debug, Clone)] +pub enum SurfaceRole { + XdgToplevel(Arc), + XDGPopup(Arc), +} + +#[derive(Debug, Clone)] +pub struct SurfaceState { + pub buffer: Option>, + pub density: f32, + pub geometry: Option, + pub min_size: Option>, + pub max_size: Option>, + clean_lock: OnceLock<()>, +} +impl Default for SurfaceState { + fn default() -> Self { + Self { + buffer: Default::default(), + density: 1.0, + geometry: None, + min_size: None, + max_size: None, + clean_lock: Default::default(), + } + } +} + +// if returning false, don't run this callback again... just remove it +pub type OnCommitCallback = Box bool + Send + Sync>; + +#[derive(Dispatcher)] +pub struct Surface { + pub id: ObjectId, + state: Mutex>, + pub message_sink: MessageSink, + pub role: Mutex>, + frame_callback_object: Mutex>>, + on_commit_handlers: Mutex>, + material: OnceLock>, + pending_material_applications: Registry, +} +impl std::fmt::Debug for Surface { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Surface") + .field("state", &self.state) + .field("message_sink", &self.message_sink) + .field("role", &self.role) + .field("frame_callback_object", &self.frame_callback_object) + .field( + "on_commit_handlers", + &format!("<{} handlers>", self.on_commit_handlers.lock().len()), + ) + .finish() + } +} +impl Surface { + pub fn new(client: &Client, id: ObjectId) -> Self { + Surface { + id, + state: Default::default(), + message_sink: client.message_sink(), + role: Mutex::new(None), + frame_callback_object: Default::default(), + on_commit_handlers: Mutex::new(Vec::new()), + material: OnceLock::new(), + pending_material_applications: Registry::new(), + } + } + + pub fn pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer> { + self.state.lock() + } + + pub fn add_commit_handler bool + Send + Sync + 'static>( + &self, + handler: F, + ) { + let mut handlers = self.on_commit_handlers.lock(); + handlers.push(Box::new(handler)); + } + + pub fn update_graphics( + &self, + materials: &mut Assets, + images: &mut Assets, + ) { + let state_lock = self.state.lock(); + if state_lock.current().clean_lock.get().is_some() { + // then we don't need to reupload the texture + return; + } + let Some(buffer) = state_lock.current().buffer.clone() else { + return; + }; + + let material = self.material.get_or_init(|| { + // // Set default shader parameters + // let mut params = mat_wrapper.0.get_all_param_info(); + // params.set_vec2("uv_scale", stereokit_rust::maths::Vec2::new(1.0, 1.0)); + // params.set_vec2("uv_offset", stereokit_rust::maths::Vec2::new(0.0, 0.0)); + // params.set_float("fcFactor", 1.0); + // params.set_float("ripple", 4.0); + // params.set_float("alpha_min", 0.0); + // params.set_float("alpha_max", 1.0); + + let material = BevyMaterial::default(); + + materials.add(material) + }); + + if let Some(new_tex) = buffer.update_tex(images) { + materials + .get_mut(material) + .unwrap() + .diffuse_texture + .replace(new_tex); + + // For SHM buffers, we can release immediately after copying to GPU + if buffer.can_release_after_update() { + let _ = self.message_sink.send(Message::ReleaseBuffer(buffer)); + } + } + + self.apply_surface_materials(); + let _ = state_lock.current().clean_lock.set(()); + } + + pub fn apply_material(&self, model_part: &Arc) { + // tracing::info!("uwu applying material"); + self.pending_material_applications.add_raw(model_part) + } + + fn apply_surface_materials(&self) { + let Some(mat) = self.material.get() else { + return; + }; + + for model_node in self.pending_material_applications.get_valid_contents() { + model_node.replace_material(mat.clone()); + } + self.pending_material_applications.clear(); + } + fn mark_dirty(&self) { + self.state.lock().pending.clean_lock = Default::default(); + } + pub fn current_state(&self) -> SurfaceState { + self.state.lock().current().clone() + } + pub fn frame_event(&self) { + if let Some(callback_obj) = self.frame_callback_object.lock().take() { + let _ = self.message_sink.send(Message::Frame(callback_obj)); + } + } + // pub fn size(&self) -> Option> { + // self.state + // .lock() + // .current() + // .buffer + // .as_ref() + // .map(|b| [b.size.x as u32, b.size.y as u32].into()) + // } + + // pub async fn release_old_buffer(&self, client: &mut Client) -> Result<()> { + // let (old_buffer, object) = { + // let lock = self.state.lock(); + + // let Some(old_buffer) = lock.current().buffer.clone() else { + // return Ok(()); + // }; + // let new_buffer = lock.pending.buffer.as_ref(); + // if new_buffer.map(Arc::as_ptr) == Some(Arc::as_ptr(&old_buffer)) { + // return Ok(()); + // } + // drop(lock); + + // (old_buffer.clone(), old_buffer.id) + // }; + // old_buffer.release(client, object).await?; + + // Ok(()) + // } +} +impl WlSurface for Surface { + /// https://wayland.app/protocols/wayland#wl_surface:request:attach + async fn attach( + &self, + client: &mut Client, + _sender_id: ObjectId, + buffer: Option, + _x: i32, + _y: i32, + ) -> Result<()> { + self.state.lock().pending.buffer = buffer.and_then(|b| client.get::(b)); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:damage + async fn damage( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + // should be more intelligent about this but for now just make it copy everything to gpu next frame again + self.mark_dirty(); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:frame + async fn frame( + &self, + client: &mut Client, + _sender_id: ObjectId, + callback_id: ObjectId, + ) -> Result<()> { + let callback = client.insert(callback_id, Callback(callback_id)); + self.frame_callback_object.lock().replace(callback); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:set_opaque_region + async fn set_opaque_region( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _region: Option, + ) -> Result<()> { + // nothing we can really do to repaint behind this so ignore it + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:set_input_region + async fn set_input_region( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _region: Option, + ) -> Result<()> { + // too complicated to implement this for now so who the hell cares + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:commit + async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + { + let mut lock = self.state.lock(); + + // If we're getting a new buffer and the current one is DMA-BUF, release it + if let Some(new_buffer) = &lock.pending.buffer { + if let Some(current_buffer) = &lock.current().buffer { + // Don't release if it's the same buffer being reused + if !Arc::ptr_eq(new_buffer, current_buffer) + && !current_buffer.can_release_after_update() + { + let _ = self + .message_sink + .send(Message::ReleaseBuffer(current_buffer.clone())); + } + } + } + + let dirty = lock.current().clean_lock.get().is_none() + || lock.pending.clean_lock.take().is_none(); + lock.apply(); + + if !dirty { + let _ = lock.current().clean_lock.set(()); + } + } + + let current_state = self.current_state(); + let mut handlers = self.on_commit_handlers.lock(); + handlers.retain(|f| (f)(self, ¤t_state)); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_transform + async fn set_buffer_transform( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _transform: Transform, + ) -> Result<()> { + // we just don't have the output transform or fullscreen at all so this optimization is never needed + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_scale + async fn set_buffer_scale( + &self, + _client: &mut Client, + _sender_id: ObjectId, + scale: i32, + ) -> Result<()> { + self.state.lock().pending.density = scale as f32; + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:damage_buffer + async fn damage_buffer( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + // we should upload only chunks to the gpu and do subimage copy but that's a lot rn so we won't + self.mark_dirty(); + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:offset + async fn offset( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + ) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/wayland#wl_surface:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} + +impl Drop for Surface { + fn drop(&mut self) { + self.role.lock().take(); + } +} diff --git a/src/wayland/core/touch.rs b/src/wayland/core/touch.rs new file mode 100644 index 0000000..619fa0d --- /dev/null +++ b/src/wayland/core/touch.rs @@ -0,0 +1,75 @@ +use crate::wayland::core::surface::Surface; +use mint::Vector2; +use std::sync::Arc; +pub use waynest::server::protocol::core::wayland::wl_touch::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +use super::seat::fixed_from_f32; + +#[derive(Debug, Dispatcher)] +pub struct Touch(pub ObjectId); +impl Touch { + pub async fn handle_touch_down( + &self, + client: &mut Client, + surface: Arc, + id: u32, + position: Vector2, + ) -> Result<()> { + let serial = client.next_event_serial(); + self.down( + client, + self.0, + serial, + 0, + surface.id, + id as i32, + fixed_from_f32(position.x), + fixed_from_f32(position.y), + ) + .await?; + self.frame(client, self.0).await + } + + pub async fn handle_touch_move( + &self, + client: &mut Client, + id: u32, + position: Vector2, + ) -> Result<()> { + self.motion( + client, + self.0, + 0, + id as i32, + fixed_from_f32(position.x), + fixed_from_f32(position.y), + ) + .await?; + self.frame(client, self.0).await + } + + pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> Result<()> { + let serial = client.next_event_serial(); + self.up(client, self.0, serial, 0, id as i32).await?; + self.frame(client, self.0).await + } + + pub async fn reset(&self, client: &mut Client) -> Result<()> { + self.frame(client, self.0).await + } +} + +impl WlTouch for Touch { + /// https://wayland.app/protocols/wayland#wl_touch:request:release + async fn release( + &self, + _client: &mut waynest::server::Client, + _sender_id: waynest::wire::ObjectId, + ) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/data_device.rs b/src/wayland/data_device.rs deleted file mode 100644 index 5af9078..0000000 --- a/src/wayland/data_device.rs +++ /dev/null @@ -1,100 +0,0 @@ -use smithay::reexports::wayland_server::{ - Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, - protocol::{ - wl_data_device::{ - Request::{Release, SetSelection, StartDrag}, - WlDataDevice, - }, - wl_data_device_manager::{ - Request::{CreateDataSource, GetDataDevice}, - WlDataDeviceManager, - }, - wl_data_source::{ - Request::{Destroy, Offer, SetActions}, - WlDataSource, - }, - }, -}; - -use super::state::WaylandState; - -impl GlobalDispatch for WaylandState { - fn bind( - _state: &mut WaylandState, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &(), - data_init: &mut DataInit<'_, WaylandState>, - ) { - let _resource = data_init.init(resource, ()); - } -} - -impl Dispatch for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - _resource: &WlDataDeviceManager, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - CreateDataSource { id } => { - data_init.init(id, ()); - } - GetDataDevice { id, seat: _ } => { - data_init.init(id, ()); - } - _ => unreachable!(), - } - } -} - -impl Dispatch for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - _resource: &WlDataSource, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - Offer { mime_type: _ } => {} - Destroy => {} - SetActions { dnd_actions: _ } => {} - _ => unreachable!(), - } - } -} - -impl Dispatch for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - _resource: &WlDataDevice, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - StartDrag { - source: _, - origin: _, - icon: _, - serial: _, - } => {} - SetSelection { - source: _, - serial: _, - } => {} - Release => {} - _ => unreachable!(), - } - } -} diff --git a/src/wayland/decoration.rs b/src/wayland/decoration.rs deleted file mode 100644 index 4074ca2..0000000 --- a/src/wayland/decoration.rs +++ /dev/null @@ -1,97 +0,0 @@ -use super::state::WaylandState; -use smithay::{ - delegate_kde_decoration, - reexports::{ - wayland_protocols::xdg::{ - decoration::zv1::server::{ - zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1}, - zxdg_toplevel_decoration_v1::{self, Mode, ZxdgToplevelDecorationV1}, - }, - shell::server::xdg_toplevel::XdgToplevel, - }, - wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{ - Mode as KdeMode, OrgKdeKwinServerDecoration, - }, - wayland_server::{ - Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak, - protocol::wl_surface::WlSurface, - }, - }, - wayland::shell::{self, kde::decoration::KdeDecorationHandler}, -}; - -impl GlobalDispatch for WaylandState { - fn bind( - _state: &mut WaylandState, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &(), - data_init: &mut DataInit<'_, WaylandState>, - ) { - data_init.init(resource, ()); - } -} - -impl Dispatch for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - _resource: &ZxdgDecorationManagerV1, - request: zxdg_decoration_manager_v1::Request, - _data: &(), - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - zxdg_decoration_manager_v1::Request::Destroy => (), - zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => { - data_init.init(id, toplevel.downgrade()); - } - _ => unreachable!(), - } - } -} -impl Dispatch, WaylandState> for WaylandState { - fn request( - _state: &mut WaylandState, - _client: &Client, - resource: &ZxdgToplevelDecorationV1, - request: zxdg_toplevel_decoration_v1::Request, - _data: &Weak, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - zxdg_toplevel_decoration_v1::Request::SetMode { mode: _ } => { - resource.configure(Mode::ServerSide); - } - zxdg_toplevel_decoration_v1::Request::UnsetMode => { - resource.configure(Mode::ServerSide); - } - zxdg_toplevel_decoration_v1::Request::Destroy => (), - _ => unreachable!(), - } - } -} - -impl KdeDecorationHandler for WaylandState { - fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState { - &self.kde_decoration_state - } - - fn new_decoration(&mut self, _surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) { - decoration.mode(KdeMode::Server); - } - - fn request_mode( - &mut self, - _surface: &WlSurface, - decoration: &OrgKdeKwinServerDecoration, - mode: WEnum, - ) { - let Ok(mode) = mode.into_result() else { return }; - decoration.mode(mode); - } -} -delegate_kde_decoration!(WaylandState); diff --git a/src/wayland/dmabuf/buffer_backing.rs b/src/wayland/dmabuf/buffer_backing.rs new file mode 100644 index 0000000..29f7818 --- /dev/null +++ b/src/wayland/dmabuf/buffer_backing.rs @@ -0,0 +1,200 @@ +use super::buffer_params::BufferParams; +use crate::wayland::{GraphicsInfo, Message, MessageSink, core::buffer::Buffer}; +use drm_fourcc::DrmFourcc; +use khronos_egl::{self as egl, ClientBuffer}; +use mint::Vector2; +use std::{ + os::fd::AsRawFd, + sync::{Arc, OnceLock}, +}; +use stereokit_rust::tex::{Tex, TexFormat, TexType}; +use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags; + +// EGL extension constants for DMA-BUF +const EGL_WIDTH: i32 = 0x3057; +const EGL_HEIGHT: i32 = 0x3056; +const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3272; +const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3373; +const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273; +const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3275; +const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443; +const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444; +const EGL_LINUX_DMA_BUF_EXT: i32 = 0x3270; +const EGL_NO_BUFFER: *mut std::ffi::c_void = std::ptr::null_mut(); + +/// Parameters for a shared memory buffer +#[derive(Debug)] +pub struct DmabufBacking { + params: Arc, + message_sink: Option, + size: Vector2, + format: DrmFourcc, + _flags: Flags, + tex: OnceLock, +} + +impl DmabufBacking { + pub fn new( + params: Arc, + message_sink: Option, + size: Vector2, + format: DrmFourcc, + flags: Flags, + ) -> Self { + tracing::info!( + "Creating new DmabufBacking with BufferParams {:?}", + params.id + ); + Self { + params, + message_sink, + size, + format, + _flags: flags, + tex: OnceLock::new(), + } + } + + fn import_dmabuf(&self, graphics_info: &mut GraphicsInfo) -> Result { + // Sanity check for required EGL extensions + let extensions = graphics_info + .instance + .query_string(Some(graphics_info.display), egl::EXTENSIONS)?; + let extensions_str = extensions.to_string_lossy(); + let extensions_list: Vec<&str> = extensions_str.split_whitespace().collect(); + if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import") { + tracing::error!("EGL extension EGL_EXT_image_dma_buf_import is not supported"); + return Err(khronos_egl::Error::BadParameter); + } + if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import_modifiers") { + tracing::error!( + "EGL extension EGL_EXT_image_dma_buf_import_modifiers is not supported" + ); + return Err(khronos_egl::Error::BadParameter); + } + + let mut tex = Tex::new( + TexType::ImageNomips | TexType::Dynamic, + TexFormat::RGBA32, + nanoid::nanoid!(), + ); + + tracing::info!(format=?self.format, "Wayland: Updating DMABuf tex"); + + // Get plane info from params + let planes = self.params.lock_planes(); + let Some(plane) = planes.get(&0) else { + tracing::error!( + "Wayland: Failed to get plane 0 from BufferParams {:?}", + self.params.id + ); + return Err(khronos_egl::Error::BadParameter); + }; + tracing::info!( + "Using plane 0 with fd {} from BufferParams {:?}", + plane.fd.as_raw_fd(), + self.params.id + ); + + // Create EGL image + let image = match graphics_info.instance.create_image( + graphics_info.display, + graphics_info.context, + EGL_LINUX_DMA_BUF_EXT as u32, + unsafe { ClientBuffer::from_ptr(EGL_NO_BUFFER) }, + &[ + EGL_LINUX_DRM_FOURCC_EXT as usize, + self.format as usize, + EGL_DMA_BUF_PLANE0_FD_EXT as usize, + plane.fd.as_raw_fd() as usize, // EGL will dup() this fd internally + EGL_DMA_BUF_PLANE0_OFFSET_EXT as usize, + plane.offset as usize, + EGL_DMA_BUF_PLANE0_PITCH_EXT as usize, + plane.stride as usize, + egl::ATTRIB_NONE, + ], + ) { + Ok(image) => image, + Err(e) => { + tracing::error!( + "Wayland: Failed to create EGL image. Error: {:?}, Params: size=({:?}, {:?}), format={:?}, fd={}, stride={}, offset={}", + e, + self.size.x, + self.size.y, + self.format, + plane.fd.as_raw_fd(), + plane.stride, + plane.offset + ); + return Err(e); + } + }; + + // The cloned fd will be consumed by create_image, so we don't need to explicitly close it + // Create and bind GL texture + let mut gl_tex = 0; + unsafe { + gl::GenTextures(1, &mut gl_tex); + if gl_tex == 0 { + tracing::error!("Wayland: Failed to generate GL texture."); + return Err(khronos_egl::Error::BadParameter); + } + gl::BindTexture(gl::TEXTURE_2D, gl_tex); + } + + // Set the native texture handle directly + // Mesa will handle the OES texture implicitly + unsafe { + tex.set_native_surface( + gl_tex as *mut std::os::raw::c_void, + TexType::ImageNomips | TexType::Dynamic, + 0x8058, // GL_RGBA8 + self.size.x as i32, + self.size.y as i32, + 1, // single surface + true, // we own this texture + ) + }; + + // Clean up EGL image + if let Err(e) = graphics_info + .instance + .destroy_image(graphics_info.display, image) + { + tracing::error!("Wayland: Failed to destroy EGL image. Error: {:?}", e); + } + + Ok(tex) + } + + pub fn init_tex(&self, graphics_info: &Arc, buffer: Arc) { + if self.tex.get().is_none() { + match self.import_dmabuf(graphics_info) { + Ok(tex) => { + let _ = self.tex.set(tex); + let _ = self + .message_sink + .as_ref() + .unwrap() + .send(Message::DmabufImportSuccess(self.params.clone(), buffer)); + } + Err(e) => { + tracing::error!("Wayland: Error initializing DMABuf tex: {:?}", e); + let _ = self + .message_sink + .as_ref() + .unwrap() + .send(Message::DmabufImportFailure(self.params.clone())); + } + }; + } + } + + pub fn get_tex(&self) -> Option<&Tex> { + self.tex.get() + } + + pub fn size(&self) -> Vector2 { + self.size + } +} diff --git a/src/wayland/dmabuf/buffer_params.rs b/src/wayland/dmabuf/buffer_params.rs new file mode 100644 index 0000000..8f86ee9 --- /dev/null +++ b/src/wayland/dmabuf/buffer_params.rs @@ -0,0 +1,163 @@ +use super::buffer_backing::DmabufBacking; +use crate::wayland::{ + core::buffer::{Buffer, BufferBacking}, + util::ClientExt, +}; +use drm_fourcc::DrmFourcc; +use parking_lot::Mutex; +use rustc_hash::FxHashMap; +use std::os::fd::{AsRawFd, OwnedFd}; +use waynest::{ + server::{ + Client, Dispatcher, Result, + protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{ + Flags, ZwpLinuxBufferParamsV1, + }, + }, + wire::ObjectId, +}; + +/// A single plane in a DMA-BUF buffer +#[derive(Debug)] +pub struct DmabufPlane { + pub fd: OwnedFd, + pub offset: u32, + pub stride: u32, + pub _modifier: u64, +} + +/// Parameters for creating a DMA-BUF-based wl_buffer +/// +/// This is a temporary object that collects dmabufs and other parameters +/// that together form a single logical buffer. The object may eventually +/// create one wl_buffer unless cancelled by destroying it. +#[derive(Debug, Dispatcher)] +pub struct BufferParams { + pub id: ObjectId, + planes: Mutex>, +} + +impl BufferParams { + pub fn new(id: ObjectId) -> Self { + tracing::info!("Creating new BufferParams with id {:?}", id); + Self { + id, + planes: Mutex::new(FxHashMap::default()), + } + } + + pub fn lock_planes(&self) -> parking_lot::MutexGuard<'_, FxHashMap> { + self.planes.lock() + } +} + +impl ZwpLinuxBufferParamsV1 for BufferParams { + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + tracing::info!("Destroying BufferParams {:?}", self.id); + Ok(()) + } + + async fn add( + &self, + _client: &mut Client, + _sender_id: ObjectId, + fd: OwnedFd, + plane_idx: u32, + offset: u32, + stride: u32, + modifier_hi: u32, + modifier_lo: u32, + ) -> Result<()> { + let fd_num = fd.as_raw_fd(); + tracing::info!( + "Adding plane {} with fd {} to BufferParams {:?}", + plane_idx, + fd_num, + self.id + ); + + let mut planes = self.planes.lock(); + + // Check if plane index is already set + if planes.contains_key(&plane_idx) { + tracing::error!( + "Plane {} already exists in BufferParams {:?}", + plane_idx, + self.id + ); + return Err(waynest::server::Error::MissingObject(self.id)); + } + + // Create plane with the provided parameters + let plane = DmabufPlane { + fd, + offset, + stride, + _modifier: ((modifier_hi as u64) << 32) | (modifier_lo as u64), + }; + + // Store the plane + planes.insert(plane_idx, plane); + Ok(()) + } + + async fn create( + &self, + client: &mut Client, + _sender_id: ObjectId, + width: i32, + height: i32, + format: u32, + flags: Flags, + ) -> Result<()> { + tracing::info!("Creating buffer from BufferParams {:?}", self.id); + // Create the buffer with DMA-BUF backing using self as the backing + let size = [width as usize, height as usize].into(); + let backing = DmabufBacking::new( + client.get::(self.id).unwrap(), + Some(client.display().message_sink.clone()), + size, + DrmFourcc::try_from(format).unwrap(), + flags, + ); + let id = client.display().next_server_id(); + Buffer::new(client, id, BufferBacking::Dmabuf(backing)); + + Ok(()) + } + + async fn create_immed( + &self, + client: &mut Client, + _sender_id: ObjectId, + buffer_id: ObjectId, + width: i32, + height: i32, + format: u32, + flags: Flags, + ) -> Result<()> { + // Create the buffer with DMA-BUF backing using self as the backing + let size = [width as usize, height as usize].into(); + let backing = DmabufBacking::new( + client.get::(self.id).unwrap(), + None, + size, + DrmFourcc::try_from(format).unwrap(), + flags, + ); + Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing)); + + Ok(()) + } +} + +impl Drop for BufferParams { + fn drop(&mut self) { + let planes = self.planes.get_mut(); + tracing::info!("BufferParams being dropped with {} planes", planes.len()); + for (idx, plane) in planes.iter() { + tracing::info!("Dropping plane {} with fd {}", idx, plane.fd.as_raw_fd()); + } + planes.clear(); + } +} diff --git a/src/wayland/dmabuf/feedback.rs b/src/wayland/dmabuf/feedback.rs new file mode 100644 index 0000000..ee0ab43 --- /dev/null +++ b/src/wayland/dmabuf/feedback.rs @@ -0,0 +1,92 @@ +use drm_fourcc::DrmFourcc; +use memfd::MemfdOptions; +use std::{ + io::Write, + os::fd::{FromRawFd, IntoRawFd, OwnedFd}, +}; +use waynest::{ + server::{ + Client, Dispatcher, Result, + protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{ + TrancheFlags, ZwpLinuxDmabufFeedbackV1, + }, + }, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher)] +pub struct DmabufFeedback; +impl DmabufFeedback { + pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { + // Send format table first + self.send_format_table(client, sender_id).await?; + + // let graphics_info = &client.display().graphics_info; + + // Get the DRM device file path using the new method + // let device_file = graphics_info.get_drm_device_file_path()?; + + // let dev_stat = std::fs::metadata(device_file)?; + // let dev_id = dev_stat.rdev().to_ne_bytes().to_vec(); + + // self.main_device(client, sender_id, dev_id.clone()).await?; + + // Send single tranche with same device since we only support the main GPU + // self.tranche_target_device(client, sender_id, dev_id) + // .await?; + + // We only have one format at index 0 + let indices = vec![0u16] + .into_iter() + .flat_map(|i| i.to_ne_bytes()) + .collect(); + self.tranche_formats(client, sender_id, indices).await?; + + // No special flags needed for simple EGL texture usage + self.tranche_flags(client, sender_id, TrancheFlags::empty()) + .await?; + + // Mark tranche complete + self.tranche_done(client, sender_id).await?; + + // Mark overall feedback complete + self.done(client, sender_id).await?; + Ok(()) + } + + pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { + // Create a temporary file for the format table + let size = 16u32; // One format+modifier pair + let mfd = MemfdOptions::default() + .create("stardustxr-format-table") + .map_err(|e| waynest::server::Error::Custom(e.to_string()))?; + + mfd.as_file().set_len(size as u64)?; + + // Format + modifier pair (16 bytes): + // - format: u32 + // - padding: 4 bytes + // - modifier: u64 + let format = DrmFourcc::Xrgb8888 as u32; // This is what clients typically want + let modifier: u64 = 0; // Linear modifier + + // Write the format+modifier pair + mfd.as_file().write_all(&format.to_ne_bytes())?; + mfd.as_file().write_all(&modifier.to_ne_bytes())?; + + self.format_table( + client, + sender_id, + unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) }, + size, + ) + .await?; + Ok(()) + } +} + +impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback { + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/dmabuf/mod.rs b/src/wayland/dmabuf/mod.rs new file mode 100644 index 0000000..cb84b46 --- /dev/null +++ b/src/wayland/dmabuf/mod.rs @@ -0,0 +1,108 @@ +pub mod buffer_backing; +pub mod buffer_params; +pub mod feedback; + +use buffer_params::BufferParams; +use drm_fourcc::DrmFourcc; +use feedback::DmabufFeedback; +use waynest::{ + server::{ + Client, Dispatcher, Result, + protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, + }, + wire::ObjectId, +}; + +use crate::core::registry::Registry; + +/// Main DMA-BUF interface implementation +/// +/// This interface allows clients to create wl_buffers from DMA-BUFs. +/// It handles: +/// - Format/modifier advertisement +/// - Buffer parameter creation +/// - Default/surface-specific feedback +/// +/// The implementation ensures: +/// - Coherency for read access in dmabuf data +/// - Proper lifetime management of dmabuf file descriptors +/// - Safe handling of buffer attachments +#[derive(Dispatcher)] +pub struct Dmabuf { + // Track supported formats and modifiers + // formats: Mutex>, + // Track active buffer parameters objects by their ID + active_params: Registry, +} + +impl Dmabuf { + /// Create a new DMA-BUF interface instance + pub fn new() -> Self { + // let mut formats = FxHashSet::default(); + + Self { + // formats: Mutex::new(formats), + active_params: Registry::new(), + } + } + + pub async fn send_modifiers(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> { + let format = DrmFourcc::Xrgb8888 as u32; + let modifier_hi = 0u32; // Linear modifier high 32 bits + let modifier_lo = 0u32; // Linear modifier low 32 bits + self.modifier(client, sender_id, format, modifier_hi, modifier_lo) + .await?; + Ok(()) + } + + /// Remove a buffer parameters object from tracking + pub(crate) fn remove_params(&self, params_id: ObjectId) { + self.active_params.retain(|params| params.id != params_id); + } +} + +impl ZwpLinuxDmabufV1 for Dmabuf { + async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> { + self.remove_params(sender_id); + Ok(()) + } + + async fn create_params( + &self, + client: &mut Client, + _sender_id: ObjectId, + params_id: ObjectId, + ) -> Result<()> { + // Create new buffer parameters object + let params = client.insert(params_id, BufferParams::new(params_id)); + self.active_params.add_raw(¶ms); + Ok(()) + } + + async fn get_default_feedback( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + // Create feedback object for default (non-surface-specific) settings + let feedback = client.insert(id, DmabufFeedback); + feedback.send_params(client, id).await?; + Ok(()) + } + + async fn get_surface_feedback( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + _surface: ObjectId, + ) -> Result<()> { + // Create feedback object for surface-specific settings + // Note: Surface-specific feedback could be optimized based on the surface's + // requirements, but for now we use the same feedback as default + let feedback = client.insert(id, DmabufFeedback); + feedback.send_params(client, id).await?; + Ok(()) + } +} diff --git a/src/wayland/drm.rs b/src/wayland/drm.rs deleted file mode 100644 index 8b9a4f3..0000000 --- a/src/wayland/drm.rs +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -// Re-export only the actual code, and then only use this re-export -// The `generated` module below is just some boilerplate to properly isolate stuff -// and avoid exposing internal details. -// -// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. -pub use generated::wl_drm; - -#[allow(non_upper_case_globals, non_camel_case_types)] -mod generated { - use smithay::reexports::wayland_server::{self, protocol::*}; - - pub mod __interfaces { - use smithay::reexports::wayland_server::protocol::__interfaces::*; - wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml"); - } - use self::__interfaces::*; - - wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml"); -} - -use super::state::WaylandState; -use smithay::{ - backend::allocator::{ - Fourcc, Modifier, - dmabuf::{Dmabuf, DmabufFlags}, - }, - reexports::wayland_server::{ - Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, - }, -}; -use std::convert::TryFrom; - -impl GlobalDispatch for WaylandState { - fn bind( - state: &mut WaylandState, - _dh: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &(), - data_init: &mut DataInit<'_, WaylandState>, - ) { - let drm_instance = data_init.init(resource, ()); - - drm_instance.device("/dev/dri/renderD128".to_string()); - if drm_instance.version() >= 2 { - drm_instance.capabilities(wl_drm::Capability::Prime as u32); - } - for format in state.drm_formats.iter() { - if let Ok(converted) = wl_drm::Format::try_from(*format as u32) { - drm_instance.format(converted as u32); - } - } - } - - fn can_view(_client: Client, _global_dataa: &()) -> bool { - true - } -} - -impl Dispatch for WaylandState { - fn request( - state: &mut WaylandState, - _client: &Client, - drm: &wl_drm::WlDrm, - request: wl_drm::Request, - _data: &(), - _dh: &DisplayHandle, - data_init: &mut DataInit<'_, WaylandState>, - ) { - match request { - wl_drm::Request::Authenticate { .. } => drm.authenticated(), - wl_drm::Request::CreateBuffer { .. } => drm.post_error( - wl_drm::Error::InvalidName, - String::from("Flink handles are unsupported, use PRIME"), - ), - wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error( - wl_drm::Error::InvalidName, - String::from("Flink handles are unsupported, use PRIME"), - ), - wl_drm::Request::CreatePrimeBuffer { - id, - name, - width, - height, - format, - offset0, - stride0, - .. - } => { - let format = match Fourcc::try_from(format) { - Ok(format) => { - if !state.drm_formats.contains(&format) { - drm.post_error( - wl_drm::Error::InvalidFormat, - String::from("Format not advertised by wl_drm"), - ); - return; - } - format - } - Err(_) => { - drm.post_error( - wl_drm::Error::InvalidFormat, - String::from("Format unknown / not advertised by wl_drm"), - ); - return; - } - }; - - if width < 1 || height < 1 { - drm.post_error( - wl_drm::Error::InvalidFormat, - String::from("width or height not positive"), - ); - return; - } - - let mut dma = Dmabuf::builder( - (width, height), - format, - Modifier::Invalid, - DmabufFlags::empty(), - ); - dma.add_plane(name, 0, offset0 as u32, stride0 as u32); - match dma.build() { - Some(dmabuf) => { - state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap(); - data_init.init(id, dmabuf); - } - None => { - // Buffer import failed. The protocol documentation heavily implies killing the - // client is the right thing to do here. - drm.post_error( - wl_drm::Error::InvalidName, - "dmabuf global was destroyed on server", - ); - } - } - } - } - } -} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index f474075..0f7cc96 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,209 +1,306 @@ -mod compositor; -mod data_device; -mod decoration; -mod seat; -mod state; -mod surface; -// mod xdg_activation; -mod drm; -mod utils; -mod xdg_shell; +pub mod core; +#[cfg(feature = "dmabuf")] +pub mod dmabuf; +pub mod util; +pub mod xdg; -use self::{state::WaylandState, surface::CORE_SURFACES}; -use crate::{core::task, wayland::state::ClientState}; -use color_eyre::eyre::{Result, ensure}; -use parking_lot::Mutex; -use smithay::{ - backend::{ - allocator::dmabuf::Dmabuf, - egl::EGLContext, - renderer::{ImportDma, Renderer, gles::GlesRenderer}, +use crate::wayland::core::seat::SeatMessage; +use crate::{ + BevyMaterial, + core::{ + error::{Result, ServerError}, + graphics_info::GraphicsInfo, + task, }, - output::Output, - reexports::wayland_server::{Display, DisplayHandle, ListeningSocket}, - wayland::dmabuf, }; +use bevy::{asset::Assets, ecs::resource::Resource, image::Image}; +use cluFlock::ToFlock; +use core::{ + buffer::{BUFFER_REGISTRY, Buffer}, + callback::Callback, + display::Display, + surface::WL_SURFACE_REGISTRY, +}; +#[cfg(feature = "dmabuf")] +use dmabuf::buffer_params::BufferParams; +use mint::Vector2; use std::{ - ffi::{OsStr, c_void}, - os::fd::AsFd, + fs::{self, OpenOptions}, + io::{self, ErrorKind}, + os::unix::fs::OpenOptionsExt, + path::PathBuf, sync::{Arc, OnceLock}, }; -use stereokit_rust::system::{Backend, BackendGraphics}; -use tokio::{ - io::unix::AsyncFd, - sync::{ - Notify, - mpsc::{self, UnboundedReceiver}, +use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle}; +use tokio_stream::StreamExt; +use tracing::{debug_span, instrument}; +#[cfg(feature = "dmabuf")] +use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1; +use waynest::{ + server::{ + self, + protocol::{ + core::wayland::{wl_buffer::WlBuffer, wl_callback::WlCallback, wl_display::WlDisplay}, + stable::xdg_shell::xdg_toplevel::XdgToplevel, + }, }, - task::AbortHandle, + wire::{DecodeError, ObjectId}, }; -use tracing::{debug_span, info, instrument}; +use xdg::toplevel::Toplevel; -pub static WAYLAND_DISPLAY: OnceLock = OnceLock::new(); +pub static WAYLAND_DISPLAY: OnceLock = OnceLock::new(); -struct EGLRawHandles { - display: *const c_void, - config: *const c_void, - context: *const c_void, +impl From for ServerError { + fn from(err: waynest::server::Error) -> Self { + ServerError::WaylandError(err) + } } -fn get_sk_egl() -> Result { - ensure!( - Backend::graphics() == BackendGraphics::OpenGLESEGL, - "StereoKit is not running using EGL!" - ); - Ok(unsafe { - EGLRawHandles { - display: stereokit_rust::system::backend_opengl_egl_get_display() as *const c_void, - config: stereokit_rust::system::backend_opengl_egl_get_config() as *const c_void, - context: stereokit_rust::system::backend_opengl_egl_get_context() as *const c_void, +pub fn get_free_wayland_socket_path() -> Option { + // Use XDG runtime directory for secure, user-specific sockets + let base_dirs = directories::BaseDirs::new()?; + let runtime_dir = base_dirs.runtime_dir()?; + + // Iterate through conventional display numbers (matches X11 behavior) + for display in 0..=32 { + let socket_path = runtime_dir.join(format!("wayland-{display}")); + let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock")); + + // Open lock file without truncation to preserve existing locks + let mut _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) + .ok()?; + + // Atomic mutual exclusion: fail if another process holds the lock + if _lock.try_exclusive_lock().is_err() { + continue; // Lock held by active compositor } - }) + + // Check for zombie sockets (file exists but nothing listening) + if socket_path.exists() { + match std::os::unix::net::UnixStream::connect(&socket_path) { + Ok(_) => continue, // Active compositor found - skip + Err(e) if e.kind() == ErrorKind::ConnectionRefused => { + // Stale socket - safe to remove since we hold the lock + let _ = fs::remove_file(&socket_path); + } + Err(_) => continue, // Transient error - conservative skip + } + } + + // Found viable candidate: lock held, socket cleared/available + return Some(socket_path); + } + + None // Exhausted all conventional display numbers } +pub enum Message { + Frame(Arc), + ReleaseBuffer(Arc), + #[cfg(feature = "dmabuf")] + DmabufImportSuccess(Arc, Arc), + #[cfg(feature = "dmabuf")] + DmabufImportFailure(Arc), + CloseToplevel(Arc), + ResizeToplevel { + toplevel: Arc, + size: Option>, + }, + SetToplevelVisualActive { + toplevel: Arc, + active: bool, + }, + Seat(SeatMessage), +} + +pub type MessageSink = mpsc::UnboundedSender; + +#[derive(Debug)] +struct WaylandClient { + abort_handle: AbortHandle, +} +impl WaylandClient { + pub fn from_stream(socket: UnixStream) -> Result { + let pid = socket.peer_cred().ok().and_then(|c| c.pid()); + let mut client = server::Client::new(socket)?; + let (message_sink, message_source) = mpsc::unbounded_channel(); + + client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid)); + let abort_handle = task::new( + || "wayland client", + Self::handle_client_messages(client, message_source), + )? + .abort_handle(); + + Ok(WaylandClient { abort_handle }) + } + async fn handle_client_messages( + mut client: server::Client, + mut render_message_rx: mpsc::UnboundedReceiver, + ) -> Result<()> { + loop { + tokio::select! { + // send all queued up messages + msg = render_message_rx.recv() => { + if let Some(msg) = msg { + Self::handle_render_message(&mut client, msg).await?; + } + } + // handle the next message + msg = client.next_message() => { + match msg { + Ok(Some(mut msg)) => { + if let Err(e) = client.handle_message(&mut msg).await { + tracing::error!("Wayland: Error handling message: {:?}", e); + break; + } + } + Err(e) => { + // wayland clients really aren't nice when disconnecting properly, are they? :p + if let server::Error::Decode(DecodeError::IoError(e)) = &e { + if e.kind() == io::ErrorKind::ConnectionReset { + if let Some(pid) = client.get::(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"); + } + break; + } + } + tracing::error!("Wayland: Error reading message: {:?}", e); + break; + } + Ok(None) => { + if let Some(pid) = client.get::(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; + } + } + } + } + } + Ok(()) + } + + async fn handle_render_message( + client: &mut server::Client, + message: Message, + ) -> Result<(), waynest::server::Error> { + match message { + Message::Frame(callback) => { + let serial = client.next_event_serial(); + callback.done(client, callback.0, serial).await?; + client + .get::(ObjectId::DISPLAY) + .unwrap() + .delete_id(client, ObjectId::DISPLAY, callback.0.as_raw()) + .await?; + client.remove(callback.0); + Ok(()) + } + #[cfg(feature = "dmabuf")] + Message::DmabufImportSuccess(params, buffer) => { + params.created(client, params.id, buffer.id).await + } + #[cfg(feature = "dmabuf")] + Message::DmabufImportFailure(params) => { + client.remove(params.id); + params.failed(client, params.id).await + } + Message::ReleaseBuffer(buffer) => buffer.release(client, buffer.id).await, + Message::CloseToplevel(toplevel) => toplevel.close(client, toplevel.id).await, + Message::ResizeToplevel { toplevel, size } => { + toplevel.set_size(size); + toplevel.reconfigure(client).await + } + Message::SetToplevelVisualActive { toplevel, active } => { + toplevel.set_activated(active); + toplevel.reconfigure(client).await + } + Message::Seat(seat_message) => { + if let Some(seat) = client.get::(ObjectId::DISPLAY).unwrap().seat.get() { + seat.handle_message(client, seat_message).await?; + } + Ok(()) + } + } + } +} +impl Drop for WaylandClient { + fn drop(&mut self) { + self.abort_handle.abort(); + } +} + +#[derive(Debug, Resource)] pub struct Wayland { - flush_notify: Arc, - client_listener: AbortHandle, - client_dispatcher: AbortHandle, - renderer: GlesRenderer, - output: Output, - dmabuf_rx: UnboundedReceiver<(Dmabuf, Option)>, + abort_handle: AbortHandle, } impl Wayland { - pub fn new() -> Result { - let egl_raw_handles = get_sk_egl()?; - let renderer = unsafe { - GlesRenderer::new(EGLContext::from_raw( - egl_raw_handles.display, - egl_raw_handles.config, - egl_raw_handles.context, - )?)? + pub fn new(socket_path: Option) -> Result { + let socket_path = if let Some(path) = socket_path { + path + } else { + get_free_wayland_socket_path().ok_or(ServerError::WaylandError( + waynest::server::Error::IoError(std::io::ErrorKind::AddrNotAvailable.into()), + ))? }; - let display: Display = Display::new()?; - let display_handle = display.handle(); + let _ = WAYLAND_DISPLAY.set(socket_path.clone()); - let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel(); + let listener = + server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?; - let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx); - let output = wayland_state.lock().output.clone(); + let abort_handle = + task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle(); - let socket = ListeningSocket::bind_auto("wayland", 0..33)?; - let socket_name = socket - .socket_name() - .and_then(OsStr::to_str) - .map(ToString::to_string); - if let Some(socket_name) = &socket_name { - let _ = WAYLAND_DISPLAY.set(socket_name.clone()); - } - info!(socket_name, "Wayland active"); - - let flush_notify = Arc::new(Notify::new()); - let client_listener = task::new( - || "Wayland client listener loop", - Wayland::client_listener_loop(display_handle, socket, wayland_state.clone()), - )? - .abort_handle(); - let client_dispatcher = task::new( - || "Wayland dispatch client loop", - Wayland::dispatch_client_loop(display, flush_notify.clone(), wayland_state), - )? - .abort_handle(); - - Ok(Wayland { - flush_notify, - client_listener, - client_dispatcher, - renderer, - output, - dmabuf_rx, - }) + Ok(Self { abort_handle }) } - - async fn client_listener_loop( - mut display_handle: DisplayHandle, - socket: ListeningSocket, - state: Arc>, - ) -> Result<()> { - let async_fd = AsyncFd::new(socket.as_fd())?; + async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> { + let mut clients = Vec::new(); loop { - let mut guard = async_fd.readable().await?; - let Ok(Some(stream)) = socket.accept() else { - guard.clear_ready(); - continue; - }; - - let stream = tokio::net::UnixStream::from_std(stream)?; - let pid = stream.peer_cred().ok().and_then(|c| c.pid()); - - // New client connected - let client_state = Arc::new(ClientState { - pid, - id: OnceLock::new(), - compositor_state: Default::default(), - seat: state.lock().seat.clone(), - }); - let _client = display_handle.insert_client(stream.into_std()?, client_state.clone())?; - } - } - - async fn dispatch_client_loop( - mut display: Display, - flush_notify: Arc, - state: Arc>, - ) -> std::io::Result<()> { - loop { - let poll_fd = display.backend().poll_fd(); - let async_fd = AsyncFd::new(poll_fd)?; - tokio::select! { - biased; - _ = async_fd.readable() => { - drop(async_fd); - let _span = debug_span!("Dispatch wayland event"); - let _span = _span.enter(); - let _ = display.dispatch_clients(&mut *state.lock()); - let _ = display.flush_clients(); - } - _ = flush_notify.notified() => { - drop(async_fd); - let _ = display.flush_clients(); - }, + if let Ok(Some(stream)) = listener.try_next().await { + debug_span!("Accept wayland client").in_scope(|| { + if let Ok(client) = WaylandClient::from_stream(stream) { + clients.push(client); + } + }); } + clients.retain(|client| !client.abort_handle.is_finished()); + } + + #[allow(unreachable_code)] + Ok(()) + } + + pub fn early_frame(graphics_info: &mut GraphicsInfo) { + for buffer in BUFFER_REGISTRY.get_valid_contents() { + buffer.init_tex(graphics_info); + } + for surface in WL_SURFACE_REGISTRY.get_valid_contents() { + surface.frame_event(); } } - #[instrument(level = "debug", name = "Wayland frame", skip(self))] - pub fn update(&mut self) { - while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() { - if self.renderer.import_dmabuf(&dmabuf, None).is_err() { - if let Some(notifier) = notifier { - notifier.failed(); - } - } - } - for core_surface in CORE_SURFACES.get_valid_contents() { - core_surface.process(&mut self.renderer); - } - let _ = self.renderer.cleanup_texture_cache(); - - self.flush_notify.notify_waiters(); - } - - pub fn frame_event(&self) { - for core_surface in CORE_SURFACES.get_valid_contents() { - core_surface.frame(self.output.clone()); - } - } - - pub fn make_context_current(&self) { - unsafe { - let _ = self.renderer.egl_context().make_current(); + #[instrument(level = "debug", name = "Wayland frame", skip_all)] + pub fn update_graphics(materials: &mut Assets, images: &mut Assets) { + for surface in WL_SURFACE_REGISTRY.get_valid_contents() { + surface.update_graphics(materials, images); } } } impl Drop for Wayland { fn drop(&mut self) { - self.client_listener.abort(); - self.client_dispatcher.abort(); + self.abort_handle.abort(); } } diff --git a/src/wayland/seat.rs b/src/wayland/seat.rs deleted file mode 100644 index d8d85f2..0000000 --- a/src/wayland/seat.rs +++ /dev/null @@ -1,313 +0,0 @@ -use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt}; -use crate::{ - core::task, - nodes::items::panel::{Backend, Geometry, KEYMAPS, PanelItem}, -}; -use mint::Vector2; -use parking_lot::Mutex; -use rustc_hash::FxHashMap; -use slotmap::KeyData; -use smithay::{ - backend::input::{AxisRelativeDirection, ButtonState, KeyState}, - delegate_seat, - input::{ - Seat, SeatHandler, - keyboard::{FilterResult, LedState}, - pointer::{AxisFrame, ButtonEvent, CursorImageStatus, CursorImageSurfaceData, MotionEvent}, - touch::{self, DownEvent, UpEvent}, - }, - reexports::wayland_server::{Resource, Weak as WlWeak, protocol::wl_surface::WlSurface}, - utils::SERIAL_COUNTER, - wayland::compositor, -}; -use std::sync::{Arc, Weak}; -use tokio::sync::watch; - -impl SeatHandler for WaylandState { - type PointerFocus = WlSurface; - type KeyboardFocus = WlSurface; - type TouchFocus = WlSurface; - - fn seat_state(&mut self) -> &mut smithay::input::SeatState { - &mut self.seat_state - } - fn focus_changed(&mut self, _seat: &Seat, _focused: Option<&Self::KeyboardFocus>) {} - fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { - self.seat.cursor_info_tx.send_modify(|c| match image { - CursorImageStatus::Hidden => c.surface = None, - CursorImageStatus::Surface(surface) => { - CoreSurface::add_to(&surface); - compositor::with_states(&surface, |data| { - if let Some(cursor_attributes) = data.data_map.get::() { - let hotspot = cursor_attributes.lock().unwrap().hotspot; - c.hotspot_x = hotspot.x; - c.hotspot_y = hotspot.y; - } - if let Some(core_surface) = data.data_map.get::>() { - core_surface.set_material_offset(1); - } - }); - c.surface = Some(surface.downgrade()) - } - _ => (), - }); - } - fn led_state_changed(&mut self, _seat: &Seat, _led_state: LedState) {} -} -delegate_seat!(WaylandState); - -pub fn handle_cursor( - panel_item: &Arc>, - mut cursor: watch::Receiver, -) { - let panel_item_weak = Arc::downgrade(panel_item); - let _ = task::new(|| "cursor handler", async move { - while cursor.changed().await.is_ok() { - let Some(panel_item) = panel_item_weak.upgrade() else { - continue; - }; - let cursor_info = cursor.borrow(); - panel_item.set_cursor(cursor_info.cursor_data()); - } - }); -} -pub struct CursorInfo { - pub surface: Option>, - pub hotspot_x: i32, - pub hotspot_y: i32, -} -impl CursorInfo { - pub fn cursor_data(&self) -> Option { - let cursor_size = self.surface.as_ref()?.upgrade().ok()?.get_size()?; - Some(Geometry { - origin: [self.hotspot_x, self.hotspot_y].into(), - size: cursor_size, - }) - } -} - -pub struct SeatWrapper { - wayland_state: Weak>, - cursor_info_tx: watch::Sender, - pub cursor_info_rx: watch::Receiver, - seat: Seat, - touches: Mutex>>, -} -impl SeatWrapper { - pub fn new(wayland_state: Weak>, seat: Seat) -> Self { - let (cursor_info_tx, cursor_info_rx) = watch::channel(CursorInfo { - surface: None, - hotspot_x: 0, - hotspot_y: 0, - }); - SeatWrapper { - wayland_state, - cursor_info_tx, - cursor_info_rx, - seat, - touches: Mutex::new(FxHashMap::default()), - } - } - pub fn unfocus_internal_state(&self, surface: &WlSurface) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - self.unfocus(surface, &mut state.lock()); - } - pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) { - let pointer = self.seat.get_pointer().unwrap(); - if pointer.current_focus() == Some(surface.clone()) { - pointer.motion( - state, - None, - &MotionEvent { - location: (0.0, 0.0).into(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }, - ) - } - let keyboard = self.seat.get_keyboard().unwrap(); - if keyboard.current_focus() == Some(surface.clone()) { - keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial()); - } - for (id, touch_surface) in self.touches.lock().iter() { - if touch_surface.id() == surface.id() { - self.touch_up(*id); - } - } - } - - pub fn pointer_motion(&self, surface: WlSurface, position: Vector2) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let mut state = state.lock(); - let Some(pointer) = self.seat.get_pointer() else { - return; - }; - pointer.motion( - &mut state, - Some((surface, (0.0, 0.0).into())), - &MotionEvent { - location: (position.x as f64, position.y as f64).into(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }, - ); - pointer.frame(&mut state); - } - pub fn pointer_button(&self, button: u32, pressed: bool) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let mut state = state.lock(); - let Some(pointer) = self.seat.get_pointer() else { - return; - }; - pointer.button( - &mut state, - &ButtonEvent { - button, - state: if pressed { - ButtonState::Pressed - } else { - ButtonState::Released - }, - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }, - ); - pointer.frame(&mut state); - } - pub fn pointer_scroll( - &self, - scroll_distance: Option>, - scroll_steps: Option>, - ) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let mut state = state.lock(); - let Some(pointer) = self.seat.get_pointer() else { - return; - }; - pointer.axis( - &mut state, - AxisFrame { - source: None, - relative_direction: ( - AxisRelativeDirection::Identical, - AxisRelativeDirection::Identical, - ), - time: 0, - axis: scroll_distance - .map(|d| (d.x as f64, d.y as f64)) - .unwrap_or((0.0, 0.0)), - v120: scroll_steps.map(|d| ((d.x * 120.0) as i32, (d.y * 120.0) as i32)), - stop: (false, false), - }, - ); - pointer.frame(&mut state); - } - - pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let Some(keyboard) = self.seat.get_keyboard() else { - return; - }; - let keymaps = KEYMAPS.lock(); - let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()).cloned() else { - return; - }; - - keyboard.set_focus( - &mut state.lock(), - Some(surface), - SERIAL_COUNTER.next_serial(), - ); - if keyboard - .set_keymap_from_string(&mut state.lock(), keymap) - .is_err() - { - return; - } - keyboard.input( - &mut state.lock(), - key.into(), - if pressed { - KeyState::Pressed - } else { - KeyState::Released - }, - SERIAL_COUNTER.next_serial(), - 0, - |_, _, _| FilterResult::Forward::<()>, - ); - } - - pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let Some(touch) = self.seat.get_touch() else { - return; - }; - touch.down( - &mut state.lock(), - Some((surface, (0.0, 0.0).into())), - &DownEvent { - slot: Some(id).into(), - location: (position.x as f64, position.y as f64).into(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }, - ); - touch.frame(&mut state.lock()); - } - pub fn touch_move(&self, id: u32, position: Vector2) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let Some(surface) = self.touches.lock().get(&id).and_then(|c| c.upgrade().ok()) else { - return; - }; - let Some(touch) = self.seat.get_touch() else { - return; - }; - touch.motion( - &mut state.lock(), - Some((surface, (0.0, 0.0).into())), - &touch::MotionEvent { - slot: Some(id).into(), - location: (position.x as f64, position.y as f64).into(), - time: 0, - }, - ); - touch.frame(&mut state.lock()); - } - pub fn touch_up(&self, id: u32) { - let Some(state) = self.wayland_state.upgrade() else { - return; - }; - let Some(touch) = self.seat.get_touch() else { - return; - }; - touch.up( - &mut state.lock(), - &UpEvent { - slot: Some(id).into(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }, - ); - touch.frame(&mut state.lock()); - } - pub fn reset_input(&self) { - for id in self.touches.lock().keys() { - self.touch_up(*id) - } - } -} diff --git a/src/wayland/state.rs b/src/wayland/state.rs deleted file mode 100644 index c8794fe..0000000 --- a/src/wayland/state.rs +++ /dev/null @@ -1,230 +0,0 @@ -use super::seat::SeatWrapper; -use crate::wayland::drm::wl_drm::WlDrm; -use parking_lot::Mutex; -use smithay::{ - backend::{ - allocator::{Fourcc, dmabuf::Dmabuf}, - egl::EGLDevice, - renderer::gles::GlesRenderer, - }, - delegate_dmabuf, delegate_output, delegate_shm, delegate_viewporter, - desktop::PopupManager, - input::{SeatState, keyboard::XkbConfig}, - output::{Mode, Output, Scale, Subpixel}, - reexports::{ - wayland_protocols::xdg::{ - decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, - shell::server::xdg_toplevel::WmCapabilities, - }, - wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode, - wayland_server::{ - DisplayHandle, - backend::{ClientData, ClientId, DisconnectReason}, - protocol::{ - wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager, - wl_output::WlOutput, - }, - }, - }, - utils::{Size, Transform}, - wayland::{ - buffer::BufferHandler, - compositor::{CompositorClientState, CompositorState}, - dmabuf::{ - self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, - }, - output::OutputHandler, - shell::{ - kde::decoration::KdeDecorationState, - xdg::{WmCapabilitySet, XdgShellState}, - }, - shm::{ShmHandler, ShmState}, - viewporter::ViewporterState, - }, -}; -use std::sync::{Arc, OnceLock}; -use tokio::sync::mpsc::UnboundedSender; -use tracing::{info, warn}; - -pub struct ClientState { - pub pid: Option, - pub id: OnceLock, - pub compositor_state: CompositorClientState, - pub seat: Arc, -} -impl ClientData for ClientState { - fn initialized(&self, client_id: ClientId) { - info!("Wayland client {:?} connected", client_id); - let _ = self.id.set(client_id); - } - - fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) { - info!( - "Wayland client {:?} disconnected because {:#?}", - client_id, reason - ); - } -} - -pub struct WaylandState { - pub compositor_state: CompositorState, - // pub xdg_activation_state: XdgActivationState, - pub kde_decoration_state: KdeDecorationState, - pub shm_state: ShmState, - dmabuf_state: (DmabufState, DmabufGlobal, Option), - pub _viewporter_state: ViewporterState, - pub drm_formats: Vec, - pub dmabuf_tx: UnboundedSender<(Dmabuf, Option)>, - pub seat_state: SeatState, - pub seat: Arc, - pub xdg_shell: XdgShellState, - pub popup_manager: PopupManager, - pub output: Output, -} - -impl WaylandState { - pub fn new( - display_handle: DisplayHandle, - renderer: &GlesRenderer, - dmabuf_tx: UnboundedSender<(Dmabuf, Option)>, - ) -> Arc> { - let compositor_state = CompositorState::new::(&display_handle); - // let xdg_activation_state = XdgActivationState::new::(&display_handle); - let kde_decoration_state = - KdeDecorationState::new::(&display_handle, DecorationMode::Server); - let shm_state = ShmState::new::(&display_handle, vec![]); - let render_node = EGLDevice::device_for_display(renderer.egl_context().display()) - .and_then(|device| device.try_get_render_node()); - let dmabuf_formats = renderer - .egl_context() - .dmabuf_render_formats() - .iter() - .cloned() - .collect::>(); - let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect(); - - let dmabuf_default_feedback = match render_node { - Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone()) - .build() - .ok(), - Ok(None) => { - warn!("failed to query render node, dmabuf will use v3"); - None - } - Err(err) => { - warn!(?err, "failed to egl device for display, dmabuf will use v3"); - None - } - }; - // if we failed to build dmabuf feedback we fall back to dmabuf v3 - // Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display) - let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback { - let mut dmabuf_state = DmabufState::new(); - let dmabuf_global = dmabuf_state.create_global_with_default_feedback::( - &display_handle, - &default_feedback, - ); - (dmabuf_state, dmabuf_global, Some(default_feedback)) - } else { - let mut dmabuf_state = DmabufState::new(); - let dmabuf_global = - dmabuf_state.create_global::(&display_handle, dmabuf_formats.clone()); - (dmabuf_state, dmabuf_global, None) - }; - - let mut seat_state = SeatState::new(); - let mut seat = seat_state.new_wl_seat(&display_handle, "seat0"); - seat.add_pointer(); - seat.add_keyboard(XkbConfig::default(), 200, 25).unwrap(); - seat.add_touch(); - - let output = Output::new( - "1x".to_owned(), - smithay::output::PhysicalProperties { - size: Size::default(), - subpixel: Subpixel::None, - make: "Virtual XR Display".to_owned(), - model: "Your Headset Name Here".to_owned(), - }, - ); - let _output_global = output.create_global::(&display_handle); - let mode = Mode { - size: (1024, 1024).into(), - refresh: 60000, - }; - output.change_current_state( - Some(mode), - Some(Transform::Normal), - Some(Scale::Integer(2)), - None, - ); - output.set_preferred(mode); - - let mut xdg_shell = XdgShellState::new::(&display_handle); - let _viewporter_state = ViewporterState::new::(&display_handle); - let popup_manager = PopupManager::default(); - let mut capabilities = WmCapabilitySet::default(); - capabilities.set(WmCapabilities::Maximize); - capabilities.set(WmCapabilities::Fullscreen); - capabilities.unset(WmCapabilities::Minimize); - capabilities.unset(WmCapabilities::WindowMenu); - xdg_shell.replace_capabilities(capabilities); - display_handle.create_global::(3, ()); - display_handle.create_global::(1, ()); - display_handle.create_global::(2, ()); - - info!("Init Wayland compositor"); - - Arc::new_cyclic(|weak| { - Mutex::new(WaylandState { - compositor_state, - // xdg_activation_state, - kde_decoration_state, - shm_state, - drm_formats, - dmabuf_state, - dmabuf_tx, - seat_state, - seat: Arc::new(SeatWrapper::new(weak.clone(), seat)), - xdg_shell, - popup_manager, - output, - _viewporter_state, - }) - }) - } -} -impl Drop for WaylandState { - fn drop(&mut self) { - info!("Cleanly shut down the Wayland compositor"); - } -} -impl BufferHandler for WaylandState { - fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} -} -impl ShmHandler for WaylandState { - fn shm_state(&self) -> &ShmState { - &self.shm_state - } -} -impl DmabufHandler for WaylandState { - fn dmabuf_state(&mut self) -> &mut DmabufState { - &mut self.dmabuf_state.0 - } - - fn dmabuf_imported( - &mut self, - _global: &DmabufGlobal, - dmabuf: Dmabuf, - notifier: dmabuf::ImportNotifier, - ) { - self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap(); - } -} -impl OutputHandler for WaylandState { - fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {} -} -delegate_dmabuf!(WaylandState); -delegate_shm!(WaylandState); -delegate_output!(WaylandState); -delegate_viewporter!(WaylandState); diff --git a/src/wayland/surface.rs b/src/wayland/surface.rs deleted file mode 100644 index 441379a..0000000 --- a/src/wayland/surface.rs +++ /dev/null @@ -1,202 +0,0 @@ -use super::utils::WlSurfaceExt; -use crate::{ - core::{delta::Delta, destroy_queue, registry::Registry}, - nodes::{ - drawable::{ - model::{MaterialWrapper, ModelPart}, - shaders::PANEL_SHADER_BYTES, - }, - items::camera::TexWrapper, - }, -}; -use parking_lot::Mutex; -use send_wrapper::SendWrapper; -use smithay::{ - backend::renderer::{ - Renderer, Texture, - gles::{GlesRenderer, GlesTexture}, - utils::{RendererSurfaceStateUserData, import_surface_tree}, - }, - desktop::utils::send_frames_surface_tree, - output::Output, - reexports::wayland_server::{self, Resource, protocol::wl_surface::WlSurface}, -}; -use std::{ - ffi::c_void, - sync::{Arc, OnceLock}, - time::Duration, -}; -use stereokit_rust::{ - material::{Material, Transparency}, - shader::Shader, - tex::{Tex, TexAddress, TexFormat, TexSample, TexType}, - util::Time, -}; - -pub static CORE_SURFACES: Registry = Registry::new(); - -pub struct CoreSurfaceData { - wl_tex: Option>, -} -impl Drop for CoreSurfaceData { - fn drop(&mut self) { - destroy_queue::add(self.wl_tex.take()); - } -} - -pub struct CoreSurface { - pub weak_surface: wayland_server::Weak, - mapped_data: Mutex>, - sk_tex: OnceLock>, - sk_mat: OnceLock>, - material_offset: Mutex>, - pub pending_material_applications: Registry, -} - -impl CoreSurface { - pub fn add_to(surface: &WlSurface) { - let core_surface = CORE_SURFACES.add(CoreSurface { - weak_surface: surface.downgrade(), - mapped_data: Mutex::new(None), - sk_tex: OnceLock::new(), - sk_mat: OnceLock::new(), - material_offset: Mutex::new(Delta::new(0)), - pending_material_applications: Registry::new(), - }); - surface.insert_data(core_surface); - } - - pub fn from_wl_surface(surf: &WlSurface) -> Option> { - surf.get_data() - } - - pub fn process(&self, renderer: &mut GlesRenderer) { - let Some(wl_surface) = self.wl_surface() else { - return; - }; - - let sk_tex = self.sk_tex.get_or_init(|| { - Mutex::new(TexWrapper(Tex::new( - TexType::ImageNomips, - TexFormat::RGBA32Linear, - nanoid::nanoid!(), - ))) - }); - self.sk_mat.get_or_init(|| { - let shader = Shader::from_memory(PANEL_SHADER_BYTES).unwrap(); - // let _ = renderer.with_context(|c| unsafe { - // shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR) - // }); - - let mut mat = Material::new(shader, None); - mat.diffuse_tex(&sk_tex.lock().0); - mat.transparency(Transparency::Blend); - Mutex::new(MaterialWrapper(mat)) - }); - - // Import all surface buffers into textures - if let Err(err) = import_surface_tree(renderer, &wl_surface) { - tracing::error!( - "Failed to import surface tree for surface {}: {}", - wl_surface.id(), - err - ); - return; - } - - self.update_textures(renderer); - self.apply_surface_materials(); - } - - pub fn update_textures(&self, renderer: &mut GlesRenderer) { - let Some(wl_surface) = self.wl_surface() else { - return; - }; - - let Some(smithay_tex) = wl_surface - .get_data_raw::(|surface_states| { - let locked = surface_states.lock().unwrap(); - locked.texture::(renderer.id()).cloned() - }) - .flatten() - else { - return; - }; - - let Some(sk_tex) = self.sk_tex.get() else { - tracing::error!("No sk_tex found for surface"); - return; - }; - let Some(sk_mat) = self.sk_mat.get() else { - tracing::error!("No sk_mat found for surface"); - return; - }; - sk_tex - .lock() - .0 - .set_native_surface( - smithay_tex.tex_id() as usize as *mut c_void, - TexType::ImageNomips, - smithay::backend::renderer::gles::ffi::RGBA8.into(), - smithay_tex.width() as i32, - smithay_tex.height() as i32, - 1, - false, - ) - .sample_mode(TexSample::Point) - .address_mode(TexAddress::Clamp); - - if let Some(material_offset) = self.material_offset.lock().delta() { - sk_mat.lock().0.queue_offset(*material_offset as i32); - } - - let new_mapped_data = CoreSurfaceData { - wl_tex: Some(SendWrapper::new(smithay_tex)), - }; - *self.mapped_data.lock() = Some(new_mapped_data); - } - - pub fn frame(&self, output: Output) { - let Some(wl_surface) = self.wl_surface() else { - return; - }; - - send_frames_surface_tree( - &wl_surface, - &output, - Duration::from_secs_f64(Time::get_total_unscaled()), - None, - |_, _| Some(output.clone()), - ); - } - - pub fn set_material_offset(&self, material_offset: u32) { - *self.material_offset.lock().value_mut() = material_offset; - } - - pub fn apply_material(&self, model_part: &Arc) { - self.pending_material_applications.add_raw(model_part) - } - - fn apply_surface_materials(&self) { - if let Some(sk_mat) = self.sk_mat.get() { - let sk_mat = sk_mat.lock(); - for model_node in self.pending_material_applications.get_valid_contents() { - model_node.replace_material_now(&sk_mat.0); - } - self.pending_material_applications.clear(); - } - } - - pub fn wl_surface(&self) -> Option { - self.weak_surface.upgrade().ok() - } -} -impl Drop for CoreSurface { - fn drop(&mut self) { - CORE_SURFACES.remove(self); - - destroy_queue::add(self.sk_tex.take()); - destroy_queue::add(self.sk_mat.take()); - } -} diff --git a/src/wayland/util.rs b/src/wayland/util.rs new file mode 100644 index 0000000..c666b34 --- /dev/null +++ b/src/wayland/util.rs @@ -0,0 +1,42 @@ +#![allow(unused)] + +use super::{MessageSink, core::display::Display}; +use std::{fmt::Debug, sync::Arc}; +use waynest::{server::Client, wire::ObjectId}; + +pub trait ClientExt { + fn message_sink(&self) -> MessageSink; + fn display(&self) -> Arc; +} +impl ClientExt for Client { + fn message_sink(&self) -> MessageSink { + self.get::(ObjectId::DISPLAY) + .unwrap() + .message_sink + .clone() + } + + fn display(&self) -> Arc { + self.get::(ObjectId::DISPLAY).unwrap() + } +} + +#[derive(Debug, Default)] +pub struct DoubleBuffer { + current: State, + pub pending: State, +} +impl DoubleBuffer { + pub fn new(initial_state: State) -> Self { + DoubleBuffer { + current: initial_state.clone(), + pending: initial_state, + } + } + pub fn apply(&mut self) { + self.current = self.pending.clone(); + } + pub fn current(&self) -> &State { + &self.current + } +} diff --git a/src/wayland/utils.rs b/src/wayland/utils.rs deleted file mode 100644 index 3625085..0000000 --- a/src/wayland/utils.rs +++ /dev/null @@ -1,121 +0,0 @@ -use mint::Vector2; -use parking_lot::Mutex; -use smithay::{ - backend::renderer::utils::RendererSurfaceStateUserData, - reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::{ - compositor, - shell::xdg::{SurfaceCachedState, XdgToplevelSurfaceData}, - }, -}; - -use crate::nodes::items::panel::{ChildInfo, Geometry, ToplevelInfo}; - -use super::xdg_shell::surface_panel_item; -pub trait WlSurfaceExt { - fn insert_data(&self, data: T) -> bool; - fn get_data(&self) -> Option; - fn get_data_raw O>(&self, f: F) -> Option; - fn get_current_surface_state(&self) -> SurfaceCachedState; - fn get_pending_surface_state(&self) -> SurfaceCachedState; - fn get_size(&self) -> Option>; - fn get_geometry(&self) -> Option; -} -impl WlSurfaceExt for WlSurface { - fn insert_data(&self, data: T) -> bool { - compositor::with_states(self, |d| { - d.data_map.insert_if_missing_threadsafe(move || data) - }) - } - fn get_data(&self) -> Option { - compositor::with_states(self, |d| d.data_map.get::().cloned()) - } - fn get_data_raw O>(&self, f: F) -> Option { - compositor::with_states(self, |d| Some((f)(d.data_map.get::()?))) - } - fn get_current_surface_state(&self) -> SurfaceCachedState { - compositor::with_states(self, |states| { - *states.cached_state.get::().current() - }) - } - fn get_pending_surface_state(&self) -> SurfaceCachedState { - compositor::with_states(self, |states| { - *states.cached_state.get::().pending() - }) - } - fn get_size(&self) -> Option> { - self.get_data_raw::(|surface_states| { - surface_states.lock().unwrap().surface_size() - }) - .flatten() - .map(|size| Vector2::from([size.w as u32, size.h as u32])) - } - fn get_geometry(&self) -> Option { - self.get_current_surface_state().geometry.map(|r| r.into()) - } -} - -pub trait ToplevelInfoExt { - fn get_toplevel_info(&self) -> Option; - fn with_toplevel_info O>(&self, f: F) -> Option; - - fn get_parent(&self) -> Option; - fn get_app_id(&self) -> Option; - fn get_title(&self) -> Option; - fn min_size(&self) -> Option>; - fn max_size(&self) -> Option>; -} -impl ToplevelInfoExt for WlSurface { - fn get_toplevel_info(&self) -> Option { - self.get_data_raw::, _, _>(|c| c.lock().clone()) - } - fn with_toplevel_info O>(&self, f: F) -> Option { - self.get_data_raw::, _, _>(|r| (f)(&mut r.lock())) - } - - fn get_parent(&self) -> Option { - self.get_data_raw::(|d| d.lock().unwrap().parent.clone()) - .flatten() - .and_then(|p| surface_panel_item(&p)) - .and_then(|p| p.node.upgrade()) - .map(|p| p.get_id()) - } - fn get_app_id(&self) -> Option { - self.get_data_raw::(|d| d.lock().ok()?.app_id.clone()) - .flatten() - } - fn get_title(&self) -> Option { - self.get_data_raw::(|d| d.lock().ok()?.title.clone()) - .flatten() - } - fn min_size(&self) -> Option> { - let state = self.get_pending_surface_state(); - let size = state.min_size; - if size.w == 0 && size.h == 0 { - None - } else { - Some(Vector2::from([size.w as u32, size.h as u32])) - } - } - fn max_size(&self) -> Option> { - let state = self.get_pending_surface_state(); - let size = state.max_size; - if size.w == 0 && size.h == 0 { - None - } else { - Some(Vector2::from([size.w as u32, size.h as u32])) - } - } -} -pub trait ChildInfoExt { - fn get_child_info(&self) -> Option; - fn with_child_info O>(&self, f: F) -> Option; -} -impl ChildInfoExt for WlSurface { - fn get_child_info(&self) -> Option { - self.get_data_raw::, _, _>(|c| c.lock().clone()) - } - fn with_child_info O>(&self, f: F) -> Option { - self.get_data_raw::, _, _>(|r| (f)(&mut r.lock())) - } -} diff --git a/src/wayland/wayland-drm.xml b/src/wayland/wayland-drm.xml deleted file mode 100644 index a710f2d..0000000 --- a/src/wayland/wayland-drm.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - Copyright © 2008-2011 Kristian Høgsberg - Copyright © 2010-2011 Intel Corporation - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that\n the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bitmask of capabilities. - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/wayland/xdg/backend.rs b/src/wayland/xdg/backend.rs new file mode 100644 index 0000000..5e140ad --- /dev/null +++ b/src/wayland/xdg/backend.rs @@ -0,0 +1,246 @@ +use super::toplevel::Toplevel; +use crate::{ + core::error::Result, + nodes::{ + drawable::model::ModelPart, + items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo}, + }, + wayland::core::surface::Surface, +}; +use mint::Vector2; +use std::sync::Arc; +use std::sync::Weak; +use tracing; + +#[derive(Debug)] +pub struct XdgBackend { + toplevel: Weak, +} + +impl XdgBackend { + pub fn new(toplevel: Arc) -> Self { + Self { + toplevel: Arc::downgrade(&toplevel), + } + } + + // Since XdgBackend is created and owned by Mapped which is owned by Toplevel, + // we can safely assume the Toplevel reference will always be valid + fn toplevel(&self) -> Arc { + self.toplevel + .upgrade() + .expect("Toplevel should always be valid while XdgBackend exists") + } + + fn surface_from_id(&self, id: SurfaceId) -> Option> { + match id { + SurfaceId::Toplevel(_) => Some(self.toplevel().surface()), + SurfaceId::Child(_) => None, + } + } +} + +impl Backend for XdgBackend { + fn start_data(&self) -> Result { + let surface_state = self.toplevel().surface().current_state(); + + let size = surface_state + .buffer + .map(|b| [b.size().x as u32, b.size().y as u32].into()) + .unwrap_or([0; 2].into()); + let toplevel = ToplevelInfo { + parent: self.toplevel().parent(), + title: self.toplevel().title(), + app_id: self.toplevel().app_id(), + size, + min_size: surface_state + .min_size + .map(|v| [v.x as f32, v.y as f32].into()), + max_size: surface_state + .max_size + .map(|v| [v.x as f32, v.y as f32].into()), + logical_rectangle: surface_state.geometry.unwrap_or(Geometry { + origin: [0; 2].into(), + size, + }), + }; + + Ok(PanelItemInitData { + cursor: None, + toplevel, + children: vec![], + pointer_grab: None, + keyboard_grab: None, + }) + } + + fn apply_cursor_material(&self, _model_part: &Arc) {} + fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc) { + if let Some(surface) = self.surface_from_id(surface) { + surface.apply_material(model_part); + } + } + + fn close_toplevel(&self) { + let _ = + self.toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::CloseToplevel( + self.toplevel().clone(), + )); + } + + fn auto_size_toplevel(&self) { + let _ = + self.toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::ResizeToplevel { + toplevel: self.toplevel().clone(), + size: None, + }); + } + + fn set_toplevel_size(&self, size: Vector2) { + let _ = + self.toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::ResizeToplevel { + toplevel: self.toplevel().clone(), + size: Some(size), + }); + } + + fn set_toplevel_focused_visuals(&self, focused: bool) { + let _ = self.toplevel().surface().message_sink.send( + crate::wayland::Message::SetToplevelVisualActive { + toplevel: self.toplevel().clone(), + active: focused, + }, + ); + } + + fn pointer_motion(&self, surface: &SurfaceId, position: Vector2) { + if let Some(surface) = self.surface_from_id(surface.clone()) { + let _ = self + .toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position }, + )); + } + } + + fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) { + if let Some(surface) = self.surface_from_id(surface.clone()) { + let _ = self + .toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::PointerButton { + surface, + button, + pressed, + }, + )); + } + } + + fn pointer_scroll( + &self, + surface: &SurfaceId, + scroll_distance: Option>, + scroll_steps: Option>, + ) { + if let Some(surface) = self.surface_from_id(surface.clone()) { + let _ = self + .toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::PointerScroll { + surface, + scroll_distance, + scroll_steps, + }, + )); + } + } + + fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) { + tracing::debug!( + "Backend: Keyboard key {} {}", + key, + if pressed { "pressed" } else { "released" } + ); + if let Some(surface) = self.surface_from_id(surface.clone()) { + let _ = self + .toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::KeyboardKey { + surface, + keymap_id, + key, + pressed, + }, + )); + } + } + + fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2) { + tracing::debug!( + "Backend: Touch down {} at ({}, {})", + id, + position.x, + position.y + ); + if let Some(surface) = self.surface_from_id(surface.clone()) { + let _ = self + .toplevel() + .surface() + .message_sink + .send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::TouchDown { + surface, + id, + position, + }, + )); + } + } + + fn touch_move(&self, id: u32, position: Vector2) { + tracing::debug!( + "Backend: Touch move {} to ({}, {})", + id, + position.x, + position.y + ); + let surface = self.toplevel().surface(); + let _ = surface.message_sink.send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::TouchMove { id, position }, + )); + } + + fn touch_up(&self, id: u32) { + tracing::debug!("Backend: Touch up {}", id); + let surface = self.toplevel().surface(); + let _ = surface.message_sink.send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::TouchUp { id }, + )); + } + + fn reset_input(&self) { + tracing::debug!("Backend: Reset input"); + let surface = self.toplevel().surface(); + let _ = surface.message_sink.send(crate::wayland::Message::Seat( + crate::wayland::core::seat::SeatMessage::Reset, + )); + } +} diff --git a/src/wayland/xdg/mod.rs b/src/wayland/xdg/mod.rs new file mode 100644 index 0000000..7a45333 --- /dev/null +++ b/src/wayland/xdg/mod.rs @@ -0,0 +1,6 @@ +pub mod backend; +pub mod popup; +pub mod positioner; +pub mod surface; +pub mod toplevel; +pub mod wm_base; diff --git a/src/wayland/xdg/popup.rs b/src/wayland/xdg/popup.rs new file mode 100644 index 0000000..84e979a --- /dev/null +++ b/src/wayland/xdg/popup.rs @@ -0,0 +1,89 @@ +use super::{ + backend::XdgBackend, + positioner::{Positioner, PositionerData}, + surface::Surface, +}; +use crate::{ + nodes::items::panel::{Geometry, PanelItem}, + wayland::util::DoubleBuffer, +}; +use parking_lot::Mutex; +use std::sync::{Arc, Weak, atomic::AtomicBool}; +use waynest::{ + server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup}, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher)] +pub struct Popup { + id: ObjectId, + parent: Weak, + surface: Weak, + pub panel_item: Weak>, + positioner_data: Mutex, + geometry: DoubleBuffer, + mapped: AtomicBool, +} +impl Popup { + pub fn new( + id: ObjectId, + parent: &Arc, + panel_item: &Arc>, + xdg_surface: &Arc, + positioner: &Positioner, + ) -> Self { + let positioner_data = positioner.data(); + Self { + id, + parent: Arc::downgrade(parent), + surface: Arc::downgrade(xdg_surface), + panel_item: Arc::downgrade(panel_item), + positioner_data: Mutex::new(positioner_data), + geometry: DoubleBuffer::new(positioner_data.infinite_geometry()), + mapped: AtomicBool::new(false), + } + } +} +impl XdgPopup for Popup { + /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab + async fn grab( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _seat: ObjectId, + _serial: u32, + ) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition + async fn reposition( + &self, + client: &mut Client, + sender_id: ObjectId, + positioner: ObjectId, + token: u32, + ) -> Result<()> { + let positioner = client.get::(positioner).unwrap(); + let positioner_data = positioner.data(); + *self.positioner_data.lock() = positioner_data; + self.repositioned(client, sender_id, token).await?; + let geometry = positioner_data.infinite_geometry(); + self.configure( + client, + sender_id, + geometry.origin.x, + geometry.origin.y, + geometry.size.x as i32, + geometry.size.y as i32, + ) + .await?; + self.surface.upgrade().unwrap().reconfigure(client).await?; + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/xdg/positioner.rs b/src/wayland/xdg/positioner.rs new file mode 100644 index 0000000..4df1992 --- /dev/null +++ b/src/wayland/xdg/positioner.rs @@ -0,0 +1,229 @@ +use crate::nodes::items::panel::Geometry; +use mint::Vector2; +use parking_lot::Mutex; +use waynest::{ + server::{ + Client, Dispatcher, Result, + protocol::stable::xdg_shell::xdg_positioner::{ + Anchor, ConstraintAdjustment, Gravity, XdgPositioner, + }, + }, + wire::ObjectId, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PositionerData { + pub size: Vector2, + pub anchor_rect: Geometry, + pub offset: Vector2, + pub anchor: Anchor, + pub constraint_adjustment: ConstraintAdjustment, + pub reactive: bool, + pub parent_size: Vector2, +} +impl PositionerData { + pub fn infinite_geometry(&self) -> Geometry { + let anchor_point = match self.anchor { + Anchor::TopLeft => self.anchor_rect.origin, + Anchor::Top => [ + self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32, + self.anchor_rect.origin.y, + ] + .into(), + Anchor::TopRight => [ + self.anchor_rect.origin.x + self.anchor_rect.size.x as i32, + self.anchor_rect.origin.y, + ] + .into(), + Anchor::Left => [ + self.anchor_rect.origin.x, + self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32, + ] + .into(), + Anchor::Right => [ + self.anchor_rect.origin.x + self.anchor_rect.size.x as i32, + self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32, + ] + .into(), + Anchor::BottomLeft => [ + self.anchor_rect.origin.x, + self.anchor_rect.origin.y + self.anchor_rect.size.y as i32, + ] + .into(), + Anchor::Bottom => [ + self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32, + self.anchor_rect.origin.y + self.anchor_rect.size.y as i32, + ] + .into(), + Anchor::BottomRight => [ + self.anchor_rect.origin.x + self.anchor_rect.size.x as i32, + self.anchor_rect.origin.y + self.anchor_rect.size.y as i32, + ] + .into(), + _ => [ + self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32, + self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32, + ] + .into(), + }; + + let mut position = anchor_point; + + // Apply gravity + if self + .constraint_adjustment + .contains(ConstraintAdjustment::FlipX) + { + 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, + } + } +} +impl Default for PositionerData { + fn default() -> Self { + Self { + size: [0; 2].into(), + anchor_rect: Default::default(), + offset: [0, 0].into(), + anchor: Anchor::TopLeft, + constraint_adjustment: ConstraintAdjustment::empty(), + reactive: false, + parent_size: [0; 2].into(), + } + } +} + +#[derive(Debug, Dispatcher)] +pub struct Positioner { + data: Mutex, +} +impl Default for Positioner { + fn default() -> Self { + Self { + data: Mutex::new(PositionerData::default()), + } + } +} +impl Positioner { + pub fn data(&self) -> PositionerData { + *self.data.lock() + } +} +impl XdgPositioner for Positioner { + async fn set_size( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _width: i32, + _height: i32, + ) -> Result<()> { + let mut data = self.data.lock(); + data.size = [_width.max(0) as u32, _height.max(0) as u32].into(); + data.reactive = true; + Ok(()) + } + + async fn set_anchor_rect( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + let mut data = self.data.lock(); + data.anchor_rect.origin = [_x, _y].into(); + data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].into(); + data.offset = [0, 0].into(); + Ok(()) + } + + async fn set_anchor( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _anchor: Anchor, + ) -> Result<()> { + let mut data = self.data.lock(); + data.anchor = _anchor; + Ok(()) + } + + async fn set_gravity( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _gravity: Gravity, + ) -> Result<()> { + Ok(()) + } + + async fn set_constraint_adjustment( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _constraint_adjustment: ConstraintAdjustment, + ) -> Result<()> { + let mut data = self.data.lock(); + data.constraint_adjustment = _constraint_adjustment; + Ok(()) + } + + async fn set_offset( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + ) -> Result<()> { + let mut data = self.data.lock(); + data.offset.x += _x; + data.offset.y += _y; + Ok(()) + } + + async fn set_reactive(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn set_parent_size( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _parent_width: i32, + _parent_height: i32, + ) -> Result<()> { + let mut data = self.data.lock(); + data.parent_size.x = _parent_width.max(0) as u32; + data.parent_size.y = _parent_height.max(0) as u32; + Ok(()) + } + + async fn set_parent_configure( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _serial: u32, + ) -> Result<()> { + Ok(()) + } + + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/xdg/surface.rs b/src/wayland/xdg/surface.rs new file mode 100644 index 0000000..b0006f0 --- /dev/null +++ b/src/wayland/xdg/surface.rs @@ -0,0 +1,206 @@ +use super::{popup::Popup, positioner::Positioner, toplevel::Mapped}; +use crate::wayland::{ + core::{display::Display, surface::SurfaceRole}, + xdg::toplevel::Toplevel, +}; +use std::sync::Arc; +use std::sync::Weak; +pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Debug, Dispatcher)] +pub struct Surface { + id: ObjectId, + wl_surface: Weak, + configured: Arc, +} +impl Surface { + pub fn new(id: ObjectId, wl_surface: Arc) -> Self { + Self { + id, + wl_surface: Arc::downgrade(&wl_surface), + configured: Arc::new(std::sync::atomic::AtomicBool::new(false)), + } + } + + pub fn wl_surface(&self) -> Arc { + // 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(); + self.configure(client, self.id, serial).await + } +} + +impl XdgSurface for Surface { + /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel + async fn get_toplevel( + &self, + client: &mut Client, + sender_id: ObjectId, + toplevel_id: ObjectId, + ) -> Result<()> { + let surface = self.wl_surface(); + let toplevel = client.insert( + toplevel_id, + Toplevel::new( + toplevel_id, + surface.clone(), + client.get::(sender_id).unwrap(), + ), + ); + + { + let mut surface_role = surface.role.lock(); + + // A surface must not have any existing role when assigning a new one + // "A surface must not have more than one role, and a role must not be assigned to more than one + // surface at a time. However, wl_surface role-specific interfaces may reassign the role, allow + // a role to be destroyed, or allow multiple role-specific interfaces to share the same role." + // - 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 serial = client.next_event_serial(); + self.configure(client, sender_id, serial).await?; + + let pid = client.get::(ObjectId::DISPLAY).unwrap().pid; + let configured = self.configured.clone(); + surface.add_commit_handler(move |surface, state| { + let Some(SurfaceRole::XdgToplevel(toplevel)) = &mut *surface.role.lock() else { + return true; + }; + + // Only proceed if configured and has valid buffer + let has_valid_buffer = state + .buffer + .as_ref() + .is_some_and(|b| b.size().x > 0 && b.size().y > 0); + + let mut mapped_lock = toplevel.mapped.lock(); + if mapped_lock.is_none() + && configured.load(std::sync::atomic::Ordering::SeqCst) + && has_valid_buffer + { + mapped_lock.replace(Mapped::create(toplevel.clone(), pid)); + return false; + } + true + }); + + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_popup + async fn get_popup( + &self, + client: &mut Client, + sender_id: ObjectId, + popup_id: ObjectId, + parent: Option, + positioner: ObjectId, + ) -> Result<()> { + let parent = client.get::(parent.unwrap()).unwrap(); + let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() { + SurfaceRole::XdgToplevel(toplevel) => { + let toplevel_lock = toplevel.mapped.lock(); + toplevel_lock.as_ref().unwrap()._panel_item.clone() + } + SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(), + }; + let positioner = client.get::(positioner).unwrap(); + + let surface = client.get::(self.id).unwrap(); + + let popup = client.insert( + popup_id, + Popup::new(popup_id, &parent, &panel_item, &surface, &positioner), + ); + + { + 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(); + self.configure(client, sender_id, serial).await?; + + // let pid = client.get::(ObjectId::DISPLAY).unwrap().pid; + // let configured = self.configured.clone(); + // surface.add_commit_handler(move |surface, state| { + // let Some(SurfaceRole::XDGPopup(popup)) = &mut *surface.role.lock() else { + // return true; + // }; + + // // Only proceed if configured and has valid buffer + // let has_valid_buffer = state + // .buffer + // .as_ref() + // .is_some_and(|b| b.size().x > 0 && b.size().y > 0); + + // let mut mapped_lock = popup.mapped.lock(); + // if mapped_lock.is_none() + // && configured.load(std::sync::atomic::Ordering::SeqCst) + // && has_valid_buffer + // { + // mapped_lock.replace(Mapped::create(popup.clone(), pid)); + // return false; + // } + // true + // }); + + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:set_window_geometry + async fn set_window_geometry( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + ) -> Result<()> { + // we're gonna delegate literally all the window management + // to 3D stuff sooo we don't care, maximized is the floating state + Ok(()) + } + + /// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure + async fn ack_configure( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _serial: u32, + ) -> Result<()> { + self.configured + .store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) + } +} diff --git a/src/wayland/xdg/toplevel.rs b/src/wayland/xdg/toplevel.rs new file mode 100644 index 0000000..72c25ae --- /dev/null +++ b/src/wayland/xdg/toplevel.rs @@ -0,0 +1,310 @@ +use super::backend::XdgBackend; +use crate::{ + nodes::{Node, items::panel::PanelItem}, + wayland::core::surface::Surface, +}; +use mint::Vector2; +use parking_lot::Mutex; +use std::sync::Arc; +use std::sync::Weak; +pub use waynest::server::protocol::stable::xdg_shell::xdg_toplevel::*; +use waynest::{ + server::{Client, Dispatcher, Result}, + wire::ObjectId, +}; + +#[derive(Debug)] +pub struct Mapped { + pub panel_item_node: Arc, + pub _panel_item: Arc>, +} +impl Mapped { + pub fn create(toplevel: Arc, pid: Option) -> Self { + let (panel_item_node, _panel_item) = + PanelItem::create(Box::new(XdgBackend::new(toplevel)), pid); + + Self { + panel_item_node, + _panel_item, + } + } +} + +#[derive(Debug, Clone)] +struct ToplevelData { + parent: Option, + app_id: Option, + title: Option, + activated: bool, + fullscreen: bool, + pub size: Option>, +} +impl Default for ToplevelData { + fn default() -> Self { + Self { + parent: None, + app_id: None, + title: None, + activated: true, + fullscreen: false, + size: None, + } + } +} + +#[derive(Debug, Dispatcher)] +pub struct Toplevel { + pub id: ObjectId, + wl_surface: Weak, + xdg_surface: Weak, + pub mapped: Mutex>, + data: Mutex, +} +impl Toplevel { + pub fn new( + object_id: ObjectId, + wl_surface: Arc, + xdg_surface: Arc, + ) -> Self { + Toplevel { + id: object_id, + wl_surface: Arc::downgrade(&wl_surface), + xdg_surface: Arc::downgrade(&xdg_surface), + mapped: Mutex::new(None), + data: Mutex::new(ToplevelData::default()), + } + } + + pub fn surface(&self) -> Arc { + // We can safely unwrap as the surface must exist for the lifetime of the toplevel + self.wl_surface + .upgrade() + .expect("Surface was dropped before toplevel") + } + + pub fn title(&self) -> Option { + self.data.lock().title.clone() + } + pub fn app_id(&self) -> Option { + self.data.lock().app_id.clone() + } + pub fn parent(&self) -> Option { + self.data.lock().parent + } + + pub fn set_size(&self, size: Option>) { + self.data.lock().size = size; + } + + pub fn set_activated(&self, activated: bool) { + self.data.lock().activated = activated; + } + + // Helper to clamp size against constraints + fn clamp_size(&self, size: Vector2) -> Vector2 { + let state = self.surface().current_state(); + let mut clamped = size; + + if let Some(min_size) = state.min_size { + clamped.x = clamped.x.max(min_size.x); + clamped.y = clamped.y.max(min_size.y); + } + if let Some(max_size) = state.max_size { + clamped.x = clamped.x.min(max_size.x); + clamped.y = clamped.y.min(max_size.y); + } + clamped + } + + pub async fn reconfigure(&self, client: &mut Client) -> Result<()> { + let data = self.data.lock().clone(); + + // Use the explicitly set size, applying constraints + let size = data.size.map(|s| self.clamp_size(s)); + + let mut states = vec![ + State::TiledTop, + State::TiledLeft, + State::TiledRight, + State::TiledBottom, + if data.fullscreen { + State::Fullscreen + } else { + State::Maximized + }, + ]; + if data.activated { + states.push(State::Activated); + } + + self.configure( + client, + self.id, + size.map(|v| v.x as i32).unwrap_or(0), + size.map(|v| v.y as i32).unwrap_or(0), + states + .into_iter() + .flat_map(|x| (x as u32).to_ne_bytes()) + .collect(), + ) + .await?; + self.xdg_surface + .upgrade() + .unwrap() + .reconfigure(client) + .await + } +} +impl XdgToplevel for Toplevel { + async fn set_parent( + &self, + client: &mut Client, + _sender_id: ObjectId, + parent: Option, + ) -> Result<()> { + // Handle case where parent is specified + if let Some(parent) = parent { + // Per spec: parent must be another xdg_toplevel surface + if let Some(parent_toplevel) = client.get::(parent) { + let Some(mapped) = &*parent_toplevel.mapped.lock() else { + // Per spec: parent surfaces must be mapped before being used as a parent + // Setting an unmapped window as parent should raise a protocol error + // For now we just unset the parent as a fallback + self.data.lock().parent.take(); + return Ok(()); + }; + + // Per spec: store parent to ensure this surface is stacked above parent + // and other ancestor surfaces. Used for proper window stacking order. + self.data + .lock() + .parent + .replace(mapped.panel_item_node.get_id()); + } + } else { + // Per spec: null parent unsets the parent, making this a top-level window + // This allows converting child windows back to independent top-level windows + self.data.lock().parent.take(); + } + + Ok(()) + } + + async fn set_title( + &self, + _client: &mut Client, + _sender_id: ObjectId, + title: String, + ) -> Result<()> { + self.data.lock().title.replace(title); + Ok(()) + } + + async fn set_app_id( + &self, + _client: &mut Client, + _sender_id: ObjectId, + app_id: String, + ) -> Result<()> { + self.data.lock().app_id.replace(app_id); + Ok(()) + } + + async fn show_window_menu( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _seat: ObjectId, + _serial: u32, + _x: i32, + _y: i32, + ) -> Result<()> { + Ok(()) + } + + async fn r#move( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _seat: ObjectId, + _serial: u32, + ) -> Result<()> { + Ok(()) + } + + async fn resize( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _seat: ObjectId, + _serial: u32, + _edges: ResizeEdge, + ) -> Result<()> { + Ok(()) + } + + async fn set_max_size( + &self, + _client: &mut Client, + _sender_id: ObjectId, + width: i32, + height: i32, + ) -> Result<()> { + self.surface().pending_state().pending.max_size = if width == 0 && height == 0 { + None + } else { + Some([width as u32, height as u32].into()) + }; + Ok(()) + } + + async fn set_min_size( + &self, + _client: &mut Client, + _sender_id: ObjectId, + width: i32, + height: i32, + ) -> Result<()> { + self.surface().pending_state().pending.min_size = if width == 0 && height == 0 { + None + } else { + Some([width as u32, height as u32].into()) + }; + Ok(()) + } + + async fn set_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn unset_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn set_fullscreen( + &self, + _client: &mut Client, + _sender_id: ObjectId, + _output: Option, + ) -> Result<()> { + Ok(()) + } + + async fn unset_fullscreen(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn set_minimized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + self.mapped.lock().take(); + Ok(()) + } +} +impl Drop for Toplevel { + fn drop(&mut self) { + self.mapped.lock().take(); + } +} diff --git a/src/wayland/xdg/wm_base.rs b/src/wayland/xdg/wm_base.rs new file mode 100644 index 0000000..113f2e6 --- /dev/null +++ b/src/wayland/xdg/wm_base.rs @@ -0,0 +1,48 @@ +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; + +#[derive(Debug, Dispatcher, Default)] +pub struct WmBase; +impl XdgWmBase for WmBase { + async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> { + Ok(()) + } + + async fn create_positioner( + &self, + client: &mut Client, + _sender_id: ObjectId, + id: ObjectId, + ) -> Result<()> { + client.insert(id, Positioner::default()); + Ok(()) + } + + async fn get_xdg_surface( + &self, + client: &mut Client, + _sender_id: ObjectId, + xdg_surface_id: ObjectId, + wl_surface_id: ObjectId, + ) -> Result<()> { + let wl_surface = client + .get::(wl_surface_id) + .ok_or(waynest::server::Error::Custom( + "can't get wayland surface id".to_string(), + ))?; + let xdg_surface = Surface::new(xdg_surface_id, wl_surface); + client.insert(xdg_surface_id, xdg_surface); + + Ok(()) + } + + async fn pong(&self, _client: &mut Client, _sender_id: ObjectId, _serial: u32) -> Result<()> { + Ok(()) + } +} diff --git a/src/wayland/xdg_activation.rs b/src/wayland/xdg_activation.rs deleted file mode 100644 index 83b3800..0000000 --- a/src/wayland/xdg_activation.rs +++ /dev/null @@ -1,34 +0,0 @@ -use smithay::{ - delegate_xdg_activation, - reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::xdg_activation::{XdgActivationHandler, XdgActivationToken, XdgActivationTokenData}, -}; - -use super::state::WaylandState; - -impl XdgActivationHandler for WaylandState { - fn activation_state(&mut self) -> &mut smithay::wayland::xdg_activation::XdgActivationState { - &mut self.xdg_activation_state - } - - fn request_activation( - &mut self, - token: XdgActivationToken, - token_data: XdgActivationTokenData, - _surface: WlSurface, - ) { - dbg!(token); - dbg!(token_data); - } - - fn destroy_activation( - &mut self, - token: XdgActivationToken, - token_data: XdgActivationTokenData, - _surface: WlSurface, - ) { - dbg!(token); - dbg!(token_data); - } -} -delegate_xdg_activation!(WaylandState); diff --git a/src/wayland/xdg_shell.rs b/src/wayland/xdg_shell.rs deleted file mode 100644 index 70d6656..0000000 --- a/src/wayland/xdg_shell.rs +++ /dev/null @@ -1,549 +0,0 @@ -use super::{ - seat::{SeatWrapper, handle_cursor}, - state::{ClientState, WaylandState}, - surface::CoreSurface, - utils::*, -}; -use crate::{ - core::error::Result, - nodes::{ - drawable::model::ModelPart, - items::panel::{ - Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo, - }, - }, -}; -use color_eyre::eyre::eyre; -use mint::Vector2; -use parking_lot::Mutex; -use rand::Rng; -use rustc_hash::FxHashMap; -use smithay::{ - delegate_xdg_shell, - desktop::PopupKind, - reexports::{ - wayland_protocols::xdg::{ - decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, - shell::server::xdg_toplevel::{ResizeEdge, State}, - }, - wayland_server::{ - Resource, - protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface}, - }, - }, - utils::{Logical, Rectangle, Serial}, - wayland::{ - compositor::add_post_commit_hook, - shell::xdg::{ - PopupSurface, PositionerState, ShellClient, ToplevelSurface, XdgShellHandler, - XdgShellState, - }, - }, -}; -use std::sync::{Arc, Weak}; -use tracing::warn; - -fn get_unconstrained_popup_geometry(positioner: &PositionerState) -> Geometry { - positioner - .get_unconstrained_geometry(Rectangle { - loc: (-100000, -100000).into(), - size: (200000, 200000).into(), - }) - .into() -} - -impl From> for Geometry { - fn from(value: Rectangle) -> Self { - Geometry { - origin: [value.loc.x, value.loc.y].into(), - size: [value.size.w as u32, value.size.h as u32].into(), - } - } -} - -pub fn surface_panel_item(wl_surface: &WlSurface) -> Option>> { - let panel_item = wl_surface - .get_data::>>() - .as_ref() - .and_then(Weak::upgrade); - if panel_item.is_none() { - warn!("Couldn't get panel item"); - } - panel_item -} - -impl XdgShellHandler for WaylandState { - fn xdg_shell_state(&mut self) -> &mut XdgShellState { - &mut self.xdg_shell - } - - fn new_client(&mut self, _client: ShellClient) {} - fn client_destroyed(&mut self, _client: ShellClient) {} - - fn new_toplevel(&mut self, toplevel: ToplevelSurface) { - toplevel.wl_surface().insert_data(SurfaceId::Toplevel(())); - toplevel.with_pending_state(|s| { - s.decoration_mode = Some(Mode::ServerSide); - s.states.set(State::TiledTop); - s.states.set(State::TiledBottom); - s.states.set(State::TiledLeft); - s.states.set(State::TiledRight); - s.states.set(State::Maximized); - s.states.unset(State::Fullscreen); - }); - - let initial_size = toplevel - .wl_surface() - .get_size() - .unwrap_or(Vector2::from([0; 2])); - - let initial_toplevel_info = ToplevelInfo { - parent: toplevel.wl_surface().get_parent(), - title: toplevel.wl_surface().get_title(), - app_id: toplevel.wl_surface().get_app_id(), - size: initial_size, - min_size: toplevel - .wl_surface() - .min_size() - .map(|s| Vector2::from([s.x as f32, s.y as f32])), - max_size: toplevel - .wl_surface() - .max_size() - .map(|s| Vector2::from([s.x as f32, s.y as f32])), - logical_rectangle: toplevel.wl_surface().get_geometry().unwrap_or(Geometry { - origin: [0; 2].into(), - size: initial_size, - }), - }; - toplevel - .wl_surface() - .insert_data(Mutex::new(initial_toplevel_info)); - - CoreSurface::add_to(toplevel.wl_surface()); - - add_post_commit_hook( - toplevel.wl_surface(), - |_state: &mut WaylandState, _dh, surf| { - let parent = surf.get_parent(); - let new_size = surf.get_size().unwrap_or(Vector2::from([0; 2])); - let min_size = surf - .min_size() - .map(|s| Vector2::from([s.x as f32, s.y as f32])); - let max_size = surf - .max_size() - .map(|s| Vector2::from([s.x as f32, s.y as f32])); - let logical_rectangle = surf.get_geometry().unwrap_or_default(); - - let mut size_changed = false; - surf.with_toplevel_info(|info| { - info.parent = parent; - if new_size != info.size { - info.size = new_size; - size_changed = true; - } - info.min_size = min_size; - info.max_size = max_size; - info.logical_rectangle = logical_rectangle; - }); - - if size_changed { - let Some(panel_item) = surface_panel_item(surf) else { - return; - }; - if let Some(toplevel_info) = surf.get_toplevel_info() { - panel_item.toplevel_size_changed(toplevel_info.size); - } - } - }, - ); - - add_post_commit_hook( - toplevel.wl_surface(), - |state: &mut WaylandState, _dh, surf| { - if surface_panel_item(surf).is_some() { - return; - } - let client = surf.client().unwrap(); - let client_state = client.get_data::().unwrap(); - let Some(toplevel) = state - .xdg_shell - .toplevel_surfaces() - .iter() - .find(|s| s.wl_surface() == surf) - else { - return; - }; - let (node, panel_item) = PanelItem::create( - Box::new(XdgBackend::create( - toplevel.clone(), - client_state.seat.clone(), - )), - client_state.pid, - ); - handle_cursor(&panel_item, panel_item.backend.seat.cursor_info_rx.clone()); - surf.insert_data(Arc::downgrade(&panel_item)); - surf.insert_data(node); - }, - ); - } - fn toplevel_destroyed(&mut self, toplevel: ToplevelSurface) { - let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else { - return; - }; - panel_item.backend.seat.unfocus(toplevel.wl_surface(), self); - panel_item.backend.toplevel.lock().take(); - panel_item.backend.children.lock().clear(); - } - fn app_id_changed(&mut self, toplevel: ToplevelSurface) { - let wl_surface = toplevel.wl_surface(); - let Some(app_id) = wl_surface.get_app_id() else { - return; - }; - - wl_surface.with_toplevel_info(|info| { - info.app_id = Some(app_id.clone()); - }); - - let Some(panel_item) = surface_panel_item(wl_surface) else { - return; - }; - panel_item.toplevel_app_id_changed(&app_id) - } - - fn title_changed(&mut self, toplevel: ToplevelSurface) { - let wl_surface = toplevel.wl_surface(); - let Some(title) = wl_surface.get_title() else { - return; - }; - - wl_surface.with_toplevel_info(|info| { - info.title = Some(title.clone()); - }); - - let Some(panel_item) = surface_panel_item(wl_surface) else { - return; - }; - panel_item.toplevel_title_changed(&title) - } - fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) { - self.popup_manager - .track_popup(PopupKind::Xdg(popup.clone())) - .unwrap(); - - let id = rand::thread_rng().gen_range(0..u64::MAX); - popup.wl_surface().insert_data(SurfaceId::Child(id)); - let Some(parent) = popup.get_parent_surface() else { - warn!("No parent surface found for popup"); - return; - }; - CoreSurface::add_to(popup.wl_surface()); - - popup.wl_surface().insert_data(Mutex::new(ChildInfo { - id, - parent: parent.get_data::().unwrap(), - geometry: get_unconstrained_popup_geometry(&positioner), - z_order: 1, - receives_input: true, - })); - - let Some(panel_item) = surface_panel_item(&parent) else { - warn!("No panel item found for popup parent"); - return; - }; - let panel_item_weak = Arc::downgrade(&panel_item); - add_post_commit_hook( - popup.wl_surface(), - move |_: &mut WaylandState, _dh, surf| { - if surface_panel_item(surf).is_some() { - return; - } - surf.insert_data(panel_item_weak.clone()); - let Some(panel) = surface_panel_item(surf) else { - warn!("Failed to get panel item for popup surface"); - return; - }; - panel.backend.new_child(surf); - }, - ); - } - fn reposition_request( - &mut self, - popup: PopupSurface, - positioner: PositionerState, - _token: u32, - ) { - let Some(panel_item) = surface_panel_item(popup.wl_surface()) else { - return; - }; - - popup.wl_surface().with_child_info(|ci| { - ci.geometry = get_unconstrained_popup_geometry(&positioner); - }); - - panel_item.backend.reposition_child(popup.wl_surface()); - } - fn popup_destroyed(&mut self, popup: PopupSurface) { - let Some(panel_item) = surface_panel_item(popup.wl_surface()) else { - return; - }; - panel_item.backend.seat.unfocus(popup.wl_surface(), self); - panel_item.backend.drop_child(popup.wl_surface()); - } - - fn grab(&mut self, _popup: PopupSurface, _seat: WlSeat, _serial: Serial) {} - - fn move_request(&mut self, toplevel: ToplevelSurface, _seat: WlSeat, _serial: Serial) { - let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else { - return; - }; - panel_item.toplevel_move_request(); - } - fn resize_request( - &mut self, - toplevel: ToplevelSurface, - _seat: WlSeat, - _serial: Serial, - edges: ResizeEdge, - ) { - let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else { - return; - }; - let (up, down, left, right) = match edges { - ResizeEdge::None => (false, false, false, false), - ResizeEdge::Top => (true, false, false, false), - ResizeEdge::Bottom => (false, true, false, false), - ResizeEdge::Left => (false, false, true, false), - ResizeEdge::TopLeft => (true, false, true, false), - ResizeEdge::BottomLeft => (false, true, true, false), - ResizeEdge::Right => (false, false, false, true), - ResizeEdge::TopRight => (true, false, false, true), - ResizeEdge::BottomRight => (false, true, false, true), - _ => (false, false, false, false), - }; - panel_item.toplevel_resize_request(up, down, left, right); - } - - fn maximize_request(&mut self, toplevel: ToplevelSurface) { - toplevel.with_pending_state(|s| { - s.states.set(State::Maximized); - s.states.unset(State::Fullscreen); - }); - toplevel.send_configure(); - - let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else { - return; - }; - panel_item.toplevel_fullscreen_active(false); - } - fn fullscreen_request(&mut self, toplevel: ToplevelSurface, _output: Option) { - toplevel.with_pending_state(|s| { - s.states.set(State::Fullscreen); - s.states.unset(State::Maximized); - }); - toplevel.send_configure(); - - let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else { - return; - }; - panel_item.toplevel_fullscreen_active(true); - } -} -delegate_xdg_shell!(WaylandState); - -pub struct XdgBackend { - toplevel: Mutex>, - pub children: Mutex>, - seat: Arc, -} -impl XdgBackend { - pub fn create(toplevel: ToplevelSurface, seat: Arc) -> Self { - XdgBackend { - toplevel: Mutex::new(Some(toplevel)), - children: Mutex::new(FxHashMap::default()), - seat, - } - } - fn wl_surface_from_id(&self, id: &SurfaceId) -> Option { - match id { - SurfaceId::Toplevel(_) => Some(self.toplevel.lock().clone()?.wl_surface().clone()), - SurfaceId::Child(id) => self.children.lock().get(id).cloned(), - } - } - fn panel_item(&self) -> Option>> { - surface_panel_item(self.toplevel.lock().clone()?.wl_surface()) - } - - pub fn new_child(&self, surface: &WlSurface) { - let Some(panel_item) = self.panel_item() else { - return; - }; - let Some(child_info) = surface.get_child_info() else { - return; - }; - - self.children.lock().insert(child_info.id, surface.clone()); - panel_item.create_child(child_info.id, &child_info); - } - pub fn reposition_child(&self, surface: &WlSurface) { - let Some(panel_item) = self.panel_item() else { - return; - }; - let Some(child_info) = surface.get_child_info() else { - return; - }; - - panel_item.reposition_child(child_info.id, &child_info.geometry); - } - pub fn drop_child(&self, surface: &WlSurface) { - let Some(panel_item) = self.panel_item() else { - return; - }; - let Some(child_info) = surface.get_child_info() else { - return; - }; - panel_item.destroy_child(child_info.id); - self.children.lock().remove(&child_info.id); - } -} -impl Backend for XdgBackend { - fn start_data(&self) -> Result { - let cursor = self - .seat - .cursor_info_rx - .borrow() - .surface - .clone() - .and_then(|s| s.upgrade().ok()) - .as_ref() - .and_then(|c| c.get_size()) - .map(|size| Geometry { - origin: [0; 2].into(), - size, - }); - - let toplevel_info = self - .toplevel - .lock() - .as_ref() - .and_then(|toplevel| toplevel.wl_surface().get_toplevel_info()) - .ok_or(eyre!("Internal: no toplevel or ToplevelInfo"))?; - - let children = self - .children - .lock() - .values() - .filter_map(|v| v.get_child_info()) - .collect(); - - Ok(PanelItemInitData { - cursor, - toplevel: toplevel_info, - children, - pointer_grab: None, - keyboard_grab: None, - }) - } - fn apply_cursor_material(&self, model_part: &Arc) { - let Some(surface) = self - .seat - .cursor_info_rx - .borrow() - .surface - .clone() - .and_then(|s| s.upgrade().ok()) - else { - return; - }; - - let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else { - return; - }; - core_surface.apply_material(model_part); - } - fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc) { - let Some(surface) = self.wl_surface_from_id(&surface) else { - return; - }; - let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else { - return; - }; - core_surface.apply_material(model_part); - } - - fn close_toplevel(&self) { - if let Some(toplevel) = self.toplevel.lock().clone() { - self.seat.unfocus_internal_state(toplevel.wl_surface()); - toplevel.send_close(); - } - } - - fn auto_size_toplevel(&self) { - let Some(toplevel) = self.toplevel.lock().clone() else { - return; - }; - toplevel.with_pending_state(|s| s.size = None); - toplevel.send_configure(); - } - fn set_toplevel_size(&self, size: Vector2) { - let Some(toplevel) = self.toplevel.lock().clone() else { - return; - }; - toplevel.with_pending_state(|s| { - s.size = Some((size.x.max(16) as i32, size.y.max(16) as i32).into()) - }); - toplevel.send_pending_configure(); - } - fn set_toplevel_focused_visuals(&self, focused: bool) { - let Some(toplevel) = self.toplevel.lock().clone() else { - return; - }; - toplevel.with_pending_state(|s| { - if focused { - s.states.set(State::Activated); - } else { - s.states.unset(State::Activated); - } - }) - } - - fn pointer_motion(&self, surface: &SurfaceId, position: Vector2) { - let Some(surface) = self.wl_surface_from_id(surface) else { - return; - }; - self.seat.pointer_motion(surface, position) - } - fn pointer_button(&self, _surface: &SurfaceId, button: u32, pressed: bool) { - self.seat.pointer_button(button, pressed) - } - fn pointer_scroll( - &self, - _surface: &SurfaceId, - scroll_distance: Option>, - scroll_steps: Option>, - ) { - self.seat.pointer_scroll(scroll_distance, scroll_steps) - } - - fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) { - let Some(surface) = self.wl_surface_from_id(surface) else { - return; - }; - self.seat.keyboard_key(surface, keymap_id, key, pressed) - } - - fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2) { - let Some(surface) = self.wl_surface_from_id(surface) else { - return; - }; - self.seat.touch_down(surface, id, position) - } - fn touch_move(&self, id: u32, position: Vector2) { - self.seat.touch_move(id, position) - } - fn touch_up(&self, id: u32) { - self.seat.touch_up(id) - } - fn reset_input(&self) { - self.seat.reset_input() - } -}