Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: Port compositor core to Rust up to Phase 14 (Keyboard Grouping)
.gitignore | 2 +
Cargo.lock | 637 ++++++++++++
Cargo.toml | 14 +-
build.rs | 162 +++
src/cursor.rs | 963 ++++++++++++++++++
src/drag_icon.rs | 92 ++
src/ffi.rs | 19 +
src/idle_inhibit_manager.rs | 144 +++
src/input_device.rs | 102 ++
src/input_manager.rs | 120 +++
src/input_popup.rs | 231 +++++
src/input_relay.rs | 283 +++++
src/keyboard.rs | 251 +++++
src/keyboard_group.rs | 453 +++++++++
src/layer_shell.rs | 861 ++++++++++++++++
src/libinput_config.rs | 194 ++++
src/lock_manager.rs | 430 ++++++++
src/main.rs | 267 ++++-
src/output.rs | 422 ++++++++
src/output_manager.rs | 648 ++++++++++++
src/pointer_binding.rs | 172 ++++
src/pointer_constraint.rs | 259 +++++
src/process.rs | 55 +
src/scene.rs | 213 ++++
src/scene_node_data.rs | 83 ++
src/seat.rs | 1036 +++++++++++++++++++
src/server.rs | 697 +++++++++++++
src/shell_surface.rs | 266 +++++
src/slotmap.rs | 195 ++++
src/tablet.rs | 51 +
src/tablet_tool.rs | 366 +++++++
src/text_input.rs | 154 +++
src/util.rs | 21 +
src/window.rs | 2038 +++++++++++++++++++++++++++++++++++++
src/window_manager.rs | 727 +++++++++++++
src/wlroots_log_wrapper.c | 650 ++++++++++++
src/wm_node.rs | 244 +++++
src/xdg_popup.rs | 142 +++
src/xdg_toplevel.rs | 651 ++++++++++++
src/xkb_bindings.rs | 520 ++++++++++
src/xkb_config.rs | 325 ++++++
src/xwayland_override_redirect.rs | 280 +++++
src/xwayland_window.rs | 418 ++++++++
wrapper.h | 257 +++++
44 files changed, 16113 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index ea8c4bf..b1df094 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/target
+*.log
+check_errors.txt
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..40a8df4
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,637 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.69.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "cc"
+version = "1.2.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "clear-river-rust"
+version = "0.1.0"
+dependencies = [
+ "bindgen",
+ "bitflags",
+ "cc",
+ "clap",
+ "env_logger",
+ "libc",
+ "log",
+ "nix",
+ "pkg-config",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "either"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+
+[[package]]
+name = "env_filter"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "home"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "jiff"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "log"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
+
+[[package]]
+name = "memchr"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
index aa6c7a9..a7d070b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,18 @@
[package]
name = "clear-river-rust"
version = "0.1.0"
-edition = "2024"
+edition = "2021"
[dependencies]
+libc = "0.2"
+nix = { version = "0.29", features = ["signal", "process", "fs", "poll", "resource"] }
+bitflags = "2"
+clap = { version = "4.4", features = ["derive"] }
+log = "0.4"
+env_logger = "0.11"
+
+[build-dependencies]
+bindgen = "0.69"
+cc = "1.0"
+pkg-config = "0.3"
+
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..774937f
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,162 @@
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+ println!("cargo:rerun-if-changed=src/wlroots_log_wrapper.c");
+ println!("cargo:rerun-if-changed=wrapper.h");
+
+ // Probe system libraries
+ let wlroots = pkg_config::Config::new()
+ .atleast_version("0.20.0")
+ .probe("wlroots-0.20")
+ .expect("wlroots-0.20 is required");
+
+ let wl_server = pkg_config::probe_library("wayland-server")
+ .expect("wayland-server is required");
+
+ let xkb = pkg_config::probe_library("xkbcommon")
+ .expect("xkbcommon is required");
+
+ let pixman = pkg_config::probe_library("pixman-1")
+ .expect("pixman-1 is required");
+
+ let libinput = pkg_config::probe_library("libinput")
+ .expect("libinput is required");
+
+ let libevdev = pkg_config::probe_library("libevdev")
+ .expect("libevdev is required");
+
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+
+ // Helper to fix XML files starting with comments instead of the XML declaration
+ let clean_xml = |src: &str, dst: &std::path::Path| {
+ let content = std::fs::read_to_string(src).expect("Failed to read XML source");
+ let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
+ if lines.len() > 1 && lines[0].starts_with("<!--") && lines[1].starts_with("<?xml") {
+ lines.swap(0, 1);
+ }
+ let cleaned = lines.join("\n");
+ std::fs::write(dst, cleaned).expect("Failed to write cleaned XML");
+ };
+
+ // Generate upstream protocol headers (header-only)
+ let upstream_protocols = vec![
+ ("wlr-layer-shell-unstable-v1.xml", "../clear-river/protocol/upstream/wlr-layer-shell-unstable-v1.xml"),
+ ("wlr-output-power-management-unstable-v1.xml", "../clear-river/protocol/upstream/wlr-output-power-management-unstable-v1.xml"),
+ ("virtual-keyboard-unstable-v1.xml", "../clear-river/protocol/upstream/virtual-keyboard-unstable-v1.xml"),
+ ];
+
+ for (name, path) in upstream_protocols {
+ let temp_xml = out_dir.join(format!("{}-temp.xml", name));
+ clean_xml(path, &temp_xml);
+ let header_name = name.replace(".xml", "-protocol.h");
+ let status = std::process::Command::new("wayland-scanner")
+ .args(&[
+ "server-header",
+ temp_xml.to_str().unwrap(),
+ out_dir.join(&header_name).to_str().unwrap(),
+ ])
+ .status()
+ .expect("failed to execute wayland-scanner");
+ assert!(status.success(), "wayland-scanner server-header failed for {}", name);
+ }
+
+ // Generate custom river protocol headers and private-code C files
+ let custom_protocols = vec![
+ ("river-window-management-v1.xml", "../clear-river/protocol/river-window-management-v1.xml"),
+ ("river-xkb-bindings-v1.xml", "../clear-river/protocol/river-xkb-bindings-v1.xml"),
+ ("river-layer-shell-v1.xml", "../clear-river/protocol/river-layer-shell-v1.xml"),
+ ("river-input-management-v1.xml", "../clear-river/protocol/river-input-management-v1.xml"),
+ ("river-libinput-config-v1.xml", "../clear-river/protocol/river-libinput-config-v1.xml"),
+ ("river-xkb-config-v1.xml", "../clear-river/protocol/river-xkb-config-v1.xml"),
+ ];
+
+ let mut generated_c_files = Vec::new();
+
+ for (name, path) in custom_protocols {
+ let temp_xml = out_dir.join(format!("{}-temp.xml", name));
+ clean_xml(path, &temp_xml);
+
+ let header_name = name.replace(".xml", "-protocol.h");
+ let status_h = std::process::Command::new("wayland-scanner")
+ .args(&[
+ "server-header",
+ temp_xml.to_str().unwrap(),
+ out_dir.join(&header_name).to_str().unwrap(),
+ ])
+ .status()
+ .expect("failed to execute wayland-scanner");
+ assert!(status_h.success(), "wayland-scanner server-header failed for {}", name);
+
+ let code_name = name.replace(".xml", "-protocol.c");
+ let code_path = out_dir.join(&code_name);
+ let status_c = std::process::Command::new("wayland-scanner")
+ .args(&[
+ "private-code",
+ temp_xml.to_str().unwrap(),
+ code_path.to_str().unwrap(),
+ ])
+ .status()
+ .expect("failed to execute wayland-scanner");
+ assert!(status_c.success(), "wayland-scanner private-code failed for {}", name);
+
+ generated_c_files.push(code_path);
+ }
+
+ // Compile the C wrapper and protocol C files
+ let mut build = cc::Build::new();
+ build.file("src/wlroots_log_wrapper.c")
+ .define("WLR_USE_UNSTABLE", None)
+ .flag("-std=c99")
+ .flag("-O2")
+ .include(&out_dir);
+
+ for c_file in generated_c_files {
+ build.file(c_file);
+ }
+
+ // Add include paths for wlroots and wayland-server
+ for path in &wlroots.include_paths {
+ build.include(path);
+ }
+ for path in &wl_server.include_paths {
+ build.include(path);
+ }
+ build.compile("wlroots_log_wrapper");
+
+
+ // Setup bindgen
+ let mut builder = bindgen::Builder::default()
+ .header("wrapper.h")
+ .clang_arg("-DWLR_USE_UNSTABLE")
+ .layout_tests(false)
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
+
+ // Pass include paths to bindgen clang argument parser
+ let mut include_paths = vec![out_dir.clone()];
+ include_paths.extend(wlroots.include_paths.clone());
+ include_paths.extend(wl_server.include_paths.clone());
+ include_paths.extend(xkb.include_paths.clone());
+ include_paths.extend(pixman.include_paths.clone());
+ include_paths.extend(libinput.include_paths.clone());
+ include_paths.extend(libevdev.include_paths.clone());
+
+ for path in include_paths {
+ builder = builder.clang_arg(format!("-I{}", path.display()));
+ }
+
+ let bindings = builder
+ .blocklist_item("FP_NAN")
+ .blocklist_item("FP_INFINITE")
+ .blocklist_item("FP_ZERO")
+ .blocklist_item("FP_SUBNORMAL")
+ .blocklist_item("FP_NORMAL")
+ .blocklist_item("wl_listener")
+ .generate()
+ .expect("Unable to generate bindings");
+
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
diff --git a/src/cursor.rs b/src/cursor.rs
new file mode 100644
index 0000000..757a0ab
--- /dev/null
+++ b/src/cursor.rs
@@ -0,0 +1,963 @@
+use crate::ffi;
+use crate::seat::{Seat, Focus};
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add, WlList};
+use crate::scene_node_data::SceneNodeDataVal;
+use crate::drag_icon::DragIcon;
+use std::collections::HashMap;
+
+pub struct Cursor {
+ pub seat: *mut Seat,
+ pub wlr_cursor: *mut ffi::wlr_cursor,
+ pub xcursor_manager: *mut ffi::wlr_xcursor_manager,
+ pub constraint: *mut crate::pointer_constraint::PointerConstraint,
+
+ pub motion_listener: ffi::wl_listener,
+ pub motion_absolute_listener: ffi::wl_listener,
+ pub button_listener: ffi::wl_listener,
+ pub axis_listener: ffi::wl_listener,
+ pub frame_listener: ffi::wl_listener,
+
+ pub tablet_tool_axis_listener: ffi::wl_listener,
+ pub tablet_tool_proximity_listener: ffi::wl_listener,
+ pub tablet_tool_tip_listener: ffi::wl_listener,
+ pub tablet_tool_button_listener: ffi::wl_listener,
+
+ pub touch_points: HashMap<i32, (f64, f64)>,
+ pub pressed: HashMap<u32, Option<*mut crate::pointer_binding::PointerBinding>>,
+
+ pub touch_down_listener: ffi::wl_listener,
+ pub touch_motion_listener: ffi::wl_listener,
+ pub touch_up_listener: ffi::wl_listener,
+ pub touch_cancel_listener: ffi::wl_listener,
+ pub touch_frame_listener: ffi::wl_listener,
+
+ pub swipe_begin_listener: ffi::wl_listener,
+ pub swipe_update_listener: ffi::wl_listener,
+ pub swipe_end_listener: ffi::wl_listener,
+
+ pub pinch_begin_listener: ffi::wl_listener,
+ pub pinch_update_listener: ffi::wl_listener,
+ pub pinch_end_listener: ffi::wl_listener,
+
+ pub hold_begin_listener: ffi::wl_listener,
+ pub hold_end_listener: ffi::wl_listener,
+}
+
+impl Default for Cursor {
+ fn default() -> Self {
+ Self {
+ seat: std::ptr::null_mut(),
+ wlr_cursor: std::ptr::null_mut(),
+ xcursor_manager: std::ptr::null_mut(),
+ constraint: std::ptr::null_mut(),
+ motion_listener: unsafe { std::mem::zeroed() },
+ motion_absolute_listener: unsafe { std::mem::zeroed() },
+ button_listener: unsafe { std::mem::zeroed() },
+ axis_listener: unsafe { std::mem::zeroed() },
+ frame_listener: unsafe { std::mem::zeroed() },
+ tablet_tool_axis_listener: unsafe { std::mem::zeroed() },
+ tablet_tool_proximity_listener: unsafe { std::mem::zeroed() },
+ tablet_tool_tip_listener: unsafe { std::mem::zeroed() },
+ tablet_tool_button_listener: unsafe { std::mem::zeroed() },
+
+ touch_points: HashMap::new(),
+ pressed: HashMap::new(),
+
+ touch_down_listener: unsafe { std::mem::zeroed() },
+ touch_motion_listener: unsafe { std::mem::zeroed() },
+ touch_up_listener: unsafe { std::mem::zeroed() },
+ touch_cancel_listener: unsafe { std::mem::zeroed() },
+ touch_frame_listener: unsafe { std::mem::zeroed() },
+
+ swipe_begin_listener: unsafe { std::mem::zeroed() },
+ swipe_update_listener: unsafe { std::mem::zeroed() },
+ swipe_end_listener: unsafe { std::mem::zeroed() },
+
+ pinch_begin_listener: unsafe { std::mem::zeroed() },
+ pinch_update_listener: unsafe { std::mem::zeroed() },
+ pinch_end_listener: unsafe { std::mem::zeroed() },
+
+ hold_begin_listener: unsafe { std::mem::zeroed() },
+ hold_end_listener: unsafe { std::mem::zeroed() },
+ }
+ }
+}
+
+impl Cursor {
+ pub unsafe fn init(
+ &mut self,
+ seat: *mut Seat,
+ output_layout: *mut ffi::wlr_output_layout,
+ ) -> Result<(), &'static str> {
+ let wlr_cursor = ffi::wlr_cursor_create();
+ if wlr_cursor.is_null() {
+ return Err("Failed to create wlr_cursor");
+ }
+ ffi::wlr_cursor_attach_output_layout(wlr_cursor, output_layout);
+
+ let xcursor_manager = ffi::wlr_xcursor_manager_create(std::ptr::null(), 24);
+ if xcursor_manager.is_null() {
+ ffi::wlr_cursor_destroy(wlr_cursor);
+ return Err("Failed to create wlr_xcursor_manager");
+ }
+
+ self.seat = seat;
+ self.wlr_cursor = wlr_cursor;
+ self.xcursor_manager = xcursor_manager;
+
+ // Load default cursor theme
+ ffi::wlr_xcursor_manager_load(xcursor_manager, 1.0);
+ ffi::wlr_cursor_set_xcursor(wlr_cursor, xcursor_manager, b"default\0".as_ptr() as *const _);
+
+ // Setup listeners
+ let motion_ptr = &mut self.motion_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*motion_ptr).notify = Some(handle_motion);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_motion_signal(wlr_cursor),
+ &mut self.motion_listener,
+ );
+
+ let absolute_ptr = &mut self.motion_absolute_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*absolute_ptr).notify = Some(handle_motion_absolute);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_motion_absolute_signal(wlr_cursor),
+ &mut self.motion_absolute_listener,
+ );
+
+ let button_ptr = &mut self.button_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*button_ptr).notify = Some(handle_button);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_button_signal(wlr_cursor),
+ &mut self.button_listener,
+ );
+
+ let axis_ptr = &mut self.axis_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*axis_ptr).notify = Some(handle_axis);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_axis_signal(wlr_cursor),
+ &mut self.axis_listener,
+ );
+
+ let frame_ptr = &mut self.frame_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*frame_ptr).notify = Some(handle_frame);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_frame_signal(wlr_cursor),
+ &mut self.frame_listener,
+ );
+
+ let tablet_axis_ptr = &mut self.tablet_tool_axis_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*tablet_axis_ptr).notify = Some(handle_tablet_tool_axis);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_tablet_tool_axis_signal(wlr_cursor),
+ &mut self.tablet_tool_axis_listener,
+ );
+
+ let tablet_proximity_ptr = &mut self.tablet_tool_proximity_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*tablet_proximity_ptr).notify = Some(handle_tablet_tool_proximity);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_tablet_tool_proximity_signal(wlr_cursor),
+ &mut self.tablet_tool_proximity_listener,
+ );
+
+ let tablet_tip_ptr = &mut self.tablet_tool_tip_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*tablet_tip_ptr).notify = Some(handle_tablet_tool_tip);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_tablet_tool_tip_signal(wlr_cursor),
+ &mut self.tablet_tool_tip_listener,
+ );
+
+ let tablet_button_ptr = &mut self.tablet_tool_button_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*tablet_button_ptr).notify = Some(handle_tablet_tool_button);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_tablet_tool_button_signal(wlr_cursor),
+ &mut self.tablet_tool_button_listener,
+ );
+
+ let touch_down_ptr = &mut self.touch_down_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*touch_down_ptr).notify = Some(handle_touch_down);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_touch_down_signal(wlr_cursor),
+ &mut self.touch_down_listener,
+ );
+
+ let touch_motion_ptr = &mut self.touch_motion_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*touch_motion_ptr).notify = Some(handle_touch_motion);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_touch_motion_signal(wlr_cursor),
+ &mut self.touch_motion_listener,
+ );
+
+ let touch_up_ptr = &mut self.touch_up_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*touch_up_ptr).notify = Some(handle_touch_up);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_touch_up_signal(wlr_cursor),
+ &mut self.touch_up_listener,
+ );
+
+ let touch_cancel_ptr = &mut self.touch_cancel_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*touch_cancel_ptr).notify = Some(handle_touch_cancel);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_touch_cancel_signal(wlr_cursor),
+ &mut self.touch_cancel_listener,
+ );
+
+ let touch_frame_ptr = &mut self.touch_frame_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*touch_frame_ptr).notify = Some(handle_touch_frame);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_touch_frame_signal(wlr_cursor),
+ &mut self.touch_frame_listener,
+ );
+
+ let swipe_begin_ptr = &mut self.swipe_begin_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*swipe_begin_ptr).notify = Some(handle_swipe_begin);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_swipe_begin_signal(wlr_cursor),
+ &mut self.swipe_begin_listener,
+ );
+
+ let swipe_update_ptr = &mut self.swipe_update_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*swipe_update_ptr).notify = Some(handle_swipe_update);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_swipe_update_signal(wlr_cursor),
+ &mut self.swipe_update_listener,
+ );
+
+ let swipe_end_ptr = &mut self.swipe_end_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*swipe_end_ptr).notify = Some(handle_swipe_end);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_swipe_end_signal(wlr_cursor),
+ &mut self.swipe_end_listener,
+ );
+
+ let pinch_begin_ptr = &mut self.pinch_begin_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*pinch_begin_ptr).notify = Some(handle_pinch_begin);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_pinch_begin_signal(wlr_cursor),
+ &mut self.pinch_begin_listener,
+ );
+
+ let pinch_update_ptr = &mut self.pinch_update_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*pinch_update_ptr).notify = Some(handle_pinch_update);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_pinch_update_signal(wlr_cursor),
+ &mut self.pinch_update_listener,
+ );
+
+ let pinch_end_ptr = &mut self.pinch_end_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*pinch_end_ptr).notify = Some(handle_pinch_end);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_pinch_end_signal(wlr_cursor),
+ &mut self.pinch_end_listener,
+ );
+
+ let hold_begin_ptr = &mut self.hold_begin_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*hold_begin_ptr).notify = Some(handle_hold_begin);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_hold_begin_signal(wlr_cursor),
+ &mut self.hold_begin_listener,
+ );
+
+ let hold_end_ptr = &mut self.hold_end_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*hold_end_ptr).notify = Some(handle_hold_end);
+ wl_signal_add(
+ ffi::river_wlr_cursor_get_hold_end_signal(wlr_cursor),
+ &mut self.hold_end_listener,
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ wl_listener_remove(&mut self.tablet_tool_axis_listener);
+ wl_listener_remove(&mut self.tablet_tool_proximity_listener);
+ wl_listener_remove(&mut self.tablet_tool_tip_listener);
+ wl_listener_remove(&mut self.tablet_tool_button_listener);
+
+ wl_listener_remove(&mut self.touch_down_listener);
+ wl_listener_remove(&mut self.touch_motion_listener);
+ wl_listener_remove(&mut self.touch_up_listener);
+ wl_listener_remove(&mut self.touch_cancel_listener);
+ wl_listener_remove(&mut self.touch_frame_listener);
+
+ wl_listener_remove(&mut self.swipe_begin_listener);
+ wl_listener_remove(&mut self.swipe_update_listener);
+ wl_listener_remove(&mut self.swipe_end_listener);
+
+ wl_listener_remove(&mut self.pinch_begin_listener);
+ wl_listener_remove(&mut self.pinch_update_listener);
+ wl_listener_remove(&mut self.pinch_end_listener);
+
+ wl_listener_remove(&mut self.hold_begin_listener);
+ wl_listener_remove(&mut self.hold_end_listener);
+
+ if !self.xcursor_manager.is_null() {
+ ffi::wlr_xcursor_manager_destroy(self.xcursor_manager);
+ self.xcursor_manager = std::ptr::null_mut();
+ }
+ if !self.wlr_cursor.is_null() {
+ ffi::wlr_cursor_destroy(self.wlr_cursor);
+ self.wlr_cursor = std::ptr::null_mut();
+ }
+ }
+
+ pub unsafe fn x(&self) -> f64 {
+ ffi::river_wlr_cursor_get_x(self.wlr_cursor)
+ }
+
+ pub unsafe fn y(&self) -> f64 {
+ ffi::river_wlr_cursor_get_y(self.wlr_cursor)
+ }
+
+ pub unsafe fn update_hovered(&mut self) {
+ let lx = self.x();
+ let ly = self.y();
+ let server = (*self.seat).server;
+
+ let at_res = (*server).scene.at(lx, ly);
+ let current_focused = &(*self.seat).focused;
+
+ if let Some(result) = at_res {
+ // Find window/surface under cursor and focus
+ match result.data {
+ SceneNodeDataVal::Window(_) => {
+ // Update hover or click focus
+ }
+ SceneNodeDataVal::LayerSurface(_) => {
+ let surface = result.surface;
+ if !matches!(current_focused, Focus::LayerSurface(s) if *s == surface) {
+ (*self.seat).focus(Focus::LayerSurface(surface));
+ }
+ }
+ SceneNodeDataVal::LockSurface(lock_surf) => {
+ if !matches!(current_focused, Focus::LockSurface(s) if *s == lock_surf) {
+ (*self.seat).focus(Focus::LockSurface(lock_surf));
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ pub unsafe fn update_state(&mut self) {
+ if !self.constraint.is_null() {
+ (*self.constraint).update_state();
+ }
+ self.update_hovered();
+ }
+
+ pub unsafe fn update_drag_icons(&mut self) {
+ let drag_icons_tree = (*(*self.seat).server).scene.drag_icons;
+ let children_head = ffi::river_scene_tree_get_children(drag_icons_tree) as *mut WlList;
+ let mut curr = (*children_head).next;
+ while curr != children_head {
+ let next = (*curr).next;
+ let node = ffi::river_scene_node_from_children_link(curr as *mut ffi::wl_list);
+ let drag_icon_raw = ffi::river_scene_node_get_data(node) as *mut DragIcon;
+ if !drag_icon_raw.is_null() {
+ let drag_icon = &mut *drag_icon_raw;
+ let drag_seat = ffi::river_wlr_drag_get_seat((*drag_icon.wlr_drag_icon).drag);
+ if drag_seat == (*self.seat).wlr_seat {
+ drag_icon.update_position(self);
+ }
+ }
+ curr = next;
+ }
+ }
+
+ pub unsafe fn set_theme(
+ &mut self,
+ theme: *const std::os::raw::c_char,
+ size: u32,
+ ) -> Result<(), &'static str> {
+ let size = if size == 0 { 24 } else { size };
+
+ let xcursor_manager = ffi::wlr_xcursor_manager_create(theme, size);
+ if xcursor_manager.is_null() {
+ return Err("Failed to create wlr_xcursor_manager");
+ }
+
+ // If this cursor belongs to the default seat, update the Xwayland cursor to match the theme.
+ let default_seat = (*(*self.seat).server).input_manager.default_seat;
+ if self.seat == default_seat {
+ if !(*(*self.seat).server).xwayland.is_null() {
+ ffi::wlr_xcursor_manager_load(xcursor_manager, 1.0);
+ let wlr_xcursor = ffi::wlr_xcursor_manager_get_xcursor(
+ xcursor_manager,
+ b"default\0".as_ptr() as *const _,
+ 1.0,
+ );
+ if !wlr_xcursor.is_null() && (*wlr_xcursor).image_count > 0 {
+ let image = *(*wlr_xcursor).images;
+ if !image.is_null() {
+ ffi::wlr_xwayland_set_cursor(
+ (*(*self.seat).server).xwayland,
+ ffi::wlr_xcursor_image_get_buffer(image),
+ (*image).hotspot_x as i32,
+ (*image).hotspot_y as i32,
+ );
+ }
+ }
+ }
+ }
+
+ if !self.xcursor_manager.is_null() {
+ ffi::wlr_xcursor_manager_destroy(self.xcursor_manager);
+ }
+ self.xcursor_manager = xcursor_manager;
+
+ ffi::wlr_xcursor_manager_load(self.xcursor_manager, 1.0);
+ ffi::wlr_cursor_set_xcursor(
+ self.wlr_cursor,
+ self.xcursor_manager,
+ b"default\0".as_ptr() as *const _,
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn op_start_pointer(&mut self) {
+ if !self.constraint.is_null() {
+ if let crate::pointer_constraint::PointerConstraintState::Active { .. } = (*self.constraint).state {
+ (*self.constraint).deactivate();
+ }
+ }
+
+ log::debug!("entering cursor mode op");
+ ffi::wlr_seat_pointer_notify_clear_focus((*self.seat).wlr_seat);
+ }
+
+ pub unsafe fn op_end_pointer(&mut self) {
+ if self.pressed.is_empty() {
+ log::debug!("entering cursor mode passthrough");
+ self.update_state();
+ } else {
+ log::debug!("entering cursor mode ignore");
+ }
+ }
+}
+
+unsafe extern "C" fn handle_motion(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, motion_listener);
+ let event = data as *mut ffi::wlr_pointer_motion_event;
+
+ let mut dx = (*event).delta_x;
+ let mut dy = (*event).delta_y;
+
+ if !cursor.constraint.is_null() {
+ (*cursor.constraint).confine(&mut dx, &mut dy);
+ }
+
+ ffi::wlr_cursor_move(cursor.wlr_cursor, std::ptr::null_mut(), dx, dy);
+ cursor.update_hovered();
+ cursor.update_drag_icons();
+
+ let seat = &mut *cursor.seat;
+ if (*seat).op.is_some() {
+ let lx = (*cursor.wlr_cursor).x as i32;
+ let ly = (*cursor.wlr_cursor).y as i32;
+ (*seat).op_update(lx, ly);
+ return;
+ }
+
+ let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
+ if !focused_client.is_null() {
+ let lx = cursor.x();
+ let ly = cursor.y();
+ let server = seat.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ ffi::wlr_seat_pointer_notify_motion(
+ seat.wlr_seat,
+ (*event).time_msec,
+ result.sx,
+ result.sy,
+ );
+ }
+ }
+}
+
+unsafe extern "C" fn handle_motion_absolute(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, motion_absolute_listener);
+ let event = data as *mut ffi::wlr_pointer_motion_absolute_event;
+
+ ffi::wlr_cursor_warp_absolute(cursor.wlr_cursor, std::ptr::null_mut(), (*event).x, (*event).y);
+ cursor.update_hovered();
+ cursor.update_drag_icons();
+
+ let seat = &mut *cursor.seat;
+ if (*seat).op.is_some() {
+ let lx = (*cursor.wlr_cursor).x as i32;
+ let ly = (*cursor.wlr_cursor).y as i32;
+ (*seat).op_update(lx, ly);
+ return;
+ }
+
+ let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
+ if !focused_client.is_null() {
+ let lx = cursor.x();
+ let ly = cursor.y();
+ let server = seat.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ ffi::wlr_seat_pointer_notify_motion(
+ seat.wlr_seat,
+ (*event).time_msec,
+ result.sx,
+ result.sy,
+ );
+ }
+ }
+}
+
+unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, button_listener);
+ let event = data as *mut ffi::wlr_pointer_button_event;
+
+ let seat = &mut *cursor.seat;
+
+ if (*event).state == ffi::wl_pointer_button_state_WL_POINTER_BUTTON_STATE_PRESSED {
+ if cursor.pressed.contains_key(&(*event).button) {
+ log::error!("ignoring duplicate pointer button {} press", (*event).button);
+ return;
+ }
+
+ if let Some(binding) = seat.match_pointer_binding((*event).button) {
+ cursor.pressed.insert((*event).button, Some(binding));
+ (*binding).pressed();
+ return;
+ }
+
+ cursor.pressed.insert((*event).button, None);
+
+ ffi::wlr_seat_pointer_notify_button(
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).button,
+ (*event).state,
+ );
+
+ // If pressed, update focus to window under cursor
+ let lx = cursor.x();
+ let ly = cursor.y();
+ let server = seat.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ match result.data {
+ SceneNodeDataVal::LayerSurface(_) => {
+ seat.focus(Focus::LayerSurface(result.surface));
+ }
+ _ => {}
+ }
+ }
+ } else {
+ assert_eq!((*event).state, ffi::wl_pointer_button_state_WL_POINTER_BUTTON_STATE_RELEASED);
+ if let Some(binding_opt) = cursor.pressed.remove(&(*event).button) {
+ if let Some(binding) = binding_opt {
+ (*binding).released();
+ if cursor.pressed.is_empty() && seat.op.is_some() {
+ seat.op_release = true;
+ (*(*seat).server).wm.dirty_windowing();
+ }
+ return;
+ }
+
+ ffi::wlr_seat_pointer_notify_button(
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).button,
+ (*event).state,
+ );
+
+ if cursor.pressed.is_empty() && seat.op.is_some() {
+ seat.op_release = true;
+ (*(*seat).server).wm.dirty_windowing();
+ }
+ } else {
+ log::error!("ignoring duplicate pointer button {} release", (*event).button);
+ }
+ }
+}
+
+unsafe extern "C" fn handle_axis(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, axis_listener);
+ let event = data as *mut ffi::wlr_pointer_axis_event;
+
+ let seat = &mut *cursor.seat;
+ ffi::wlr_seat_pointer_notify_axis(
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).orientation,
+ (*event).delta,
+ (*event).delta_discrete,
+ (*event).source,
+ (*event).relative_direction,
+ );
+}
+
+unsafe extern "C" fn handle_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, frame_listener);
+
+ let seat = &mut *cursor.seat;
+ ffi::wlr_seat_pointer_notify_frame(seat.wlr_seat);
+}
+
+unsafe extern "C" fn handle_tablet_tool_axis(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, tablet_tool_axis_listener);
+ let event = data as *mut ffi::wlr_tablet_tool_axis_event;
+
+ let wlr_tablet = (*event).tablet;
+ let wlr_device = &mut (*wlr_tablet).base as *mut ffi::wlr_input_device;
+ let device = ffi::river_wlr_input_device_get_data(wlr_device) as *mut crate::input_device::InputDevice;
+ if device.is_null() {
+ return;
+ }
+ let seat = (*device).seat;
+ (*seat).handle_activity();
+
+ let tablet = (*device).destroy_data as *mut crate::tablet::Tablet;
+ if tablet.is_null() {
+ return;
+ }
+
+ if let Ok(tool) = crate::tablet_tool::TabletTool::get((*seat).wlr_seat, (*event).tool) {
+ (*tool).axis(tablet, event);
+ }
+}
+
+unsafe extern "C" fn handle_tablet_tool_proximity(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, tablet_tool_proximity_listener);
+ let event = data as *mut ffi::wlr_tablet_tool_proximity_event;
+
+ let wlr_tablet = (*event).tablet;
+ let wlr_device = &mut (*wlr_tablet).base as *mut ffi::wlr_input_device;
+ let device = ffi::river_wlr_input_device_get_data(wlr_device) as *mut crate::input_device::InputDevice;
+ if device.is_null() {
+ return;
+ }
+ let seat = (*device).seat;
+ (*seat).handle_activity();
+
+ let tablet = (*device).destroy_data as *mut crate::tablet::Tablet;
+ if tablet.is_null() {
+ return;
+ }
+
+ if let Ok(tool) = crate::tablet_tool::TabletTool::get((*seat).wlr_seat, (*event).tool) {
+ (*tool).proximity(tablet, event);
+ }
+}
+
+unsafe extern "C" fn handle_tablet_tool_tip(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, tablet_tool_tip_listener);
+ let event = data as *mut ffi::wlr_tablet_tool_tip_event;
+
+ let wlr_tablet = (*event).tablet;
+ let wlr_device = &mut (*wlr_tablet).base as *mut ffi::wlr_input_device;
+ let device = ffi::river_wlr_input_device_get_data(wlr_device) as *mut crate::input_device::InputDevice;
+ if device.is_null() {
+ return;
+ }
+ let seat = (*device).seat;
+ (*seat).handle_activity();
+
+ let tablet = (*device).destroy_data as *mut crate::tablet::Tablet;
+ if tablet.is_null() {
+ return;
+ }
+
+ if let Ok(tool) = crate::tablet_tool::TabletTool::get((*seat).wlr_seat, (*event).tool) {
+ (*tool).tip(tablet, event);
+ }
+}
+
+unsafe extern "C" fn handle_tablet_tool_button(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, tablet_tool_button_listener);
+ let event = data as *mut ffi::wlr_tablet_tool_button_event;
+
+ let wlr_tablet = (*event).tablet;
+ let wlr_device = &mut (*wlr_tablet).base as *mut ffi::wlr_input_device;
+ let device = ffi::river_wlr_input_device_get_data(wlr_device) as *mut crate::input_device::InputDevice;
+ if device.is_null() {
+ return;
+ }
+ let seat = (*device).seat;
+ (*seat).handle_activity();
+
+ let tablet = (*device).destroy_data as *mut crate::tablet::Tablet;
+ if tablet.is_null() {
+ return;
+ }
+
+ if let Ok(tool) = crate::tablet_tool::TabletTool::get((*seat).wlr_seat, (*event).tool) {
+ (*tool).button(tablet, event);
+ }
+}
+
+unsafe extern "C" fn handle_touch_down(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, touch_down_listener);
+ let event = data as *mut ffi::wlr_touch_down_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let mut lx = 0.0;
+ let mut ly = 0.0;
+ let wlr_device = &mut (*(*event).touch).base as *mut ffi::wlr_input_device;
+ ffi::wlr_cursor_absolute_to_layout_coords(
+ cursor.wlr_cursor,
+ wlr_device,
+ (*event).x,
+ (*event).y,
+ &mut lx,
+ &mut ly,
+ );
+
+ cursor.touch_points.insert((*event).touch_id, (lx, ly));
+
+ let server = seat.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ match result.data {
+ SceneNodeDataVal::LayerSurface(_) => {
+ seat.focus(Focus::LayerSurface(result.surface));
+ }
+ _ => {}
+ }
+ if !result.surface.is_null() {
+ ffi::wlr_seat_touch_notify_down(
+ seat.wlr_seat,
+ result.surface,
+ (*event).time_msec,
+ (*event).touch_id,
+ result.sx,
+ result.sy,
+ );
+ }
+ }
+}
+
+unsafe extern "C" fn handle_touch_motion(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, touch_motion_listener);
+ let event = data as *mut ffi::wlr_touch_motion_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ if cursor.touch_points.contains_key(&(*event).touch_id) {
+ let wlr_device = &mut (*(*event).touch).base as *mut ffi::wlr_input_device;
+ let mut lx: f64 = 0.0;
+ let mut ly: f64 = 0.0;
+ ffi::wlr_cursor_absolute_to_layout_coords(
+ cursor.wlr_cursor,
+ wlr_device,
+ (*event).x,
+ (*event).y,
+ &mut lx,
+ &mut ly,
+ );
+
+ cursor.touch_points.insert((*event).touch_id, (lx, ly));
+
+ cursor.update_drag_icons();
+
+ let server = seat.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ ffi::wlr_seat_touch_notify_motion(
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).touch_id,
+ result.sx,
+ result.sy,
+ );
+ }
+ }
+}
+
+unsafe extern "C" fn handle_touch_up(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, touch_up_listener);
+ let event = data as *mut ffi::wlr_touch_up_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ if cursor.touch_points.remove(&(*event).touch_id).is_some() {
+ ffi::wlr_seat_touch_notify_up(
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).touch_id,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_touch_cancel(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, touch_cancel_listener);
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ cursor.touch_points.clear();
+
+ ffi::river_wlr_seat_touch_cancel_all(seat.wlr_seat);
+}
+
+unsafe extern "C" fn handle_touch_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, touch_frame_listener);
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ ffi::wlr_seat_touch_notify_frame(seat.wlr_seat);
+}
+
+unsafe extern "C" fn handle_swipe_begin(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, swipe_begin_listener);
+ let event = data as *mut ffi::wlr_pointer_swipe_begin_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_swipe_begin(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).fingers,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, swipe_update_listener);
+ let event = data as *mut ffi::wlr_pointer_swipe_update_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_swipe_update(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).dx,
+ (*event).dy,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_swipe_end(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, swipe_end_listener);
+ let event = data as *mut ffi::wlr_pointer_swipe_end_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_swipe_end(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).cancelled,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_pinch_begin(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, pinch_begin_listener);
+ let event = data as *mut ffi::wlr_pointer_pinch_begin_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_pinch_begin(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).fingers,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_pinch_update(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, pinch_update_listener);
+ let event = data as *mut ffi::wlr_pointer_pinch_update_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_pinch_update(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).dx,
+ (*event).dy,
+ (*event).scale,
+ (*event).rotation,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_pinch_end(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, pinch_end_listener);
+ let event = data as *mut ffi::wlr_pointer_pinch_end_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_pinch_end(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).cancelled,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_hold_begin(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, hold_begin_listener);
+ let event = data as *mut ffi::wlr_pointer_hold_begin_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_hold_begin(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).fingers,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_hold_end(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let cursor = &mut *crate::container_of!(listener, Cursor, hold_end_listener);
+ let event = data as *mut ffi::wlr_pointer_hold_end_event;
+
+ let seat = &mut *cursor.seat;
+ seat.handle_activity();
+
+ let server = seat.server;
+ let pointer_gestures = (*server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_hold_end(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ (*event).cancelled,
+ );
+ }
+}
+
diff --git a/src/drag_icon.rs b/src/drag_icon.rs
new file mode 100644
index 0000000..67422a9
--- /dev/null
+++ b/src/drag_icon.rs
@@ -0,0 +1,92 @@
+// SPDX-FileCopyrightText: © 2024 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::cursor::Cursor;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
+
+#[repr(C)]
+pub struct DragIcon {
+ pub wlr_drag_icon: *mut ffi::wlr_drag_icon,
+ pub scene_drag_icon: *mut ffi::wlr_scene_tree,
+ pub destroy: ffi::wl_listener,
+}
+
+impl DragIcon {
+ pub unsafe fn create(
+ wlr_drag_icon: *mut ffi::wlr_drag_icon,
+ cursor: *mut Cursor,
+ ) -> Result<(), &'static str> {
+ let server = (*(*cursor).seat).server;
+
+ let scene_drag_icon = ffi::wlr_scene_drag_icon_create((*server).scene.drag_icons, wlr_drag_icon);
+ if scene_drag_icon.is_null() {
+ return Err("Failed to create scene drag icon");
+ }
+
+ let drag_icon = Box::new(Self {
+ wlr_drag_icon,
+ scene_drag_icon,
+ destroy: std::mem::zeroed(),
+ });
+ let raw = Box::into_raw(drag_icon);
+
+ ffi::river_scene_node_set_data(
+ scene_drag_icon as *mut ffi::wlr_scene_node,
+ raw as *mut std::ffi::c_void,
+ );
+
+ let drag_icon_ref = &mut *raw;
+ drag_icon_ref.update_position(cursor);
+
+ let destroy_listener = &mut drag_icon_ref.destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener).notify = Some(handle_destroy);
+ wl_signal_add(
+ &mut (*wlr_drag_icon).events.destroy as *mut ffi::wl_signal,
+ &mut drag_icon_ref.destroy,
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn update_position(&mut self, cursor: *mut Cursor) {
+ let grab_type = ffi::river_wlr_drag_get_grab_type((*self.wlr_drag_icon).drag);
+ match grab_type {
+ ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD => {
+ // unreachable
+ }
+ ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_POINTER => {
+ let x = (*cursor).x();
+ let y = (*cursor).y();
+ ffi::wlr_scene_node_set_position(
+ self.scene_drag_icon as *mut ffi::wlr_scene_node,
+ x as i32,
+ y as i32,
+ );
+ }
+ ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_TOUCH => {
+ let touch_id = ffi::river_wlr_drag_get_touch_id((*self.wlr_drag_icon).drag);
+ if let Some(&(lx, ly)) = (*cursor).touch_points.get(&touch_id) {
+ ffi::wlr_scene_node_set_position(
+ self.scene_drag_icon as *mut ffi::wlr_scene_node,
+ lx as i32,
+ ly as i32,
+ );
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+unsafe extern "C" fn handle_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let drag_icon_ptr = crate::container_of!(listener, DragIcon, destroy);
+ let mut drag_icon = Box::from_raw(drag_icon_ptr);
+ wl_listener_remove(&mut drag_icon.destroy);
+
+ // Destroy the scene node hierarchy.
+ ffi::wlr_scene_node_destroy(drag_icon.scene_drag_icon as *mut ffi::wlr_scene_node);
+}
diff --git a/src/ffi.rs b/src/ffi.rs
new file mode 100644
index 0000000..acbd2c3
--- /dev/null
+++ b/src/ffi.rs
@@ -0,0 +1,19 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct wl_listener {
+ pub link: wl_list,
+ pub notify: ::std::option::Option<
+ unsafe extern "C" fn(listener: *mut wl_listener, data: *mut ::std::os::raw::c_void),
+ >,
+}
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+extern "C" {
+ pub fn river_init_wlroots_log(importance: wlr_log_importance);
+}
diff --git a/src/idle_inhibit_manager.rs b/src/idle_inhibit_manager.rs
new file mode 100644
index 0000000..e2d9d27
--- /dev/null
+++ b/src/idle_inhibit_manager.rs
@@ -0,0 +1,144 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, wl_signal_add, wl_listener_remove};
+use crate::scene_node_data::SceneNodeData;
+
+pub struct IdleInhibitManager {
+ pub wlr_manager: *mut ffi::wlr_idle_inhibit_manager_v1,
+ pub new_idle_inhibitor: ffi::wl_listener,
+ pub inhibitors: ffi::wl_list,
+ pub server: *mut Server,
+}
+
+impl IdleInhibitManager {
+ pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.server = server;
+ ffi::wl_list_init(&mut self.inhibitors);
+
+ let wlr_manager = ffi::wlr_idle_inhibit_v1_create((*server).wl_server);
+ if wlr_manager.is_null() {
+ return Err("Failed to create wlr_idle_inhibit_manager_v1");
+ }
+ self.wlr_manager = wlr_manager;
+
+ let new_inhibitor_listener = &mut self.new_idle_inhibitor as *mut ffi::wl_listener as *mut WlListener;
+ (*new_inhibitor_listener).notify = Some(handle_new_idle_inhibitor);
+ wl_signal_add(
+ &mut (*self.wlr_manager).events.new_inhibitor,
+ &mut self.new_idle_inhibitor,
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ wl_listener_remove(&mut self.new_idle_inhibitor);
+
+ let inhibitors_head = &mut self.inhibitors as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*inhibitors_head).next;
+ while curr != inhibitors_head {
+ let next = (*curr).next;
+ let inhibitor = crate::container_of!(curr, IdleInhibitor, link);
+ IdleInhibitor::destroy(inhibitor);
+ curr = next;
+ }
+ }
+
+ pub unsafe fn check_active(&self) {
+ let mut inhibited = false;
+
+ let inhibitors_head = &self.inhibitors as *const ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*inhibitors_head).next;
+ while curr != inhibitors_head {
+ let next = (*curr).next;
+ let inhibitor = crate::container_of!(curr, IdleInhibitor, link);
+
+ let surface = (*(*inhibitor).wlr_inhibitor).surface;
+ if let Some(node_data) = SceneNodeData::from_surface(surface) {
+ match node_data.data {
+ crate::scene_node_data::SceneNodeDataVal::Window(_) |
+ crate::scene_node_data::SceneNodeDataVal::ShellSurface(_) |
+ crate::scene_node_data::SceneNodeDataVal::LockSurface(_) |
+ crate::scene_node_data::SceneNodeDataVal::LayerSurface(_) |
+ crate::scene_node_data::SceneNodeDataVal::OverrideRedirect(_) => {
+ inhibited = true;
+ break;
+ }
+ }
+ }
+ curr = next;
+ }
+
+ let notifier = (*self.server).input_manager.idle_notifier;
+ if !notifier.is_null() {
+ ffi::wlr_idle_notifier_v1_set_inhibited(notifier, inhibited);
+ }
+ }
+}
+
+pub struct IdleInhibitor {
+ pub inhibit_manager: *mut IdleInhibitManager,
+ pub wlr_inhibitor: *mut ffi::wlr_idle_inhibitor_v1,
+ pub listen_destroy: ffi::wl_listener,
+ pub link: ffi::wl_list,
+}
+
+impl IdleInhibitor {
+ pub unsafe fn create(
+ wlr_inhibitor: *mut ffi::wlr_idle_inhibitor_v1,
+ inhibit_manager: *mut IdleInhibitManager,
+ ) -> Result<*mut Self, &'static str> {
+ let inhibitor = Box::into_raw(Box::new(Self {
+ inhibit_manager,
+ wlr_inhibitor,
+ listen_destroy: std::mem::zeroed(),
+ link: std::mem::zeroed(),
+ }));
+
+ let destroy_listener = &mut (*inhibitor).listen_destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener).notify = Some(handle_inhibitor_destroy);
+ wl_signal_add(
+ &mut (*wlr_inhibitor).events.destroy,
+ &mut (*inhibitor).listen_destroy,
+ );
+
+ let list_head = &mut (*inhibit_manager).inhibitors as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert((*list_head).prev, &mut (*inhibitor).link as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ (*inhibit_manager).check_active();
+
+ Ok(inhibitor)
+ }
+
+ pub unsafe fn destroy(inhibitor: *mut Self) {
+ wl_listener_remove(&mut (*inhibitor).listen_destroy);
+ crate::server::wl_list_remove(&mut (*inhibitor).link as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ let manager = (*inhibitor).inhibit_manager;
+ let _boxed = Box::from_raw(inhibitor);
+
+ (*manager).check_active();
+ }
+}
+
+unsafe extern "C" fn handle_new_idle_inhibitor(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let manager = &mut *crate::container_of!(listener, IdleInhibitManager, new_idle_inhibitor);
+ let inhibitor = data as *mut ffi::wlr_idle_inhibitor_v1;
+
+ if IdleInhibitor::create(inhibitor, manager).is_err() {
+ log::error!("failed to create idle inhibitor");
+ }
+}
+
+unsafe extern "C" fn handle_inhibitor_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let inhibitor = crate::container_of!(listener, IdleInhibitor, listen_destroy);
+ IdleInhibitor::destroy(inhibitor);
+}
diff --git a/src/input_device.rs b/src/input_device.rs
new file mode 100644
index 0000000..40d455e
--- /dev/null
+++ b/src/input_device.rs
@@ -0,0 +1,102 @@
+use crate::ffi;
+use crate::seat::Seat;
+use crate::server::{WlListener, wl_listener_remove};
+
+pub struct InputDeviceConfig {
+ pub scroll_factor: f64,
+ pub map_to_output: *mut ffi::wlr_output,
+ pub map_to_rectangle: ffi::wlr_box,
+}
+
+pub struct InputDevice {
+ pub seat: *mut Seat,
+ pub wlr_device: *mut ffi::wlr_input_device,
+ pub virtual_device: bool,
+ pub destroy_listener: ffi::wl_listener,
+ pub config: InputDeviceConfig,
+ pub destroy_fn: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
+ pub destroy_data: *mut std::ffi::c_void,
+}
+
+impl InputDevice {
+ pub unsafe fn new(
+ seat: *mut Seat,
+ wlr_device: *mut ffi::wlr_input_device,
+ virtual_device: bool,
+ ) -> *mut Self {
+ let device = Box::into_raw(Box::new(Self {
+ seat,
+ wlr_device,
+ virtual_device,
+ destroy_listener: std::mem::zeroed(),
+ config: InputDeviceConfig {
+ scroll_factor: 1.0,
+ map_to_output: std::ptr::null_mut(),
+ map_to_rectangle: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ },
+ destroy_fn: None,
+ destroy_data: std::ptr::null_mut(),
+ }));
+
+ ffi::river_wlr_input_device_set_data(wlr_device, device as *mut _);
+
+ let destroy_listener_ptr = &mut (*device).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener_ptr).notify = Some(handle_device_destroy);
+
+ let destroy_signal = ffi::river_wlr_input_device_get_destroy_signal(wlr_device);
+ crate::server::wl_signal_add(destroy_signal, &mut (*device).destroy_listener);
+
+ device
+ }
+
+ pub unsafe fn assign_to_seat(&mut self, new_seat: *mut Seat) {
+ if self.seat == new_seat {
+ return;
+ }
+ if !self.seat.is_null() {
+ (*self.seat).detach_device(self);
+ }
+ self.seat = new_seat;
+ if !self.seat.is_null() {
+ (*self.seat).attach_device(self);
+ }
+ }
+
+ pub unsafe fn active_mapping(&self) -> ffi::wlr_box {
+ let mut mapping = self.config.map_to_rectangle;
+ if !ffi::wlr_box_empty(&mapping) {
+ return mapping;
+ }
+ if !self.config.map_to_output.is_null() {
+ let server = (*self.seat).server;
+ ffi::wlr_output_layout_get_box((*server).om.output_layout, self.config.map_to_output, &mut mapping);
+ }
+ mapping
+ }
+}
+
+unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let device_ptr = crate::container_of!(listener, InputDevice, destroy_listener) as *mut InputDevice;
+ let device = &mut *device_ptr;
+
+ log::debug!(
+ "removed input device: {:?}",
+ ffi::river_wlr_input_device_get_type(device.wlr_device)
+ );
+
+ // Call custom destroy callback if set (e.g. to clean up Tablet/Keyboard wrappers)
+ if let Some(destroy_fn) = device.destroy_fn {
+ destroy_fn(device.destroy_data);
+ }
+
+ // Remove destroy listener
+ wl_listener_remove(&mut device.destroy_listener);
+
+ // Detach from seat if attached
+ if !device.seat.is_null() {
+ (*device.seat).detach_device(device);
+ }
+
+ // Free wrapper memory
+ let _boxed = Box::from_raw(device_ptr);
+}
diff --git a/src/input_manager.rs b/src/input_manager.rs
new file mode 100644
index 0000000..b6db300
--- /dev/null
+++ b/src/input_manager.rs
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, wl_signal_add, WlListener, wl_listener_remove};
+use crate::seat::Seat;
+
+pub struct InputManager {
+ pub server: *mut Server,
+ pub default_seat: *mut Seat,
+ pub seats: ffi::wl_list,
+
+ pub idle_notifier: *mut ffi::wlr_idle_notifier_v1,
+ pub relative_pointer_manager: *mut ffi::wlr_relative_pointer_manager_v1,
+ pub pointer_gestures: *mut ffi::wlr_pointer_gestures_v1,
+ pub virtual_pointer_manager: *mut ffi::wlr_virtual_pointer_manager_v1,
+ pub virtual_keyboard_manager: *mut ffi::wlr_virtual_keyboard_manager_v1,
+ pub pointer_constraints: *mut ffi::wlr_pointer_constraints_v1,
+ pub input_method_manager: *mut ffi::wlr_input_method_manager_v2,
+ pub text_input_manager: *mut ffi::wlr_text_input_manager_v3,
+ pub tablet_manager: *mut ffi::wlr_tablet_manager_v2,
+
+ pub new_input_listener: ffi::wl_listener,
+ pub new_text_input: ffi::wl_listener,
+ pub new_input_method: ffi::wl_listener,
+}
+
+impl Default for InputManager {
+ fn default() -> Self {
+ unsafe { std::mem::zeroed() }
+ }
+}
+
+impl InputManager {
+ pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.server = server;
+ let wl_server = (*server).wl_server;
+
+ ffi::wl_list_init(&mut self.seats);
+
+ self.idle_notifier = ffi::wlr_idle_notifier_v1_create(wl_server);
+ self.relative_pointer_manager = ffi::wlr_relative_pointer_manager_v1_create(wl_server);
+ self.pointer_gestures = ffi::wlr_pointer_gestures_v1_create(wl_server);
+ self.virtual_pointer_manager = ffi::wlr_virtual_pointer_manager_v1_create(wl_server);
+ self.virtual_keyboard_manager = ffi::wlr_virtual_keyboard_manager_v1_create(wl_server);
+ self.pointer_constraints = ffi::wlr_pointer_constraints_v1_create(wl_server);
+ self.input_method_manager = ffi::wlr_input_method_manager_v2_create(wl_server);
+ self.text_input_manager = ffi::wlr_text_input_manager_v3_create(wl_server);
+ self.tablet_manager = ffi::wlr_tablet_v2_create(wl_server);
+
+ // Create default seat
+ self.default_seat = Seat::create(server, "default")?;
+
+ let new_input_ptr = &mut self.new_input_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*new_input_ptr).notify = Some(handle_new_input);
+
+ let signal = ffi::river_wlr_backend_get_new_input_signal((*server).backend);
+ wl_signal_add(signal, &mut self.new_input_listener);
+
+ // Connect new_text_input listener
+ let new_text_input_ptr = &mut self.new_text_input as *mut ffi::wl_listener as *mut WlListener;
+ (*new_text_input_ptr).notify = Some(handle_new_text_input);
+ wl_signal_add(&mut (*self.text_input_manager).events.new_text_input, &mut self.new_text_input);
+
+ // Connect new_input_method listener
+ let new_input_method_ptr = &mut self.new_input_method as *mut ffi::wl_listener as *mut WlListener;
+ (*new_input_method_ptr).notify = Some(handle_new_input_method);
+ wl_signal_add(&mut (*self.input_method_manager).events.new_input_method, &mut self.new_input_method);
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ if !self.default_seat.is_null() {
+ Seat::destroy(self.default_seat);
+ self.default_seat = std::ptr::null_mut();
+ }
+ wl_listener_remove(&mut self.new_input_listener);
+ wl_listener_remove(&mut self.new_text_input);
+ wl_listener_remove(&mut self.new_input_method);
+ }
+}
+
+unsafe extern "C" fn handle_new_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let im = &mut *crate::container_of!(listener, InputManager, new_input_listener);
+ let wlr_device = data as *mut ffi::wlr_input_device;
+
+ log::info!("new input device connected");
+
+ let device = crate::input_device::InputDevice::new(im.default_seat, wlr_device, false);
+
+ let dev_type = ffi::river_wlr_input_device_get_type(wlr_device);
+ if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
+ crate::keyboard::Keyboard::create(device);
+ } else if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET {
+ let _ = crate::tablet::Tablet::create(im.default_seat, wlr_device, false);
+ }
+}
+
+unsafe extern "C" fn handle_new_text_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let _im = crate::container_of!(listener, InputManager, new_text_input);
+ let wlr_text_input = data as *mut ffi::wlr_text_input_v3;
+
+ if let Err(e) = crate::text_input::TextInput::create(wlr_text_input) {
+ log::error!("failed to create text input: {}", e);
+ ffi::wl_resource_post_no_memory((*wlr_text_input).resource);
+ }
+}
+
+unsafe extern "C" fn handle_new_input_method(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let im = &mut *crate::container_of!(listener, InputManager, new_input_method);
+ let input_method = data as *mut ffi::wlr_input_method_v2;
+ let seat = ffi::river_wlr_seat_get_data((*input_method).seat) as *mut Seat;
+ if !seat.is_null() {
+ (*seat).relay.new_input_method(input_method);
+ } else {
+ // Fallback to default seat if input_method.seat has no data set yet
+ (*im.default_seat).relay.new_input_method(input_method);
+ }
+}
diff --git a/src/input_popup.rs b/src/input_popup.rs
new file mode 100644
index 0000000..c07e908
--- /dev/null
+++ b/src/input_popup.rs
@@ -0,0 +1,231 @@
+// SPDX-FileCopyrightText: © 2024 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add, WlList};
+use crate::input_relay::InputRelay;
+use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
+
+#[repr(C)]
+pub struct InputPopup {
+ pub link: ffi::wl_list,
+ pub input_relay: *mut InputRelay,
+ pub wlr_popup: *mut ffi::wlr_input_popup_surface_v2,
+ pub surface_tree: *mut ffi::wlr_scene_tree,
+
+ pub destroy: ffi::wl_listener,
+ pub map: ffi::wl_listener,
+ pub unmap: ffi::wl_listener,
+ pub commit: ffi::wl_listener,
+}
+
+unsafe fn connect_listener(
+ signal: *mut ffi::wl_signal,
+ listener: *mut ffi::wl_listener,
+ callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
+) {
+ let wl_lis = listener as *mut WlListener;
+ (*wl_lis).notify = Some(callback);
+ wl_signal_add(signal, listener);
+}
+
+unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
+ let prev = (*listener).link.prev;
+ let next = (*listener).link.next;
+ if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
+ ffi::wl_list_remove(&mut (*listener).link);
+ (*listener).link.prev = std::ptr::null_mut();
+ (*listener).link.next = std::ptr::null_mut();
+ }
+}
+
+impl InputPopup {
+ pub unsafe fn create(
+ wlr_popup: *mut ffi::wlr_input_popup_surface_v2,
+ input_relay: *mut InputRelay,
+ ) -> Result<(), &'static str> {
+ let server = (*(*input_relay).seat).server;
+ let hidden_tree = (*server).scene.hidden_tree;
+
+ let surface_tree = ffi::wlr_scene_subsurface_tree_create(hidden_tree, (*wlr_popup).surface);
+ if surface_tree.is_null() {
+ return Err("Failed to create subsurface tree for input popup");
+ }
+
+ let mut input_popup = Box::new(InputPopup {
+ link: std::mem::zeroed(),
+ input_relay,
+ wlr_popup,
+ surface_tree,
+ destroy: std::mem::zeroed(),
+ map: std::mem::zeroed(),
+ unmap: std::mem::zeroed(),
+ commit: std::mem::zeroed(),
+ });
+
+ ffi::wl_list_init(&mut input_popup.link);
+
+ let raw = Box::into_raw(input_popup);
+
+ // Append to input_relay.input_popups
+ let popups_list = &mut (*input_relay).input_popups as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert((*popups_list).prev, &mut (*raw).link as *mut ffi::wl_list as *mut WlList);
+
+ connect_listener(&mut (*wlr_popup).events.destroy, &mut (*raw).destroy, handle_destroy);
+ connect_listener(
+ ffi::river_wlr_surface_get_map_signal((*wlr_popup).surface),
+ &mut (*raw).map,
+ handle_map,
+ );
+ connect_listener(
+ ffi::river_wlr_surface_get_unmap_signal((*wlr_popup).surface),
+ &mut (*raw).unmap,
+ handle_unmap,
+ );
+ connect_listener(
+ ffi::river_wlr_surface_get_commit_signal((*wlr_popup).surface),
+ &mut (*raw).commit,
+ handle_commit,
+ );
+
+ (*raw).update();
+
+ Ok(())
+ }
+
+ pub unsafe fn update(&mut self) {
+ let text_input = (*self.input_relay).text_input;
+ if text_input.is_null() {
+ let server = (*(*self.input_relay).seat).server;
+ let hidden_tree = (*server).scene.hidden_tree;
+ ffi::wlr_scene_node_reparent(
+ self.surface_tree as *mut ffi::wlr_scene_node,
+ hidden_tree,
+ );
+ return;
+ }
+
+ if !ffi::river_wlr_surface_is_mapped((*self.wlr_popup).surface) {
+ return;
+ }
+
+ let focused_surface = (*(*text_input).wlr_text_input).focused_surface;
+ if focused_surface.is_null() {
+ return;
+ }
+
+ assert_eq!(ffi::wlr_surface_get_root_surface(focused_surface), focused_surface);
+
+ let focused = match SceneNodeData::from_surface(focused_surface) {
+ Some(f) => f,
+ None => return,
+ };
+
+ let server = (*(*self.input_relay).seat).server;
+
+ let popup_tree = match focused.data {
+ SceneNodeDataVal::Window(window) => (*window).popup_tree,
+ SceneNodeDataVal::ShellSurface(shell_surface) => (*shell_surface).popup_tree,
+ SceneNodeDataVal::LockSurface(_) => (*server).scene.layers.popups,
+ SceneNodeDataVal::LayerSurface(layer_surface) => (*layer_surface).popup_tree,
+ SceneNodeDataVal::OverrideRedirect(_) => panic!("Xwayland doesn't use text-input protocol"),
+ };
+
+ ffi::wlr_scene_node_reparent(self.surface_tree as *mut ffi::wlr_scene_node, popup_tree);
+
+ // cursor_rectangle features check: check if WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is active
+ let active_features = (*(*text_input).wlr_text_input).active_features;
+ let feature_cursor_rect = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE;
+ if (active_features & feature_cursor_rect) == 0 {
+ ffi::wlr_scene_node_set_position(self.surface_tree as *mut ffi::wlr_scene_node, 0, 0);
+ return;
+ }
+
+ let mut focused_x: std::os::raw::c_int = 0;
+ let mut focused_y: std::os::raw::c_int = 0;
+ ffi::wlr_scene_node_coords(focused.node, &mut focused_x, &mut focused_y);
+
+ let mut cursor_box = (*(*text_input).wlr_text_input).current.cursor_rectangle;
+
+ let wlr_output = (*server).om.output_at(
+ (focused_x + cursor_box.x) as f64,
+ (focused_y + cursor_box.y) as f64,
+ );
+ if wlr_output.is_null() {
+ return;
+ }
+
+ let mut output_box = ffi::wlr_box {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ };
+ ffi::wlr_output_layout_get_box((*server).om.output_layout, wlr_output, &mut output_box);
+
+ cursor_box.x += focused_x - output_box.x;
+ cursor_box.y += focused_y - output_box.y;
+
+ let popup_width = ffi::river_wlr_surface_get_width((*self.wlr_popup).surface);
+ let popup_height = ffi::river_wlr_surface_get_height((*self.wlr_popup).surface);
+
+ let popup_x = if output_box.width - cursor_box.x >= popup_width {
+ cursor_box.x
+ } else {
+ cursor_box.x + cursor_box.width - popup_width
+ };
+
+ let popup_y = if output_box.height - (cursor_box.y + cursor_box.height) >= popup_height {
+ cursor_box.y + cursor_box.height
+ } else {
+ cursor_box.y - popup_height
+ };
+
+ ffi::wlr_scene_node_set_position(
+ self.surface_tree as *mut ffi::wlr_scene_node,
+ popup_x - focused_x + output_box.x,
+ popup_y - focused_y + output_box.y,
+ );
+
+ cursor_box.x -= popup_x;
+ cursor_box.y -= popup_y;
+ ffi::wlr_input_popup_surface_v2_send_text_input_rectangle(self.wlr_popup, &mut cursor_box);
+ }
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let input_popup = crate::container_of!(listener, InputPopup, destroy);
+
+ wl_listener_remove_safe(&mut (*input_popup).destroy);
+ wl_listener_remove_safe(&mut (*input_popup).map);
+ wl_listener_remove_safe(&mut (*input_popup).unmap);
+ wl_listener_remove_safe(&mut (*input_popup).commit);
+
+ ffi::wl_list_remove(&mut (*input_popup).link);
+
+ let _ = Box::from_raw(input_popup);
+}
+
+unsafe extern "C" fn handle_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let input_popup = crate::container_of!(listener, InputPopup, map);
+ (*input_popup).update();
+}
+
+unsafe extern "C" fn handle_unmap(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let input_popup = crate::container_of!(listener, InputPopup, unmap);
+ let server = (*(*(*input_popup).input_relay).seat).server;
+ let hidden_tree = (*server).scene.hidden_tree;
+
+ ffi::wlr_scene_node_reparent(
+ (*input_popup).surface_tree as *mut ffi::wlr_scene_node,
+ hidden_tree,
+ );
+}
+
+unsafe extern "C" fn handle_commit(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let input_popup = crate::container_of!(listener, InputPopup, commit);
+ (*input_popup).update();
+}
diff --git a/src/input_relay.rs b/src/input_relay.rs
new file mode 100644
index 0000000..7ada558
--- /dev/null
+++ b/src/input_relay.rs
@@ -0,0 +1,283 @@
+// SPDX-FileCopyrightText: © 2021 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
+use crate::text_input::TextInput;
+use crate::seat::Seat;
+
+#[repr(C)]
+pub struct InputRelay {
+ pub seat: *mut Seat,
+ pub text_inputs: ffi::wl_list,
+ pub input_method: *mut ffi::wlr_input_method_v2,
+ pub input_popups: ffi::wl_list,
+ pub text_input: *mut TextInput,
+
+ pub input_method_commit: ffi::wl_listener,
+ pub grab_keyboard: ffi::wl_listener,
+ pub input_method_destroy: ffi::wl_listener,
+ pub input_method_new_popup: ffi::wl_listener,
+
+ pub grab_keyboard_destroy: ffi::wl_listener,
+}
+
+unsafe fn connect_listener(
+ signal: *mut ffi::wl_signal,
+ listener: *mut ffi::wl_listener,
+ callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
+) {
+ let wl_lis = listener as *mut WlListener;
+ (*wl_lis).notify = Some(callback);
+ wl_signal_add(signal, listener);
+}
+
+unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
+ let prev = (*listener).link.prev;
+ let next = (*listener).link.next;
+ if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
+ ffi::wl_list_remove(&mut (*listener).link);
+ (*listener).link.prev = std::ptr::null_mut();
+ (*listener).link.next = std::ptr::null_mut();
+ }
+}
+
+impl InputRelay {
+ pub unsafe fn init(&mut self, seat: *mut Seat) {
+ self.seat = seat;
+ ffi::wl_list_init(&mut self.text_inputs);
+ ffi::wl_list_init(&mut self.input_popups);
+ self.input_method = std::ptr::null_mut();
+ self.text_input = std::ptr::null_mut();
+ self.input_method_commit = std::mem::zeroed();
+ self.grab_keyboard = std::mem::zeroed();
+ self.input_method_destroy = std::mem::zeroed();
+ self.input_method_new_popup = std::mem::zeroed();
+ self.grab_keyboard_destroy = std::mem::zeroed();
+ }
+
+ pub unsafe fn new_input_method(&mut self, input_method: *mut ffi::wlr_input_method_v2) {
+ let seat = crate::container_of!(self, Seat, relay);
+ let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat))
+ .to_str()
+ .unwrap_or("unknown");
+ log::debug!("new input method on seat {}", seat_name);
+
+ if !self.input_method.is_null() {
+ log::info!("seat {} already has an input method", seat_name);
+ ffi::wlr_input_method_v2_send_unavailable(input_method);
+ return;
+ }
+
+ self.input_method = input_method;
+
+ connect_listener(&mut (*input_method).events.commit, &mut self.input_method_commit, handle_input_method_commit);
+ connect_listener(&mut (*input_method).events.grab_keyboard, &mut self.grab_keyboard, handle_input_method_grab_keyboard);
+ connect_listener(&mut (*input_method).events.destroy, &mut self.input_method_destroy, handle_input_method_destroy);
+ connect_listener(&mut (*input_method).events.new_popup_surface, &mut self.input_method_new_popup, handle_input_method_new_popup);
+
+ let focused_surface = (*seat).focused.surface();
+ if !focused_surface.is_null() {
+ self.focus(focused_surface);
+ }
+ }
+
+ pub unsafe fn disable_text_input(&mut self) {
+ assert!(!self.text_input.is_null());
+ self.text_input = std::ptr::null_mut();
+
+ if !self.input_method.is_null() {
+ let mut pos = self.input_popups.next;
+ let head_ptr = &self.input_popups as *const ffi::wl_list;
+ while pos != head_ptr as *mut ffi::wl_list {
+ let next_pos = (*pos).next;
+ let popup = crate::container_of!(pos, crate::input_popup::InputPopup, link);
+ (*popup).update();
+ pos = next_pos;
+ }
+
+ ffi::wlr_input_method_v2_send_deactivate(self.input_method);
+ ffi::wlr_input_method_v2_send_done(self.input_method);
+ }
+ }
+
+ pub unsafe fn send_input_method_state(&mut self) {
+ let input_method = self.input_method;
+ let wlr_text_input = (*self.text_input).wlr_text_input;
+
+ let active_features = (*wlr_text_input).active_features;
+ let feature_surrounding = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT;
+ let feature_content = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE;
+
+ if (active_features & feature_surrounding) != 0 {
+ let text = (*wlr_text_input).current.surrounding.text;
+ if !text.is_null() {
+ ffi::wlr_input_method_v2_send_surrounding_text(
+ input_method,
+ text,
+ (*wlr_text_input).current.surrounding.cursor,
+ (*wlr_text_input).current.surrounding.anchor,
+ );
+ }
+ }
+
+ ffi::wlr_input_method_v2_send_text_change_cause(input_method, (*wlr_text_input).current.text_change_cause);
+
+ if (active_features & feature_content) != 0 {
+ ffi::wlr_input_method_v2_send_content_type(
+ input_method,
+ (*wlr_text_input).current.content_type.hint,
+ (*wlr_text_input).current.content_type.purpose,
+ );
+ }
+
+ let mut pos = self.input_popups.next;
+ let head_ptr = &self.input_popups as *const ffi::wl_list;
+ while pos != head_ptr as *mut ffi::wl_list {
+ let next_pos = (*pos).next;
+ let popup = crate::container_of!(pos, crate::input_popup::InputPopup, link);
+ (*popup).update();
+ pos = next_pos;
+ }
+
+ ffi::wlr_input_method_v2_send_done(input_method);
+ }
+
+ pub unsafe fn focus(&mut self, new_focus: *mut ffi::wlr_surface) {
+ // Send leave events
+ let mut pos = self.text_inputs.next;
+ let head_ptr = &self.text_inputs as *const ffi::wl_list;
+ while pos != head_ptr as *mut ffi::wl_list {
+ let next_pos = (*pos).next;
+ let text_input = crate::container_of!(pos, TextInput, link);
+ let focused = (*(*text_input).wlr_text_input).focused_surface;
+ if !focused.is_null() {
+ assert!(focused != new_focus);
+ ffi::wlr_text_input_v3_send_leave((*text_input).wlr_text_input);
+ }
+ pos = next_pos;
+ }
+
+ // Clear currently enabled text input
+ if !self.text_input.is_null() {
+ self.disable_text_input();
+ }
+
+ // Send enter events if we have an input method
+ if !new_focus.is_null() && !self.input_method.is_null() {
+ let new_client = ffi::wl_resource_get_client(ffi::river_wlr_surface_get_resource(new_focus));
+ let mut pos = self.text_inputs.next;
+ while pos != head_ptr as *mut ffi::wl_list {
+ let next_pos = (*pos).next;
+ let text_input = crate::container_of!(pos, TextInput, link);
+ let text_input_resource = (*(*text_input).wlr_text_input).resource;
+ let client = ffi::wl_resource_get_client(text_input_resource);
+ if client == new_client {
+ ffi::wlr_text_input_v3_send_enter((*text_input).wlr_text_input, new_focus);
+ }
+ pos = next_pos;
+ }
+ }
+ }
+}
+
+unsafe extern "C" fn handle_input_method_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let relay = crate::container_of!(listener, InputRelay, input_method_commit);
+ let input_method = (*relay).input_method;
+
+ if !(*input_method).client_active {
+ return;
+ }
+
+ let text_input = (*relay).text_input;
+ if text_input.is_null() {
+ return;
+ }
+
+ let preedit_text = (*input_method).current.preedit.text;
+ if !preedit_text.is_null() {
+ ffi::wlr_text_input_v3_send_preedit_string(
+ (*text_input).wlr_text_input,
+ preedit_text,
+ (*input_method).current.preedit.cursor_begin,
+ (*input_method).current.preedit.cursor_end,
+ );
+ }
+
+ let commit_text = (*input_method).current.commit_text;
+ if !commit_text.is_null() {
+ ffi::wlr_text_input_v3_send_commit_string((*text_input).wlr_text_input, commit_text);
+ }
+
+ if (*input_method).current.delete.before_length != 0
+ || (*input_method).current.delete.after_length != 0
+ {
+ ffi::wlr_text_input_v3_send_delete_surrounding_text(
+ (*text_input).wlr_text_input,
+ (*input_method).current.delete.before_length,
+ (*input_method).current.delete.after_length,
+ );
+ }
+
+ ffi::wlr_text_input_v3_send_done((*text_input).wlr_text_input);
+}
+
+unsafe extern "C" fn handle_input_method_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let relay = crate::container_of!(listener, InputRelay, input_method_destroy);
+
+ wl_listener_remove_safe(&mut (*relay).input_method_commit);
+ wl_listener_remove_safe(&mut (*relay).grab_keyboard);
+ wl_listener_remove_safe(&mut (*relay).input_method_destroy);
+ wl_listener_remove_safe(&mut (*relay).input_method_new_popup);
+ (*relay).input_method = std::ptr::null_mut();
+
+ (*relay).focus(std::ptr::null_mut());
+
+ assert!((*relay).text_input.is_null());
+}
+
+unsafe extern "C" fn handle_input_method_grab_keyboard(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let relay = crate::container_of!(listener, InputRelay, grab_keyboard);
+ let keyboard_grab = data as *mut ffi::wlr_input_method_keyboard_grab_v2;
+ let seat = crate::container_of!(relay, Seat, relay);
+
+ let active_keyboard = ffi::river_wlr_seat_get_keyboard((*seat).wlr_seat);
+ ffi::wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, active_keyboard);
+
+ connect_listener(
+ ffi::river_wlr_input_method_keyboard_grab_v2_get_destroy_signal(keyboard_grab),
+ &mut (*relay).grab_keyboard_destroy,
+ handle_input_method_grab_keyboard_destroy,
+ );
+}
+
+unsafe extern "C" fn handle_input_method_new_popup(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let relay = crate::container_of!(listener, InputRelay, input_method_new_popup);
+ let wlr_popup = data as *mut ffi::wlr_input_popup_surface_v2;
+
+ if let Err(e) = crate::input_popup::InputPopup::create(wlr_popup, relay) {
+ log::error!("failed to create input popup: {}", e);
+ }
+}
+
+unsafe extern "C" fn handle_input_method_grab_keyboard_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let relay = crate::container_of!(listener, InputRelay, grab_keyboard_destroy);
+ let input_method = (*relay).input_method;
+ let keyboard_grab = (*input_method).keyboard_grab;
+ wl_listener_remove_safe(&mut (*relay).grab_keyboard_destroy);
+
+ let keyboard = ffi::river_wlr_input_method_keyboard_grab_v2_get_keyboard(keyboard_grab);
+ if !keyboard.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(keyboard);
+ ffi::wlr_seat_keyboard_notify_modifiers((*input_method).seat, modifiers);
+ }
+}
diff --git a/src/keyboard.rs b/src/keyboard.rs
new file mode 100644
index 0000000..45b6317
--- /dev/null
+++ b/src/keyboard.rs
@@ -0,0 +1,251 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::input_device::InputDevice;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
+use crate::keyboard_group::KeyboardGroup;
+use std::collections::HashSet;
+
+#[derive(Clone, Copy)]
+pub struct KeyboardConfig {
+ pub keymap: *mut ffi::xkb_keymap,
+ pub repeat_rate: i32,
+ pub repeat_delay: i32,
+}
+
+pub struct Keyboard {
+ pub device: *mut InputDevice,
+ pub wlr_keyboard: *mut ffi::wlr_keyboard,
+ pub key_listener: ffi::wl_listener,
+ pub modifiers_listener: ffi::wl_listener,
+ pub pressed: HashSet<u32>,
+ pub group: *mut KeyboardGroup,
+ pub group_link: ffi::wl_list,
+ pub config: KeyboardConfig,
+}
+
+impl Keyboard {
+ pub unsafe fn create(device: *mut InputDevice) -> *mut Self {
+ let wlr_keyboard = ffi::wlr_keyboard_from_input_device((*device).wlr_device);
+
+ let virtual_device = (*device).virtual_device;
+ let mut keymap = std::ptr::null_mut();
+ if virtual_device {
+ let kbd_keymap = ffi::river_wlr_keyboard_get_keymap(wlr_keyboard);
+ if !kbd_keymap.is_null() {
+ keymap = kbd_keymap;
+ ffi::xkb_keymap_ref(keymap);
+ }
+ } else {
+ let server = (*(*device).seat).server;
+ keymap = (*server).xkb_config.default_keymap;
+ if !keymap.is_null() {
+ ffi::xkb_keymap_ref(keymap);
+ }
+ }
+
+ let keyboard = Box::into_raw(Box::new(Self {
+ device,
+ wlr_keyboard,
+ key_listener: std::mem::zeroed(),
+ modifiers_listener: std::mem::zeroed(),
+ pressed: HashSet::new(),
+ group: std::ptr::null_mut(),
+ group_link: std::mem::zeroed(),
+ config: KeyboardConfig {
+ keymap,
+ repeat_rate: 40,
+ repeat_delay: 400,
+ },
+ }));
+
+ ffi::wl_list_init(&mut (*keyboard).group_link);
+ ffi::river_wlr_keyboard_set_data(wlr_keyboard, keyboard as *mut _);
+
+ let key_listener_ptr = &mut (*keyboard).key_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*key_listener_ptr).notify = Some(handle_key);
+
+ let modifiers_listener_ptr = &mut (*keyboard).modifiers_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*modifiers_listener_ptr).notify = Some(handle_modifiers);
+
+ let key_signal = ffi::river_wlr_keyboard_get_key_signal(wlr_keyboard);
+ wl_signal_add(key_signal, &mut (*keyboard).key_listener);
+
+ let modifiers_signal = ffi::river_wlr_keyboard_get_modifiers_signal(wlr_keyboard);
+ wl_signal_add(modifiers_signal, &mut (*keyboard).modifiers_listener);
+
+ if !virtual_device && should_set_keymap((*(*device).seat).server) {
+ if !keymap.is_null() {
+ ffi::wlr_keyboard_set_keymap(wlr_keyboard, keymap);
+ }
+ }
+
+ (*device).destroy_fn = Some(destroy_callback);
+ (*device).destroy_data = keyboard as *mut _;
+
+ keyboard
+ }
+
+ pub unsafe fn destroy(keyboard: *mut Self) {
+ wl_listener_remove(&mut (*keyboard).key_listener);
+ wl_listener_remove(&mut (*keyboard).modifiers_listener);
+
+ if !(*keyboard).group.is_null() {
+ let keys: Vec<u32> = (*keyboard).pressed.iter().cloned().collect();
+ (*(*keyboard).group).unref(&keys);
+ (*keyboard).group = std::ptr::null_mut();
+ }
+
+ if !(*keyboard).config.keymap.is_null() {
+ ffi::xkb_keymap_unref((*keyboard).config.keymap);
+ }
+
+ let _boxed = Box::from_raw(keyboard);
+ }
+
+ pub unsafe fn set_group(&mut self) {
+ assert!(self.group.is_null());
+ let seat = (*self.device).seat;
+
+ if !(*self.device).virtual_device {
+ let seat_groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*seat_groups_head).next;
+ while curr != seat_groups_head {
+ let next = (*curr).next;
+ let group = crate::container_of!(curr, KeyboardGroup, link);
+ let config_ptr = &mut self.config as *mut KeyboardConfig;
+ if (*group).match_config(config_ptr) {
+ self.group = (*group).ref_group();
+ let keyboards_head = &mut (*self.group).keyboards as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert(keyboards_head, &mut self.group_link as *mut ffi::wl_list as *mut crate::server::WlList);
+ return;
+ }
+ curr = next;
+ }
+ }
+
+ match KeyboardGroup::create(seat, self.config, (*self.device).virtual_device) {
+ Ok(group_ptr) => {
+ self.group = group_ptr;
+ let keyboards_head = &mut (*self.group).keyboards as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert(keyboards_head, &mut self.group_link as *mut ffi::wl_list as *mut crate::server::WlList);
+ }
+ Err(err) => {
+ log::error!("failed to create KeyboardGroup: {}", err);
+ }
+ }
+ }
+
+ pub unsafe fn set_repeat_info(&mut self, rate: i32, delay: i32) {
+ assert!(!(*self.device).virtual_device);
+ self.config.repeat_rate = rate;
+ self.config.repeat_delay = delay;
+ if !self.group.is_null() {
+ let keys: Vec<u32> = self.pressed.iter().cloned().collect();
+ crate::server::wl_list_remove(&mut self.group_link as *mut ffi::wl_list as *mut crate::server::WlList);
+ (*self.group).unref(&keys);
+ self.group = std::ptr::null_mut();
+ }
+ self.set_group();
+ }
+
+ pub unsafe fn set_keymap(&mut self, keymap: *mut ffi::xkb_keymap) {
+ assert!(!(*self.device).virtual_device);
+ if should_set_keymap((*(*self.device).seat).server) {
+ ffi::wlr_keyboard_set_keymap(self.wlr_keyboard, keymap);
+ }
+ if !self.config.keymap.is_null() {
+ ffi::xkb_keymap_unref(self.config.keymap);
+ }
+ self.config.keymap = keymap;
+ if !keymap.is_null() {
+ ffi::xkb_keymap_ref(keymap);
+ }
+ if !self.group.is_null() {
+ let keys: Vec<u32> = self.pressed.iter().cloned().collect();
+ crate::server::wl_list_remove(&mut self.group_link as *mut ffi::wl_list as *mut crate::server::WlList);
+ (*self.group).unref(&keys);
+ self.group = std::ptr::null_mut();
+ }
+ self.set_group();
+ }
+}
+
+unsafe extern "C" fn destroy_callback(data: *mut std::ffi::c_void) {
+ Keyboard::destroy(data as *mut Keyboard);
+}
+
+unsafe fn should_set_keymap(server: *mut crate::server::Server) -> bool {
+ let backend = (*server).backend;
+ ffi::wlr_backend_is_wl(backend) || ffi::wlr_backend_is_x11(backend)
+}
+
+pub unsafe fn keysym_is_modifier(sym: u32) -> bool {
+ match sym {
+ ffi::XKB_KEY_Shift_L |
+ ffi::XKB_KEY_Shift_R |
+ ffi::XKB_KEY_Control_L |
+ ffi::XKB_KEY_Control_R |
+ ffi::XKB_KEY_Caps_Lock |
+ ffi::XKB_KEY_Shift_Lock |
+
+ ffi::XKB_KEY_Meta_L |
+ ffi::XKB_KEY_Meta_R |
+ ffi::XKB_KEY_Alt_L |
+ ffi::XKB_KEY_Alt_R |
+ ffi::XKB_KEY_Super_L |
+ ffi::XKB_KEY_Super_R |
+ ffi::XKB_KEY_Hyper_L |
+ ffi::XKB_KEY_Hyper_R |
+
+ ffi::XKB_KEY_Num_Lock |
+
+ ffi::XKB_KEY_ISO_Lock |
+ ffi::XKB_KEY_ISO_Level2_Latch |
+ ffi::XKB_KEY_ISO_Level3_Shift |
+ ffi::XKB_KEY_ISO_Level3_Latch |
+ ffi::XKB_KEY_ISO_Level3_Lock |
+ ffi::XKB_KEY_ISO_Level5_Shift |
+ ffi::XKB_KEY_ISO_Level5_Latch |
+ ffi::XKB_KEY_ISO_Level5_Lock |
+ ffi::XKB_KEY_ISO_Group_Shift |
+ ffi::XKB_KEY_ISO_Group_Latch |
+ ffi::XKB_KEY_ISO_Group_Lock |
+ ffi::XKB_KEY_ISO_Next_Group |
+ ffi::XKB_KEY_ISO_Next_Group_Lock |
+ ffi::XKB_KEY_ISO_Prev_Group |
+ ffi::XKB_KEY_ISO_Prev_Group_Lock |
+ ffi::XKB_KEY_ISO_First_Group |
+ ffi::XKB_KEY_ISO_First_Group_Lock |
+ ffi::XKB_KEY_ISO_Last_Group |
+ ffi::XKB_KEY_ISO_Last_Group_Lock => true,
+ _ => false,
+ }
+}
+
+unsafe extern "C" fn handle_key(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let keyboard = &mut *crate::container_of!(listener, Keyboard, key_listener);
+ let event = data as *mut ffi::wlr_keyboard_key_event;
+
+ if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_RELEASED {
+ keyboard.pressed.remove(&(*event).keycode);
+ } else if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ if keyboard.pressed.len() < 32 {
+ keyboard.pressed.insert((*event).keycode);
+ }
+ }
+
+ if !keyboard.group.is_null() {
+ (*keyboard.group).process_key(event);
+ }
+}
+
+unsafe extern "C" fn handle_modifiers(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let keyboard = &mut *crate::container_of!(listener, Keyboard, modifiers_listener);
+
+ if !keyboard.group.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(keyboard.wlr_keyboard);
+ (*keyboard.group).process_modifiers(*modifiers);
+ }
+}
diff --git a/src/keyboard_group.rs b/src/keyboard_group.rs
new file mode 100644
index 0000000..7d885e7
--- /dev/null
+++ b/src/keyboard_group.rs
@@ -0,0 +1,453 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::seat::Seat;
+use crate::keyboard::{Keyboard, KeyboardConfig};
+use crate::xkb_bindings::XkbBinding;
+use crate::server::wl_listener_remove;
+use std::collections::HashMap;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum KeyConsumer {
+ Builtin,
+ Binding(*mut XkbBinding),
+ EnsureEaten,
+ ImGrab,
+ Focus,
+}
+
+pub struct Press {
+ pub consumer: KeyConsumer,
+ pub count: u32,
+}
+
+pub struct KeyboardGroup {
+ pub ref_count: u32,
+ pub seat: *mut Seat,
+ pub link: ffi::wl_list, // Seat.keyboard_groups
+ pub virtual_device: bool,
+ pub config: KeyboardConfig,
+ pub wlr_keyboard: ffi::wlr_keyboard,
+ pub modifiers_old: u32,
+ pub pressed: HashMap<u32, Press>,
+ pub key_listener: ffi::wl_listener,
+ pub modifiers_listener: ffi::wl_listener,
+ pub keyboards: ffi::wl_list, // list of keyboards in this group
+}
+
+impl KeyboardGroup {
+ pub unsafe fn create(
+ seat: *mut Seat,
+ config: KeyboardConfig,
+ virtual_device: bool,
+ ) -> Result<*mut Self, &'static str> {
+ let mut group = Box::new(Self {
+ ref_count: 1,
+ seat,
+ link: std::mem::zeroed(),
+ virtual_device,
+ config,
+ wlr_keyboard: std::mem::zeroed(),
+ modifiers_old: 0,
+ pressed: HashMap::new(),
+ key_listener: std::mem::zeroed(),
+ modifiers_listener: std::mem::zeroed(),
+ keyboards: std::mem::zeroed(),
+ });
+
+ ffi::wl_list_init(&mut group.keyboards);
+ ffi::wl_list_init(&mut group.link);
+
+ // Add to seat.keyboard_groups
+ let seat_groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert((*seat_groups_head).prev, &mut group.link as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ let group_ptr = Box::into_raw(group);
+
+ ffi::river_wlr_keyboard_init(
+ &mut (*group_ptr).wlr_keyboard,
+ Some(led_update),
+ b"river.KeyboardGroup\0".as_ptr() as *const _,
+ );
+
+ ffi::river_wlr_keyboard_set_data(&mut (*group_ptr).wlr_keyboard, group_ptr as *mut _);
+
+ if !config.keymap.is_null() {
+ ffi::wlr_keyboard_set_keymap(&mut (*group_ptr).wlr_keyboard, config.keymap);
+ }
+ ffi::wlr_keyboard_set_repeat_info(&mut (*group_ptr).wlr_keyboard, config.repeat_rate, config.repeat_delay);
+
+ let key_listener_ptr = &mut (*group_ptr).key_listener as *mut ffi::wl_listener as *mut crate::server::WlListener;
+ (*key_listener_ptr).notify = Some(handle_group_key);
+ let key_signal = ffi::river_wlr_keyboard_get_key_signal(&mut (*group_ptr).wlr_keyboard);
+ crate::server::wl_signal_add(key_signal, &mut (*group_ptr).key_listener);
+
+ let modifiers_listener_ptr = &mut (*group_ptr).modifiers_listener as *mut ffi::wl_listener as *mut crate::server::WlListener;
+ (*modifiers_listener_ptr).notify = Some(handle_group_modifiers);
+ let modifiers_signal = ffi::river_wlr_keyboard_get_modifiers_signal(&mut (*group_ptr).wlr_keyboard);
+ crate::server::wl_signal_add(modifiers_signal, &mut (*group_ptr).modifiers_listener);
+
+ if !config.keymap.is_null() {
+ ffi::xkb_keymap_ref(config.keymap);
+ }
+
+ Ok(group_ptr)
+ }
+
+ pub unsafe fn ref_group(&mut self) -> *mut Self {
+ self.ref_count += 1;
+ self
+ }
+
+ pub unsafe fn unref(&mut self, to_release: &[u32]) {
+ for &keycode in to_release {
+ let mut event = ffi::wlr_keyboard_key_event {
+ time_msec: crate::util::msec_timestamp(),
+ keycode,
+ update_state: true,
+ state: ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_RELEASED,
+ };
+ self.process_key(&mut event);
+ }
+
+ self.ref_count -= 1;
+ if self.ref_count > 0 {
+ return;
+ }
+
+ crate::server::wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut crate::server::WlList);
+ wl_listener_remove(&mut self.key_listener);
+ wl_listener_remove(&mut self.modifiers_listener);
+
+ // If the currently active keyboard of a seat is destroyed, we need to set a new active keyboard.
+ let active_wlr_kbd = ffi::river_wlr_seat_get_keyboard((*self.seat).wlr_seat);
+ if active_wlr_kbd == &mut self.wlr_keyboard {
+ let seat_groups_head = &mut (*self.seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+ let first_node = (*seat_groups_head).next;
+ if first_node != seat_groups_head {
+ let other_group = crate::container_of!(first_node, KeyboardGroup, link);
+ ffi::wlr_seat_set_keyboard((*self.seat).wlr_seat, &mut (*other_group).wlr_keyboard);
+ } else {
+ ffi::wlr_seat_set_keyboard((*self.seat).wlr_seat, std::ptr::null_mut());
+ }
+ }
+
+ ffi::wlr_keyboard_finish(&mut self.wlr_keyboard);
+
+ if !self.config.keymap.is_null() {
+ ffi::xkb_keymap_unref(self.config.keymap);
+ }
+
+ let _boxed = Box::from_raw(self);
+ }
+
+ pub unsafe fn match_config(&self, config: *mut KeyboardConfig) -> bool {
+ if self.config.repeat_rate != (*config).repeat_rate {
+ return false;
+ }
+ if self.config.repeat_delay != (*config).repeat_delay {
+ return false;
+ }
+ if self.config.keymap == (*config).keymap {
+ return true;
+ }
+ if self.config.keymap.is_null() || (*config).keymap.is_null() {
+ return false;
+ }
+
+ let a_string_ptr = ffi::xkb_keymap_get_as_string(self.config.keymap, ffi::xkb_keymap_format_XKB_KEYMAP_FORMAT_TEXT_V1);
+ if a_string_ptr.is_null() {
+ return false;
+ }
+ let b_string_ptr = ffi::xkb_keymap_get_as_string((*config).keymap, ffi::xkb_keymap_format_XKB_KEYMAP_FORMAT_TEXT_V1);
+ if b_string_ptr.is_null() {
+ libc::free(a_string_ptr as *mut _);
+ return false;
+ }
+
+ let a_str = std::ffi::CStr::from_ptr(a_string_ptr);
+ let b_str = std::ffi::CStr::from_ptr(b_string_ptr);
+ let matched = a_str == b_str;
+
+ libc::free(a_string_ptr as *mut _);
+ libc::free(b_string_ptr as *mut _);
+
+ if matched {
+ ffi::xkb_keymap_unref((*config).keymap);
+ (*config).keymap = self.config.keymap;
+ ffi::xkb_keymap_ref((*config).keymap);
+ }
+
+ matched
+ }
+
+ pub unsafe fn process_key(&mut self, event: *const ffi::wlr_keyboard_key_event) {
+ if let Some(key) = self.pressed.get_mut(&(*event).keycode) {
+ assert!(key.count > 0);
+ if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ key.count += 1;
+ } else {
+ key.count -= 1;
+ if key.count == 0 {
+ let mut key_event = ffi::wlr_keyboard_key_event {
+ time_msec: (*event).time_msec,
+ keycode: (*event).keycode,
+ update_state: true,
+ state: ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_RELEASED,
+ };
+ ffi::wlr_keyboard_notify_key(&mut self.wlr_keyboard, &mut key_event);
+ }
+ }
+ } else if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ if self.pressed.len() < 32 {
+ let mut key_event = ffi::wlr_keyboard_key_event {
+ time_msec: (*event).time_msec,
+ keycode: (*event).keycode,
+ update_state: true,
+ state: ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED,
+ };
+ ffi::wlr_keyboard_notify_key(&mut self.wlr_keyboard, &mut key_event);
+ }
+ }
+ }
+
+ pub unsafe fn process_modifiers(&mut self, modifiers: ffi::wlr_keyboard_modifiers) {
+ ffi::wlr_keyboard_notify_modifiers(
+ &mut self.wlr_keyboard,
+ modifiers.depressed,
+ modifiers.latched,
+ modifiers.locked,
+ modifiers.group,
+ );
+ }
+
+ pub unsafe fn process_keymap(&mut self, keymap: *mut ffi::xkb_keymap) {
+ ffi::wlr_keyboard_set_keymap(&mut self.wlr_keyboard, keymap);
+ }
+
+ pub unsafe fn get_input_method_grab(&self) -> *mut ffi::wlr_input_method_keyboard_grab_v2 {
+ if self.virtual_device {
+ return std::ptr::null_mut();
+ }
+ let input_method = (*self.seat).relay.input_method;
+ if !input_method.is_null() {
+ return (*input_method).keyboard_grab;
+ }
+ std::ptr::null_mut()
+ }
+
+ pub unsafe fn send_state(&mut self) {
+ let keymap = self.config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+ let layout_index = self.wlr_keyboard.modifiers.group;
+ let _layout_name = ffi::xkb_keymap_layout_get_name(keymap, layout_index);
+ let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
+ let _capslock = if caps_idx != ffi::XKB_MOD_INVALID {
+ let caps_mask = 1 << caps_idx;
+ (self.wlr_keyboard.modifiers.locked & caps_mask) != 0
+ } else {
+ false
+ };
+ let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
+ let _numlock = if num_idx != ffi::XKB_MOD_INVALID {
+ let num_mask = 1 << num_idx;
+ (self.wlr_keyboard.modifiers.locked & num_mask) != 0
+ } else {
+ false
+ };
+
+ // Note: Layout configurations broadcasting (river_xkb_keyboard_v1) is stubbed out
+ // in xkb_config.rs and will be fully ported in a later phase.
+ }
+}
+
+unsafe fn handle_builtin_binding(seat: *mut Seat, keysym: u32) -> bool {
+ match keysym {
+ ffi::XKB_KEY_XF86Switch_VT_1..=ffi::XKB_KEY_XF86Switch_VT_12 => {
+ log::debug!("switch VT keysym received");
+ let server = (*seat).server;
+ let session = (*server).session;
+ if !session.is_null() {
+ let vt = keysym - ffi::XKB_KEY_XF86Switch_VT_1 + 1;
+ log::info!("switching to VT {}", vt);
+ ffi::wlr_session_change_vt(session, vt);
+ }
+ true
+ }
+ _ => false,
+ }
+}
+
+unsafe extern "C" fn led_update(wlr_keyboard: *mut ffi::wlr_keyboard, leds: u32) {
+ let group = ffi::river_wlr_keyboard_get_data(wlr_keyboard) as *mut KeyboardGroup;
+ if group.is_null() {
+ return;
+ }
+
+ let keyboards_head = &mut (*group).keyboards as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*keyboards_head).next;
+ while curr != keyboards_head {
+ let next = (*curr).next;
+ let keyboard = crate::container_of!(curr, crate::keyboard::Keyboard, group_link);
+ ffi::wlr_keyboard_led_update((*keyboard).wlr_keyboard, leds);
+ curr = next;
+ }
+}
+
+unsafe extern "C" fn handle_group_key(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let group = &mut *crate::container_of!(listener, KeyboardGroup, key_listener);
+ let event = data as *mut ffi::wlr_keyboard_key_event;
+
+ let xkb_state = group.wlr_keyboard.xkb_state;
+ if xkb_state.is_null() {
+ log::error!("no xkb_state available");
+ return;
+ }
+
+ // Cancel active binding repeats
+ let seat_groups_head = &mut (*group.seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr_g = (*seat_groups_head).next;
+ while curr_g != seat_groups_head {
+ let next_g = (*curr_g).next;
+ let g = crate::container_of!(curr_g, KeyboardGroup, link);
+ for press in (*g).pressed.values() {
+ if let KeyConsumer::Binding(binding) = press.consumer {
+ if !binding.is_null() {
+ (*binding).stop_repeat();
+ }
+ }
+ }
+ curr_g = next_g;
+ }
+
+ let consumer: KeyConsumer = if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_RELEASED {
+ if let Some(kv) = group.pressed.remove(&(*event).keycode) {
+ assert!(kv.count == 0);
+ kv.consumer
+ } else {
+ KeyConsumer::Focus
+ }
+ } else {
+ let xkb_keycode = (*event).keycode + 8;
+
+ let mut matched_builtin = false;
+ let mut syms_ptr: *const ffi::xkb_keysym_t = std::ptr::null();
+ let num_syms = ffi::xkb_state_key_get_syms(xkb_state, xkb_keycode, &mut syms_ptr);
+ if num_syms > 0 && !syms_ptr.is_null() {
+ let syms = std::slice::from_raw_parts(syms_ptr, num_syms as usize);
+ for &sym in syms {
+ if handle_builtin_binding(group.seat, sym) {
+ matched_builtin = true;
+ break;
+ }
+ }
+ }
+
+ if matched_builtin {
+ KeyConsumer::Builtin
+ } else if let Some(binding) = (*group.seat).match_xkb_binding(xkb_keycode, &mut group.wlr_keyboard) {
+ log::debug!("matched xkb binding");
+ (*group.seat).xkb_bindings_seat.ensure_next_key_eaten = false;
+ KeyConsumer::Binding(if (*binding).sent_pressed {
+ std::ptr::null_mut()
+ } else {
+ binding
+ })
+ } else if (*group.seat).xkb_bindings_seat.ensure_next_key_eaten {
+ let mut has_non_modifier = false;
+ let mut syms_ptr: *const ffi::xkb_keysym_t = std::ptr::null();
+ let num_syms = ffi::xkb_state_key_get_syms(xkb_state, xkb_keycode, &mut syms_ptr);
+ if num_syms > 0 && !syms_ptr.is_null() {
+ let syms = std::slice::from_raw_parts(syms_ptr, num_syms as usize);
+ for &sym in syms {
+ if !crate::keyboard::keysym_is_modifier(sym) {
+ has_non_modifier = true;
+ break;
+ }
+ }
+ }
+ if has_non_modifier {
+ (*group.seat).xkb_bindings_seat.ensure_next_key_eaten = false;
+ KeyConsumer::EnsureEaten
+ } else {
+ KeyConsumer::Focus
+ }
+ } else if !group.get_input_method_grab().is_null() {
+ KeyConsumer::ImGrab
+ } else {
+ KeyConsumer::Focus
+ }
+ };
+
+ if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ group.pressed.insert(
+ (*event).keycode,
+ Press {
+ consumer,
+ count: 1,
+ },
+ );
+ }
+
+ match consumer {
+ KeyConsumer::Builtin => {}
+ KeyConsumer::Binding(binding) => {
+ if !binding.is_null() {
+ if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ (*binding).pressed();
+ } else {
+ (*binding).released();
+ }
+ }
+ }
+ KeyConsumer::EnsureEaten => {
+ if (*event).state == ffi::wl_keyboard_key_state_WL_KEYBOARD_KEY_STATE_PRESSED {
+ (*group.seat).xkb_bindings_seat.scheduled_ate_unbound_key = true;
+ (*(*group.seat).server).wm.dirty_windowing();
+ }
+ }
+ KeyConsumer::ImGrab => {
+ let grab = group.get_input_method_grab();
+ if !grab.is_null() {
+ ffi::wlr_input_method_keyboard_grab_v2_set_keyboard(grab, &mut group.wlr_keyboard);
+ ffi::wlr_input_method_keyboard_grab_v2_send_key(grab, (*event).time_msec, (*event).keycode, (*event).state);
+ }
+ }
+ KeyConsumer::Focus => {
+ ffi::wlr_seat_set_keyboard((*group.seat).wlr_seat, &mut group.wlr_keyboard);
+ ffi::wlr_seat_keyboard_notify_key((*group.seat).wlr_seat, (*event).time_msec, (*event).keycode, (*event).state);
+ }
+ }
+
+ group.send_state();
+}
+
+unsafe extern "C" fn handle_group_modifiers(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let group = &mut *crate::container_of!(listener, KeyboardGroup, modifiers_listener);
+
+ let old = group.modifiers_old;
+ let new = ffi::wlr_keyboard_get_modifiers(&mut group.wlr_keyboard);
+ let watched = (*group.seat).xkb_bindings_seat.requested_mods_watched;
+ if (old & watched) != (new & watched) {
+ (*group.seat).xkb_bindings_seat.scheduled_mods_update = Some(crate::xkb_bindings::XkbBindingsSeatModsUpdate {
+ old,
+ new,
+ });
+ (*(*group.seat).server).wm.dirty_windowing();
+ }
+ group.modifiers_old = new;
+
+ let grab = group.get_input_method_grab();
+ if !grab.is_null() {
+ ffi::wlr_input_method_keyboard_grab_v2_set_keyboard(grab, &mut group.wlr_keyboard);
+ ffi::wlr_input_method_keyboard_grab_v2_send_modifiers(grab, &mut group.wlr_keyboard.modifiers);
+ } else {
+ ffi::wlr_seat_set_keyboard((*group.seat).wlr_seat, &mut group.wlr_keyboard);
+ ffi::wlr_seat_keyboard_notify_modifiers((*group.seat).wlr_seat, &mut group.wlr_keyboard.modifiers);
+ }
+
+ group.send_state();
+}
diff --git a/src/layer_shell.rs b/src/layer_shell.rs
new file mode 100644
index 0000000..e97644a
--- /dev/null
+++ b/src/layer_shell.rs
@@ -0,0 +1,861 @@
+// SPDX-FileCopyrightText: © 2025 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use std::ffi::CStr;
+use crate::ffi;
+use crate::server::{Server, WlListener, WlList, wl_list_insert, wl_list_remove, wl_signal_add, wl_listener_remove};
+use crate::slotmap::{SlotMap, Key};
+use crate::output::Output;
+use crate::seat::Seat;
+use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
+use crate::xdg_popup::XdgPopup;
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum LayerShellSeatFocus {
+ Exclusive(Key),
+ NonExclusive(Key),
+ None,
+}
+
+pub struct LayerShellObject {
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
+}
+
+pub struct LayerShell {
+ pub server: *mut Server,
+ pub global: *mut ffi::wl_global,
+ pub wlr_shell: *mut ffi::wlr_layer_shell_v1,
+ pub objects: ffi::wl_list,
+ pub surfaces: SlotMap<*mut LayerSurface>,
+ pub new_surface: ffi::wl_listener,
+}
+
+impl LayerShell {
+ pub unsafe fn init(&mut self, wl_display: *mut ffi::wl_display) -> Result<(), ()> {
+ self.global = ffi::wl_global_create(
+ wl_display,
+ &ffi::river_layer_shell_v1_interface,
+ 1,
+ self as *mut LayerShell as *mut _,
+ Some(bind),
+ );
+ if self.global.is_null() {
+ return Err(());
+ }
+
+ self.wlr_shell = ffi::wlr_layer_shell_v1_create(wl_display, 4);
+ if self.wlr_shell.is_null() {
+ ffi::wl_global_destroy(self.global);
+ self.global = std::ptr::null_mut();
+ return Err(());
+ }
+
+ ffi::wl_list_init(&mut self.objects);
+
+ let new_surface_ptr = &mut self.new_surface as *mut ffi::wl_listener as *mut WlListener;
+ (*new_surface_ptr).notify = Some(handle_new_surface);
+ wl_signal_add(&mut (*self.wlr_shell).events.new_surface, &mut self.new_surface);
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ if !self.global.is_null() {
+ ffi::wl_global_destroy(self.global);
+ self.global = std::ptr::null_mut();
+ }
+ if !self.new_surface.link.prev.is_null() {
+ wl_listener_remove(&mut self.new_surface);
+ }
+ }
+
+ pub unsafe fn supported(&self) -> bool {
+ let wm_v1 = (*self.server).wm.object;
+ if wm_v1.is_null() {
+ return false;
+ }
+ let wm_client = ffi::wl_resource_get_client(wm_v1);
+
+ let objects_list = &self.objects as *const ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_list).next;
+ while curr != objects_list {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, LayerShellObject, link);
+ let obj_client = ffi::wl_resource_get_client((*obj).resource);
+ if obj_client == wm_client {
+ return true;
+ }
+ curr = next;
+ }
+ false
+ }
+
+ pub unsafe fn check_exclusive_focus(&mut self) {
+ let layers = [
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+ ];
+ let mut to_focus: *mut LayerSurface = std::ptr::null_mut();
+
+ 'outer: for &layer in &layers {
+ let tree = (*self.server).scene.layer_surface_tree(layer);
+ let children_head = ffi::river_scene_tree_get_children(tree) as *mut WlList;
+ let mut curr = (*children_head).prev;
+ while curr != children_head {
+ let prev = (*curr).prev;
+ let node = ffi::river_scene_node_from_children_link(curr as *mut ffi::wl_list);
+ if let Some(node_data) = SceneNodeData::from_node(node) {
+ if let SceneNodeDataVal::LayerSurface(layer_surface) = node_data.data {
+ let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
+ if ffi::river_wlr_surface_is_mapped((*wlr_layer_surface).surface) &&
+ (*wlr_layer_surface).current.keyboard_interactive == ffi::zwlr_layer_surface_v1_keyboard_interactivity_ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE {
+ to_focus = layer_surface;
+ break 'outer;
+ }
+ }
+ }
+ curr = prev;
+ }
+ }
+
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ if !to_focus.is_null() {
+ (*seat).layer_shell.scheduled_focus = LayerShellSeatFocus::Exclusive((*to_focus).ref_key);
+ } else if matches!((*seat).layer_shell.scheduled_focus, LayerShellSeatFocus::Exclusive(_)) {
+ (*seat).layer_shell.scheduled_focus = LayerShellSeatFocus::None;
+ }
+ curr = next;
+ }
+ }
+}
+
+unsafe extern "C" fn bind(
+ client: *mut ffi::wl_client,
+ data: *mut std::ffi::c_void,
+ version: u32,
+ id: u32,
+) {
+ let layer_shell = data as *mut LayerShell;
+ if layer_shell.is_null() {
+ return;
+ }
+
+ let resource = ffi::wl_resource_create(client, &ffi::river_layer_shell_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ log::error!("out of memory binding river_layer_shell_v1");
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(LayerShellObject {
+ resource,
+ link: std::mem::zeroed(),
+ }));
+ ffi::wl_list_init(&mut (*obj).link);
+ let objects_list = &mut (*layer_shell).objects as *mut ffi::wl_list as *mut WlList;
+ let link_custom = &mut (*obj).link as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert(objects_list, link_custom);
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LAYER_SHELL_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_destroy_resource),
+ );
+}
+
+unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LayerShellObject;
+ if !obj.is_null() {
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+unsafe extern "C" fn layer_shell_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let _ = client;
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn layer_shell_get_output(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ output_resource: *mut ffi::wl_resource,
+) {
+ let output = ffi::wl_resource_get_user_data(output_resource) as *mut Output;
+ if output.is_null() {
+ return;
+ }
+ if !(*output).layer_shell.object.is_null() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_layer_shell_v1_error_RIVER_LAYER_SHELL_V1_ERROR_OBJECT_ALREADY_CREATED,
+ b"river_layer_shell_output_v1 already created\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ let version = ffi::wl_resource_get_version(resource);
+ (*output).layer_shell.create_object(client, version as u32, id, output);
+}
+
+unsafe extern "C" fn layer_shell_get_seat(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ seat_resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(seat_resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*seat).layer_shell.object.is_null() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_layer_shell_v1_error_RIVER_LAYER_SHELL_V1_ERROR_OBJECT_ALREADY_CREATED,
+ b"river_layer_shell_seat_v1 already created\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ let version = ffi::wl_resource_get_version(resource);
+ (*seat).layer_shell.create_object(client, version as u32, id, seat);
+}
+
+static LAYER_SHELL_INTERFACE: ffi::river_layer_shell_v1_interface = ffi::river_layer_shell_v1_interface {
+ destroy: Some(layer_shell_destroy),
+ get_output: Some(layer_shell_get_output),
+ get_seat: Some(layer_shell_get_seat),
+};
+
+unsafe extern "C" fn handle_new_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let layer_shell = crate::container_of!(listener, LayerShell, new_surface);
+ let wlr_layer_surface = data as *mut ffi::wlr_layer_surface_v1;
+
+ log::debug!(
+ "new layer surface: namespace {:?}, layer {}, anchor {}, size {}x{}, margin: top={}, right={}, bottom={}, left={}, exclusive_zone={}",
+ CStr::from_ptr((*wlr_layer_surface).namespace),
+ (*wlr_layer_surface).current.layer,
+ (*wlr_layer_surface).current.anchor,
+ (*wlr_layer_surface).current.desired_width,
+ (*wlr_layer_surface).current.desired_height,
+ (*wlr_layer_surface).current.margin.top,
+ (*wlr_layer_surface).current.margin.right,
+ (*wlr_layer_surface).current.margin.bottom,
+ (*wlr_layer_surface).current.margin.left,
+ (*wlr_layer_surface).current.exclusive_zone,
+ );
+
+ if !(*layer_shell).supported() {
+ log::info!("window manager did not bind river_layer_shell_v1, closing layer surface");
+ ffi::wlr_layer_surface_v1_destroy(wlr_layer_surface);
+ return;
+ }
+
+ if (*wlr_layer_surface).output.is_null() {
+ let outputs = &mut (*(*layer_shell).server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs).next;
+ while curr != outputs {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, Output, link);
+ if (*output).layer_shell.requested.default {
+ (*wlr_layer_surface).output = (*output).wlr_output;
+ break;
+ }
+ curr = next;
+ }
+
+ if (*wlr_layer_surface).output.is_null() {
+ let first_node = (*outputs).next;
+ if first_node != outputs {
+ let output = crate::container_of!(first_node, Output, link);
+ log::info!("window manager did not set default layer surface output, choosing arbitrary output");
+ (*wlr_layer_surface).output = (*output).wlr_output;
+ } else {
+ log::error!("no output available for layer surface {:?}", CStr::from_ptr((*wlr_layer_surface).namespace));
+ ffi::wlr_layer_surface_v1_destroy(wlr_layer_surface);
+ return;
+ }
+ }
+ }
+
+ if let Err(e) = LayerSurface::create(wlr_layer_surface, (*layer_shell).server) {
+ log::error!("Failed to create layer surface: {}", e);
+ ffi::wl_resource_post_no_memory((*wlr_layer_surface).resource);
+ }
+}
+
+pub struct LayerSurface {
+ pub ref_key: Key,
+ pub server: *mut Server,
+ pub wlr_layer_surface: *mut ffi::wlr_layer_surface_v1,
+ pub scene_layer_surface: *mut ffi::wlr_scene_layer_surface_v1,
+ pub popup_tree: *mut ffi::wlr_scene_tree,
+
+ pub destroy: ffi::wl_listener,
+ pub map: ffi::wl_listener,
+ pub unmap: ffi::wl_listener,
+ pub commit: ffi::wl_listener,
+ pub new_popup: ffi::wl_listener,
+}
+
+impl LayerSurface {
+ pub unsafe fn create(
+ wlr_layer_surface: *mut ffi::wlr_layer_surface_v1,
+ server: *mut Server,
+ ) -> Result<*mut Self, &'static str> {
+ let layer_tree = (*server).scene.layer_surface_tree((*wlr_layer_surface).current.layer);
+ let scene_layer_surface = ffi::wlr_scene_layer_surface_v1_create(layer_tree, wlr_layer_surface);
+ if scene_layer_surface.is_null() {
+ return Err("Failed to create wlr_scene_layer_surface_v1");
+ }
+
+ let popup_tree = ffi::wlr_scene_tree_create((*server).scene.layers.popups);
+ if popup_tree.is_null() {
+ ffi::wlr_scene_node_destroy((*scene_layer_surface).tree as *mut ffi::wlr_scene_node);
+ return Err("Failed to create popup_tree");
+ }
+
+ let layer_surface = Box::into_raw(Box::new(LayerSurface {
+ ref_key: Key { generation: 0, index: 0 },
+ server,
+ wlr_layer_surface,
+ scene_layer_surface,
+ popup_tree,
+ destroy: std::mem::zeroed(),
+ map: std::mem::zeroed(),
+ unmap: std::mem::zeroed(),
+ commit: std::mem::zeroed(),
+ new_popup: std::mem::zeroed(),
+ }));
+
+ let key = (*server).layer_shell.surfaces.put(layer_surface);
+ (*layer_surface).ref_key = key;
+
+ SceneNodeData::attach((*scene_layer_surface).tree as *mut _, SceneNodeDataVal::LayerSurface(layer_surface));
+ SceneNodeData::attach(popup_tree as *mut _, SceneNodeDataVal::LayerSurface(layer_surface));
+
+ ffi::river_wlr_surface_set_data((*wlr_layer_surface).surface, (*scene_layer_surface).tree as *mut _);
+
+ let destroy_ptr = &mut (*layer_surface).destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_ptr).notify = Some(handle_layer_surface_destroy);
+ wl_signal_add(&mut (*wlr_layer_surface).events.destroy, &mut (*layer_surface).destroy);
+
+ let map_ptr = &mut (*layer_surface).map as *mut ffi::wl_listener as *mut WlListener;
+ (*map_ptr).notify = Some(handle_layer_surface_map);
+ wl_signal_add(ffi::river_wlr_surface_get_map_signal((*wlr_layer_surface).surface), &mut (*layer_surface).map);
+
+ let unmap_ptr = &mut (*layer_surface).unmap as *mut ffi::wl_listener as *mut WlListener;
+ (*unmap_ptr).notify = Some(handle_layer_surface_unmap);
+ wl_signal_add(ffi::river_wlr_surface_get_unmap_signal((*wlr_layer_surface).surface), &mut (*layer_surface).unmap);
+
+ let commit_ptr = &mut (*layer_surface).commit as *mut ffi::wl_listener as *mut WlListener;
+ (*commit_ptr).notify = Some(handle_layer_surface_commit);
+ wl_signal_add(ffi::river_wlr_surface_get_commit_signal((*wlr_layer_surface).surface), &mut (*layer_surface).commit);
+
+ let new_popup_ptr = &mut (*layer_surface).new_popup as *mut ffi::wl_listener as *mut WlListener;
+ (*new_popup_ptr).notify = Some(handle_layer_surface_new_popup);
+ wl_signal_add(&mut (*wlr_layer_surface).events.new_popup, &mut (*layer_surface).new_popup);
+
+ Ok(layer_surface)
+ }
+
+ pub unsafe fn destroy_popups(&mut self) {
+ let popups_list = &mut (*self.wlr_layer_surface).popups as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*popups_list).next;
+ while curr != popups_list {
+ let next = (*curr).next;
+ let wlr_xdg_popup = crate::container_of!(curr, ffi::wlr_xdg_popup, link);
+ ffi::wlr_xdg_popup_destroy(wlr_xdg_popup);
+ curr = next;
+ }
+ }
+}
+
+unsafe extern "C" fn handle_layer_surface_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let layer_surface = crate::container_of!(listener, LayerSurface, destroy);
+
+ log::debug!("layer surface {:?} destroyed", CStr::from_ptr((*(*layer_surface).wlr_layer_surface).namespace));
+
+ wl_listener_remove(&mut (*layer_surface).destroy);
+ wl_listener_remove(&mut (*layer_surface).map);
+ wl_listener_remove(&mut (*layer_surface).unmap);
+ wl_listener_remove(&mut (*layer_surface).commit);
+ wl_listener_remove(&mut (*layer_surface).new_popup);
+
+ (*layer_surface).destroy_popups();
+
+ ffi::wlr_scene_node_destroy((*layer_surface).popup_tree as *mut ffi::wlr_scene_node);
+
+ ffi::river_wlr_surface_set_data((*(*layer_surface).wlr_layer_surface).surface, std::ptr::null_mut());
+
+ let server = (*layer_surface).server;
+ (*server).layer_shell.surfaces.remove((*layer_surface).ref_key);
+ let _ = Box::from_raw(layer_surface);
+}
+
+unsafe extern "C" fn handle_layer_surface_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let layer_surface = crate::container_of!(listener, LayerSurface, map);
+ let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
+
+ log::debug!("layer surface {:?} mapped", CStr::from_ptr((*wlr_layer_surface).namespace));
+
+ let server = (*layer_surface).server;
+
+ if (*wlr_layer_surface).current.keyboard_interactive == ffi::zwlr_layer_surface_v1_keyboard_interactivity_ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND {
+ let seats = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ if !matches!((*seat).layer_shell.scheduled_focus, LayerShellSeatFocus::Exclusive(_)) {
+ (*seat).layer_shell.scheduled_focus = LayerShellSeatFocus::NonExclusive((*layer_surface).ref_key);
+ }
+ curr = next;
+ }
+ }
+
+ let output = ffi::river_wlr_output_get_data((*wlr_layer_surface).output) as *mut Output;
+ (*output).layer_shell.arrange(output);
+ (*server).layer_shell.check_exclusive_focus();
+ (*server).wm.dirty_windowing();
+}
+
+unsafe extern "C" fn handle_layer_surface_unmap(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let layer_surface = crate::container_of!(listener, LayerSurface, unmap);
+ let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
+
+ log::debug!("layer surface {:?} unmapped", CStr::from_ptr((*wlr_layer_surface).namespace));
+
+ let server = (*layer_surface).server;
+
+ let seats = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ if let crate::seat::Focus::LayerSurface(surface) = (*seat).focused {
+ if surface == (*wlr_layer_surface).surface {
+ (*seat).focus(crate::seat::Focus::None);
+ }
+ }
+ if let LayerShellSeatFocus::NonExclusive(key) = (*seat).layer_shell.scheduled_focus {
+ if key == (*layer_surface).ref_key {
+ (*seat).layer_shell.scheduled_focus = LayerShellSeatFocus::None;
+ }
+ }
+ curr = next;
+ }
+
+ let output = ffi::river_wlr_output_get_data((*wlr_layer_surface).output) as *mut Output;
+ (*output).layer_shell.arrange(output);
+ (*server).layer_shell.check_exclusive_focus();
+ (*server).wm.dirty_windowing();
+}
+
+unsafe extern "C" fn handle_layer_surface_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let layer_surface = crate::container_of!(listener, LayerSurface, commit);
+ let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
+
+ assert!(!(*wlr_layer_surface).output.is_null());
+
+ let server = (*layer_surface).server;
+
+ // Check if layer was changed
+ if (*wlr_layer_surface).current.committed & ffi::wlr_layer_surface_v1_state_field_WLR_LAYER_SURFACE_V1_STATE_LAYER != 0 {
+ let tree = (*server).scene.layer_surface_tree((*wlr_layer_surface).current.layer);
+ ffi::wlr_scene_node_reparent(
+ (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+ tree,
+ );
+ }
+
+ if (*wlr_layer_surface).initial_commit || ((*wlr_layer_surface).current.committed != 0) {
+ let output = ffi::river_wlr_output_get_data((*wlr_layer_surface).output) as *mut Output;
+ (*output).layer_shell.arrange(output);
+ (*server).layer_shell.check_exclusive_focus();
+ (*server).wm.dirty_windowing();
+ }
+}
+
+unsafe extern "C" fn handle_layer_surface_new_popup(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let layer_surface = crate::container_of!(listener, LayerSurface, new_popup);
+ let wlr_xdg_popup = data as *mut ffi::wlr_xdg_popup;
+
+ if let Err(e) = XdgPopup::create(wlr_xdg_popup, (*layer_surface).popup_tree, std::ptr::null_mut()) {
+ log::error!("Failed to create layer surface popup: {}", e);
+ ffi::wl_resource_post_no_memory((*wlr_xdg_popup).resource);
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct LayerShellOutputScheduled {
+ pub non_exclusive_area: ffi::wlr_box,
+}
+
+#[derive(Clone, Copy)]
+pub struct LayerShellOutputSent {
+ pub non_exclusive_area: Option<ffi::wlr_box>,
+}
+
+#[derive(Clone, Copy)]
+pub struct LayerShellOutputRequested {
+ pub default: bool,
+}
+
+pub struct LayerShellOutput {
+ pub object: *mut ffi::wl_resource, // river_layer_shell_output_v1
+ pub scheduled: LayerShellOutputScheduled,
+ pub sent: LayerShellOutputSent,
+ pub requested: LayerShellOutputRequested,
+}
+
+impl Default for LayerShellOutput {
+ fn default() -> Self {
+ Self {
+ object: std::ptr::null_mut(),
+ scheduled: LayerShellOutputScheduled {
+ non_exclusive_area: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ },
+ sent: LayerShellOutputSent {
+ non_exclusive_area: None,
+ },
+ requested: LayerShellOutputRequested {
+ default: false,
+ },
+ }
+ }
+}
+
+impl LayerShellOutput {
+ pub unsafe fn create_object(&mut self, client: *mut ffi::wl_client, version: u32, id: u32, output: *mut Output) {
+ assert!(self.object.is_null());
+ let resource = ffi::wl_resource_create(client, &ffi::river_layer_shell_output_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ log::error!("out of memory creating river_layer_shell_output_v1");
+ return;
+ }
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LAYER_SHELL_OUTPUT_INTERFACE as *const _ as *const _,
+ self as *mut LayerShellOutput as *mut _,
+ Some(handle_layer_shell_output_destroy),
+ );
+ self.object = resource;
+ (*(*output).server).wm.dirty_windowing();
+ }
+
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_set_implementation(
+ self.object,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+ self.object = std::ptr::null_mut();
+ }
+ }
+
+ pub unsafe fn arrange(&mut self, output: *mut Output) {
+ let (w, h) = (*output).scheduled.dimensions();
+ let box_geom = ffi::wlr_box {
+ x: (*output).scheduled.x,
+ y: (*output).scheduled.y,
+ width: w,
+ height: h,
+ };
+ self.scheduled.non_exclusive_area = box_geom;
+ self.send_configures(output, true);
+ self.send_configures(output, false);
+
+ let area_changed = match self.sent.non_exclusive_area {
+ Some(sent_box) => {
+ sent_box.x != self.scheduled.non_exclusive_area.x ||
+ sent_box.y != self.scheduled.non_exclusive_area.y ||
+ sent_box.width != self.scheduled.non_exclusive_area.width ||
+ sent_box.height != self.scheduled.non_exclusive_area.height
+ }
+ None => true,
+ };
+
+ if area_changed {
+ (*(*output).server).wm.dirty_windowing();
+ }
+ }
+
+ unsafe fn send_configures(&mut self, output: *mut Output, exclusive: bool) {
+ let (output_width, output_height) = (*output).scheduled.dimensions();
+ let output_box = ffi::wlr_box {
+ x: (*output).scheduled.x,
+ y: (*output).scheduled.y,
+ width: output_width,
+ height: output_height,
+ };
+
+ let layers = [
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
+ ];
+
+ for &layer in &layers {
+ let tree = (*(*output).server).scene.layer_surface_tree(layer);
+ let children_head = ffi::river_scene_tree_get_children(tree) as *mut WlList;
+ let mut curr = (*children_head).next;
+ while curr != children_head {
+ let next = (*curr).next;
+ let node = ffi::river_scene_node_from_children_link(curr as *mut ffi::wl_list);
+ if let Some(node_data) = SceneNodeData::from_node(node) {
+ if let SceneNodeDataVal::LayerSurface(layer_surface) = node_data.data {
+ let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
+ if !ffi::river_wlr_surface_is_mapped((*wlr_layer_surface).surface) && !(*wlr_layer_surface).initial_commit {
+ curr = next;
+ continue;
+ }
+ if (*wlr_layer_surface).output != (*output).wlr_output {
+ curr = next;
+ continue;
+ }
+ let current_exclusive = (*wlr_layer_surface).current.exclusive_zone > 0;
+ if current_exclusive != exclusive {
+ curr = next;
+ continue;
+ }
+
+ let mut new_area = self.scheduled.non_exclusive_area;
+ ffi::wlr_scene_layer_surface_v1_configure(
+ (*layer_surface).scene_layer_surface,
+ &output_box,
+ &mut new_area,
+ );
+
+ if new_area.width < (output_width / 2) || new_area.height < (output_height / 2) {
+ ffi::wlr_layer_surface_v1_destroy(wlr_layer_surface);
+ curr = next;
+ continue;
+ }
+ self.scheduled.non_exclusive_area = new_area;
+
+ let x = ffi::river_scene_node_get_x((*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node);
+ let y = ffi::river_scene_node_get_y((*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_set_position((*layer_surface).popup_tree as *mut ffi::wlr_scene_node, x, y);
+
+ let clip = ffi::wlr_box {
+ x: -(x - (*output).scheduled.x),
+ y: -(y - (*output).scheduled.y),
+ width: output_width,
+ height: output_height,
+ };
+ ffi::wlr_scene_subsurface_tree_set_clip(
+ (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+ &clip,
+ );
+ }
+ }
+ curr = next;
+ }
+ }
+ }
+
+ pub unsafe fn manage_start(&mut self, output: *mut Output) {
+ let state = (*output).scheduled.state;
+ assert!(
+ matches!(state, crate::output::OutputStateValue::Enabled)
+ || matches!(state, crate::output::OutputStateValue::DisabledSoft)
+ );
+
+ let (w, h) = (*output).scheduled.dimensions();
+ let scheduled_box = ffi::wlr_box {
+ x: (*output).scheduled.x,
+ y: (*output).scheduled.y,
+ width: w,
+ height: h,
+ };
+
+ let (w_sent, h_sent) = (*output).sent.dimensions();
+ let sent_box = ffi::wlr_box {
+ x: (*output).sent.x,
+ y: (*output).sent.y,
+ width: w_sent,
+ height: h_sent,
+ };
+
+ let box_changed = scheduled_box.x != sent_box.x ||
+ scheduled_box.y != sent_box.y ||
+ scheduled_box.width != sent_box.width ||
+ scheduled_box.height != sent_box.height;
+
+ if box_changed {
+ self.scheduled.non_exclusive_area = scheduled_box;
+ self.send_configures(output, true);
+ self.send_configures(output, false);
+ }
+
+ let area_changed = match self.sent.non_exclusive_area {
+ Some(sent_box) => {
+ sent_box.x != self.scheduled.non_exclusive_area.x ||
+ sent_box.y != self.scheduled.non_exclusive_area.y ||
+ sent_box.width != self.scheduled.non_exclusive_area.width ||
+ sent_box.height != self.scheduled.non_exclusive_area.height
+ }
+ None => true,
+ };
+
+ if area_changed {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(
+ self.object,
+ 1, // send_non_exclusive_area
+ self.scheduled.non_exclusive_area.x,
+ self.scheduled.non_exclusive_area.y,
+ self.scheduled.non_exclusive_area.width,
+ self.scheduled.non_exclusive_area.height,
+ );
+ self.sent.non_exclusive_area = Some(self.scheduled.non_exclusive_area);
+ }
+ }
+ }
+}
+
+unsafe extern "C" fn handle_layer_shell_output_destroy(resource: *mut ffi::wl_resource) {
+ let layer_shell_output = ffi::wl_resource_get_user_data(resource) as *mut LayerShellOutput;
+ if !layer_shell_output.is_null() {
+ (*layer_shell_output).object = std::ptr::null_mut();
+ (*layer_shell_output).sent.non_exclusive_area = None;
+ (*layer_shell_output).requested.default = false;
+ }
+}
+
+unsafe extern "C" fn layer_shell_output_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let _ = client;
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn layer_shell_output_set_default(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let _ = client;
+ let layer_shell_output = ffi::wl_resource_get_user_data(resource) as *mut LayerShellOutput;
+ if layer_shell_output.is_null() {
+ return;
+ }
+ let server = if !(*layer_shell_output).object.is_null() {
+ // Find server. We can get it via finding Output from the parent link.
+ // Let's traverse the outputs to set requested.default to false on all outputs
+ let output = container_of_output(layer_shell_output);
+ (*output).server
+ } else {
+ std::ptr::null_mut()
+ };
+
+ if !server.is_null() {
+ let outputs = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs).next;
+ while curr != outputs {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, Output, link);
+ (*output).layer_shell.requested.default = false;
+ curr = next;
+ }
+ (*layer_shell_output).requested.default = true;
+ }
+}
+
+static LAYER_SHELL_OUTPUT_INTERFACE: ffi::river_layer_shell_output_v1_interface = ffi::river_layer_shell_output_v1_interface {
+ destroy: Some(layer_shell_output_destroy),
+ set_default: Some(layer_shell_output_set_default),
+};
+
+unsafe fn container_of_output(layer_shell_output: *mut LayerShellOutput) -> *mut Output {
+ crate::container_of!(layer_shell_output, Output, layer_shell)
+}
+
+pub struct LayerShellSeat {
+ pub object: *mut ffi::wl_resource, // river_layer_shell_seat_v1
+ pub scheduled_focus: LayerShellSeatFocus,
+ pub sent_focus: LayerShellSeatFocus,
+}
+
+impl Default for LayerShellSeat {
+ fn default() -> Self {
+ Self {
+ object: std::ptr::null_mut(),
+ scheduled_focus: LayerShellSeatFocus::None,
+ sent_focus: LayerShellSeatFocus::None,
+ }
+ }
+}
+
+impl LayerShellSeat {
+ pub unsafe fn create_object(&mut self, client: *mut ffi::wl_client, version: u32, id: u32, seat: *mut Seat) {
+ assert!(self.object.is_null());
+ let resource = ffi::wl_resource_create(client, &ffi::river_layer_shell_seat_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ log::error!("out of memory creating river_layer_shell_seat_v1");
+ return;
+ }
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LAYER_SHELL_SEAT_INTERFACE as *const _ as *const _,
+ self as *mut LayerShellSeat as *mut _,
+ Some(handle_layer_shell_seat_destroy),
+ );
+ self.object = resource;
+ (*(*seat).server).wm.dirty_windowing();
+ }
+
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_set_implementation(
+ self.object,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+ self.object = std::ptr::null_mut();
+ }
+ }
+
+ pub unsafe fn manage_start(&mut self) {
+ if self.scheduled_focus != self.sent_focus {
+ if !self.object.is_null() {
+ match self.scheduled_focus {
+ LayerShellSeatFocus::Exclusive(_) => {
+ ffi::wl_resource_post_event(self.object, 1); // sendFocusExclusive
+ }
+ LayerShellSeatFocus::NonExclusive(_) => {
+ ffi::wl_resource_post_event(self.object, 2); // sendFocusNonExclusive
+ }
+ LayerShellSeatFocus::None => {
+ ffi::wl_resource_post_event(self.object, 0); // sendFocusNone
+ }
+ }
+ }
+ self.sent_focus = self.scheduled_focus;
+ }
+ }
+}
+
+unsafe extern "C" fn handle_layer_shell_seat_destroy(resource: *mut ffi::wl_resource) {
+ let layer_shell_seat = ffi::wl_resource_get_user_data(resource) as *mut LayerShellSeat;
+ if !layer_shell_seat.is_null() {
+ (*layer_shell_seat).object = std::ptr::null_mut();
+ }
+}
+
+unsafe extern "C" fn layer_shell_seat_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let _ = client;
+ ffi::wl_resource_destroy(resource);
+}
+
+static LAYER_SHELL_SEAT_INTERFACE: ffi::river_layer_shell_seat_v1_interface = ffi::river_layer_shell_seat_v1_interface {
+ destroy: Some(layer_shell_seat_destroy),
+};
diff --git a/src/libinput_config.rs b/src/libinput_config.rs
new file mode 100644
index 0000000..653ec9a
--- /dev/null
+++ b/src/libinput_config.rs
@@ -0,0 +1,194 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, wl_signal_add, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
+
+pub struct LibinputConfig {
+ pub server: *mut Server,
+ pub global: *mut ffi::wl_global,
+ pub objects: ffi::wl_list,
+}
+
+pub struct LibinputConfigObject {
+ pub config: *mut LibinputConfig,
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
+}
+
+impl Default for LibinputConfig {
+ fn default() -> Self {
+ unsafe { std::mem::zeroed() }
+ }
+}
+
+impl LibinputConfig {
+ pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.server = server;
+ ffi::wl_list_init(&mut self.objects);
+
+ self.global = ffi::wl_global_create(
+ (*server).wl_server,
+ &ffi::river_libinput_config_v1_interface,
+ 2,
+ self as *mut LibinputConfig as *mut _,
+ Some(bind_libinput_config),
+ );
+ if self.global.is_null() {
+ return Err("Failed to create river_libinput_config_v1 global");
+ }
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ if !self.global.is_null() {
+ ffi::wl_global_destroy(self.global);
+ self.global = std::ptr::null_mut();
+ }
+
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, LibinputConfigObject, link);
+ ffi::wl_resource_destroy((*obj).resource);
+ curr = next;
+ }
+ }
+}
+
+unsafe extern "C" fn bind_libinput_config(
+ client: *mut ffi::wl_client,
+ data: *mut std::ffi::c_void,
+ version: u32,
+ id: u32,
+) {
+ let config = data as *mut LibinputConfig;
+ let resource = ffi::wl_resource_create(client, &ffi::river_libinput_config_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(LibinputConfigObject {
+ config,
+ resource,
+ link: std::mem::zeroed(),
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LIBINPUT_CONFIG_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_object_destroy),
+ );
+
+ let list_head = &mut (*config).objects as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+}
+
+unsafe extern "C" fn handle_object_destroy(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputConfigObject;
+ if !obj.is_null() {
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+static LIBINPUT_CONFIG_INTERFACE: ffi::river_libinput_config_v1_interface = ffi::river_libinput_config_v1_interface {
+ stop: Some(libinput_config_stop),
+ destroy: Some(libinput_config_destroy),
+ create_accel_config: Some(libinput_config_create_accel_config),
+};
+
+static LIBINPUT_CONFIG_INERT_INTERFACE: ffi::river_libinput_config_v1_interface = ffi::river_libinput_config_v1_interface {
+ stop: None,
+ destroy: Some(libinput_config_inert_destroy),
+ create_accel_config: None,
+};
+
+unsafe extern "C" fn libinput_config_stop(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputConfigObject;
+ if obj.is_null() {
+ return;
+ }
+
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ ffi::wl_list_init(&mut (*obj).link);
+
+ ffi::wl_resource_post_event(resource, 0); // finished event
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LIBINPUT_CONFIG_INERT_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_object_destroy),
+ );
+}
+
+unsafe extern "C" fn libinput_config_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_config_v1_error_RIVER_LIBINPUT_CONFIG_V1_ERROR_INVALID_DESTROY,
+ b"destroy before finished event sent\0".as_ptr() as *const _,
+ );
+}
+
+unsafe extern "C" fn libinput_config_inert_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn libinput_config_create_accel_config(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ _profile: u32,
+) {
+ let accel_res = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_accel_config_v1_interface,
+ ffi::wl_resource_get_version(resource),
+ id,
+ );
+ if accel_res.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ static LIBINPUT_ACCEL_CONFIG_INTERFACE: ffi::river_libinput_accel_config_v1_interface = ffi::river_libinput_accel_config_v1_interface {
+ destroy: Some(libinput_accel_config_destroy),
+ set_points: Some(libinput_accel_config_set_points),
+ };
+
+ ffi::wl_resource_set_implementation(
+ accel_res,
+ &LIBINPUT_ACCEL_CONFIG_INTERFACE as *const _ as *const _,
+ std::ptr::null_mut(),
+ None,
+ );
+}
+
+unsafe extern "C" fn libinput_accel_config_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn libinput_accel_config_set_points(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ _accel_type: u32,
+ _step: *mut ffi::wl_array,
+ _points: *mut ffi::wl_array,
+) {
+ let result_res = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_result_v1_interface,
+ ffi::wl_resource_get_version(resource),
+ result_id,
+ );
+ if !result_res.is_null() {
+ ffi::wl_resource_post_event(result_res, 1); // unsupported event
+ ffi::wl_resource_destroy(result_res);
+ }
+}
diff --git a/src/lock_manager.rs b/src/lock_manager.rs
new file mode 100644
index 0000000..daf9f9b
--- /dev/null
+++ b/src/lock_manager.rs
@@ -0,0 +1,430 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, wl_signal_add, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
+use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
+use crate::seat::Focus;
+
+pub struct LockManager {
+ pub wlr_manager: *mut ffi::wlr_session_lock_manager_v1,
+ pub state: LockState,
+ pub lock: *mut ffi::wlr_session_lock_v1,
+ pub lock_surfaces_timer: *mut ffi::wl_event_source,
+ pub server: *mut Server,
+
+ pub new_lock: ffi::wl_listener,
+ pub unlock: ffi::wl_listener,
+ pub destroy: ffi::wl_listener,
+ pub new_surface: ffi::wl_listener,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum LockState {
+ Unlocked,
+ Locked,
+ WaitingForBlank,
+ WaitingForLockSurfaces,
+}
+
+impl Default for LockManager {
+ fn default() -> Self {
+ unsafe { std::mem::zeroed() }
+ }
+}
+
+impl LockManager {
+ pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.server = server;
+ self.state = LockState::Unlocked;
+
+ let wlr_manager = ffi::wlr_session_lock_manager_v1_create((*server).wl_server);
+ if wlr_manager.is_null() {
+ return Err("Failed to create wlr_session_lock_manager_v1");
+ }
+ self.wlr_manager = wlr_manager;
+
+ let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
+ let timer = ffi::wl_event_loop_add_timer(
+ event_loop,
+ Some(handle_lock_surfaces_timeout),
+ self as *mut LockManager as *mut _,
+ );
+ if timer.is_null() {
+ return Err("Failed to create lock surfaces timer");
+ }
+ self.lock_surfaces_timer = timer;
+
+ let new_lock_ptr = &mut self.new_lock as *mut ffi::wl_listener as *mut WlListener;
+ (*new_lock_ptr).notify = Some(handle_new_lock);
+ wl_signal_add(&mut (*self.wlr_manager).events.new_lock, &mut self.new_lock);
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ if !self.lock_surfaces_timer.is_null() {
+ ffi::wl_event_source_remove(self.lock_surfaces_timer);
+ self.lock_surfaces_timer = std::ptr::null_mut();
+ }
+ wl_listener_remove(&mut self.new_lock);
+ }
+
+ pub unsafe fn lock_surface_from_output(
+ &self,
+ output: *mut crate::output::Output,
+ ) -> Option<*mut LockSurface> {
+ if self.lock.is_null() {
+ return None;
+ }
+
+ let surfaces_head = &mut (*self.lock).surfaces as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*surfaces_head).next;
+ while curr != surfaces_head {
+ let next = (*curr).next;
+ let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
+ let lock_surface = (*wlr_lock_surface).data as *mut LockSurface;
+ if !lock_surface.is_null() && (*lock_surface).get_output() == output {
+ return Some(lock_surface);
+ }
+ curr = next;
+ }
+
+ None
+ }
+
+ pub unsafe fn maybe_lock(&mut self) {
+ let mut all_outputs_blanked = true;
+ let mut all_outputs_rendered_lock_surface = true;
+
+ let outputs_head = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs_head).next;
+ while curr != outputs_head {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, crate::output::Output, link);
+ let wlr_output = (*output).wlr_output;
+ if !wlr_output.is_null() && ffi::river_wlr_output_get_enabled(wlr_output) {
+ match (*output).lock_render_state {
+ crate::output::LockRenderState::PendingUnlock
+ | crate::output::LockRenderState::Unlocked
+ | crate::output::LockRenderState::PendingBlank
+ | crate::output::LockRenderState::PendingLockSurface => {
+ all_outputs_blanked = false;
+ all_outputs_rendered_lock_surface = false;
+ }
+ crate::output::LockRenderState::Blanked => {
+ all_outputs_rendered_lock_surface = false;
+ }
+ crate::output::LockRenderState::LockSurface => {}
+ }
+ }
+ curr = next;
+ }
+
+ match self.state {
+ LockState::WaitingForLockSurfaces => {
+ if all_outputs_rendered_lock_surface {
+ self.send_locked();
+ ffi::wlr_scene_node_set_enabled((*self.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
+ ffi::wl_event_source_timer_update(self.lock_surfaces_timer, 0);
+ }
+ }
+ LockState::WaitingForBlank => {
+ if all_outputs_blanked {
+ self.send_locked();
+ }
+ }
+ _ => {}
+ }
+ }
+
+ pub unsafe fn send_locked(&mut self) {
+ log::info!("session locked");
+ if !self.lock.is_null() {
+ ffi::wlr_session_lock_v1_send_locked(self.lock);
+ }
+ self.state = LockState::Locked;
+ (*self.server).wm.dirty_windowing();
+ }
+}
+
+pub struct LockSurface {
+ pub tree: *mut ffi::wlr_scene_tree,
+ pub wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
+ pub lock: *mut ffi::wlr_session_lock_v1,
+ pub manager: *mut LockManager,
+
+ pub idle_update_focus: *mut ffi::wl_event_source,
+
+ pub map: ffi::wl_listener,
+ pub surface_destroy: ffi::wl_listener,
+}
+
+impl LockSurface {
+ pub unsafe fn create(
+ wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
+ lock: *mut ffi::wlr_session_lock_v1,
+ manager: *mut LockManager,
+ ) -> Result<*mut Self, &'static str> {
+ let tree = ffi::wlr_scene_subsurface_tree_create(
+ (*(*manager).server).scene.locked_tree,
+ (*wlr_lock_surface).surface,
+ );
+ if tree.is_null() {
+ return Err("Failed to create subsurface tree for lock surface");
+ }
+
+ let lock_surface = Box::into_raw(Box::new(Self {
+ tree,
+ wlr_lock_surface,
+ lock,
+ manager,
+ idle_update_focus: std::ptr::null_mut(),
+ map: std::mem::zeroed(),
+ surface_destroy: std::mem::zeroed(),
+ }));
+
+ (*wlr_lock_surface).data = lock_surface as *mut _;
+
+ SceneNodeData::attach(tree as *mut ffi::wlr_scene_node, SceneNodeDataVal::LockSurface(lock_surface));
+ ffi::river_wlr_surface_set_data((*wlr_lock_surface).surface, tree as *mut ffi::wlr_scene_node as *mut _);
+
+ let map_ptr = &mut (*lock_surface).map as *mut ffi::wl_listener as *mut WlListener;
+ (*map_ptr).notify = Some(handle_lock_surface_map);
+ wl_signal_add(
+ ffi::river_wlr_surface_get_map_signal((*wlr_lock_surface).surface),
+ &mut (*lock_surface).map,
+ );
+
+ let destroy_ptr = &mut (*lock_surface).surface_destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_ptr).notify = Some(handle_lock_surface_destroy);
+ wl_signal_add(
+ &mut (*wlr_lock_surface).events.destroy,
+ &mut (*lock_surface).surface_destroy,
+ );
+
+ (*lock_surface).configure();
+
+ Ok(lock_surface)
+ }
+
+ pub unsafe fn destroy(lock_surface: *mut Self) {
+ let mut new_focus = Focus::None;
+ let surfaces_head = &mut (*(*lock_surface).lock).surfaces as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*surfaces_head).next;
+ while curr != surfaces_head {
+ let next = (*curr).next;
+ let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
+ if wlr_lock_surface != (*lock_surface).wlr_lock_surface {
+ let other_surf = (*wlr_lock_surface).data as *mut LockSurface;
+ if !other_surf.is_null() {
+ new_focus = Focus::LockSurface(other_surf);
+ break;
+ }
+ }
+ curr = next;
+ }
+
+ let server = (*(*lock_surface).manager).server;
+ let seats_head = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ if let Focus::LockSurface(focused_surf) = (*seat).focused {
+ if focused_surf == lock_surface {
+ (*seat).focus(new_focus);
+ }
+ }
+ (*seat).cursor.update_state();
+ curr = next;
+ }
+
+ if !(*lock_surface).idle_update_focus.is_null() {
+ ffi::wl_event_source_remove((*lock_surface).idle_update_focus);
+ }
+
+ wl_listener_remove(&mut (*lock_surface).map);
+ wl_listener_remove(&mut (*lock_surface).surface_destroy);
+
+ ffi::river_wlr_surface_set_data((*(*lock_surface).wlr_lock_surface).surface, std::ptr::null_mut());
+
+ let _ = Box::from_raw(lock_surface);
+ }
+
+ pub unsafe fn get_output(&self) -> *mut crate::output::Output {
+ ffi::river_wlr_output_get_data((*self.wlr_lock_surface).output) as *mut crate::output::Output
+ }
+
+ pub unsafe fn configure(&self) {
+ let mut width: i32 = 0;
+ let mut height: i32 = 0;
+ ffi::wlr_output_effective_resolution((*self.wlr_lock_surface).output, &mut width, &mut height);
+ ffi::wlr_session_lock_surface_v1_configure(self.wlr_lock_surface, width as u32, height as u32);
+ }
+}
+
+unsafe extern "C" fn handle_lock_surfaces_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
+ let manager = &mut *(data as *mut LockManager);
+ log::error!("waiting for lock surfaces timed out, imperfect frames may be shown");
+
+ assert!(manager.state == LockState::WaitingForLockSurfaces);
+ manager.state = LockState::WaitingForBlank;
+
+ ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
+
+ manager.maybe_lock();
+
+ 0
+}
+
+unsafe extern "C" fn handle_new_lock(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let manager = &mut *crate::container_of!(listener, LockManager, new_lock);
+ let lock = data as *mut ffi::wlr_session_lock_v1;
+
+ log::debug!("session lock client made lock request");
+
+ if !manager.lock.is_null() {
+ log::info!("denying new session lock client, an active one already exists");
+ ffi::wlr_session_lock_v1_destroy(lock);
+ return;
+ }
+
+ manager.lock = lock;
+
+ if manager.state == LockState::Unlocked {
+ manager.state = LockState::WaitingForLockSurfaces;
+
+ ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, true);
+
+ ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 200);
+
+ let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).focus(Focus::None);
+ curr = next;
+ }
+ } else {
+ if manager.state == LockState::Locked {
+ ffi::wlr_session_lock_v1_send_locked(lock);
+ }
+ log::info!("new session lock client given control of already locked session");
+ }
+
+ let unlock_ptr = &mut manager.unlock as *mut ffi::wl_listener as *mut WlListener;
+ (*unlock_ptr).notify = Some(handle_unlock);
+ wl_signal_add(&mut (*lock).events.unlock, &mut manager.unlock);
+
+ let destroy_ptr = &mut manager.destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_ptr).notify = Some(handle_destroy);
+ wl_signal_add(&mut (*lock).events.destroy, &mut manager.destroy);
+
+ let new_surface_ptr = &mut manager.new_surface as *mut ffi::wl_listener as *mut WlListener;
+ (*new_surface_ptr).notify = Some(handle_surface);
+ wl_signal_add(&mut (*lock).events.new_surface, &mut manager.new_surface);
+}
+
+unsafe extern "C" fn handle_unlock(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let manager = &mut *crate::container_of!(listener, LockManager, unlock);
+
+ manager.state = LockState::Unlocked;
+ log::info!("session unlocked");
+
+ ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, true);
+ ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, false);
+
+ let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).focus(Focus::None);
+ curr = next;
+ }
+
+ handle_destroy(&mut manager.destroy, std::ptr::null_mut());
+
+ (*manager.server).wm.dirty_windowing();
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let manager = &mut *crate::container_of!(listener, LockManager, destroy);
+
+ log::debug!("ext_session_lock_v1 destroyed");
+
+ wl_listener_remove(&mut manager.new_surface);
+ wl_listener_remove(&mut manager.unlock);
+ wl_listener_remove(&mut manager.destroy);
+
+ manager.lock = std::ptr::null_mut();
+ if manager.state == LockState::WaitingForLockSurfaces {
+ manager.state = LockState::WaitingForBlank;
+ ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 0);
+ }
+}
+
+unsafe extern "C" fn handle_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let manager = &mut *crate::container_of!(listener, LockManager, new_surface);
+ let wlr_lock_surface = data as *mut ffi::wlr_session_lock_surface_v1;
+
+ log::debug!("new ext_session_lock_surface_v1 created");
+
+ assert!(manager.state != LockState::Unlocked);
+ assert!(!manager.lock.is_null());
+
+ if LockSurface::create(wlr_lock_surface, manager.lock, manager).is_err() {
+ log::error!("out of memory");
+ ffi::wl_resource_post_no_memory((*wlr_lock_surface).resource);
+ }
+}
+
+unsafe extern "C" fn update_focus(data: *mut std::ffi::c_void) {
+ let lock_surface = data as *mut LockSurface;
+ let manager = (*lock_surface).manager;
+
+ let seats_head = &mut (*(*manager).server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ if !matches!((*seat).focused, Focus::LockSurface(s) if s == lock_surface) {
+ (*seat).focus(Focus::LockSurface(lock_surface));
+ }
+ (*seat).cursor.update_state();
+ curr = next;
+ }
+
+ (*lock_surface).idle_update_focus = std::ptr::null_mut();
+}
+
+unsafe extern "C" fn handle_lock_surface_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let lock_surface = crate::container_of!(listener, LockSurface, map);
+
+ let output = (*lock_surface).get_output();
+ let x = (*output).sent.x;
+ let y = (*output).sent.y;
+ ffi::wlr_scene_node_set_position((*lock_surface).tree as *mut ffi::wlr_scene_node, x, y);
+
+ let server = (*(*lock_surface).manager).server;
+ let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
+ assert!((*lock_surface).idle_update_focus.is_null());
+
+ let idle = ffi::wl_event_loop_add_idle(
+ event_loop,
+ Some(update_focus),
+ lock_surface as *mut _,
+ );
+ if idle.is_null() {
+ log::error!("Failed to create idle update focus event source");
+ return;
+ }
+ (*lock_surface).idle_update_focus = idle;
+}
+
+unsafe extern "C" fn handle_lock_surface_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let lock_surface = crate::container_of!(listener, LockSurface, surface_destroy);
+ LockSurface::destroy(lock_surface);
+}
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..780802a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,268 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+mod ffi;
+mod server;
+mod process;
+mod util;
+mod slotmap;
+mod window_manager;
+mod xkb_bindings;
+mod layer_shell;
+mod scene;
+mod scene_node_data;
+mod output;
+mod output_manager;
+mod input_manager;
+mod libinput_config;
+mod xkb_config;
+mod idle_inhibit_manager;
+mod lock_manager;
+mod input_device;
+mod pointer_constraint;
+mod keyboard;
+mod cursor;
+mod seat;
+pub mod tablet;
+pub mod tablet_tool;
+pub mod window;
+pub mod xdg_toplevel;
+pub mod xdg_popup;
+pub mod shell_surface;
+pub mod wm_node;
+pub mod xwayland_window;
+pub mod xwayland_override_redirect;
+pub mod text_input;
+pub mod input_relay;
+pub mod input_popup;
+pub mod drag_icon;
+pub mod pointer_binding;
+pub mod keyboard_group;
+
+
+use clap::Parser;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+
+
+const USAGE: &str = "\
+usage: river [options]
+
+ -h, --help Print this help message and exit.
+ --version Print the version number and exit.
+ -c <command> Run `sh -c <command>` on startup instead of the default init executable.
+ --log-level <level> Set the log level to error, warning, info, or debug.
+ --no-xwayland Disable xwayland even if built with support.
+";
+
+#[derive(Parser, Debug)]
+#[command(disable_help_flag = true)]
+struct Args {
+ #[arg(short = 'h', long = "help")]
+ help: bool,
+
+ #[arg(long = "version")]
+ version: bool,
+
+ #[arg(short = 'c')]
+ command: Option<String>,
+
+ #[arg(long = "log-level", default_value = "info")]
+ log_level: String,
+
+ #[arg(long = "no-xwayland")]
+ no_xwayland: bool,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn river_wlroots_log_callback(
+ importance: ffi::wlr_log_importance,
+ ptr: *const c_char,
+ len: usize,
+) {
+ let message_slice = std::slice::from_raw_parts(ptr as *const u8, len);
+ let message = String::from_utf8_lossy(message_slice);
+ let message_trimmed = message.trim();
+
+ match importance {
+ ffi::wlr_log_importance_WLR_ERROR => log::error!(target: "wlroots", "{}", message_trimmed),
+ ffi::wlr_log_importance_WLR_INFO => log::info!(target: "wlroots", "{}", message_trimmed),
+ ffi::wlr_log_importance_WLR_DEBUG => log::debug!(target: "wlroots", "{}", message_trimmed),
+ _ => {}
+ }
+}
+
+fn default_init_path() -> Option<String> {
+ let path = if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
+ format!("{}/river/init", xdg_config_home)
+ } else if let Ok(home) = std::env::var("HOME") {
+ format!("{}/.config/river/init", home)
+ } else {
+ return None;
+ };
+
+ if std::path::Path::new(&path).exists() {
+ Some(path)
+ } else {
+ None
+ }
+}
+
+fn detect_classic(path: &str) {
+ if let Ok(content) = std::fs::read_to_string(path) {
+ if content.contains("riverctl") {
+ log::error!(
+ "The init file {} contains the string \"riverctl\".\n\
+ This version of river does not support riverctl.\n\
+ See https://isaacfreund.com/software/river for more information.",
+ path
+ );
+ std::process::exit(1);
+ }
+ }
+}
+
fn main() {
- println!("Hello, world!");
+ let args = match Args::try_parse() {
+ Ok(a) => a,
+ Err(_) => {
+ eprintln!("{}", USAGE);
+ std::process::exit(1);
+ }
+ };
+
+ if args.help {
+ println!("{}", USAGE);
+ std::process::exit(0);
+ }
+
+ if args.version {
+ println!("0.5.0-dev -xwayland (Rust rewrite starter)");
+ std::process::exit(0);
+ }
+
+ let log_level = match args.log_level.as_str() {
+ "error" => log::LevelFilter::Error,
+ "warning" => log::LevelFilter::Warn,
+ "info" => log::LevelFilter::Info,
+ "debug" => log::LevelFilter::Debug,
+ _ => log::LevelFilter::Info,
+ };
+
+ env_logger::Builder::new()
+ .filter(None, log_level)
+ .init();
+
+ log::info!("initializing river (Rust rewrite)");
+
+ let importance = match log_level {
+ log::LevelFilter::Debug => ffi::wlr_log_importance_WLR_DEBUG,
+ log::LevelFilter::Info => ffi::wlr_log_importance_WLR_INFO,
+ _ => ffi::wlr_log_importance_WLR_ERROR,
+ };
+
+ unsafe {
+ ffi::river_init_wlroots_log(importance);
+ }
+
+ let startup_command = if let Some(cmd) = args.command {
+ Some(cmd)
+ } else {
+ default_init_path()
+ };
+
+ if let Some(ref cmd) = startup_command {
+ detect_classic(cmd);
+ }
+
+ let mut server = server::Server::default();
+ if let Err(e) = server.init(!args.no_xwayland) {
+ log::error!("failed to initialize server: {}", e);
+ std::process::exit(1);
+ }
+
+ process::setup();
+
+ let socket_ptr = unsafe {
+ ffi::wl_display_add_socket_auto(server.wl_server)
+ };
+ if socket_ptr.is_null() {
+ log::error!("failed to add wayland socket");
+ server.deinit();
+ std::process::exit(1);
+ }
+ let socket_str = unsafe { CStr::from_ptr(socket_ptr).to_string_lossy().into_owned() };
+ log::info!("running server on display socket: {}", socket_str);
+
+ let started = unsafe { ffi::wlr_backend_start(server.backend) };
+ if !started {
+ log::error!("failed to start wlr_backend");
+ server.deinit();
+ std::process::exit(1);
+ }
+
+ struct ChildGuard(Option<nix::unistd::Pid>);
+ impl Drop for ChildGuard {
+ fn drop(&mut self) {
+ if let Some(pid) = self.0 {
+ log::info!("sending SIGTERM to child process group {}", pid);
+ let _ = nix::sys::signal::kill(
+ nix::unistd::Pid::from_raw(-pid.as_raw()),
+ nix::sys::signal::Signal::SIGTERM,
+ );
+ }
+ }
+ }
+
+ let child_pgid = if let Some(ref cmd) = startup_command {
+ log::info!("running init executable '{}'", cmd);
+ unsafe {
+ match nix::unistd::fork() {
+ Ok(nix::unistd::ForkResult::Child) => {
+ process::cleanup_child();
+ std::env::set_var("WAYLAND_DISPLAY", &socket_str);
+
+ if !args.no_xwayland && !server.xwayland.is_null() {
+ let xwayland_cast = server.xwayland as *mut server::WlrXwayland;
+ if !(*xwayland_cast).display_name.is_null() {
+ let display_name = CStr::from_ptr((*xwayland_cast).display_name)
+ .to_string_lossy()
+ .into_owned();
+ std::env::set_var("DISPLAY", display_name);
+ }
+ }
+
+ let cmd_c = CString::new(cmd.clone()).unwrap();
+ let sh_c = CString::new("/bin/sh").unwrap();
+ let c_c = CString::new("-c").unwrap();
+ let args = [sh_c.as_c_str(), c_c.as_c_str(), cmd_c.as_c_str()];
+
+ let env: Vec<CString> = std::env::vars()
+ .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
+ .collect();
+ let env_ptrs: Vec<&CStr> = env.iter().map(|s| s.as_c_str()).collect();
+
+ let _ = nix::unistd::execve(&sh_c, &args, &env_ptrs);
+ std::process::exit(1);
+ }
+ Ok(nix::unistd::ForkResult::Parent { child }) => Some(child),
+ Err(_) => {
+ log::error!("failed to fork child process");
+ None
+ }
+ }
+ }
+ } else {
+ None
+ };
+
+ let _guard = ChildGuard(child_pgid);
+
+ log::info!("running server");
+ unsafe {
+ ffi::wl_display_run(server.wl_server);
+ }
+
+ log::info!("shutting down server");
+ server.deinit();
}
diff --git a/src/output.rs b/src/output.rs
new file mode 100644
index 0000000..fac4ca6
--- /dev/null
+++ b/src/output.rs
@@ -0,0 +1,422 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, WlList, wl_signal_add, wl_listener_remove, wl_list_insert, wl_list_remove};
+use crate::layer_shell::LayerShellOutput;
+use crate::lock_manager::{LockSurface, LockState};
+use crate::util;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum OutputStateValue {
+ Enabled,
+ DisabledSoft,
+ DisabledHard,
+ Destroying,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum OutputMode {
+ Standard(*mut ffi::wlr_output_mode),
+ Custom {
+ width: i32,
+ height: i32,
+ refresh: i32,
+ },
+ None,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct OutputState {
+ pub state: OutputStateValue,
+ pub x: i32,
+ pub y: i32,
+ pub mode: OutputMode,
+ pub scale: f32,
+ pub transform: ffi::wl_output_transform,
+ pub adaptive_sync: bool,
+ pub auto_layout: bool,
+}
+
+impl OutputState {
+ pub fn mode_none(&self) -> bool {
+ matches!(self.mode, OutputMode::None)
+ }
+
+ pub unsafe fn from_head_state(state: *const ffi::wlr_output_head_v1_state) -> Self {
+ assert!((*state).enabled);
+ let mode = if !(*state).mode.is_null() {
+ OutputMode::Standard((*state).mode)
+ } else {
+ OutputMode::Custom {
+ width: (*state).custom_mode.width,
+ height: (*state).custom_mode.height,
+ refresh: (*state).custom_mode.refresh,
+ }
+ };
+
+ // Round to nearest 1/120 to ensure the scale is exactly represented
+ // in the fractional-scale-v1 protocol.
+ let scale = ((*state).scale * 120.0).round() / 120.0;
+
+ Self {
+ state: OutputStateValue::Enabled,
+ mode,
+ x: (*state).x,
+ y: (*state).y,
+ scale,
+ transform: (*state).transform,
+ adaptive_sync: (*state).adaptive_sync_enabled,
+ auto_layout: false,
+ }
+ }
+
+ pub unsafe fn dimensions(&self) -> (i32, i32) {
+ let (mut w, mut h) = match self.mode {
+ OutputMode::Standard(mode) => ((*mode).width, (*mode).height),
+ OutputMode::Custom { width, height, .. } => (width, height),
+ OutputMode::None => (0, 0),
+ };
+ if (self.transform as u32) % 2 != 0 {
+ std::mem::swap(&mut w, &mut h);
+ }
+ (
+ ((w as f32) / self.scale) as i32,
+ ((h as f32) / self.scale) as i32,
+ )
+ }
+
+ pub unsafe fn box_layout(&self) -> ffi::wlr_box {
+ let (w, h) = self.dimensions();
+ ffi::wlr_box {
+ x: self.x,
+ y: self.y,
+ width: w,
+ height: h,
+ }
+ }
+
+ pub unsafe fn apply_no_modeset(&self, wlr_state: *mut ffi::wlr_output_state) {
+ ffi::wlr_output_state_set_scale(wlr_state, self.scale);
+ ffi::wlr_output_state_set_transform(wlr_state, self.transform);
+ }
+
+ pub unsafe fn apply_modeset(&self, wlr_state: *mut ffi::wlr_output_state) {
+ let enabled = self.state == OutputStateValue::Enabled;
+ ffi::wlr_output_state_set_enabled(wlr_state, enabled);
+ if !enabled {
+ return;
+ }
+ self.apply_no_modeset(wlr_state);
+ match self.mode {
+ OutputMode::Standard(mode) => {
+ ffi::wlr_output_state_set_mode(wlr_state, mode);
+ }
+ OutputMode::Custom { width, height, refresh } => {
+ ffi::wlr_output_state_set_custom_mode(wlr_state, width, height, refresh);
+ }
+ OutputMode::None => {}
+ }
+ ffi::wlr_output_state_set_adaptive_sync_enabled(wlr_state, self.adaptive_sync);
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum LockRenderState {
+ PendingUnlock,
+ Unlocked,
+ PendingBlank,
+ Blanked,
+ PendingLockSurface,
+ LockSurface,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct RenderingState {
+ pub tearing: bool,
+}
+
+pub struct Output {
+ pub server: *mut Server,
+ pub wlr_output: *mut ffi::wlr_output,
+ pub scene_output: *mut ffi::wlr_scene_output,
+ pub object: *mut ffi::wl_resource, // river_output_v1 resource
+ pub layer_shell: LayerShellOutput,
+ pub lock_render_state: LockRenderState,
+ pub link: ffi::wl_list,
+ pub link_sent: ffi::wl_list,
+ pub scheduled: OutputState,
+ pub sent: OutputState,
+ pub current: OutputState,
+ pub sent_wl_output: bool,
+ pub rendering_requested: RenderingState,
+ pub rendering_current: RenderingState,
+
+ pub destroy: ffi::wl_listener,
+ pub request_state: ffi::wl_listener,
+ pub frame: ffi::wl_listener,
+ pub present: ffi::wl_listener,
+}
+
+impl Output {
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 0); // river_output.removed
+ ffi::wl_resource_set_implementation(
+ self.object,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+ self.layer_shell.make_inert();
+ self.object = std::ptr::null_mut();
+ self.sent_wl_output = false;
+ }
+ }
+
+ pub unsafe fn create(server: *mut Server, wlr_output: *mut ffi::wlr_output) -> Result<(), &'static str> {
+ let title = format!("river - {}\0", std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output)).to_string_lossy());
+
+ // Check if output is Wayland/X11 and set application title/app_id
+ if ffi::wlr_output_is_wl(wlr_output) {
+ ffi::wlr_wl_output_set_app_id(wlr_output, "river\0".as_ptr() as *const _);
+ ffi::wlr_wl_output_set_title(wlr_output, title.as_ptr() as *const _);
+ } else if ffi::wlr_output_is_x11(wlr_output) {
+ ffi::wlr_x11_output_set_title(wlr_output, title.as_ptr() as *const _);
+ }
+
+ if !ffi::wlr_output_init_render(wlr_output, (*server).allocator, (*server).renderer) {
+ return Err("Failed to initialize renderer for output");
+ }
+
+ let scene_output = ffi::wlr_scene_output_create((*server).scene.wlr_scene, wlr_output);
+ if scene_output.is_null() {
+ return Err("Failed to create wlr_scene_output");
+ }
+
+ let initial = OutputState {
+ state: OutputStateValue::DisabledHard,
+ x: 0,
+ y: 0,
+ mode: OutputMode::None,
+ scale: 1.0,
+ transform: ffi::wl_output_transform_WL_OUTPUT_TRANSFORM_NORMAL,
+ adaptive_sync: ffi::river_wlr_output_get_adaptive_sync_status(wlr_output) == ffi::wlr_output_adaptive_sync_status_WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED,
+ auto_layout: true,
+ };
+
+ let output = Box::new(Output {
+ server,
+ wlr_output,
+ scene_output,
+ object: std::ptr::null_mut(),
+ layer_shell: LayerShellOutput::default(),
+ lock_render_state: LockRenderState::Blanked,
+ link: std::mem::zeroed(),
+ link_sent: std::mem::zeroed(),
+ scheduled: initial,
+ sent: initial,
+ current: initial,
+ sent_wl_output: false,
+ rendering_requested: RenderingState { tearing: false },
+ rendering_current: RenderingState { tearing: false },
+ destroy: std::mem::zeroed(),
+ request_state: std::mem::zeroed(),
+ frame: std::mem::zeroed(),
+ present: std::mem::zeroed(),
+ });
+
+ let raw = Box::into_raw(output);
+ ffi::river_wlr_output_set_data(wlr_output, raw as *mut std::ffi::c_void);
+
+ let list_head = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let link_custom = &mut (*raw).link as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, link_custom);
+
+ ffi::wl_list_init(&mut (*raw).link_sent);
+
+ // Add event listeners
+ let d_listener = &mut (*raw).destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*d_listener).notify = Some(handle_destroy);
+ wl_signal_add(ffi::river_wlr_output_get_destroy_signal(wlr_output), &mut (*raw).destroy);
+
+ let req_listener = &mut (*raw).request_state as *mut ffi::wl_listener as *mut WlListener;
+ (*req_listener).notify = Some(handle_request_state);
+ wl_signal_add(ffi::river_wlr_output_get_request_state_signal(wlr_output), &mut (*raw).request_state);
+
+ let frame_listener = &mut (*raw).frame as *mut ffi::wl_listener as *mut WlListener;
+ (*frame_listener).notify = Some(handle_frame);
+ wl_signal_add(ffi::river_wlr_output_get_frame_signal(wlr_output), &mut (*raw).frame);
+
+ let pres_listener = &mut (*raw).present as *mut ffi::wl_listener as *mut WlListener;
+ (*pres_listener).notify = Some(handle_present);
+ wl_signal_add(ffi::river_wlr_output_get_present_signal(wlr_output), &mut (*raw).present);
+
+ (*raw).scheduled.state = OutputStateValue::Enabled;
+ let preferred = ffi::wlr_output_preferred_mode(wlr_output);
+ if !preferred.is_null() {
+ (*raw).scheduled.mode = OutputMode::Standard(preferred);
+ } else {
+ (*raw).scheduled.mode = OutputMode::Custom { width: 1280, height: 720, refresh: 0 };
+ }
+ // Trigger windowing manager update
+ // We will implement the actual server.wm.dirty_windowing() call in Phase 4, but for now we can stub it
+ // by log output or call a direct FFI / loop callback if needed.
+ log::debug!("Output created, dirty windowing");
+ Ok(())
+ }
+
+ pub unsafe fn render_and_commit(&mut self) -> Result<(), &'static str> {
+ if !ffi::wlr_scene_output_needs_frame(self.scene_output) {
+ return Ok(());
+ }
+
+ let mut state = std::mem::zeroed();
+ ffi::wlr_output_state_init(&mut state);
+
+ self.current.apply_no_modeset(&mut state);
+
+ if !ffi::wlr_scene_output_build_state(self.scene_output, &mut state, std::ptr::null()) {
+ ffi::wlr_output_state_finish(&mut state);
+ return Err("Failed to build scene state");
+ }
+
+ if self.rendering_current.tearing {
+ state.tearing_page_flip = true;
+ if !ffi::wlr_output_test_state(self.wlr_output, &state) {
+ state.tearing_page_flip = false;
+ }
+ }
+
+ if !ffi::wlr_output_commit_state(self.wlr_output, &state) {
+ ffi::wlr_output_state_finish(&mut state);
+ return Err("Failed to commit state");
+ }
+
+ ffi::wlr_output_state_finish(&mut state);
+
+ match (*self.server).lock_manager.state {
+ LockState::Unlocked => {
+ if self.lock_render_state != LockRenderState::Unlocked {
+ self.lock_render_state = LockRenderState::PendingUnlock;
+ }
+ }
+ LockState::Locked => {
+ // Assert normal tree disabled, lock surface rendered
+ }
+ LockState::WaitingForBlank => {
+ if self.lock_render_state != LockRenderState::Blanked {
+ self.lock_render_state = LockRenderState::PendingBlank;
+ }
+ }
+ LockState::WaitingForLockSurfaces => {
+ if let Some(_lock_surf) = (*self.server).lock_manager.lock_surface_from_output(self) {
+ if self.lock_render_state != LockRenderState::LockSurface {
+ self.lock_render_state = LockRenderState::PendingLockSurface;
+ }
+ } else {
+ if self.lock_render_state != LockRenderState::Unlocked {
+ self.lock_render_state = LockRenderState::PendingUnlock;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let output_ptr = crate::container_of!(listener, Output, destroy);
+ let mut output = Box::from_raw(output_ptr);
+
+ log::debug!("Output destroyed");
+
+ // Remove listeners
+ wl_listener_remove(&mut output.destroy);
+ wl_listener_remove(&mut output.request_state);
+ wl_listener_remove(&mut output.frame);
+ wl_listener_remove(&mut output.present);
+
+ // Remove from outputs list
+ let link_custom = &mut output.link as *mut ffi::wl_list as *mut WlList;
+ wl_list_remove(link_custom);
+
+ let link_sent_custom = &mut output.link_sent as *mut ffi::wl_list as *mut WlList;
+ wl_list_remove(link_sent_custom);
+
+ if !output.wlr_output.is_null() {
+ ffi::river_wlr_output_set_data(output.wlr_output, std::ptr::null_mut());
+ }
+}
+
+unsafe extern "C" fn handle_request_state(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let output = &mut *crate::container_of!(listener, Output, request_state);
+ let event = data as *mut ffi::wlr_output_event_request_state;
+
+ let committed: u32 = std::mem::transmute((*(*event).state).committed);
+ // mode field is bit 1 (mode)
+ if committed & 1 != 0 {
+ if !(*(*event).state).mode.is_null() {
+ output.scheduled.mode = OutputMode::Standard((*(*event).state).mode);
+ } else {
+ output.scheduled.mode = OutputMode::Custom {
+ width: (*(*event).state).custom_mode.width,
+ height: (*(*event).state).custom_mode.height,
+ refresh: (*(*event).state).custom_mode.refresh,
+ };
+ }
+ }
+}
+
+unsafe extern "C" fn handle_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let output = &mut *crate::container_of!(listener, Output, frame);
+ if let Err(e) = output.render_and_commit() {
+ log::error!("{}", e);
+ }
+ let now = util::timestamp();
+ let mut ffi_now = ffi::timespec {
+ tv_sec: now.tv_sec,
+ tv_nsec: now.tv_nsec,
+ };
+ ffi::wlr_scene_output_send_frame_done(output.scene_output, &mut ffi_now);
+}
+
+unsafe extern "C" fn handle_present(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let output = &mut *crate::container_of!(listener, Output, present);
+ let event = data as *mut ffi::wlr_output_event_present;
+ if !(*event).presented {
+ return;
+ }
+ match output.lock_render_state {
+ LockRenderState::PendingUnlock => {
+ output.lock_render_state = LockRenderState::Unlocked;
+ }
+ LockRenderState::PendingBlank => {
+ output.lock_render_state = LockRenderState::Blanked;
+ (*output.server).lock_manager.maybe_lock();
+ }
+ LockRenderState::PendingLockSurface => {
+ output.lock_render_state = LockRenderState::LockSurface;
+ (*output.server).lock_manager.maybe_lock();
+ }
+ _ => {}
+ }
+}
+
+// Helpers for raw Wayland FFI protocol events
+pub unsafe fn river_output_send_removed(resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_post_event(resource, 0);
+}
+
+pub unsafe fn river_output_send_wl_output(resource: *mut ffi::wl_resource, name: u32) {
+ ffi::wl_resource_post_event(resource, 1, name);
+}
+
+pub unsafe fn river_output_send_position(resource: *mut ffi::wl_resource, x: i32, y: i32) {
+ ffi::wl_resource_post_event(resource, 2, x, y);
+}
+
+pub unsafe fn river_output_send_dimensions(resource: *mut ffi::wl_resource, width: i32, height: i32) {
+ ffi::wl_resource_post_event(resource, 3, width, height);
+}
diff --git a/src/output_manager.rs b/src/output_manager.rs
new file mode 100644
index 0000000..7db4d3f
--- /dev/null
+++ b/src/output_manager.rs
@@ -0,0 +1,648 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, WlList, wl_signal_add, wl_listener_remove, wl_list_remove};
+use crate::output::{Output, OutputStateValue, OutputMode};
+
+#[repr(C)]
+pub struct WlrOutputManagerV1Events {
+ pub apply: ffi::wl_signal,
+ pub test: ffi::wl_signal,
+ pub destroy: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrOutputManagerV1 {
+ pub display: *mut ffi::wl_display,
+ pub global: *mut ffi::wl_global,
+ pub resources: ffi::wl_list,
+ pub heads: ffi::wl_list,
+ pub serial: u32,
+ pub current_configuration_dirty: bool,
+ pub events: WlrOutputManagerV1Events,
+}
+
+#[repr(C)]
+pub struct WlrOutputPowerManagerV1Events {
+ pub set_mode: ffi::wl_signal,
+ pub destroy: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrOutputPowerManagerV1 {
+ pub global: *mut ffi::wl_global,
+ pub output_powers: ffi::wl_list,
+ pub events: WlrOutputPowerManagerV1Events,
+}
+
+pub struct OutputManager {
+ pub first_modeset: bool,
+ pub new_output: ffi::wl_listener,
+ pub output_layout: *mut ffi::wlr_output_layout,
+ pub presentation: *mut ffi::wlr_presentation,
+ pub xdg_output_manager: *mut ffi::wlr_xdg_output_manager_v1,
+ pub wlr_output_manager: *mut ffi::wlr_output_manager_v1,
+ pub manager_apply: ffi::wl_listener,
+ pub manager_test: ffi::wl_listener,
+ pub power_manager: *mut ffi::wlr_output_power_manager_v1,
+ pub power_manager_set_mode: ffi::wl_listener,
+ pub gamma_control_manager: *mut ffi::wlr_gamma_control_manager_v1,
+ pub outputs: ffi::wl_list,
+}
+
+impl OutputManager {
+ pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.first_modeset = true;
+
+ let output_layout = ffi::wlr_output_layout_create((*server).wl_server);
+ if output_layout.is_null() {
+ return Err("Failed to create wlr_output_layout");
+ }
+ self.output_layout = output_layout;
+
+ let gamma_control_manager = ffi::wlr_gamma_control_manager_v1_create((*server).wl_server);
+ if gamma_control_manager.is_null() {
+ ffi::wlr_output_layout_destroy(output_layout);
+ return Err("Failed to create wlr_gamma_control_manager_v1");
+ }
+ self.gamma_control_manager = gamma_control_manager;
+ ffi::wlr_scene_set_gamma_control_manager_v1((*server).scene.wlr_scene, gamma_control_manager);
+
+ let presentation = ffi::wlr_presentation_create((*server).wl_server, (*server).backend, 2);
+ if presentation.is_null() {
+ return Err("Failed to create wlr_presentation");
+ }
+ self.presentation = presentation;
+
+ let xdg_output_manager = ffi::wlr_xdg_output_manager_v1_create((*server).wl_server, output_layout);
+ if xdg_output_manager.is_null() {
+ return Err("Failed to create wlr_xdg_output_manager_v1");
+ }
+ self.xdg_output_manager = xdg_output_manager;
+
+ let wlr_output_manager = ffi::wlr_output_manager_v1_create((*server).wl_server);
+ if wlr_output_manager.is_null() {
+ return Err("Failed to create wlr_output_manager_v1");
+ }
+ self.wlr_output_manager = wlr_output_manager;
+
+ let power_manager = ffi::wlr_output_power_manager_v1_create((*server).wl_server);
+ if power_manager.is_null() {
+ return Err("Failed to create wlr_output_power_manager_v1");
+ }
+ self.power_manager = power_manager;
+
+ ffi::wl_list_init(&mut self.outputs);
+
+ // Add backend new_output listener
+ let backend_cast = (*server).backend as *mut crate::server::WlrBackend;
+ let new_output_ptr = &mut self.new_output as *mut ffi::wl_listener as *mut WlListener;
+ (*new_output_ptr).notify = Some(handle_new_output);
+ wl_signal_add(&mut (*backend_cast).events.new_output, &mut self.new_output);
+
+ // Add apply/test listeners
+ let manager_cast = self.wlr_output_manager as *mut WlrOutputManagerV1;
+
+ let apply_ptr = &mut self.manager_apply as *mut ffi::wl_listener as *mut WlListener;
+ (*apply_ptr).notify = Some(handle_manager_apply);
+ wl_signal_add(&mut (*manager_cast).events.apply, &mut self.manager_apply);
+
+ let test_ptr = &mut self.manager_test as *mut ffi::wl_listener as *mut WlListener;
+ (*test_ptr).notify = Some(handle_manager_test);
+ wl_signal_add(&mut (*manager_cast).events.test, &mut self.manager_test);
+
+ // Add power manager set_mode listener
+ let power_cast = self.power_manager as *mut WlrOutputPowerManagerV1;
+ let set_mode_ptr = &mut self.power_manager_set_mode as *mut ffi::wl_listener as *mut WlListener;
+ (*set_mode_ptr).notify = Some(handle_power_manager_set_mode);
+ wl_signal_add(&mut (*power_cast).events.set_mode, &mut self.power_manager_set_mode);
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ wl_listener_remove(&mut self.manager_apply);
+ wl_listener_remove(&mut self.manager_test);
+ wl_listener_remove(&mut self.power_manager_set_mode);
+ wl_listener_remove(&mut self.new_output);
+
+ if !self.output_layout.is_null() {
+ ffi::wlr_output_layout_destroy(self.output_layout);
+ }
+ }
+
+ pub unsafe fn output_at(&mut self, lx: f64, ly: f64) -> *mut ffi::wlr_output {
+ let mut output_lx: f64 = 0.0;
+ let mut output_ly: f64 = 0.0;
+ ffi::wlr_output_layout_closest_point(
+ self.output_layout,
+ std::ptr::null_mut(),
+ lx,
+ ly,
+ &mut output_lx,
+ &mut output_ly,
+ );
+ ffi::wlr_output_layout_output_at(self.output_layout, output_lx, output_ly)
+ }
+
+ pub unsafe fn auto_layout(&mut self) {
+ let mut rightmost_edge: i32 = 0;
+ let mut row_y: i32 = 0;
+
+ let mut link = self.outputs.next;
+ while link != &mut self.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link);
+ if !output.scheduled.auto_layout {
+ let (w, _) = output.scheduled.dimensions();
+ let x = output.scheduled.x + w;
+ if x > rightmost_edge {
+ rightmost_edge = x;
+ row_y = output.scheduled.y;
+ }
+ }
+ link = (*link).next;
+ }
+
+ let mut link = self.outputs.next;
+ while link != &mut self.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link);
+ if output.scheduled.auto_layout {
+ output.scheduled.x = rightmost_edge;
+ output.scheduled.y = row_y;
+ let (w, _) = output.scheduled.dimensions();
+ rightmost_edge += w;
+ }
+ link = (*link).next;
+ }
+ }
+
+ pub unsafe fn commit_output_state(&mut self, server: *mut Server) {
+ let wm = &mut (*server).wm;
+
+ let mut link = wm.sent.outputs.next;
+ while link != &mut wm.sent.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link_sent);
+ assert!(output.sent.state != OutputStateValue::Destroying);
+ output.rendering_current = output.rendering_requested;
+
+ let wlr_output = output.wlr_output;
+ if !wlr_output.is_null() {
+ match output.sent.state {
+ OutputStateValue::Enabled | OutputStateValue::DisabledSoft => {
+ ffi::wlr_scene_output_set_position(output.scene_output, output.sent.x, output.sent.y);
+ ffi::wlr_output_layout_add(self.output_layout, wlr_output, output.sent.x, output.sent.y);
+ if let Some(lock_surface) = (*server).lock_manager.lock_surface_from_output(output) {
+ ffi::wlr_scene_node_set_position(
+ (*lock_surface).tree as *mut ffi::wlr_scene_node,
+ output.sent.x,
+ output.sent.y,
+ );
+ }
+ }
+ OutputStateValue::DisabledHard => {
+ ffi::wlr_output_layout_remove(self.output_layout, wlr_output);
+ }
+ OutputStateValue::Destroying => unreachable!(),
+ }
+ }
+ link = (*link).next;
+ }
+
+ let mut need_modeset = false;
+ let mut link = wm.sent.outputs.next;
+ while link != &mut wm.sent.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link_sent);
+ let wlr_output = output.wlr_output;
+ if wlr_output.is_null() {
+ link = (*link).next;
+ continue;
+ }
+
+ let wlr_enabled = ffi::river_wlr_output_get_enabled(wlr_output);
+ match output.sent.state {
+ OutputStateValue::Enabled => {
+ if !wlr_enabled {
+ need_modeset = true;
+ break;
+ }
+ }
+ OutputStateValue::DisabledSoft | OutputStateValue::DisabledHard => {
+ if wlr_enabled {
+ need_modeset = true;
+ break;
+ }
+ }
+ OutputStateValue::Destroying => unreachable!(),
+ }
+
+ match output.sent.mode {
+ OutputMode::Standard(mode) => {
+ if mode != ffi::river_wlr_output_get_current_mode(wlr_output) {
+ need_modeset = true;
+ break;
+ }
+ }
+ OutputMode::Custom { width, height, refresh } => {
+ if width != ffi::river_wlr_output_get_width(wlr_output)
+ || height != ffi::river_wlr_output_get_height(wlr_output)
+ || refresh != ffi::river_wlr_output_get_refresh(wlr_output) {
+ need_modeset = true;
+ break;
+ }
+ }
+ OutputMode::None => {
+ assert!(output.sent.state == OutputStateValue::DisabledHard);
+ }
+ }
+
+ if output.current.mode_none() && output.sent.state == OutputStateValue::Enabled {
+ need_modeset = true;
+ break;
+ }
+
+ let wlr_adaptive = ffi::river_wlr_output_get_adaptive_sync_status(wlr_output)
+ == ffi::wlr_output_adaptive_sync_status_WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
+ if output.sent.adaptive_sync != wlr_adaptive {
+ need_modeset = true;
+ break;
+ }
+
+ link = (*link).next;
+ }
+
+ if need_modeset {
+ log::debug!("committing output state requires modeset");
+
+ let mut states_vec = Vec::new();
+ let mut link = wm.sent.outputs.next;
+ while link != &mut wm.sent.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link_sent);
+ let wlr_output = output.wlr_output;
+ if wlr_output.is_null() {
+ link = (*link).next;
+ continue;
+ }
+
+ let mut state = std::mem::zeroed();
+ ffi::wlr_output_state_init(&mut state);
+ output.sent.apply_modeset(&mut state);
+
+ states_vec.push(ffi::wlr_backend_output_state {
+ output: wlr_output,
+ base: state,
+ });
+
+ link = (*link).next;
+ }
+
+ let mut swapchain_manager = std::mem::zeroed();
+ ffi::wlr_output_swapchain_manager_init(&mut swapchain_manager, (*server).backend);
+
+ if !ffi::wlr_output_swapchain_manager_prepare(
+ &mut swapchain_manager,
+ states_vec.as_ptr(),
+ states_vec.len(),
+ ) {
+ log::error!("failed to prepare new output configuration");
+ self.modeset_failed(server);
+ for state in &mut states_vec {
+ ffi::wlr_output_state_finish(&mut state.base);
+ }
+ ffi::wlr_output_swapchain_manager_finish(&mut swapchain_manager);
+ return;
+ }
+
+ for state in &mut states_vec {
+ let output = &mut *(ffi::river_wlr_output_get_data(state.output) as *mut Output);
+ let sc = ffi::wlr_output_swapchain_manager_get_swapchain(&mut swapchain_manager, state.output);
+ let options = ffi::wlr_scene_output_state_options {
+ swapchain: sc,
+ ..std::mem::zeroed()
+ };
+ if !ffi::wlr_scene_output_build_state(output.scene_output, &mut state.base, &options) {
+ log::error!("failed to render scene for {:?}", std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(state.output)));
+ }
+ }
+
+ if !ffi::wlr_backend_commit((*server).backend, states_vec.as_ptr(), states_vec.len()) {
+ log::error!("failed to commit new output configuration");
+ self.modeset_failed(server);
+ for state in &mut states_vec {
+ ffi::wlr_output_state_finish(&mut state.base);
+ }
+ ffi::wlr_output_swapchain_manager_finish(&mut swapchain_manager);
+ return;
+ }
+
+ self.first_modeset = false;
+ ffi::wlr_output_swapchain_manager_apply(&mut swapchain_manager);
+
+ for mut state in states_vec {
+ ffi::wlr_output_state_finish(&mut state.base);
+ }
+ ffi::wlr_output_swapchain_manager_finish(&mut swapchain_manager);
+ }
+
+ if !wm.sent.output_config.is_null() {
+ ffi::wlr_output_configuration_v1_send_succeeded(wm.sent.output_config);
+ ffi::wlr_output_configuration_v1_destroy(wm.sent.output_config);
+ wm.sent.output_config = std::ptr::null_mut();
+ }
+
+ let mut link = wm.sent.outputs.next;
+ while link != &mut wm.sent.outputs as *mut ffi::wl_list {
+ let next_link = (*link).next;
+ let output = &mut *crate::container_of!(link, Output, link_sent);
+ let wlr_output = output.wlr_output;
+ if wlr_output.is_null() {
+ link = next_link;
+ continue;
+ }
+
+ if !output.sent_wl_output {
+ let global = ffi::river_wlr_output_get_global(wlr_output);
+ if !global.is_null() {
+ if !output.object.is_null() {
+ let name = ffi::wl_global_get_name(global, ffi::wl_resource_get_client(output.object));
+ crate::output::river_output_send_wl_output(output.object, name);
+ output.sent_wl_output = true;
+ }
+ }
+ }
+
+ match output.sent.state {
+ OutputStateValue::Enabled => {
+ assert!(ffi::river_wlr_output_get_enabled(wlr_output));
+ ffi::wlr_output_schedule_frame(wlr_output);
+ }
+ OutputStateValue::DisabledSoft | OutputStateValue::DisabledHard => {
+ assert!(!ffi::river_wlr_output_get_enabled(wlr_output));
+ output.lock_render_state = crate::output::LockRenderState::Blanked;
+ if output.sent.state == OutputStateValue::DisabledHard {
+ wl_list_remove(&mut output.link_sent as *mut ffi::wl_list as *mut WlList);
+ ffi::wl_list_init(&mut output.link_sent);
+ }
+ }
+ OutputStateValue::Destroying => unreachable!(),
+ }
+
+ output.current = output.sent;
+ link = next_link;
+ }
+
+ let _ = self.send_config(server);
+ }
+
+ pub unsafe fn modeset_failed(&mut self, server: *mut Server) {
+ let wm = &mut (*server).wm;
+
+ if self.first_modeset {
+ log::error!("initial modeset failed, exiting river");
+ ffi::wl_display_terminate((*server).wl_server);
+ return;
+ }
+
+ if !wm.sent.output_config.is_null() {
+ ffi::wlr_output_configuration_v1_send_failed(wm.sent.output_config);
+ ffi::wlr_output_configuration_v1_destroy(wm.sent.output_config);
+ wm.sent.output_config = std::ptr::null_mut();
+ }
+
+ let mut link = wm.sent.outputs.next;
+ while link != &mut wm.sent.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link_sent);
+ output.scheduled = output.current;
+ output.sent = output.current;
+ link = (*link).next;
+ }
+ wm.dirty_windowing();
+ }
+
+ pub unsafe fn send_config(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ let config = ffi::wlr_output_configuration_v1_create();
+ if config.is_null() {
+ return Err("Failed to create configuration v1");
+ }
+
+ let mut link = self.outputs.next;
+ while link != &mut self.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link);
+ let wlr_output = output.wlr_output;
+ if wlr_output.is_null() {
+ link = (*link).next;
+ continue;
+ }
+
+ let head = ffi::wlr_output_configuration_head_v1_create(config, wlr_output);
+ if head.is_null() {
+ ffi::wlr_output_configuration_v1_destroy(config);
+ return Err("Failed to create configuration head");
+ }
+
+ (*head).state.enabled = match output.current.state {
+ OutputStateValue::Enabled | OutputStateValue::DisabledSoft => true,
+ OutputStateValue::DisabledHard => false,
+ OutputStateValue::Destroying => unreachable!(),
+ };
+ (*head).state.scale = output.current.scale;
+ (*head).state.transform = output.current.transform;
+ (*head).state.x = output.current.x;
+ (*head).state.y = output.current.y;
+
+ link = (*link).next;
+ }
+
+ ffi::wlr_output_manager_v1_set_configuration(self.wlr_output_manager, config);
+ Ok(())
+ }
+
+ pub unsafe fn max_overlap_output(&mut self, box_: *const ffi::wlr_box) -> *mut ffi::wlr_output {
+ let mut max_overlap_area = 0;
+ let mut max_overlap_output = std::ptr::null_mut();
+
+ let mut link = self.outputs.next;
+ while link != &mut self.outputs as *mut ffi::wl_list {
+ let output = &mut *crate::container_of!(link, Output, link);
+ let wlr_output = output.wlr_output;
+ if wlr_output.is_null() {
+ link = (*link).next;
+ continue;
+ }
+
+ let mut overlap: ffi::wlr_box = std::mem::zeroed();
+ ffi::wlr_output_layout_get_box(self.output_layout, wlr_output, &mut overlap);
+
+ if overlap.width <= 0 || overlap.height <= 0 {
+ link = (*link).next;
+ continue;
+ }
+
+ let mut dest: ffi::wlr_box = std::mem::zeroed();
+ if ffi::wlr_box_intersection(&mut dest, &overlap, box_) {
+ let overlap_area = dest.width * dest.height;
+ if overlap_area > max_overlap_area {
+ max_overlap_area = overlap_area;
+ max_overlap_output = wlr_output;
+ }
+ }
+
+ link = (*link).next;
+ }
+
+ max_overlap_output
+ }
+}
+
+fn validate_config_coordinates(server: *mut Server, config: *mut ffi::wlr_output_configuration_v1) -> bool {
+ unsafe {
+ let mut head_link = (*config).heads.next;
+ while head_link != &mut (*config).heads {
+ let head = crate::container_of!(head_link, ffi::wlr_output_configuration_head_v1, link);
+ let output_global = ffi::river_wlr_output_get_global((*head).state.output);
+
+ if (*head).state.enabled {
+ let proposed = crate::output::OutputState::from_head_state(&((*head).state));
+ if !(*server).xwayland.is_null() {
+ if proposed.x < 0 || proposed.y < 0 {
+ log::error!(
+ "Attempted to set negative coordinates for output. Negative output coordinates are disallowed if Xwayland is active."
+ );
+ return false;
+ }
+ let (width, height) = proposed.dimensions();
+ if proposed.x + width > i16::MAX as i32 || proposed.y + height > i16::MAX as i32 {
+ log::error!(
+ "Attempted to set too-large coordinates for output. Coordinates greater than {} are disallowed if Xwayland is active.",
+ i16::MAX
+ );
+ return false;
+ }
+ }
+ }
+ head_link = (*head_link).next;
+ }
+ }
+ true
+}
+
+unsafe extern "C" fn handle_new_output(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let om = &mut *crate::container_of!(listener, OutputManager, new_output);
+ let server = crate::container_of!(om as *mut OutputManager, Server, om);
+ let wlr_output = data as *mut ffi::wlr_output;
+ log::debug!("new output {:?}", std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output)));
+ if let Err(e) = Output::create(server, wlr_output) {
+ log::error!("failed to create output: {}", e);
+ ffi::wlr_output_destroy(wlr_output);
+ }
+}
+
+unsafe extern "C" fn handle_manager_test(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let om = &mut *crate::container_of!(listener, OutputManager, manager_test);
+ let server = crate::container_of!(om as *mut OutputManager, Server, om);
+ let config = data as *mut ffi::wlr_output_configuration_v1;
+
+ if !validate_config_coordinates(server, config) {
+ ffi::wlr_output_configuration_v1_send_failed(config);
+ ffi::wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+
+ let mut states_len: usize = 0;
+ let states = ffi::wlr_output_configuration_v1_build_state(config, &mut states_len);
+ if states.is_null() {
+ log::error!("out of memory");
+ ffi::wlr_output_configuration_v1_send_failed(config);
+ ffi::wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+
+ let mut swapchain_manager = std::mem::zeroed();
+ ffi::wlr_output_swapchain_manager_init(&mut swapchain_manager, (*server).backend);
+
+ if ffi::wlr_output_swapchain_manager_prepare(&mut swapchain_manager, states, states_len) {
+ ffi::wlr_output_configuration_v1_send_succeeded(config);
+ } else {
+ ffi::wlr_output_configuration_v1_send_failed(config);
+ }
+
+ ffi::wlr_output_swapchain_manager_finish(&mut swapchain_manager);
+ libc::free(states as *mut std::ffi::c_void);
+ ffi::wlr_output_configuration_v1_destroy(config);
+}
+
+unsafe extern "C" fn handle_manager_apply(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let om = &mut *crate::container_of!(listener, OutputManager, manager_apply);
+ let server = crate::container_of!(om as *mut OutputManager, Server, om);
+ let config = data as *mut ffi::wlr_output_configuration_v1;
+
+ log::info!("applying output configuration");
+
+ if !validate_config_coordinates(server, config) {
+ ffi::wlr_output_configuration_v1_send_failed(config);
+ ffi::wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+
+ let mut head_link = (*config).heads.next;
+ while head_link != &mut (*config).heads {
+ let head = crate::container_of!(head_link, ffi::wlr_output_configuration_head_v1, link);
+ let output = &mut *(ffi::river_wlr_output_get_data((*head).state.output) as *mut Output);
+
+ if (*head).state.enabled {
+ let previous = output.scheduled.state;
+ output.scheduled = crate::output::OutputState::from_head_state(&((*head).state));
+ if previous == crate::output::OutputStateValue::DisabledSoft {
+ output.scheduled.state = crate::output::OutputStateValue::DisabledSoft;
+ } else {
+ assert!(output.scheduled.state == crate::output::OutputStateValue::Enabled);
+ }
+ } else {
+ output.scheduled.state = crate::output::OutputStateValue::DisabledHard;
+ }
+
+ head_link = (*head_link).next;
+ }
+
+ if !(*server).wm.scheduled.output_config.is_null() {
+ ffi::wlr_output_configuration_v1_send_failed((*server).wm.scheduled.output_config);
+ ffi::wlr_output_configuration_v1_destroy((*server).wm.scheduled.output_config);
+ }
+ (*server).wm.scheduled.output_config = config;
+
+ (*server).wm.dirty_windowing();
+}
+
+unsafe extern "C" fn handle_power_manager_set_mode(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let om = &mut *crate::container_of!(listener, OutputManager, power_manager_set_mode);
+ let server = crate::container_of!(om as *mut OutputManager, Server, om);
+ let event = data as *mut ffi::wlr_output_power_v1_set_mode_event;
+
+ let output_data = ffi::river_wlr_output_get_data((*event).output);
+ if output_data.is_null() {
+ return;
+ }
+ let output = &mut *(output_data as *mut Output);
+
+ log::debug!("client requested dpms mode {:?} for output {:?}", (*event).mode, std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name((*event).output)));
+
+ match output.scheduled.state {
+ OutputStateValue::Enabled => {
+ if (*event).mode == ffi::zwlr_output_power_v1_mode_ZWLR_OUTPUT_POWER_V1_MODE_OFF {
+ output.scheduled.state = OutputStateValue::DisabledSoft;
+ } else {
+ return;
+ }
+ }
+ OutputStateValue::DisabledSoft => {
+ if (*event).mode == ffi::zwlr_output_power_v1_mode_ZWLR_OUTPUT_POWER_V1_MODE_ON {
+ output.scheduled.state = OutputStateValue::Enabled;
+ } else {
+ return;
+ }
+ }
+ OutputStateValue::DisabledHard | OutputStateValue::Destroying => return,
+ }
+
+ (*server).wm.dirty_windowing();
+}
diff --git a/src/pointer_binding.rs b/src/pointer_binding.rs
new file mode 100644
index 0000000..cfc2115
--- /dev/null
+++ b/src/pointer_binding.rs
@@ -0,0 +1,172 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::seat::Seat;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum PointerBindingStateChange {
+ None,
+ Pressed,
+ Released,
+}
+
+pub struct PointerBindingScheduled {
+ pub state_change: PointerBindingStateChange,
+}
+
+pub struct PointerBindingRequested {
+ pub enabled: bool,
+}
+
+pub struct PointerBinding {
+ pub seat: *mut Seat,
+ pub object: *mut ffi::wl_resource,
+ pub button: u32,
+ pub modifiers: u32,
+ pub wm_scheduled: PointerBindingScheduled,
+ pub wm_requested: PointerBindingRequested,
+ pub sent_pressed: bool,
+ pub link: ffi::wl_list,
+}
+
+impl PointerBinding {
+ pub unsafe fn create(
+ seat: *mut Seat,
+ client: *mut ffi::wl_client,
+ version: u32,
+ id: u32,
+ button: u32,
+ modifiers: u32,
+ ) -> Result<(), &'static str> {
+ let binding_ptr = Box::into_raw(Box::new(Self {
+ seat,
+ object: std::ptr::null_mut(),
+ button,
+ modifiers,
+ wm_scheduled: PointerBindingScheduled {
+ state_change: PointerBindingStateChange::None,
+ },
+ wm_requested: PointerBindingRequested {
+ enabled: false,
+ },
+ sent_pressed: false,
+ link: std::mem::zeroed(),
+ }));
+
+ let resource = ffi::wl_resource_create(client, &ffi::river_pointer_binding_v1_interface, version as i32, id);
+ if resource.is_null() {
+ let _ = Box::from_raw(binding_ptr);
+ return Err("wl_resource_create failed");
+ }
+
+ (*binding_ptr).object = resource;
+ ffi::wl_resource_set_implementation(
+ resource,
+ &POINTER_BINDING_INTERFACE as *const _ as *const _,
+ binding_ptr as *mut _,
+ Some(handle_binding_resource_destroy),
+ );
+
+ let pointer_bindings_list = &mut (*seat).pointer_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert((*pointer_bindings_list).prev, &mut (*binding_ptr).link as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ log::debug!(
+ "new river_pointer_binding_v1: button: {} modifiers: {}",
+ button,
+ modifiers
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn destroy(binding: *mut Self) {
+ ffi::wl_resource_set_implementation(
+ (*binding).object,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+ handle_binding_resource_destroy((*binding).object);
+ }
+
+ pub unsafe fn pressed(&mut self) {
+ assert!(!self.sent_pressed);
+ assert!(matches!(self.wm_scheduled.state_change, PointerBindingStateChange::None));
+ self.wm_scheduled.state_change = PointerBindingStateChange::Pressed;
+ (*(*self.seat).server).wm.dirty_windowing();
+ }
+
+ pub unsafe fn released(&mut self) {
+ assert!(self.sent_pressed);
+ assert!(matches!(self.wm_scheduled.state_change, PointerBindingStateChange::None));
+ self.wm_scheduled.state_change = PointerBindingStateChange::Released;
+ (*(*self.seat).server).wm.dirty_windowing();
+ }
+
+ pub fn match_binding(&self, button: u32, modifiers: u32) -> bool {
+ if !self.wm_requested.enabled {
+ return false;
+ }
+ button == self.button && modifiers == self.modifiers
+ }
+}
+
+unsafe extern "C" fn handle_binding_resource_destroy(resource: *mut ffi::wl_resource) {
+ let binding = ffi::wl_resource_get_user_data(resource) as *mut PointerBinding;
+ if !binding.is_null() {
+ // It is possible for the window manager to create duplicate pointer bindings.
+ let cursor = &mut (*(*binding).seat).cursor;
+ if let Some(val) = cursor.pressed.get_mut(&(*binding).button) {
+ if *val == Some(binding) {
+ *val = None;
+ }
+ }
+
+ crate::server::wl_list_remove(&mut (*binding).link as *mut ffi::wl_list as *mut crate::server::WlList);
+ let _ = Box::from_raw(binding);
+ }
+}
+
+static POINTER_BINDING_INTERFACE: ffi::river_pointer_binding_v1_interface = ffi::river_pointer_binding_v1_interface {
+ destroy: Some(pointer_binding_destroy),
+ enable: Some(pointer_binding_enable),
+ disable: Some(pointer_binding_disable),
+};
+
+unsafe extern "C" fn pointer_binding_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn pointer_binding_enable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let binding = ffi::wl_resource_get_user_data(resource) as *mut PointerBinding;
+ if binding.is_null() {
+ return;
+ }
+ let server = (*(*binding).seat).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*binding).wm_requested.enabled = true;
+}
+
+unsafe extern "C" fn pointer_binding_disable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let binding = ffi::wl_resource_get_user_data(resource) as *mut PointerBinding;
+ if binding.is_null() {
+ return;
+ }
+ let server = (*(*binding).seat).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*binding).wm_requested.enabled = false;
+}
diff --git a/src/pointer_constraint.rs b/src/pointer_constraint.rs
new file mode 100644
index 0000000..4b7629d
--- /dev/null
+++ b/src/pointer_constraint.rs
@@ -0,0 +1,259 @@
+use crate::ffi;
+use crate::seat::{Seat, Focus};
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
+
+#[derive(Clone, Copy)]
+pub enum PointerConstraintState {
+ Inactive,
+ Active {
+ node: *mut ffi::wlr_scene_node,
+ sx: f64,
+ sy: f64,
+ },
+}
+
+pub struct PointerConstraint {
+ pub wlr_constraint: *mut ffi::wlr_pointer_constraint_v1,
+ pub state: PointerConstraintState,
+ pub destroy_listener: ffi::wl_listener,
+ pub commit_listener: ffi::wl_listener,
+ pub node_destroy_listener: ffi::wl_listener,
+}
+
+impl PointerConstraint {
+ pub unsafe fn create(wlr_constraint: *mut ffi::wlr_pointer_constraint_v1) -> *mut Self {
+ let constraint = Box::into_raw(Box::new(Self {
+ wlr_constraint,
+ state: PointerConstraintState::Inactive,
+ destroy_listener: std::mem::zeroed(),
+ commit_listener: std::mem::zeroed(),
+ node_destroy_listener: std::mem::zeroed(),
+ }));
+
+ (*wlr_constraint).data = constraint as *mut _;
+
+ let destroy_listener_ptr = &mut (*constraint).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener_ptr).notify = Some(handle_destroy);
+
+ let commit_listener_ptr = &mut (*constraint).commit_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*commit_listener_ptr).notify = Some(handle_commit);
+
+ let node_destroy_listener_ptr = &mut (*constraint).node_destroy_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*node_destroy_listener_ptr).notify = Some(handle_node_destroy);
+
+ wl_signal_add(&mut (*wlr_constraint).events.destroy, &mut (*constraint).destroy_listener);
+ let commit_signal = ffi::river_wlr_surface_get_commit_signal((*wlr_constraint).surface);
+ wl_signal_add(commit_signal, &mut (*constraint).commit_listener);
+
+ let seat = river_wlr_seat_get_data_safe((*wlr_constraint).seat);
+ if !seat.is_null() {
+ if let Focus::LayerSurface(surface) = (*seat).focused {
+ if surface == (*wlr_constraint).surface {
+ (*seat).cursor.constraint = constraint;
+ (*constraint).maybe_activate();
+ }
+ }
+ }
+
+ constraint
+ }
+
+ pub unsafe fn maybe_activate(&mut self) {
+ let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
+ if seat.is_null() {
+ return;
+ }
+
+ if (*seat).cursor.constraint != self {
+ return;
+ }
+
+ if matches!(self.state, PointerConstraintState::Active { .. }) {
+ return;
+ }
+
+ // Retrieve cursor coordinates
+ let cx = (*seat).cursor.x();
+ let cy = (*seat).cursor.y();
+
+ let server = (*seat).server;
+ if let Some(result) = (*server).scene.at(cx, cy) {
+ if result.surface != (*self.wlr_constraint).surface {
+ return;
+ }
+
+ let sx = result.sx;
+ let sy = result.sy;
+
+ // Check if within the pixman region
+ if ffi::pixman_region32_contains_point(&mut (*self.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
+ return;
+ }
+
+ self.state = PointerConstraintState::Active {
+ node: result.node,
+ sx,
+ sy,
+ };
+
+ let destroy_signal = ffi::river_scene_node_get_destroy_signal(result.node);
+ wl_signal_add(destroy_signal, &mut self.node_destroy_listener);
+
+ log::info!("activating pointer constraint");
+ ffi::wlr_pointer_constraint_v1_send_activated(self.wlr_constraint);
+ }
+ }
+
+ pub unsafe fn deactivate(&mut self) {
+ if !matches!(self.state, PointerConstraintState::Active { .. }) {
+ return;
+ }
+
+ self.warp_to_hint_if_set();
+
+ self.state = PointerConstraintState::Inactive;
+ wl_listener_remove(&mut self.node_destroy_listener);
+ ffi::wlr_pointer_constraint_v1_send_deactivated(self.wlr_constraint);
+ }
+
+ pub unsafe fn update_state(&mut self) {
+ self.maybe_activate();
+
+ if let PointerConstraintState::Active { node, sx, sy } = self.state {
+ let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
+ if seat.is_null() {
+ return;
+ }
+
+ let mut lx = 0;
+ let mut ly = 0;
+ // Get coordinates of scene node relative to layout
+ // In wlroots, scene node coords are retrieved
+ if !wlr_scene_node_coords_safe(node, &mut lx, &mut ly) {
+ log::info!("deactivating pointer constraint, scene node disabled");
+ self.deactivate();
+ return;
+ }
+
+ let warp_lx = lx as f64 + sx;
+ let warp_ly = ly as f64 + sy;
+
+ if !ffi::wlr_cursor_warp((*seat).cursor.wlr_cursor, std::ptr::null_mut(), warp_lx, warp_ly) {
+ log::info!("deactivating pointer constraint, could not warp cursor");
+ self.deactivate();
+ return;
+ }
+
+ if ffi::pixman_region32_contains_point(&mut (*self.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
+ log::info!("deactivating pointer constraint, cursor outside region despite warp");
+ self.deactivate();
+ }
+ }
+ }
+
+ pub unsafe fn confine(&mut self, dx: &mut f64, dy: &mut f64) {
+ if let PointerConstraintState::Active { sx, sy, node } = self.state {
+ let mut new_sx = 0.0;
+ let mut new_sy = 0.0;
+
+ // Call wlroots / pixman confine helper
+ // river does: wlr.region.confine(region, sx, sy, sx + dx, sy + dy, &new_sx, &new_sy)
+ // If the region contains the line segment or we can confine it.
+ // Let's implement confinement using wlroots wlr_region_confine (or similar, or custom simple confinement).
+ // Actually, wlroots has: wlr_region_confine(struct pixman_region32 *region, double sx1, double sy1, double sx2, double sy2, double *ak_sx, double *ak_sy)
+ // Let's call it:
+ if ffi::wlr_region_confine(&mut (*self.wlr_constraint).region, sx, sy, sx + *dx, sy + *dy, &mut new_sx, &mut new_sy) {
+ *dx = new_sx - sx;
+ *dy = new_sy - sy;
+
+ self.state = PointerConstraintState::Active {
+ node,
+ sx: new_sx,
+ sy: new_sy,
+ };
+ }
+ }
+ }
+
+ unsafe fn warp_to_hint_if_set(&mut self) {
+ let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
+ if seat.is_null() {
+ return;
+ }
+
+ if (*self.wlr_constraint).current.cursor_hint.enabled {
+ if let PointerConstraintState::Active { node, .. } = self.state {
+ let mut lx = 0;
+ let mut ly = 0;
+ wlr_scene_node_coords_safe(node, &mut lx, &mut ly);
+
+ let sx = (*self.wlr_constraint).current.cursor_hint.x;
+ let sy = (*self.wlr_constraint).current.cursor_hint.y;
+ ffi::wlr_cursor_warp((*seat).cursor.wlr_cursor, std::ptr::null_mut(), lx as f64 + sx, ly as f64 + sy);
+ ffi::wlr_seat_pointer_warp((*seat).wlr_seat, sx, sy);
+ }
+ }
+ }
+}
+
+unsafe fn river_wlr_seat_get_data_safe(wlr_seat: *mut ffi::wlr_seat) -> *mut Seat {
+ if wlr_seat.is_null() {
+ std::ptr::null_mut()
+ } else {
+ ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat
+ }
+}
+
+unsafe fn wlr_scene_node_coords_safe(node: *mut ffi::wlr_scene_node, x: *mut i32, y: *mut i32) -> bool {
+ if node.is_null() {
+ false
+ } else {
+ ffi::wlr_scene_node_coords(node, x, y)
+ }
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let constraint_ptr = crate::container_of!(listener, PointerConstraint, destroy_listener) as *mut PointerConstraint;
+ let constraint = &mut *constraint_ptr;
+ let seat = river_wlr_seat_get_data_safe((*constraint.wlr_constraint).seat);
+
+ if matches!(constraint.state, PointerConstraintState::Active { .. }) {
+ constraint.warp_to_hint_if_set();
+ wl_listener_remove(&mut constraint.node_destroy_listener);
+ }
+
+ wl_listener_remove(&mut constraint.destroy_listener);
+ wl_listener_remove(&mut constraint.commit_listener);
+
+ if !seat.is_null() && (*seat).cursor.constraint == constraint_ptr {
+ (*seat).cursor.constraint = std::ptr::null_mut();
+ }
+
+ let _boxed = Box::from_raw(constraint_ptr);
+}
+
+unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let constraint_ptr = crate::container_of!(listener, PointerConstraint, commit_listener) as *mut PointerConstraint;
+ let constraint = &mut *constraint_ptr;
+ let seat = river_wlr_seat_get_data_safe((*constraint.wlr_constraint).seat);
+
+ match constraint.state {
+ PointerConstraintState::Active { sx, sy, .. } => {
+ if ffi::pixman_region32_contains_point(&mut (*constraint.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
+ log::info!("deactivating pointer constraint, input region change left pointer outside");
+ constraint.deactivate();
+ }
+ }
+ PointerConstraintState::Inactive => {
+ if !seat.is_null() && (*seat).cursor.constraint == constraint_ptr {
+ constraint.maybe_activate();
+ }
+ }
+ }
+}
+
+unsafe extern "C" fn handle_node_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let constraint_ptr = crate::container_of!(listener, PointerConstraint, node_destroy_listener) as *mut PointerConstraint;
+ log::info!("deactivating pointer constraint, scene node destroyed");
+ (*constraint_ptr).deactivate();
+}
diff --git a/src/process.rs b/src/process.rs
new file mode 100644
index 0000000..def8814
--- /dev/null
+++ b/src/process.rs
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: © 2022 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use std::sync::Mutex;
+use nix::sys::resource::{getrlimit, setrlimit, Resource};
+
+static ORIGINAL_RLIMIT: Mutex<Option<libc::rlimit>> = Mutex::new(None);
+
+pub fn setup() {
+ // Ignore SIGPIPE so we don't get killed when writing to a socket that
+ // has had its read end closed by another process.
+ unsafe {
+ libc::signal(libc::SIGPIPE, libc::SIG_IGN);
+ }
+
+ // Most unix systems have a default limit of 1024 file descriptors.
+ // Raise it to avoid issues with many wayland clients.
+ if let Ok((cur, max)) = getrlimit(Resource::RLIMIT_NOFILE) {
+ let mut orig = ORIGINAL_RLIMIT.lock().unwrap();
+ *orig = Some(libc::rlimit {
+ rlim_cur: cur,
+ rlim_max: max,
+ });
+
+ let new_cur = std::cmp::min(4096, max);
+ if let Err(e) = setrlimit(Resource::RLIMIT_NOFILE, new_cur, max) {
+ log::error!("setrlimit failed: {}, using system default limit of {}", e, cur);
+ } else {
+ log::info!("raised file descriptor limit of the river process to {}", new_cur);
+ }
+ } else {
+ log::error!("getrlimit failed, using system default file descriptor limit");
+ }
+}
+
+pub fn cleanup_child() {
+ unsafe {
+ if libc::setsid() < 0 {
+ // setsid failed
+ }
+
+ let mut empty_mask: libc::sigset_t = std::mem::zeroed();
+ libc::sigemptyset(&mut empty_mask);
+ libc::sigprocmask(libc::SIG_SETMASK, &empty_mask, std::ptr::null_mut());
+
+ libc::signal(libc::SIGPIPE, libc::SIG_DFL);
+ }
+
+ let orig = ORIGINAL_RLIMIT.lock().unwrap();
+ if let Some(original) = *orig {
+ unsafe {
+ libc::setrlimit(libc::RLIMIT_NOFILE, &original);
+ }
+ }
+}
diff --git a/src/scene.rs b/src/scene.rs
new file mode 100644
index 0000000..c7239cb
--- /dev/null
+++ b/src/scene.rs
@@ -0,0 +1,213 @@
+// SPDX-FileCopyrightText: © 2024 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
+
+pub struct SceneLayers {
+ pub background: *mut ffi::wlr_scene_tree,
+ pub bottom: *mut ffi::wlr_scene_tree,
+ pub wm: *mut ffi::wlr_scene_tree,
+ pub top: *mut ffi::wlr_scene_tree,
+ pub fullscreen: *mut ffi::wlr_scene_tree,
+ pub overlay: *mut ffi::wlr_scene_tree,
+ pub popups: *mut ffi::wlr_scene_tree,
+ pub override_redirect: *mut ffi::wlr_scene_tree,
+}
+
+pub struct Scene {
+ pub wlr_scene: *mut ffi::wlr_scene,
+ pub interactive_tree: *mut ffi::wlr_scene_tree,
+ pub drag_icons: *mut ffi::wlr_scene_tree,
+ pub hidden_tree: *mut ffi::wlr_scene_tree,
+ pub normal_tree: *mut ffi::wlr_scene_tree,
+ pub locked_tree: *mut ffi::wlr_scene_tree,
+ pub layers: SceneLayers,
+}
+
+impl Scene {
+ pub fn new() -> Self {
+ Self {
+ wlr_scene: std::ptr::null_mut(),
+ interactive_tree: std::ptr::null_mut(),
+ drag_icons: std::ptr::null_mut(),
+ hidden_tree: std::ptr::null_mut(),
+ normal_tree: std::ptr::null_mut(),
+ locked_tree: std::ptr::null_mut(),
+ layers: SceneLayers {
+ background: std::ptr::null_mut(),
+ bottom: std::ptr::null_mut(),
+ wm: std::ptr::null_mut(),
+ top: std::ptr::null_mut(),
+ fullscreen: std::ptr::null_mut(),
+ overlay: std::ptr::null_mut(),
+ popups: std::ptr::null_mut(),
+ override_redirect: std::ptr::null_mut(),
+ },
+ }
+ }
+
+ pub unsafe fn init(
+ &mut self,
+ linux_dmabuf: *mut ffi::wlr_linux_dmabuf_v1,
+ color_manager: *mut ffi::wlr_color_manager_v1,
+ ) -> Result<(), &'static str> {
+ let wlr_scene = ffi::wlr_scene_create();
+ if wlr_scene.is_null() {
+ return Err("Failed to create wlr_scene");
+ }
+ self.wlr_scene = wlr_scene;
+
+ if !linux_dmabuf.is_null() {
+ ffi::wlr_scene_set_linux_dmabuf_v1(wlr_scene, linux_dmabuf);
+ }
+ if !color_manager.is_null() {
+ ffi::wlr_scene_set_color_manager_v1(wlr_scene, color_manager);
+ }
+
+ let interactive_tree = ffi::wlr_scene_tree_create(&mut (*wlr_scene).tree);
+ let drag_icons = ffi::wlr_scene_tree_create(&mut (*wlr_scene).tree);
+ let hidden_tree = ffi::wlr_scene_tree_create(&mut (*wlr_scene).tree);
+ if interactive_tree.is_null() || drag_icons.is_null() || hidden_tree.is_null() {
+ return Err("Failed to create root scene trees");
+ }
+ self.interactive_tree = interactive_tree;
+ self.drag_icons = drag_icons;
+ self.hidden_tree = hidden_tree;
+
+ ffi::wlr_scene_node_set_enabled(hidden_tree as *mut ffi::wlr_scene_node, false);
+
+ let normal_tree = ffi::wlr_scene_tree_create(interactive_tree);
+ let locked_tree = ffi::wlr_scene_tree_create(interactive_tree);
+ if normal_tree.is_null() || locked_tree.is_null() {
+ return Err("Failed to create normal/locked scene trees");
+ }
+ self.normal_tree = normal_tree;
+ self.locked_tree = locked_tree;
+
+ ffi::wlr_scene_node_set_enabled(locked_tree as *mut ffi::wlr_scene_node, false);
+
+ self.layers.background = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.bottom = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.wm = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.top = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.fullscreen = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.overlay = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.popups = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.override_redirect = ffi::wlr_scene_tree_create(normal_tree);
+
+ if self.layers.background.is_null()
+ || self.layers.bottom.is_null()
+ || self.layers.wm.is_null()
+ || self.layers.top.is_null()
+ || self.layers.fullscreen.is_null()
+ || self.layers.overlay.is_null()
+ || self.layers.popups.is_null()
+ || self.layers.override_redirect.is_null()
+ {
+ return Err("Failed to create scene layer trees");
+ }
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {}
+
+ pub unsafe fn at(&self, lx: f64, ly: f64) -> Option<AtResult> {
+ let mut sx: f64 = 0.0;
+ let mut sy: f64 = 0.0;
+ let node = ffi::wlr_scene_node_at(self.interactive_tree as *mut ffi::wlr_scene_node, lx, ly, &mut sx, &mut sy);
+ if node.is_null() {
+ return None;
+ }
+
+ let surface = ffi::river_scene_node_get_surface(node);
+
+ if let Some(scene_node_data) = SceneNodeData::from_node(node) {
+ Some(AtResult {
+ node,
+ surface,
+ sx,
+ sy,
+ data: scene_node_data.data,
+ })
+ } else {
+ None
+ }
+ }
+
+ pub unsafe fn layer_surface_tree(&self, layer: u32) -> *mut ffi::wlr_scene_tree {
+ // layer is zwlr_layer_shell_v1_layer enum values
+ match layer {
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND => self.layers.background,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM => self.layers.bottom,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_TOP => self.layers.top,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY => self.layers.overlay,
+ _ => std::ptr::null_mut(),
+ }
+ }
+}
+
+pub struct AtResult {
+ pub node: *mut ffi::wlr_scene_node,
+ pub surface: *mut ffi::wlr_surface,
+ pub sx: f64,
+ pub sy: f64,
+ pub data: SceneNodeDataVal,
+}
+
+pub struct SaveableSurfaces {
+ pub enabled: bool,
+ pub saved: bool,
+ pub tree: *mut ffi::wlr_scene_tree,
+ pub saved_tree: *mut ffi::wlr_scene_tree,
+}
+
+impl SaveableSurfaces {
+ pub unsafe fn init(parent: *mut ffi::wlr_scene_tree) -> Result<Self, &'static str> {
+ let tree = ffi::wlr_scene_tree_create(parent);
+ let saved_tree = ffi::wlr_scene_tree_create(parent);
+ if tree.is_null() || saved_tree.is_null() {
+ return Err("Failed to create saveable surfaces trees");
+ }
+ let surfaces = SaveableSurfaces {
+ enabled: true,
+ saved: false,
+ tree,
+ saved_tree,
+ };
+ surfaces.sync_enabled();
+ Ok(surfaces)
+ }
+
+ pub unsafe fn sync_enabled(&self) {
+ ffi::wlr_scene_node_set_enabled(self.tree as *mut ffi::wlr_scene_node, self.enabled && !self.saved);
+ ffi::wlr_scene_node_set_enabled(self.saved_tree as *mut ffi::wlr_scene_node, self.enabled && self.saved);
+ }
+
+ pub unsafe fn set_enabled(&mut self, enabled: bool) {
+ if self.enabled == enabled {
+ return;
+ }
+ self.enabled = enabled;
+ self.sync_enabled();
+ }
+
+ pub unsafe fn save(&mut self) {
+ if self.saved {
+ return;
+ }
+ ffi::river_scene_tree_save_buffers(self.tree, self.saved_tree);
+ self.saved = true;
+ self.sync_enabled();
+ }
+
+ pub unsafe fn drop_saved(&mut self) {
+ if !self.saved {
+ return;
+ }
+ ffi::river_scene_tree_clear_children(self.saved_tree);
+ self.saved = false;
+ self.sync_enabled();
+ }
+}
diff --git a/src/scene_node_data.rs b/src/scene_node_data.rs
new file mode 100644
index 0000000..8aecb78
--- /dev/null
+++ b/src/scene_node_data.rs
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: © 2023 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{WlListener, wl_signal_add, wl_listener_remove};
+use crate::window_manager::{Window, ShellSurface, XwaylandOverrideRedirect};
+use crate::layer_shell::LayerSurface;
+use crate::lock_manager::LockSurface;
+
+#[derive(Clone, Copy)]
+pub enum SceneNodeDataVal {
+ Window(*mut Window),
+ ShellSurface(*mut ShellSurface),
+ LockSurface(*mut LockSurface),
+ LayerSurface(*mut LayerSurface),
+ OverrideRedirect(*mut XwaylandOverrideRedirect),
+}
+
+#[repr(C)]
+pub struct SceneNodeData {
+ pub node: *mut ffi::wlr_scene_node,
+ pub data: SceneNodeDataVal,
+ pub destroy: ffi::wl_listener,
+}
+
+impl SceneNodeData {
+ pub unsafe fn attach(node: *mut ffi::wlr_scene_node, data: SceneNodeDataVal) {
+ let scene_node_data = Box::new(SceneNodeData {
+ node,
+ data,
+ destroy: std::mem::zeroed(),
+ });
+
+ let raw = Box::into_raw(scene_node_data);
+ ffi::river_scene_node_set_data(node, raw as *mut std::ffi::c_void);
+
+ let destroy_listener = &mut (*raw).destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener).notify = Some(handle_destroy);
+ wl_signal_add(ffi::river_scene_node_get_destroy_signal(node), &mut (*raw).destroy);
+ }
+
+ pub unsafe fn from_node(node: *mut ffi::wlr_scene_node) -> Option<&'static SceneNodeData> {
+ let mut n = node;
+ while !n.is_null() {
+ let data_ptr = ffi::river_scene_node_get_data(n) as *mut SceneNodeData;
+ if !data_ptr.is_null() {
+ return Some(&*data_ptr);
+ }
+ let parent_tree = ffi::river_scene_node_get_parent(n);
+ if !parent_tree.is_null() {
+ n = parent_tree as *mut ffi::wlr_scene_node;
+ } else {
+ break;
+ }
+ }
+ None
+ }
+
+ pub unsafe fn from_surface(surface: *mut ffi::wlr_surface) -> Option<&'static SceneNodeData> {
+ if surface.is_null() {
+ return None;
+ }
+ let root_surface = ffi::wlr_surface_get_root_surface(surface);
+ if root_surface.is_null() {
+ return None;
+ }
+ let node_ptr = ffi::river_wlr_surface_get_data(root_surface) as *mut ffi::wlr_scene_node;
+ if !node_ptr.is_null() {
+ Self::from_node(node_ptr)
+ } else {
+ None
+ }
+ }
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let scene_node_data_ptr = crate::container_of!(listener, SceneNodeData, destroy);
+ let mut scene_node_data = Box::from_raw(scene_node_data_ptr);
+ wl_listener_remove(&mut scene_node_data.destroy);
+ if !scene_node_data.node.is_null() {
+ ffi::river_scene_node_set_data(scene_node_data.node, std::ptr::null_mut());
+ }
+}
diff --git a/src/seat.rs b/src/seat.rs
new file mode 100644
index 0000000..8614c8c
--- /dev/null
+++ b/src/seat.rs
@@ -0,0 +1,1036 @@
+use crate::ffi;
+use crate::server::{Server, WlListener, wl_listener_remove, wl_signal_add};
+use crate::cursor::Cursor;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum SeatOpInput {
+ Pointer,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct SeatOp {
+ pub sent_release: bool,
+ pub input: SeatOpInput,
+ pub start_x: i32,
+ pub start_y: i32,
+ pub x: i32,
+ pub y: i32,
+}
+
+#[derive(Clone, Copy, PartialEq)]
+pub enum Focus {
+ None,
+ LayerSurface(*mut ffi::wlr_surface),
+ Window(*mut crate::window::Window),
+ LockSurface(*mut crate::lock_manager::LockSurface),
+ OverrideRedirect(*mut crate::xwayland_override_redirect::XwaylandOverrideRedirect),
+ ShellSurface(*mut crate::shell_surface::ShellSurface),
+}
+
+impl Focus {
+ pub unsafe fn surface(&self) -> *mut ffi::wlr_surface {
+ match *self {
+ Focus::None => std::ptr::null_mut(),
+ Focus::LayerSurface(surface) => surface,
+ Focus::Window(window) => if window.is_null() { std::ptr::null_mut() } else { (*window).root_surface() },
+ Focus::LockSurface(lock_surface) => if lock_surface.is_null() { std::ptr::null_mut() } else { (*(*lock_surface).wlr_lock_surface).surface },
+ Focus::OverrideRedirect(or) => if or.is_null() { std::ptr::null_mut() } else { (*(*or).xsurface).surface },
+ Focus::ShellSurface(shell_surface) => if shell_surface.is_null() { std::ptr::null_mut() } else { (*shell_surface).surface },
+ }
+ }
+}
+
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DragState {
+ None,
+ Pointer,
+ Touch,
+}
+
+pub struct Seat {
+ pub server: *mut Server,
+ pub wlr_seat: *mut ffi::wlr_seat,
+ pub cursor: Cursor,
+ pub focused: Focus,
+ pub relay: crate::input_relay::InputRelay,
+ pub layer_shell: crate::layer_shell::LayerShellSeat,
+ pub xkb_bindings_seat: crate::xkb_bindings::XkbBindingsSeat,
+ pub xkb_bindings: ffi::wl_list,
+ pub pointer_bindings: ffi::wl_list,
+ pub keyboard_groups: ffi::wl_list,
+ pub modifiers_old: u32,
+ pub op: Option<SeatOp>,
+ pub op_release: bool,
+ pub wm_sent_x: i32,
+ pub wm_sent_y: i32,
+
+ pub request_set_cursor: ffi::wl_listener,
+ pub request_set_selection: ffi::wl_listener,
+ pub request_start_drag: ffi::wl_listener,
+ pub start_drag: ffi::wl_listener,
+ pub request_set_primary_selection: ffi::wl_listener,
+
+ pub drag: DragState,
+ pub drag_destroy: ffi::wl_listener,
+
+ pub link: ffi::wl_list,
+ pub link_sent: ffi::wl_list,
+ pub object: *mut ffi::wl_resource,
+ pub destroying: bool,
+}
+
+impl Seat {
+ pub unsafe fn create(server: *mut Server, name: &str) -> Result<*mut Self, &'static str> {
+ let name_c = std::ffi::CString::new(name).unwrap();
+ let wlr_seat = ffi::wlr_seat_create((*server).wl_server, name_c.as_ptr());
+ if wlr_seat.is_null() {
+ return Err("Failed to create wlr_seat");
+ }
+
+ let seat = Box::into_raw(Box::new(Self {
+ server,
+ wlr_seat,
+ cursor: Cursor::default(),
+ focused: Focus::None,
+ relay: std::mem::zeroed(),
+ layer_shell: crate::layer_shell::LayerShellSeat::default(),
+ xkb_bindings_seat: crate::xkb_bindings::XkbBindingsSeat::default(),
+ xkb_bindings: std::mem::zeroed(),
+ pointer_bindings: std::mem::zeroed(),
+ keyboard_groups: std::mem::zeroed(),
+ modifiers_old: 0,
+ op: None,
+ op_release: false,
+ wm_sent_x: 0,
+ wm_sent_y: 0,
+ request_set_cursor: std::mem::zeroed(),
+ request_set_selection: std::mem::zeroed(),
+ request_start_drag: std::mem::zeroed(),
+ start_drag: std::mem::zeroed(),
+ request_set_primary_selection: std::mem::zeroed(),
+ drag: DragState::None,
+ drag_destroy: std::mem::zeroed(),
+ link: std::mem::zeroed(),
+ link_sent: std::mem::zeroed(),
+ object: std::ptr::null_mut(),
+ destroying: false,
+ }));
+
+ ffi::wl_list_init(&mut (*seat).link);
+ ffi::wl_list_init(&mut (*seat).link_sent);
+ ffi::wl_list_init(&mut (*seat).xkb_bindings);
+ ffi::wl_list_init(&mut (*seat).pointer_bindings);
+ ffi::wl_list_init(&mut (*seat).keyboard_groups);
+
+ // Add to input_manager seats
+ let seats_list = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert((*seats_list).prev, &mut (*seat).link as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ ffi::river_wlr_seat_set_data(wlr_seat, seat as *mut _);
+ (*seat).relay.init(seat);
+
+ // Initialize Cursor
+ let output_layout = (*server).om.output_layout;
+ (*seat).cursor.init(seat, output_layout)?;
+
+ // Setup listeners
+ let set_cursor_ptr = &mut (*seat).request_set_cursor as *mut ffi::wl_listener as *mut WlListener;
+ (*set_cursor_ptr).notify = Some(handle_request_set_cursor);
+ wl_signal_add(
+ ffi::river_wlr_seat_get_request_set_cursor_signal(wlr_seat),
+ &mut (*seat).request_set_cursor,
+ );
+
+ let set_sel_ptr = &mut (*seat).request_set_selection as *mut ffi::wl_listener as *mut WlListener;
+ (*set_sel_ptr).notify = Some(handle_request_set_selection);
+ wl_signal_add(
+ ffi::river_wlr_seat_get_request_set_selection_signal(wlr_seat),
+ &mut (*seat).request_set_selection,
+ );
+
+ let start_drag_req_ptr = &mut (*seat).request_start_drag as *mut ffi::wl_listener as *mut WlListener;
+ (*start_drag_req_ptr).notify = Some(handle_request_start_drag);
+ wl_signal_add(
+ ffi::river_wlr_seat_get_request_start_drag_signal(wlr_seat),
+ &mut (*seat).request_start_drag,
+ );
+
+ let start_drag_ptr = &mut (*seat).start_drag as *mut ffi::wl_listener as *mut WlListener;
+ (*start_drag_ptr).notify = Some(handle_start_drag);
+ wl_signal_add(
+ ffi::river_wlr_seat_get_start_drag_signal(wlr_seat),
+ &mut (*seat).start_drag,
+ );
+
+ let set_prim_ptr = &mut (*seat).request_set_primary_selection as *mut ffi::wl_listener as *mut WlListener;
+ (*set_prim_ptr).notify = Some(handle_request_set_primary_selection);
+ wl_signal_add(
+ ffi::river_wlr_seat_get_request_set_primary_selection_signal(wlr_seat),
+ &mut (*seat).request_set_primary_selection,
+ );
+
+ (*seat).update_capabilities();
+
+ Ok(seat)
+ }
+
+ pub unsafe fn destroy(seat: *mut Self) {
+ (*seat).layer_shell.make_inert();
+ (*seat).xkb_bindings_seat.make_inert();
+
+ let bindings_head = &mut (*seat).xkb_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*bindings_head).next;
+ while curr != bindings_head {
+ let next = (*curr).next;
+ let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
+ crate::xkb_bindings::XkbBinding::destroy(binding);
+ curr = next;
+ }
+
+ let ptr_bindings_head = &mut (*seat).pointer_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr_ptr = (*ptr_bindings_head).next;
+ while curr_ptr != ptr_bindings_head {
+ let next = (*curr_ptr).next;
+ let binding = crate::container_of!(curr_ptr, crate::pointer_binding::PointerBinding, link);
+ crate::pointer_binding::PointerBinding::destroy(binding);
+ curr_ptr = next;
+ }
+
+ (*seat).cursor.deinit();
+
+ // Verify keyboard_groups is empty
+ let groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+ assert_eq!((*groups_head).next, groups_head);
+
+ crate::server::wl_list_remove(&mut (*seat).link as *mut ffi::wl_list as *mut crate::server::WlList);
+ crate::server::wl_list_remove(&mut (*seat).link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
+
+ wl_listener_remove(&mut (*seat).request_set_cursor);
+ wl_listener_remove(&mut (*seat).request_set_selection);
+ wl_listener_remove(&mut (*seat).request_start_drag);
+ wl_listener_remove(&mut (*seat).start_drag);
+ wl_listener_remove(&mut (*seat).request_set_primary_selection);
+
+ if (*seat).drag != DragState::None {
+ wl_listener_remove(&mut (*seat).drag_destroy);
+ }
+
+ ffi::wlr_seat_destroy((*seat).wlr_seat);
+ let _boxed = Box::from_raw(seat);
+ }
+
+ pub unsafe fn attach_device(&mut self, device: *mut crate::input_device::InputDevice) {
+ (*device).seat = self;
+ let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
+ match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD => {
+ let keyboard = ffi::river_wlr_input_device_get_data((*device).wlr_device) as *mut crate::keyboard::Keyboard;
+ if !keyboard.is_null() {
+ (*keyboard).set_group();
+ if !(*keyboard).group.is_null() {
+ ffi::wlr_seat_set_keyboard(self.wlr_seat, &mut (*(*keyboard).group).wlr_keyboard);
+ let focused_surface = ffi::river_wlr_seat_get_keyboard_focused_surface(self.wlr_seat);
+ if !focused_surface.is_null() {
+ self.keyboard_notify_enter(focused_surface);
+ }
+ }
+ }
+ }
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => {
+ ffi::wlr_cursor_attach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
+ }
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH | ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
+ ffi::wlr_cursor_attach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
+ if !(*device).config.map_to_output.is_null() {
+ ffi::wlr_cursor_map_input_to_output(self.cursor.wlr_cursor, (*device).wlr_device, (*device).config.map_to_output);
+ }
+ ffi::wlr_cursor_map_input_to_region(self.cursor.wlr_cursor, (*device).wlr_device, &mut (*device).config.map_to_rectangle);
+ }
+ _ => {}
+ }
+ self.update_capabilities();
+ }
+
+ pub unsafe fn detach_device(&mut self, device: *mut crate::input_device::InputDevice) {
+ ffi::wlr_cursor_detach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
+
+ let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
+ if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
+ let keyboard = ffi::river_wlr_input_device_get_data((*device).wlr_device) as *mut crate::keyboard::Keyboard;
+ if !keyboard.is_null() {
+ if !(*keyboard).group.is_null() {
+ let keys: Vec<u32> = (*keyboard).pressed.iter().cloned().collect();
+ crate::server::wl_list_remove(&mut (*keyboard).group_link as *mut ffi::wl_list as *mut crate::server::WlList);
+ (*(*keyboard).group).unref(&keys);
+ (*keyboard).group = std::ptr::null_mut();
+ }
+ }
+ }
+ self.update_capabilities();
+ }
+
+ pub unsafe fn handle_activity(&mut self) {
+ let notifier = (*self.server).input_manager.idle_notifier;
+ if !notifier.is_null() {
+ ffi::wlr_idle_notifier_v1_notify_activity(notifier, self.wlr_seat);
+ }
+ }
+
+ pub unsafe fn update_capabilities(&mut self) {
+ let caps = ffi::wl_seat_capability_WL_SEAT_CAPABILITY_POINTER
+ | ffi::wl_seat_capability_WL_SEAT_CAPABILITY_KEYBOARD;
+ ffi::wlr_seat_set_capabilities(self.wlr_seat, caps);
+ }
+
+ pub unsafe fn focus(&mut self, new_focus: Focus) {
+ if self.focused == new_focus {
+ return;
+ }
+
+ match self.focused {
+ Focus::None => {}
+ Focus::LayerSurface(_) | Focus::Window(_) | Focus::LockSurface(_) | Focus::OverrideRedirect(_) | Focus::ShellSurface(_) => {
+ ffi::wlr_seat_keyboard_notify_clear_focus(self.wlr_seat);
+ let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(self.wlr_seat);
+ if !focused_client.is_null() {
+ ffi::wlr_seat_pointer_notify_clear_focus(self.wlr_seat);
+ }
+ }
+ }
+
+ self.focused = new_focus;
+
+ match new_focus {
+ Focus::None => {}
+ Focus::LayerSurface(surface) => {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ modifiers,
+ );
+ } else {
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+
+ let lx = self.cursor.x();
+ let ly = self.cursor.y();
+ let server = self.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if result.surface == surface {
+ ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
+ }
+ }
+ }
+ Focus::Window(window) => {
+ // Focus root surface of window
+ let surface = (*window).root_surface();
+ if !surface.is_null() {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ modifiers,
+ );
+ } else {
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+
+ let lx = self.cursor.x();
+ let ly = self.cursor.y();
+ let server = self.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if result.surface == surface {
+ ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
+ }
+ }
+ }
+ }
+ Focus::LockSurface(lock_surface) => {
+ let surface = (*(*lock_surface).wlr_lock_surface).surface;
+ if !surface.is_null() {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ modifiers,
+ );
+ } else {
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+
+ let lx = self.cursor.x();
+ let ly = self.cursor.y();
+ let server = self.server;
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if result.surface == surface {
+ ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
+ }
+ }
+ }
+ }
+ Focus::OverrideRedirect(or) => {
+ let surface = (*(*or).xsurface).surface;
+ if !surface.is_null() {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ modifiers,
+ );
+ } else {
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+ }
+ }
+ Focus::ShellSurface(shell_surface) => {
+ let surface = (*shell_surface).surface;
+ if !surface.is_null() {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ modifiers,
+ );
+ } else {
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+ }
+ }
+ }
+ let target_surface = new_focus.surface();
+ self.relay.focus(target_surface);
+ }
+
+ pub unsafe fn keyboard_notify_enter(&mut self, wlr_surface: *mut ffi::wlr_surface) {
+ let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if !kbd.is_null() {
+ let group_ptr = ffi::river_wlr_keyboard_get_data(kbd) as *mut crate::keyboard_group::KeyboardGroup;
+ if !group_ptr.is_null() {
+ let mut buffer = [0u32; 32];
+ let mut count = 0;
+ for &keycode in (*group_ptr).pressed.keys() {
+ if count >= 32 {
+ break;
+ }
+ buffer[count] = keycode + 8;
+ count += 1;
+ }
+ let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ wlr_surface,
+ buffer.as_mut_ptr(),
+ count,
+ modifiers,
+ );
+ return;
+ }
+ }
+ ffi::wlr_seat_keyboard_notify_enter(
+ self.wlr_seat,
+ wlr_surface,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ );
+ }
+
+ pub unsafe fn keyboard_enter_or_leave(&mut self, target_surface: *mut ffi::wlr_surface) {
+ if !target_surface.is_null() {
+ self.keyboard_notify_enter(target_surface);
+ } else {
+ ffi::wlr_seat_keyboard_notify_clear_focus(self.wlr_seat);
+ }
+ self.relay.focus(target_surface);
+ }
+
+ pub unsafe fn manage_start(&mut self) {
+ if self.destroying {
+ Self::destroy(self);
+ return;
+ }
+
+ self.layer_shell.manage_start();
+
+ let wm_v1 = (*self.server).wm.object;
+ if !wm_v1.is_null() {
+ let new = self.object.is_null();
+ if new {
+ let client = ffi::wl_resource_get_client(wm_v1);
+ let version = ffi::wl_resource_get_version(wm_v1);
+ let seat_v1 = ffi::wl_resource_create(client, &ffi::river_seat_v1_interface, version, 0);
+ if seat_v1.is_null() {
+ log::error!("out of memory creating river_seat_v1");
+ return;
+ }
+ self.object = seat_v1;
+
+ ffi::wl_resource_set_implementation(
+ seat_v1,
+ &SEAT_INTERFACE as *const _ as *const _,
+ self as *mut Seat as *mut _,
+ Some(handle_destroy_resource),
+ );
+
+ ffi::wl_resource_post_event(wm_v1, 8, seat_v1); // river_window_manager_v1.seat
+
+ crate::server::wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
+ let sent_seats = &mut (*self.server).wm.sent.seats as *mut ffi::wl_list as *mut crate::server::WlList;
+ crate::server::wl_list_insert((*sent_seats).prev, &mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
+ }
+
+ if new {
+ let seat_v1 = self.object;
+ let client = ffi::wl_resource_get_client(seat_v1);
+ let wl_seat_name = ffi::wl_global_get_name(ffi::river_wlr_seat_get_global(self.wlr_seat), client);
+ ffi::wl_resource_post_event(seat_v1, 1, wl_seat_name); // river_seat_v1.wl_seat
+ }
+
+ self.xkb_bindings_seat.manage_start();
+
+ // Dispatch xkb binding events
+ let bindings_head = &mut self.xkb_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*bindings_head).next;
+ while curr != bindings_head {
+ let next = (*curr).next;
+ let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
+ match (*binding).wm_scheduled.state_change {
+ crate::xkb_bindings::XkbBindingStateChange::None => {},
+ crate::xkb_bindings::XkbBindingStateChange::Pressed => {
+ assert!(!(*binding).sent_pressed);
+ (*binding).sent_pressed = true;
+ ffi::wl_resource_post_event((*binding).object, 0);
+ },
+ crate::xkb_bindings::XkbBindingStateChange::StopRepeat => {
+ assert!((*binding).sent_pressed);
+ if ffi::wl_resource_get_version((*binding).object) >= 2 {
+ ffi::wl_resource_post_event((*binding).object, 2);
+ }
+ },
+ crate::xkb_bindings::XkbBindingStateChange::Released => {
+ assert!((*binding).sent_pressed);
+ (*binding).sent_pressed = false;
+ ffi::wl_resource_post_event((*binding).object, 1);
+ },
+ }
+ (*binding).wm_scheduled.state_change = crate::xkb_bindings::XkbBindingStateChange::None;
+ curr = next;
+ }
+
+ // Dispatch pointer binding events
+ let ptr_bindings_head = &mut self.pointer_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr_ptr = (*ptr_bindings_head).next;
+ while curr_ptr != ptr_bindings_head {
+ let next = (*curr_ptr).next;
+ let binding = crate::container_of!(curr_ptr, crate::pointer_binding::PointerBinding, link);
+ match (*binding).wm_scheduled.state_change {
+ crate::pointer_binding::PointerBindingStateChange::None => {}
+ crate::pointer_binding::PointerBindingStateChange::Pressed => {
+ assert!(!(*binding).sent_pressed);
+ (*binding).sent_pressed = true;
+ ffi::wl_resource_post_event((*binding).object, 0); // pressed
+ }
+ crate::pointer_binding::PointerBindingStateChange::Released => {
+ assert!((*binding).sent_pressed);
+ (*binding).sent_pressed = false;
+ ffi::wl_resource_post_event((*binding).object, 1); // released
+ }
+ }
+ (*binding).wm_scheduled.state_change = crate::pointer_binding::PointerBindingStateChange::None;
+ curr_ptr = next;
+ }
+
+ // Dispatch pointer operation events
+ if let Some(ref mut op) = self.op {
+ let dx = op.x - op.start_x;
+ let dy = op.y - op.start_y;
+ ffi::wl_resource_post_event(self.object, 6, dx, dy); // op_delta
+
+ if self.op_release && !op.sent_release {
+ ffi::wl_resource_post_event(self.object, 7); // op_release
+ self.op_release = false;
+ op.sent_release = true;
+ }
+ }
+
+ // Dispatch pointer position event
+ if ffi::wl_resource_get_version(self.object) >= 2 {
+ let x = (*self.cursor.wlr_cursor).x as i32;
+ let y = (*self.cursor.wlr_cursor).y as i32;
+ if x != self.wm_sent_x || y != self.wm_sent_y {
+ ffi::wl_resource_post_event(self.object, 8, x, y); // pointer_position
+ self.wm_sent_x = x;
+ self.wm_sent_y = y;
+ }
+ }
+ }
+ }
+
+ pub unsafe fn manage_finish(&mut self) {
+ self.xkb_bindings_seat.manage_finish();
+ }
+
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 0); // river_seat_v1.removed
+ ffi::wl_resource_set_implementation(
+ self.object,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+ self.object = std::ptr::null_mut();
+ (*self.server).wm.dirty_windowing();
+ }
+ self.layer_shell.make_inert();
+ self.xkb_bindings_seat.make_inert();
+ }
+
+ pub unsafe fn match_xkb_binding(
+ &self,
+ keycode: u32,
+ wlr_keyboard: *mut ffi::wlr_keyboard,
+ ) -> Option<*mut crate::xkb_bindings::XkbBinding> {
+ let xkb_state = (*wlr_keyboard).xkb_state;
+ if xkb_state.is_null() {
+ return None;
+ }
+
+ let modifiers = ffi::wlr_keyboard_get_modifiers(wlr_keyboard);
+
+ let bindings_head = &self.xkb_bindings as *const ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*bindings_head).next;
+ let mut found: Option<*mut crate::xkb_bindings::XkbBinding> = None;
+
+ while curr != bindings_head {
+ let next = (*curr).next;
+ let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
+ if (*binding).match_keycode(keycode, modifiers, xkb_state, false) {
+ if found.is_none() {
+ found = Some(binding);
+ } else {
+ log::debug!("already found a matching xkb_binding, ignoring additional match");
+ }
+ }
+ curr = next;
+ }
+
+ if found.is_some() {
+ return found;
+ }
+
+ curr = (*bindings_head).next;
+ while curr != bindings_head {
+ let next = (*curr).next;
+ let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
+ if (*binding).match_keycode(keycode, modifiers, xkb_state, true) {
+ if found.is_none() {
+ found = Some(binding);
+ } else {
+ log::debug!("already found a matching xkb_binding, ignoring additional match");
+ }
+ }
+ curr = next;
+ }
+
+ found
+ }
+
+ pub unsafe fn match_pointer_binding(
+ &self,
+ button: u32,
+ ) -> Option<*mut crate::pointer_binding::PointerBinding> {
+ let wlr_keyboard = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
+ if wlr_keyboard.is_null() {
+ return None;
+ }
+ let modifiers = ffi::wlr_keyboard_get_modifiers(wlr_keyboard);
+
+ let bindings_head = &self.pointer_bindings as *const ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*bindings_head).next;
+ let mut found: Option<*mut crate::pointer_binding::PointerBinding> = None;
+
+ while curr != bindings_head {
+ let next = (*curr).next;
+ let binding = crate::container_of!(curr, crate::pointer_binding::PointerBinding, link);
+ if (*binding).match_binding(button, modifiers) {
+ if found.is_none() {
+ found = Some(binding);
+ } else {
+ log::debug!("already found a matching pointer binding, ignoring additional match");
+ }
+ }
+ curr = next;
+ }
+ found
+ }
+
+ pub unsafe fn op_update(&mut self, x: i32, y: i32) {
+ if let Some(ref mut op) = self.op {
+ op.x = x;
+ op.y = y;
+ (*self.server).wm.dirty_windowing();
+ }
+ }
+
+ pub unsafe fn op_end(&mut self) {
+ if let Some(op) = self.op.take() {
+ log::debug!("end seat op");
+ match op.input {
+ SeatOpInput::Pointer => {
+ self.cursor.op_end_pointer();
+ }
+ }
+ }
+ }
+}
+
+unsafe extern "C" fn handle_request_set_cursor(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, request_set_cursor);
+ let event = data as *mut ffi::wlr_seat_pointer_request_set_cursor_event;
+
+ let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
+ if focused_client == (*event).seat_client {
+ ffi::wlr_cursor_set_surface(
+ seat.cursor.wlr_cursor,
+ (*event).surface,
+ (*event).hotspot_x,
+ (*event).hotspot_y,
+ );
+ }
+}
+
+unsafe extern "C" fn handle_request_set_selection(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, request_set_selection);
+ let event = data as *mut ffi::wlr_seat_request_set_selection_event;
+ ffi::wlr_seat_set_selection(seat.wlr_seat, (*event).source, (*event).serial);
+}
+
+unsafe extern "C" fn handle_request_start_drag(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, request_start_drag);
+ let event = data as *mut ffi::wlr_seat_request_start_drag_event;
+
+ assert!(seat.drag == DragState::None);
+
+ if ffi::wlr_seat_validate_pointer_grab_serial(seat.wlr_seat, (*event).origin, (*event).serial) {
+ ffi::wlr_seat_start_pointer_drag(seat.wlr_seat, (*event).drag, (*event).serial);
+ return;
+ }
+
+ let mut point: *mut ffi::wlr_touch_point = std::ptr::null_mut();
+ if ffi::wlr_seat_validate_touch_grab_serial(seat.wlr_seat, (*event).origin, (*event).serial, &mut point) {
+ ffi::wlr_seat_start_touch_drag(seat.wlr_seat, (*event).drag, (*event).serial, point);
+ return;
+ }
+
+ let source = ffi::river_wlr_drag_get_source((*event).drag);
+ if !source.is_null() {
+ ffi::wlr_data_source_destroy(source);
+ }
+}
+
+unsafe extern "C" fn handle_start_drag(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, start_drag);
+ let wlr_drag = data as *mut ffi::wlr_drag;
+
+ assert!(seat.drag == DragState::None);
+ let grab_type = ffi::river_wlr_drag_get_grab_type(wlr_drag);
+ match grab_type {
+ ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_POINTER => {
+ seat.drag = DragState::Pointer;
+ }
+ ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_TOUCH => {
+ seat.drag = DragState::Touch;
+ }
+ _ => {}
+ }
+
+ let drag_destroy_ptr = &mut seat.drag_destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*drag_destroy_ptr).notify = Some(handle_drag_destroy);
+ wl_signal_add(
+ ffi::river_wlr_drag_get_destroy_signal(wlr_drag),
+ &mut seat.drag_destroy,
+ );
+
+ let wlr_drag_icon = ffi::river_wlr_drag_get_icon(wlr_drag);
+ if !wlr_drag_icon.is_null() {
+ if let Err(err) = crate::drag_icon::DragIcon::create(wlr_drag_icon, &mut seat.cursor) {
+ log::error!("Failed to create drag icon: {}", err);
+ let seat_client = ffi::river_wlr_drag_get_seat_client(wlr_drag);
+ if !seat_client.is_null() {
+ let client = ffi::river_wlr_seat_client_get_client(seat_client);
+ if !client.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ }
+ }
+ }
+ }
+}
+
+unsafe extern "C" fn handle_drag_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, drag_destroy);
+ wl_listener_remove(&mut seat.drag_destroy);
+
+ match seat.drag {
+ DragState::None => unreachable!(),
+ DragState::Pointer => {
+ seat.cursor.update_state();
+ }
+ DragState::Touch => {}
+ }
+ seat.drag = DragState::None;
+}
+
+unsafe extern "C" fn handle_request_set_primary_selection(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let seat = &mut *crate::container_of!(listener, Seat, request_set_primary_selection);
+ let event = data as *mut ffi::wlr_seat_request_set_primary_selection_event;
+ ffi::wlr_seat_set_primary_selection(seat.wlr_seat, (*event).source, (*event).serial);
+}
+
+unsafe extern "C" fn seat_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn seat_focus_window(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ window_resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*(*seat).server).wm.ensure_windowing() {
+ return;
+ }
+ if window_resource.is_null() {
+ (*seat).focus(Focus::None);
+ return;
+ }
+ let window = ffi::wl_resource_get_user_data(window_resource) as *mut crate::window::Window;
+ if !window.is_null() {
+ (*seat).focus(Focus::Window(window));
+ }
+}
+
+unsafe extern "C" fn seat_focus_shell_surface(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ shell_surface_resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*(*seat).server).wm.ensure_windowing() {
+ return;
+ }
+ if shell_surface_resource.is_null() {
+ (*seat).focus(Focus::None);
+ return;
+ }
+ let shell_surface = ffi::wl_resource_get_user_data(shell_surface_resource) as *mut crate::shell_surface::ShellSurface;
+ if !shell_surface.is_null() {
+ (*seat).focus(Focus::ShellSurface(shell_surface));
+ }
+}
+
+unsafe extern "C" fn seat_clear_focus(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if !seat.is_null() {
+ if (*(*seat).server).wm.ensure_windowing() {
+ (*seat).focus(Focus::None);
+ }
+ }
+}
+
+unsafe extern "C" fn seat_op_start_pointer(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*(*seat).server).wm.ensure_windowing() {
+ return;
+ }
+ if (*seat).op.is_none() {
+ log::debug!("start seat op pointer");
+ let cursor_x = (*(*seat).cursor.wlr_cursor).x;
+ let cursor_y = (*(*seat).cursor.wlr_cursor).y;
+ (*seat).op = Some(SeatOp {
+ sent_release: false,
+ input: SeatOpInput::Pointer,
+ start_x: cursor_x as i32,
+ start_y: cursor_y as i32,
+ x: cursor_x as i32,
+ y: cursor_y as i32,
+ });
+ (*seat).cursor.op_start_pointer();
+ }
+}
+
+unsafe extern "C" fn seat_op_end(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*(*seat).server).wm.ensure_windowing() {
+ return;
+ }
+ (*seat).op_end();
+}
+
+unsafe extern "C" fn seat_get_pointer_binding(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ button: u32,
+ modifiers: u32,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ let version = ffi::wl_resource_get_version(resource) as u32;
+ if let Err(err) = crate::pointer_binding::PointerBinding::create(
+ seat,
+ client,
+ version,
+ id,
+ button,
+ modifiers,
+ ) {
+ log::error!("failed to create pointer binding: {}", err);
+ ffi::wl_client_post_no_memory(client);
+ }
+}
+
+unsafe extern "C" fn seat_set_xcursor_theme(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ name: *const ::std::os::raw::c_char,
+ size: u32,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if let Err(err) = (*seat).cursor.set_theme(name, size) {
+ log::error!("failed to set xcursor theme: {}", err);
+ ffi::wl_client_post_no_memory(client);
+ }
+}
+
+unsafe extern "C" fn seat_pointer_warp(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ x: i32,
+ y: i32,
+) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+ if !(*(*seat).server).wm.ensure_windowing() {
+ return;
+ }
+ let cursor = &mut (*seat).cursor;
+ ffi::wlr_cursor_warp_absolute(cursor.wlr_cursor, std::ptr::null_mut(), x as f64, y as f64);
+}
+
+static SEAT_INTERFACE: ffi::river_seat_v1_interface = ffi::river_seat_v1_interface {
+ destroy: Some(seat_destroy),
+ focus_window: Some(seat_focus_window),
+ focus_shell_surface: Some(seat_focus_shell_surface),
+ clear_focus: Some(seat_clear_focus),
+ op_start_pointer: Some(seat_op_start_pointer),
+ op_end: Some(seat_op_end),
+ get_pointer_binding: Some(seat_get_pointer_binding),
+ set_xcursor_theme: Some(seat_set_xcursor_theme),
+ pointer_warp: Some(seat_pointer_warp),
+};
+
+unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
+ let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
+ if !seat.is_null() {
+ (*seat).object = std::ptr::null_mut();
+ }
+}
diff --git a/src/server.rs b/src/server.rs
new file mode 100644
index 0000000..f284aac
--- /dev/null
+++ b/src/server.rs
@@ -0,0 +1,697 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use std::ptr;
+
+use crate::window_manager::WindowManager;
+use crate::xkb_bindings::XkbBindings;
+use crate::layer_shell::LayerShell;
+use crate::scene::Scene;
+use crate::output_manager::OutputManager;
+use crate::input_manager::InputManager;
+use crate::libinput_config::LibinputConfig;
+use crate::xkb_config::XkbConfig;
+use crate::idle_inhibit_manager::IdleInhibitManager;
+use crate::lock_manager::LockManager;
+
+// Helper macro equivalent to @fieldParentPtr in Zig
+#[macro_export]
+macro_rules! container_of {
+ ($ptr:expr, $container:path, $field:ident) => {{
+ let offset = {
+ let dummy = std::mem::MaybeUninit::<$container>::uninit();
+ let dummy_ptr = dummy.as_ptr();
+ let field_ptr = std::ptr::addr_of!((*dummy_ptr).$field);
+ (field_ptr as usize).wrapping_sub(dummy_ptr as usize)
+ };
+ ((($ptr as *const _) as usize).wrapping_sub(offset)) as *mut $container
+ }};
+}
+
+// Custom non-opaque layouts for FFI casting
+#[repr(C)]
+pub struct WlList {
+ pub prev: *mut WlList,
+ pub next: *mut WlList,
+}
+
+#[repr(C)]
+pub struct WlListener {
+ pub link: WlList,
+ pub notify: Option<unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void)>,
+}
+
+#[repr(C)]
+pub struct WlrRendererEvents {
+ pub destroy: ffi::wl_signal,
+ pub lost: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrRendererFeatures {
+ pub input_color_transform: bool,
+ pub output_color_transform: bool,
+ pub timeline: bool,
+}
+
+#[repr(C)]
+pub struct WlrRenderer {
+ pub render_buffer_caps: u32,
+ pub color_encodings: u32,
+ pub events: WlrRendererEvents,
+ pub features: WlrRendererFeatures,
+}
+
+#[repr(C)]
+pub struct WlrBackendFeatures {
+ pub timeline: bool,
+}
+
+#[repr(C)]
+pub struct WlrBackendEvents {
+ pub destroy: ffi::wl_signal,
+ pub new_input: ffi::wl_signal,
+ pub new_output: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrBackend {
+ pub impl_: *const std::ffi::c_void,
+ pub buffer_caps: u32,
+ pub features: WlrBackendFeatures,
+ pub events: WlrBackendEvents,
+}
+
+#[repr(C)]
+pub struct WlrXdgShellEvents {
+ pub new_surface: ffi::wl_signal,
+ pub new_toplevel: ffi::wl_signal,
+ pub new_popup: ffi::wl_signal,
+ pub destroy: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrXdgShell {
+ pub global: *mut ffi::wl_global,
+ pub version: u32,
+ pub clients: ffi::wl_list,
+ pub popup_grabs: ffi::wl_list,
+ pub ping_timeout: u32,
+ pub events: WlrXdgShellEvents,
+}
+
+#[repr(C)]
+pub struct WlrXdgDecorationManagerV1Events {
+ pub new_toplevel_decoration: ffi::wl_signal,
+ pub destroy: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrXdgDecorationManagerV1 {
+ pub global: *mut ffi::wl_global,
+ pub decorations: ffi::wl_list,
+ pub events: WlrXdgDecorationManagerV1Events,
+}
+
+#[repr(C)]
+pub struct WlrXdgActivationV1Events {
+ pub destroy: ffi::wl_signal,
+ pub request_activate: ffi::wl_signal,
+ pub new_token: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrXdgActivationV1 {
+ pub global: *mut ffi::wl_global,
+ pub token_timeout_msec: u32,
+ pub tokens: ffi::wl_list,
+ pub events: WlrXdgActivationV1Events,
+}
+
+#[repr(C)]
+pub struct WlrCursorShapeManagerV1Events {
+ pub request_set_shape: ffi::wl_signal,
+ pub destroy: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrCursorShapeManagerV1 {
+ pub global: *mut ffi::wl_global,
+ pub events: WlrCursorShapeManagerV1Events,
+}
+
+#[repr(C)]
+pub struct WlrExtForeignToplevelImageCaptureSourceManagerV1Events {
+ pub destroy: ffi::wl_signal,
+ pub new_request: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrExtForeignToplevelImageCaptureSourceManagerV1 {
+ pub global: *mut ffi::wl_global,
+ pub events: WlrExtForeignToplevelImageCaptureSourceManagerV1Events,
+}
+
+#[repr(C)]
+pub struct WlrXwaylandEvents {
+ pub destroy: ffi::wl_signal,
+ pub ready: ffi::wl_signal,
+ pub new_surface: ffi::wl_signal,
+ pub remove_startup_info: ffi::wl_signal,
+}
+
+#[repr(C)]
+pub struct WlrXwayland {
+ pub server: *mut std::ffi::c_void,
+ pub own_server: bool,
+ pub xwm: *mut std::ffi::c_void,
+ pub shell_v1: *mut std::ffi::c_void,
+ pub display_name: *const std::os::raw::c_char,
+ pub wl_display: *mut ffi::wl_display,
+ pub compositor: *mut ffi::wlr_compositor,
+ pub seat: *mut std::ffi::c_void,
+ pub events: WlrXwaylandEvents,
+}
+
+// Wayland list manipulation utilities
+pub unsafe fn wl_list_insert(list: *mut WlList, elm: *mut WlList) {
+ (*elm).prev = list;
+ (*elm).next = (*list).next;
+ (*(*list).next).prev = elm;
+ (*list).next = elm;
+}
+
+pub unsafe fn wl_list_remove(elm: *mut WlList) {
+ (*(*elm).next).prev = (*elm).prev;
+ (*(*elm).prev).next = (*elm).next;
+ (*elm).next = std::ptr::null_mut();
+ (*elm).prev = std::ptr::null_mut();
+}
+
+pub unsafe fn wl_signal_add(signal: *mut ffi::wl_signal, listener: *mut ffi::wl_listener) {
+ let sig_list = &mut (*signal).listener_list as *mut ffi::wl_list as *mut WlList;
+ let listener_custom = listener as *mut WlListener;
+ wl_list_insert((*sig_list).prev, &mut (*listener_custom).link);
+}
+
+pub unsafe fn wl_listener_remove(listener: *mut ffi::wl_listener) {
+ let listener_custom = listener as *mut WlListener;
+ wl_list_remove(&mut (*listener_custom).link);
+}
+
+
+pub struct Server {
+ pub wl_server: *mut ffi::wl_display,
+ pub sigint_source: *mut ffi::wl_event_source,
+ pub sigterm_source: *mut ffi::wl_event_source,
+ pub fixes: *mut ffi::wlr_fixes,
+ pub backend: *mut ffi::wlr_backend,
+ pub session: *mut ffi::wlr_session,
+ pub renderer: *mut ffi::wlr_renderer,
+ pub allocator: *mut ffi::wlr_allocator,
+ pub gpu_reset_recover: *mut ffi::wl_event_source,
+ pub security_context_manager: *mut ffi::wlr_security_context_manager_v1,
+ pub shm: *mut ffi::wlr_shm,
+ pub linux_dmabuf: *mut ffi::wlr_linux_dmabuf_v1,
+ pub linux_drm_syncobj_manager: *mut ffi::wlr_linux_drm_syncobj_manager_v1,
+ pub single_pixel_buffer_manager: *mut ffi::wlr_single_pixel_buffer_manager_v1,
+ pub alpha_modifier: *mut ffi::wlr_alpha_modifier_v1,
+ pub color_manager: *mut ffi::wlr_color_manager_v1,
+ pub color_representation_manager: *mut ffi::wlr_color_representation_manager_v1,
+ pub viewporter: *mut ffi::wlr_viewporter,
+ pub fractional_scale_manager: *mut ffi::wlr_fractional_scale_manager_v1,
+ pub compositor: *mut ffi::wlr_compositor,
+ pub subcompositor: *mut ffi::wlr_subcompositor,
+ pub cursor_shape_manager: *mut ffi::wlr_cursor_shape_manager_v1,
+ pub xdg_shell: *mut ffi::wlr_xdg_shell,
+ pub xdg_decoration_manager: *mut ffi::wlr_xdg_decoration_manager_v1,
+ pub xdg_activation: *mut ffi::wlr_xdg_activation_v1,
+ pub xdg_foreign_registry: *mut ffi::wlr_xdg_foreign_registry,
+ pub xdg_foreign_v2: *mut ffi::wlr_xdg_foreign_v2,
+ pub data_device_manager: *mut ffi::wlr_data_device_manager,
+ pub primary_selection_manager: *mut ffi::wlr_primary_selection_v1_device_manager,
+ pub data_control_manager: *mut ffi::wlr_ext_data_control_manager_v1,
+ pub wlr_data_control_manager: *mut ffi::wlr_data_control_manager_v1,
+ pub export_dmabuf_manager: *mut ffi::wlr_export_dmabuf_manager_v1,
+ pub screencopy_manager: *mut ffi::wlr_screencopy_manager_v1,
+ pub image_copy_capture_manager: *mut ffi::wlr_ext_image_copy_capture_manager_v1,
+ pub output_image_capture_source_manager: *mut ffi::wlr_ext_output_image_capture_source_manager_v1,
+ pub wlr_foreign_toplevel_manager: *mut ffi::wlr_foreign_toplevel_manager_v1,
+ pub foreign_toplevel_list: *mut ffi::wlr_ext_foreign_toplevel_list_v1,
+ pub toplevel_capture_source_manager: *mut ffi::wlr_ext_foreign_toplevel_image_capture_source_manager_v1,
+ pub tearing_control_manager: *mut ffi::wlr_tearing_control_manager_v1,
+
+ pub xwayland: *mut ffi::wlr_xwayland,
+
+ // Subcomponents
+ pub wm: WindowManager,
+ pub xkb_bindings: XkbBindings,
+ pub layer_shell: LayerShell,
+ pub scene: Scene,
+ pub om: OutputManager,
+ pub input_manager: InputManager,
+ pub libinput_config: LibinputConfig,
+ pub xkb_config: XkbConfig,
+ pub idle_inhibit_manager: IdleInhibitManager,
+ pub lock_manager: LockManager,
+
+ // Event listeners
+ pub renderer_lost: ffi::wl_listener,
+ pub new_xdg_toplevel: ffi::wl_listener,
+ pub new_toplevel_decoration: ffi::wl_listener,
+ pub request_activate: ffi::wl_listener,
+ pub request_set_cursor_shape: ffi::wl_listener,
+ pub toplevel_capture_request: ffi::wl_listener,
+ pub new_xsurface: ffi::wl_listener,
+}
+
+unsafe extern "C" fn terminate(_signum: std::os::raw::c_int, data: *mut std::ffi::c_void) -> std::os::raw::c_int {
+ let wl_server = data as *mut ffi::wl_display;
+ ffi::wl_display_terminate(wl_server);
+ 0
+}
+
+unsafe extern "C" fn handle_renderer_lost(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let _server = container_of!(listener, Server, renderer_lost);
+ log::info!("received GPU reset event");
+}
+
+unsafe extern "C" fn handle_new_xdg_toplevel(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let server = container_of!(listener, Server, new_xdg_toplevel);
+ let xdg_toplevel = data as *mut ffi::wlr_xdg_toplevel;
+ log::info!("new xdg toplevel surface");
+ if let Err(e) = crate::xdg_toplevel::XdgToplevel::create(xdg_toplevel, server) {
+ log::error!("Failed to create xdg toplevel: {}", e);
+ let client = ffi::wl_resource_get_client((*xdg_toplevel).resource);
+ ffi::wl_client_post_no_memory(client);
+ }
+}
+
+unsafe extern "C" fn handle_new_toplevel_decoration(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let _server = container_of!(listener, Server, new_toplevel_decoration);
+ let decoration = data as *mut ffi::wlr_xdg_toplevel_decoration_v1;
+ log::info!("new toplevel decoration");
+ crate::xdg_toplevel::XdgDecoration::init(decoration);
+}
+
+unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let _server = container_of!(listener, Server, request_activate);
+ log::info!("xdg activation request");
+}
+
+unsafe extern "C" fn handle_request_set_cursor_shape(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let _server = container_of!(listener, Server, request_set_cursor_shape);
+ log::info!("request set cursor shape");
+}
+
+unsafe extern "C" fn handle_toplevel_capture_request(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let _server = container_of!(listener, Server, toplevel_capture_request);
+ log::info!("toplevel capture request");
+}
+
+unsafe extern "C" fn handle_new_xwayland_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+ let server = container_of!(listener, Server, new_xsurface);
+ let xsurface = data as *mut ffi::wlr_xwayland_surface;
+ log::info!("new xwayland surface");
+
+ if (*xsurface).override_redirect {
+ if let Err(e) = crate::xwayland_override_redirect::XwaylandOverrideRedirect::create(xsurface, server) {
+ log::error!("Failed to create xwayland override redirect surface: {}", e);
+ }
+ } else {
+ if let Err(e) = crate::xwayland_window::XwaylandWindow::create(xsurface, server) {
+ log::error!("Failed to create xwayland window surface: {}", e);
+ }
+ }
+}
+
+impl Server {
+ pub fn init(&mut self, runtime_xwayland: bool) -> Result<(), &'static str> {
+ unsafe {
+ let wl_server = ffi::wl_display_create();
+ if wl_server.is_null() {
+ return Err("Failed to create wayland server");
+ }
+ self.wl_server = wl_server;
+
+ let loop_ = ffi::wl_display_get_event_loop(wl_server);
+ if loop_.is_null() {
+ return Err("Failed to get event loop");
+ }
+
+ self.sigint_source = ffi::wl_event_loop_add_signal(loop_, libc::SIGINT, Some(terminate), wl_server as *mut _);
+ self.sigterm_source = ffi::wl_event_loop_add_signal(loop_, libc::SIGTERM, Some(terminate), wl_server as *mut _);
+
+ let mut session: *mut ffi::wlr_session = ptr::null_mut();
+ let backend = ffi::wlr_backend_autocreate(loop_, &mut session);
+ if backend.is_null() {
+ return Err("Failed to autocreate wlr_backend");
+ }
+ self.backend = backend;
+ self.session = session;
+
+ let renderer = ffi::wlr_renderer_autocreate(backend);
+ if renderer.is_null() {
+ return Err("Failed to autocreate wlr_renderer");
+ }
+ self.renderer = renderer;
+
+ let compositor = ffi::wlr_compositor_create(wl_server, 6, renderer);
+ if compositor.is_null() {
+ return Err("Failed to create wlr_compositor");
+ }
+ self.compositor = compositor;
+
+ let xdg_foreign_registry = ffi::wlr_xdg_foreign_registry_create(wl_server);
+ if xdg_foreign_registry.is_null() {
+ return Err("Failed to create xdg_foreign_registry");
+ }
+ self.xdg_foreign_registry = xdg_foreign_registry;
+
+ let fixes = ffi::wlr_fixes_create(wl_server, 1);
+ if fixes.is_null() {
+ return Err("Failed to create wlr_fixes");
+ }
+ self.fixes = fixes;
+
+ let allocator = ffi::wlr_allocator_autocreate(backend, renderer);
+ if allocator.is_null() {
+ return Err("Failed to autocreate allocator");
+ }
+ self.allocator = allocator;
+
+ let security_context_manager = ffi::wlr_security_context_manager_v1_create(wl_server);
+ if security_context_manager.is_null() {
+ return Err("Failed to create security context manager");
+ }
+ self.security_context_manager = security_context_manager;
+
+ let shm = ffi::wlr_shm_create_with_renderer(wl_server, 2, renderer);
+ if shm.is_null() {
+ return Err("Failed to create shm");
+ }
+ self.shm = shm;
+
+ let single_pixel_buffer_manager = ffi::wlr_single_pixel_buffer_manager_v1_create(wl_server);
+ if single_pixel_buffer_manager.is_null() {
+ return Err("Failed to create single pixel buffer manager");
+ }
+ self.single_pixel_buffer_manager = single_pixel_buffer_manager;
+
+ let alpha_modifier = ffi::wlr_alpha_modifier_v1_create(wl_server);
+ if alpha_modifier.is_null() {
+ return Err("Failed to create alpha modifier");
+ }
+ self.alpha_modifier = alpha_modifier;
+
+ let color_representation_manager = ffi::wlr_color_representation_manager_v1_create_with_renderer(wl_server, 1, renderer);
+ if color_representation_manager.is_null() {
+ return Err("Failed to create color representation manager");
+ }
+ self.color_representation_manager = color_representation_manager;
+
+ let viewporter = ffi::wlr_viewporter_create(wl_server);
+ if viewporter.is_null() {
+ return Err("Failed to create viewporter");
+ }
+ self.viewporter = viewporter;
+
+ let fractional_scale_manager = ffi::wlr_fractional_scale_manager_v1_create(wl_server, 1);
+ if fractional_scale_manager.is_null() {
+ return Err("Failed to create fractional scale manager");
+ }
+ self.fractional_scale_manager = fractional_scale_manager;
+
+ let subcompositor = ffi::wlr_subcompositor_create(wl_server);
+ if subcompositor.is_null() {
+ return Err("Failed to create subcompositor");
+ }
+ self.subcompositor = subcompositor;
+
+ let cursor_shape_manager = ffi::wlr_cursor_shape_manager_v1_create(wl_server, 2);
+ if cursor_shape_manager.is_null() {
+ return Err("Failed to create cursor shape manager");
+ }
+ self.cursor_shape_manager = cursor_shape_manager;
+
+ let xdg_shell = ffi::wlr_xdg_shell_create(wl_server, 5);
+ if xdg_shell.is_null() {
+ return Err("Failed to create xdg shell");
+ }
+ self.xdg_shell = xdg_shell;
+
+ let xdg_decoration_manager = ffi::wlr_xdg_decoration_manager_v1_create(wl_server);
+ if xdg_decoration_manager.is_null() {
+ return Err("Failed to create xdg decoration manager");
+ }
+ self.xdg_decoration_manager = xdg_decoration_manager;
+
+ let xdg_activation = ffi::wlr_xdg_activation_v1_create(wl_server);
+ if xdg_activation.is_null() {
+ return Err("Failed to create xdg activation");
+ }
+ self.xdg_activation = xdg_activation;
+
+ let xdg_foreign_v2 = ffi::wlr_xdg_foreign_v2_create(wl_server, xdg_foreign_registry);
+ if xdg_foreign_v2.is_null() {
+ return Err("Failed to create xdg foreign v2");
+ }
+ self.xdg_foreign_v2 = xdg_foreign_v2;
+
+ let data_device_manager = ffi::wlr_data_device_manager_create(wl_server);
+ if data_device_manager.is_null() {
+ return Err("Failed to create data device manager");
+ }
+ self.data_device_manager = data_device_manager;
+
+ let primary_selection_manager = ffi::wlr_primary_selection_v1_device_manager_create(wl_server);
+ if primary_selection_manager.is_null() {
+ return Err("Failed to create primary selection manager");
+ }
+ self.primary_selection_manager = primary_selection_manager;
+
+ let data_control_manager = ffi::wlr_ext_data_control_manager_v1_create(wl_server, 1);
+ if data_control_manager.is_null() {
+ return Err("Failed to create ext data control manager");
+ }
+ self.data_control_manager = data_control_manager;
+
+ let wlr_data_control_manager = ffi::wlr_data_control_manager_v1_create(wl_server);
+ if wlr_data_control_manager.is_null() {
+ return Err("Failed to create data control manager");
+ }
+ self.wlr_data_control_manager = wlr_data_control_manager;
+
+ let export_dmabuf_manager = ffi::wlr_export_dmabuf_manager_v1_create(wl_server);
+ if export_dmabuf_manager.is_null() {
+ return Err("Failed to create export dmabuf manager");
+ }
+ self.export_dmabuf_manager = export_dmabuf_manager;
+
+ let screencopy_manager = ffi::wlr_screencopy_manager_v1_create(wl_server);
+ if screencopy_manager.is_null() {
+ return Err("Failed to create screencopy manager");
+ }
+ self.screencopy_manager = screencopy_manager;
+
+ let image_copy_capture_manager = ffi::wlr_ext_image_copy_capture_manager_v1_create(wl_server, 1);
+ if image_copy_capture_manager.is_null() {
+ return Err("Failed to create image copy capture manager");
+ }
+ self.image_copy_capture_manager = image_copy_capture_manager;
+
+ let output_image_capture_source_manager = ffi::wlr_ext_output_image_capture_source_manager_v1_create(wl_server, 1);
+ if output_image_capture_source_manager.is_null() {
+ return Err("Failed to create output image capture source manager");
+ }
+ self.output_image_capture_source_manager = output_image_capture_source_manager;
+
+ let wlr_foreign_toplevel_manager = ffi::wlr_foreign_toplevel_manager_v1_create(wl_server);
+ if wlr_foreign_toplevel_manager.is_null() {
+ return Err("Failed to create foreign toplevel manager");
+ }
+ self.wlr_foreign_toplevel_manager = wlr_foreign_toplevel_manager;
+
+ let foreign_toplevel_list = ffi::wlr_ext_foreign_toplevel_list_v1_create(wl_server, 1);
+ if foreign_toplevel_list.is_null() {
+ return Err("Failed to create foreign toplevel list");
+ }
+ self.foreign_toplevel_list = foreign_toplevel_list;
+
+ let toplevel_capture_source_manager = ffi::wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(wl_server, 1);
+ if toplevel_capture_source_manager.is_null() {
+ return Err("Failed to create toplevel capture source manager");
+ }
+ self.toplevel_capture_source_manager = toplevel_capture_source_manager;
+
+ let tearing_control_manager = ffi::wlr_tearing_control_manager_v1_create(wl_server, 1);
+ if tearing_control_manager.is_null() {
+ return Err("Failed to create tearing control manager");
+ }
+ self.tearing_control_manager = tearing_control_manager;
+
+ // Setup Xwayland if runtime requested
+ if runtime_xwayland {
+ let xwayland = ffi::wlr_xwayland_create(wl_server, compositor, false);
+ if xwayland.is_null() {
+ return Err("Failed to create xwayland server");
+ }
+ self.xwayland = xwayland;
+ }
+
+ // Setup linux dmabuf if supported
+ if !ffi::wlr_renderer_get_texture_formats(renderer, ffi::wlr_buffer_cap_WLR_BUFFER_CAP_DMABUF).is_null() {
+ self.linux_dmabuf = ffi::wlr_linux_dmabuf_v1_create_with_renderer(wl_server, 5, renderer);
+ }
+
+ // Setup linux drm syncobj if supported
+ let renderer_cast = renderer as *mut WlrRenderer;
+ let backend_cast = backend as *mut WlrBackend;
+ if (*renderer_cast).features.timeline && (*backend_cast).features.timeline {
+ let drm_fd = ffi::wlr_renderer_get_drm_fd(renderer);
+ if drm_fd >= 0 {
+ self.linux_drm_syncobj_manager = ffi::wlr_linux_drm_syncobj_manager_v1_create(wl_server, 1, drm_fd);
+ }
+ }
+
+ // Setup color manager if supported
+ if (*renderer_cast).features.input_color_transform {
+ let mut len: usize = 0;
+ let primaries = ffi::wlr_color_manager_v1_primaries_list_from_renderer(renderer, &mut len);
+ let transfer_functions = ffi::wlr_color_manager_v1_transfer_function_list_from_renderer(renderer, &mut len);
+
+ let render_intents = [ffi::wp_color_manager_v1_render_intent_WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL];
+
+ self.color_manager = ffi::wlr_color_manager_v1_create(
+ wl_server,
+ 2,
+ &ffi::wlr_color_manager_v1_options {
+ features: ffi::wlr_color_manager_v1_features {
+ icc_v2_v4: false,
+ parametric: true,
+ set_primaries: false,
+ set_tf_power: false,
+ set_luminances: false,
+ set_mastering_display_primaries: true,
+ extended_target_volume: false,
+ windows_scrgb: false,
+ },
+ render_intents: render_intents.as_ptr(),
+ render_intents_len: render_intents.len(),
+ primaries,
+ primaries_len: len,
+ transfer_functions,
+ transfer_functions_len: len,
+ }
+ );
+
+ libc::free(primaries as *mut _);
+ libc::free(transfer_functions as *mut _);
+ }
+
+ // Setup subcomponents stubs
+ let server_ptr = self as *mut Server;
+ self.wm.init_with_server(server_ptr).map_err(|_| "Failed to init wm")?;
+ self.xkb_bindings.init(server_ptr, self.wl_server).map_err(|_| "Failed to init xkb_bindings")?;
+ self.layer_shell.init(self.wl_server).map_err(|_| "Failed to init layer_shell")?;
+ self.scene.init(self.linux_dmabuf, self.color_manager).map_err(|_| "Failed to init scene")?;
+ self.om.init(server_ptr).map_err(|_| "Failed to init om")?;
+ self.input_manager.init(server_ptr).map_err(|_| "Failed to init input_manager")?;
+ self.libinput_config.init(server_ptr).map_err(|_| "Failed to init libinput_config")?;
+ self.xkb_config.init(server_ptr).map_err(|_| "Failed to init xkb_config")?;
+ self.idle_inhibit_manager.init(server_ptr).map_err(|_| "Failed to init idle_inhibit_manager")?;
+ self.lock_manager.init(server_ptr).map_err(|_| "Failed to init lock_manager")?;
+
+ // Setup listeners
+ let r_lost = &mut self.renderer_lost as *mut ffi::wl_listener as *mut WlListener;
+ (*r_lost).notify = Some(handle_renderer_lost);
+
+ let new_xdg = &mut self.new_xdg_toplevel as *mut ffi::wl_listener as *mut WlListener;
+ (*new_xdg).notify = Some(handle_new_xdg_toplevel);
+
+ let new_dec = &mut self.new_toplevel_decoration as *mut ffi::wl_listener as *mut WlListener;
+ (*new_dec).notify = Some(handle_new_toplevel_decoration);
+
+ let req_act = &mut self.request_activate as *mut ffi::wl_listener as *mut WlListener;
+ (*req_act).notify = Some(handle_request_activate);
+
+ let req_cursor = &mut self.request_set_cursor_shape as *mut ffi::wl_listener as *mut WlListener;
+ (*req_cursor).notify = Some(handle_request_set_cursor_shape);
+
+ let cap_req = &mut self.toplevel_capture_request as *mut ffi::wl_listener as *mut WlListener;
+ (*cap_req).notify = Some(handle_toplevel_capture_request);
+
+ let xdg_shell_cast = self.xdg_shell as *mut WlrXdgShell;
+ let xdg_decoration_manager_cast = self.xdg_decoration_manager as *mut WlrXdgDecorationManagerV1;
+ let xdg_activation_cast = self.xdg_activation as *mut WlrXdgActivationV1;
+ let cursor_shape_manager_cast = self.cursor_shape_manager as *mut WlrCursorShapeManagerV1;
+ let toplevel_capture_source_manager_cast = self.toplevel_capture_source_manager as *mut WlrExtForeignToplevelImageCaptureSourceManagerV1;
+
+ wl_signal_add(&mut (*renderer_cast).events.lost, &mut self.renderer_lost);
+ wl_signal_add(&mut (*xdg_shell_cast).events.new_toplevel, &mut self.new_xdg_toplevel);
+ wl_signal_add(&mut (*xdg_decoration_manager_cast).events.new_toplevel_decoration, &mut self.new_toplevel_decoration);
+ wl_signal_add(&mut (*xdg_activation_cast).events.request_activate, &mut self.request_activate);
+ wl_signal_add(&mut (*cursor_shape_manager_cast).events.request_set_shape, &mut self.request_set_cursor_shape);
+ wl_signal_add(&mut (*toplevel_capture_source_manager_cast).events.new_request, &mut self.toplevel_capture_request);
+
+ // Register Xwayland surface listener if active
+ if !self.xwayland.is_null() {
+ let new_x = &mut self.new_xsurface as *mut ffi::wl_listener as *mut WlListener;
+ (*new_x).notify = Some(handle_new_xwayland_surface);
+
+ let xwayland_cast = self.xwayland as *mut WlrXwayland;
+ wl_signal_add(&mut (*xwayland_cast).events.new_surface, &mut self.new_xsurface);
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn deinit(&mut self) {
+ unsafe {
+ ffi::wl_event_source_remove(self.sigint_source);
+ ffi::wl_event_source_remove(self.sigterm_source);
+
+ wl_listener_remove(&mut self.renderer_lost);
+ wl_listener_remove(&mut self.new_xdg_toplevel);
+ wl_listener_remove(&mut self.new_toplevel_decoration);
+ wl_listener_remove(&mut self.request_activate);
+ wl_listener_remove(&mut self.request_set_cursor_shape);
+ wl_listener_remove(&mut self.toplevel_capture_request);
+
+ if !self.xwayland.is_null() {
+ wl_listener_remove(&mut self.new_xsurface);
+ ffi::wlr_xwayland_destroy(self.xwayland);
+ }
+
+ ffi::wl_display_destroy_clients(self.wl_server);
+ ffi::wlr_backend_destroy(self.backend);
+
+ ffi::wlr_renderer_destroy(self.renderer);
+ ffi::wlr_allocator_destroy(self.allocator);
+
+ self.om.deinit();
+ self.input_manager.deinit();
+ self.idle_inhibit_manager.deinit();
+ self.lock_manager.deinit();
+ self.layer_shell.deinit();
+
+ ffi::wl_display_destroy(self.wl_server);
+ }
+ }
+}
+
+impl Default for Server {
+ fn default() -> Self {
+ let mut server = std::mem::MaybeUninit::<Server>::uninit();
+ unsafe {
+ // Zero-initialize the memory (C structures and primitive fields)
+ std::ptr::write_bytes(server.as_mut_ptr(), 0, 1);
+ // Overwrite SlotMap with a valid SlotMap::new() to avoid UB from null vec pointers
+ std::ptr::write(&mut (*server.as_mut_ptr()).wm.windows, crate::slotmap::SlotMap::new());
+ std::ptr::write(&mut (*server.as_mut_ptr()).layer_shell.surfaces, crate::slotmap::SlotMap::new());
+ server.assume_init()
+ }
+ }
+}
diff --git a/src/shell_surface.rs b/src/shell_surface.rs
new file mode 100644
index 0000000..d397625
--- /dev/null
+++ b/src/shell_surface.rs
@@ -0,0 +1,266 @@
+// SPDX-FileCopyrightText: © 2024 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlList, wl_list_insert, wl_list_remove};
+use crate::wm_node::WmNode;
+
+pub struct ShellSurfaceRenderingRequested {
+ pub x: i32,
+ pub y: i32,
+ pub sync_next_commit: bool,
+}
+
+pub struct ShellSurface {
+ pub server: *mut Server,
+ pub object: *mut ffi::wl_resource, // river_shell_surface_v1
+ pub surface: *mut ffi::wlr_surface,
+ pub tree: *mut ffi::wlr_scene_tree,
+ pub surfaces: crate::scene::SaveableSurfaces,
+ pub popup_tree: *mut ffi::wlr_scene_tree,
+ pub node: WmNode,
+ pub rendering_requested: ShellSurfaceRenderingRequested,
+}
+
+impl ShellSurface {
+ pub unsafe fn create(
+ client: *mut ffi::wl_client,
+ version: u32,
+ id: u32,
+ surface: *mut ffi::wlr_surface,
+ server: *mut Server,
+ ) -> Result<(), &'static str> {
+ log::debug!("new river_shell_surface_v1");
+
+ let shell_surface_v1 = ffi::wl_resource_create(client, &ffi::river_shell_surface_v1_interface, version as i32, id);
+ if shell_surface_v1.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return Err("wl_resource_create failed");
+ }
+
+ if !ffi::wlr_surface_set_role(
+ surface,
+ &SHELL_SURFACE_ROLE,
+ shell_surface_v1,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_ROLE,
+ ) {
+ return Err("wlr_surface_set_role failed");
+ }
+ ffi::river_wlr_surface_set_role_object(surface, shell_surface_v1);
+
+ let shell_surface = Box::new(ShellSurface {
+ server,
+ object: shell_surface_v1,
+ surface,
+ tree: std::ptr::null_mut(),
+ surfaces: std::mem::zeroed(),
+ popup_tree: std::ptr::null_mut(),
+ node: std::mem::zeroed(),
+ rendering_requested: ShellSurfaceRenderingRequested {
+ x: 0,
+ y: 0,
+ sync_next_commit: false,
+ },
+ });
+ let raw = Box::into_raw(shell_surface);
+
+ ffi::wl_resource_set_implementation(
+ shell_surface_v1,
+ &SHELL_SURFACE_INTERFACE as *const _ as *const _,
+ raw as *mut _,
+ None,
+ );
+
+ let hidden_tree = (*server).scene.hidden_tree;
+ let tree = ffi::wlr_scene_tree_create(hidden_tree);
+ if tree.is_null() {
+ let _ = Box::from_raw(raw);
+ return Err("Failed to create scene tree");
+ }
+ let popup_tree = ffi::wlr_scene_tree_create(hidden_tree);
+ if popup_tree.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ let _ = Box::from_raw(raw);
+ return Err("Failed to create popup tree");
+ }
+
+ let surfaces = match crate::scene::SaveableSurfaces::init(tree) {
+ Ok(s) => s,
+ Err(e) => {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
+ let _ = Box::from_raw(raw);
+ return Err(e);
+ }
+ };
+
+ let subsurface_tree = ffi::wlr_scene_subsurface_tree_create(surfaces.tree, surface);
+ if subsurface_tree.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
+ let _ = Box::from_raw(raw);
+ return Err("Failed to create scene subsurface tree");
+ }
+
+ (*raw).tree = tree;
+ (*raw).popup_tree = popup_tree;
+ (*raw).surfaces = surfaces;
+ (*raw).node.init(crate::wm_node::WmNodeTag::ShellSurface);
+
+ let list_head = &mut (*server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, &mut (*raw).node.link as *mut ffi::wl_list as *mut WlList);
+
+ crate::scene_node_data::SceneNodeData::attach(
+ tree as *mut ffi::wlr_scene_node,
+ crate::scene_node_data::SceneNodeDataVal::ShellSurface(raw),
+ );
+ crate::scene_node_data::SceneNodeData::attach(
+ popup_tree as *mut ffi::wlr_scene_node,
+ crate::scene_node_data::SceneNodeDataVal::ShellSurface(raw),
+ );
+
+ Ok(())
+ }
+
+ pub unsafe fn render_finish(&mut self) {
+ if self.rendering_requested.sync_next_commit {
+ self.rendering_requested.sync_next_commit = false;
+
+ if !self.surfaces.saved {
+ ffi::wl_resource_post_error(
+ self.object,
+ ffi::river_shell_surface_v1_error_RIVER_SHELL_SURFACE_V1_ERROR_NO_COMMIT,
+ b"no wl_surface.commit after sync_next_commit and before update_rendering_finish\0".as_ptr() as *const _,
+ );
+ }
+ }
+
+ self.surfaces.drop_saved();
+
+ ffi::wlr_scene_node_set_position(
+ self.tree as *mut ffi::wlr_scene_node,
+ self.rendering_requested.x,
+ self.rendering_requested.y,
+ );
+ ffi::wlr_scene_node_set_position(
+ self.popup_tree as *mut ffi::wlr_scene_node,
+ self.rendering_requested.x,
+ self.rendering_requested.y,
+ );
+ }
+}
+
+pub unsafe fn from_wlr_surface(surface: *mut ffi::wlr_surface) -> *mut ShellSurface {
+ if surface.is_null() {
+ return std::ptr::null_mut();
+ }
+ let role_ptr = ffi::river_wlr_surface_get_role(surface);
+ if role_ptr != &SHELL_SURFACE_ROLE as *const _ {
+ return std::ptr::null_mut();
+ }
+ let resource = ffi::river_wlr_surface_get_role_resource(surface);
+ if resource.is_null() {
+ return std::ptr::null_mut();
+ }
+ ffi::wl_resource_get_user_data(resource) as *mut ShellSurface
+}
+
+unsafe extern "C" fn client_commit(surface: *mut ffi::wlr_surface) {
+ let shell_surface = from_wlr_surface(surface);
+ if shell_surface.is_null() {
+ return;
+ }
+ if (*shell_surface).rendering_requested.sync_next_commit {
+ (*shell_surface).surfaces.save();
+ }
+}
+
+unsafe extern "C" fn commit(surface: *mut ffi::wlr_surface) {
+ if ffi::wlr_surface_has_buffer(surface) {
+ ffi::wlr_surface_map(surface);
+ }
+}
+
+unsafe extern "C" fn role_destroy(surface: *mut ffi::wlr_surface) {
+ let shell_surface = from_wlr_surface(surface);
+ if shell_surface.is_null() {
+ return;
+ }
+
+ ffi::wlr_surface_unmap((*shell_surface).surface);
+
+ (*shell_surface).node.make_inert();
+ (*shell_surface).node.deinit();
+
+ ffi::wlr_scene_node_destroy((*shell_surface).tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy((*shell_surface).popup_tree as *mut ffi::wlr_scene_node);
+
+ let _ = Box::from_raw(shell_surface);
+}
+
+unsafe extern "C" fn shell_surface_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn shell_surface_get_node(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+) {
+ let shell_surface = ffi::wl_resource_get_user_data(resource) as *mut ShellSurface;
+ if shell_surface.is_null() {
+ return;
+ }
+
+ if !(*shell_surface).node.object.is_null() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_shell_surface_v1_error_RIVER_SHELL_SURFACE_V1_ERROR_NODE_EXISTS,
+ b"shell surface already has a node object\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ (*shell_surface).node.create_object(
+ client,
+ ffi::wl_resource_get_version(resource) as u32,
+ id,
+ );
+}
+
+unsafe extern "C" fn shell_surface_sync_next_commit(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let shell_surface = ffi::wl_resource_get_user_data(resource) as *mut ShellSurface;
+ if shell_surface.is_null() {
+ return;
+ }
+
+ let server = (*shell_surface).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+
+ (*shell_surface).rendering_requested.sync_next_commit = true;
+}
+
+static SHELL_SURFACE_INTERFACE: ffi::river_shell_surface_v1_interface = ffi::river_shell_surface_v1_interface {
+ destroy: Some(shell_surface_destroy),
+ get_node: Some(shell_surface_get_node),
+ sync_next_commit: Some(shell_surface_sync_next_commit),
+};
+
+#[no_mangle]
+pub static mut SHELL_SURFACE_ROLE: ffi::wlr_surface_role = ffi::wlr_surface_role {
+ name: b"river_shell_surface_v1\0".as_ptr() as *const _,
+ no_object: false,
+ client_commit: Some(client_commit),
+ commit: Some(commit),
+ map: None,
+ unmap: None,
+ destroy: Some(role_destroy),
+};
diff --git a/src/slotmap.rs b/src/slotmap.rs
new file mode 100644
index 0000000..5bab508
--- /dev/null
+++ b/src/slotmap.rs
@@ -0,0 +1,195 @@
+// SPDX-FileCopyrightText: © 2025 Isaac Freund
+// SPDX-License-Identifier: 0BSD
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Key {
+ pub generation: u32,
+ pub index: u32,
+}
+
+enum SlotData<T> {
+ Value(T),
+ NextFree(u32),
+}
+
+struct Slot<T> {
+ generation: u32,
+ data: SlotData<T>,
+}
+
+pub struct SlotMap<T> {
+ slots: Vec<Slot<T>>,
+ count: u32,
+ first_free: u32,
+}
+
+impl<T> SlotMap<T> {
+ pub fn new() -> Self {
+ Self {
+ slots: Vec::new(),
+ count: 0,
+ first_free: 1,
+ }
+ }
+}
+
+impl<T> Default for SlotMap<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T> SlotMap<T> {
+
+ pub fn put(&mut self, value: T) -> Key {
+ let index = self.first_free as usize;
+ if index < self.slots.len() {
+ let slot = &mut self.slots[index];
+ if let SlotData::NextFree(next) = slot.data {
+ self.first_free = next;
+ } else {
+ panic!("SlotMap state corrupted: expected NextFree slot");
+ }
+ slot.data = SlotData::Value(value);
+ self.count += 1;
+ Key {
+ generation: slot.generation,
+ index: index as u32,
+ }
+ } else {
+ self.slots.push(Slot {
+ generation: 0,
+ data: SlotData::Value(value),
+ });
+ let new_index = self.slots.len() - 1;
+ self.count += 1;
+ self.first_free += 1;
+ Key {
+ generation: 0,
+ index: new_index as u32,
+ }
+ }
+ }
+
+ pub fn get(&self, key: Key) -> Option<&T> {
+ let idx = key.index as usize;
+ if idx < self.slots.len() {
+ let slot = &self.slots[idx];
+ if slot.generation == key.generation {
+ if let SlotData::Value(ref val) = slot.data {
+ return Some(val);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn get_mut(&mut self, key: Key) -> Option<&mut T> {
+ let idx = key.index as usize;
+ if idx < self.slots.len() {
+ let slot = &mut self.slots[idx];
+ if slot.generation == key.generation {
+ if let SlotData::Value(ref mut val) = slot.data {
+ return Some(val);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn remove(&mut self, key: Key) -> Option<T> {
+ let idx = key.index as usize;
+ if idx < self.slots.len() {
+ let slot = &mut self.slots[idx];
+ if slot.generation == key.generation {
+ if let SlotData::Value(_) = slot.data {
+ let old_data = std::mem::replace(&mut slot.data, SlotData::NextFree(self.first_free));
+ slot.generation = slot.generation.wrapping_add(1);
+ self.count -= 1;
+ self.first_free = key.index;
+ if let SlotData::Value(val) = old_data {
+ return Some(val);
+ }
+ }
+ }
+ }
+ None
+ }
+
+ pub fn count(&self) -> u32 {
+ self.count
+ }
+
+ pub fn iter(&self) -> Iter<'_, T> {
+ Iter {
+ slots: &self.slots,
+ index: 0,
+ }
+ }
+}
+
+pub struct Iter<'a, T> {
+ slots: &'a [Slot<T>],
+ index: usize,
+}
+
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = &'a T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while self.index < self.slots.len() {
+ let slot = &self.slots[self.index];
+ self.index += 1;
+ if let SlotData::Value(ref val) = slot.data {
+ return Some(val);
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_basic() {
+ let mut map = SlotMap::new();
+
+ let key5 = map.put(5);
+ assert_eq!(map.get(key5), Some(&5));
+
+ assert_eq!(map.remove(key5), Some(5));
+ assert_eq!(map.get(key5), None);
+
+ let key6 = map.put(6);
+ assert_eq!(map.get(key6), Some(&6));
+ assert_eq!(map.get(key5), None);
+
+ let key7 = map.put(7);
+ let key8 = map.put(8);
+ let key9 = map.put(9);
+
+ assert_eq!(map.get(key6), Some(&6));
+ assert_eq!(map.get(key7), Some(&7));
+ assert_eq!(map.get(key8), Some(&8));
+ assert_eq!(map.get(key9), Some(&9));
+
+ map.remove(key8);
+ assert_eq!(map.get(key8), None);
+ assert_eq!(map.get(key9), Some(&9));
+ }
+
+ #[test]
+ fn test_iteration() {
+ let mut map = SlotMap::new();
+ map.put(5);
+ map.put(6);
+ map.put(7);
+ map.put(8);
+ map.put(9);
+
+ let vals: Vec<&i32> = map.iter().collect();
+ assert_eq!(vals, vec![&5, &6, &7, &8, &9]);
+ }
+}
diff --git a/src/tablet.rs b/src/tablet.rs
new file mode 100644
index 0000000..166b44a
--- /dev/null
+++ b/src/tablet.rs
@@ -0,0 +1,51 @@
+use crate::ffi;
+use crate::input_device::InputDevice;
+use crate::seat::Seat;
+
+pub struct Tablet {
+ pub device: *mut InputDevice,
+ pub wp_tablet: *mut ffi::wlr_tablet_v2_tablet,
+}
+
+impl Tablet {
+ pub unsafe fn create(
+ seat: *mut Seat,
+ wlr_device: *mut ffi::wlr_input_device,
+ virtual_device: bool,
+ ) -> Result<*mut Self, &'static str> {
+ let server = (*seat).server;
+ let tablet_manager = (*server).input_manager.tablet_manager;
+ if tablet_manager.is_null() {
+ return Err("Tablet manager is null");
+ }
+
+ let wp_tablet = ffi::wlr_tablet_create(tablet_manager, (*seat).wlr_seat, wlr_device);
+ if wp_tablet.is_null() {
+ return Err("Failed to create wp_tablet");
+ }
+
+ let device = InputDevice::new(seat, wlr_device, virtual_device);
+ if device.is_null() {
+ return Err("Failed to create input device");
+ }
+
+ let tablet = Box::into_raw(Box::new(Self {
+ device,
+ wp_tablet,
+ }));
+
+ // Set up the custom destroy callback on the input device
+ (*device).destroy_fn = Some(destroy_tablet_callback);
+ (*device).destroy_data = tablet as *mut _;
+
+ Ok(tablet)
+ }
+
+ pub unsafe fn destroy(tablet: *mut Self) {
+ let _boxed = Box::from_raw(tablet);
+ }
+}
+
+unsafe extern "C" fn destroy_tablet_callback(data: *mut std::ffi::c_void) {
+ Tablet::destroy(data as *mut Tablet);
+}
diff --git a/src/tablet_tool.rs b/src/tablet_tool.rs
new file mode 100644
index 0000000..d9ef066
--- /dev/null
+++ b/src/tablet_tool.rs
@@ -0,0 +1,366 @@
+use crate::ffi;
+use crate::tablet::Tablet;
+use crate::seat::Seat;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum TabletToolMode {
+ Passthrough,
+ Down {
+ lx: f64,
+ ly: f64,
+ sx: f64,
+ sy: f64,
+ },
+}
+
+pub struct TabletTool {
+ pub wp_tool: *mut ffi::wlr_tablet_v2_tablet_tool,
+ pub wlr_cursor: *mut ffi::wlr_cursor,
+ pub mode: TabletToolMode,
+ pub tilt_x: f64,
+ pub tilt_y: f64,
+
+ pub destroy_listener: ffi::wl_listener,
+ pub set_cursor_listener: ffi::wl_listener,
+}
+
+impl TabletTool {
+ pub unsafe fn get(
+ wlr_seat: *mut ffi::wlr_seat,
+ wlr_tool: *mut ffi::wlr_tablet_tool,
+ ) -> Result<*mut Self, &'static str> {
+ let data_ptr = (*wlr_tool).data as *mut Self;
+ if !data_ptr.is_null() {
+ Ok(data_ptr)
+ } else {
+ Self::create(wlr_seat, wlr_tool)
+ }
+ }
+
+ pub unsafe fn create(
+ wlr_seat: *mut ffi::wlr_seat,
+ wlr_tool: *mut ffi::wlr_tablet_tool,
+ ) -> Result<*mut Self, &'static str> {
+ let wlr_cursor = ffi::wlr_cursor_create();
+ if wlr_cursor.is_null() {
+ return Err("Failed to create wlr_cursor");
+ }
+
+ let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat;
+ if seat.is_null() {
+ ffi::wlr_cursor_destroy(wlr_cursor);
+ return Err("Seat data is null");
+ }
+ let server = (*seat).server;
+ let output_layout = (*server).om.output_layout;
+
+ ffi::wlr_cursor_attach_output_layout(wlr_cursor, output_layout);
+
+ let tablet_manager = (*server).input_manager.tablet_manager;
+ let wp_tool = ffi::wlr_tablet_tool_create(tablet_manager, wlr_seat, wlr_tool);
+ if wp_tool.is_null() {
+ ffi::wlr_cursor_destroy(wlr_cursor);
+ return Err("Failed to create wp_tool");
+ }
+
+ let tool = Box::into_raw(Box::new(Self {
+ wp_tool,
+ wlr_cursor,
+ mode: TabletToolMode::Passthrough,
+ tilt_x: 0.0,
+ tilt_y: 0.0,
+ destroy_listener: std::mem::zeroed(),
+ set_cursor_listener: std::mem::zeroed(),
+ }));
+
+ (*wlr_tool).data = tool as *mut _;
+
+ let destroy_listener_ptr = &mut (*tool).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*destroy_listener_ptr).notify = Some(handle_destroy);
+ wl_signal_add(&mut (*wlr_tool).events.destroy, &mut (*tool).destroy_listener);
+
+ let set_cursor_listener_ptr = &mut (*tool).set_cursor_listener as *mut ffi::wl_listener as *mut WlListener;
+ (*set_cursor_listener_ptr).notify = Some(handle_set_cursor);
+ let set_cursor_signal = ffi::river_wlr_tablet_v2_tablet_tool_get_set_cursor_signal(wp_tool);
+ wl_signal_add(set_cursor_signal, &mut (*tool).set_cursor_listener);
+
+ Ok(tool)
+ }
+
+ pub unsafe fn cursor_x(&self) -> f64 {
+ ffi::river_wlr_cursor_get_x(self.wlr_cursor)
+ }
+
+ pub unsafe fn cursor_y(&self) -> f64 {
+ ffi::river_wlr_cursor_get_y(self.wlr_cursor)
+ }
+
+ pub unsafe fn allow_set_cursor(
+ &self,
+ seat_client: *mut ffi::wlr_seat_client,
+ serial: u32,
+ ) -> bool {
+ let focused_surface = ffi::river_wlr_tablet_v2_tablet_tool_get_focused_surface(self.wp_tool);
+ if focused_surface.is_null() {
+ log::debug!("client tried to set cursor without focus");
+ return false;
+ }
+
+ let surface_resource = ffi::river_wlr_surface_get_resource(focused_surface);
+ if surface_resource.is_null() {
+ return false;
+ }
+
+ let surface_client = ffi::wl_resource_get_client(surface_resource);
+ let seat_client_ptr = ffi::river_wlr_seat_client_get_client(seat_client);
+
+ if surface_client != seat_client_ptr {
+ log::debug!("client tried to set cursor without focus");
+ return false;
+ }
+
+ let proximity_serial = ffi::river_wlr_tablet_v2_tablet_tool_get_proximity_serial(self.wp_tool);
+ if serial != proximity_serial {
+ log::debug!("focused client tried to set cursor with incorrect serial");
+ return false;
+ }
+
+ true
+ }
+
+ pub unsafe fn attach(&mut self, tablet: *mut Tablet) {
+ let dev = (*(*tablet).device).wlr_device;
+ ffi::wlr_cursor_attach_input_device(self.wlr_cursor, dev);
+ ffi::wlr_cursor_map_input_to_output(self.wlr_cursor, dev, (*(*tablet).device).config.map_to_output);
+ ffi::wlr_cursor_map_input_to_region(self.wlr_cursor, dev, &mut (*(*tablet).device).config.map_to_rectangle as *mut _);
+ }
+
+ pub unsafe fn detach(&mut self, tablet: *mut Tablet) {
+ let dev = (*(*tablet).device).wlr_device;
+ ffi::wlr_cursor_detach_input_device(self.wlr_cursor, dev);
+ }
+
+ pub unsafe fn axis(&mut self, tablet: *mut Tablet, event: *mut ffi::wlr_tablet_tool_axis_event) {
+ self.attach(tablet);
+
+ let updated = (*event).updated_axes;
+ let has_x = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_X) != 0;
+ let has_y = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_Y) != 0;
+
+ let wlr_tool = ffi::river_wlr_tablet_v2_tablet_tool_get_wlr_tool(self.wp_tool);
+ let tool_type = (*wlr_tool).type_;
+
+ if has_x || has_y {
+ match tool_type {
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_PEN |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_ERASER |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_BRUSH |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_PENCIL |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_AIRBRUSH |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_TOTEM => {
+ let warp_x = if has_x { (*event).x } else { f64::NAN };
+ let warp_y = if has_y { (*event).y } else { f64::NAN };
+ ffi::wlr_cursor_warp_absolute(self.wlr_cursor, (*(*tablet).device).wlr_device, warp_x, warp_y);
+ }
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_LENS |
+ ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_MOUSE => {
+ ffi::wlr_cursor_move(self.wlr_cursor, (*(*tablet).device).wlr_device, (*event).dx, (*event).dy);
+ }
+ _ => {}
+ }
+
+ match self.mode {
+ TabletToolMode::Passthrough => {
+ self.passthrough(tablet);
+ }
+ TabletToolMode::Down { lx, ly, sx, sy } => {
+ let dx = self.cursor_x() - lx;
+ let dy = self.cursor_y() - ly;
+ ffi::wlr_tablet_v2_tablet_tool_notify_motion(self.wp_tool, sx + dx, sy + dy);
+ }
+ }
+ }
+
+ let has_dist = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_DISTANCE) != 0;
+ if has_dist {
+ ffi::wlr_tablet_v2_tablet_tool_notify_distance(self.wp_tool, (*event).distance);
+ }
+
+ let has_press = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_PRESSURE) != 0;
+ if has_press {
+ ffi::wlr_tablet_v2_tablet_tool_notify_pressure(self.wp_tool, (*event).pressure);
+ }
+
+ let has_tilt_x = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_TILT_X) != 0;
+ let has_tilt_y = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_TILT_Y) != 0;
+ if has_tilt_x || has_tilt_y {
+ if has_tilt_x {
+ self.tilt_x = (*event).tilt_x;
+ }
+ if has_tilt_y {
+ self.tilt_y = (*event).tilt_y;
+ }
+ ffi::wlr_tablet_v2_tablet_tool_notify_tilt(self.wp_tool, self.tilt_x, self.tilt_y);
+ }
+
+ let has_rot = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_ROTATION) != 0;
+ if has_rot {
+ ffi::wlr_tablet_v2_tablet_tool_notify_rotation(self.wp_tool, (*event).rotation);
+ }
+
+ let has_slider = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_SLIDER) != 0;
+ if has_slider {
+ ffi::wlr_tablet_v2_tablet_tool_notify_slider(self.wp_tool, (*event).slider);
+ }
+
+ let has_wheel = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_WHEEL) != 0;
+ if has_wheel {
+ ffi::wlr_tablet_v2_tablet_tool_notify_wheel(self.wp_tool, (*event).wheel_delta, 0);
+ }
+
+ self.detach(tablet);
+ }
+
+ pub unsafe fn proximity(
+ &mut self,
+ tablet: *mut Tablet,
+ event: *mut ffi::wlr_tablet_tool_proximity_event,
+ ) {
+ let state = (*event).state;
+ if state == ffi::wlr_tablet_tool_proximity_state_WLR_TABLET_TOOL_PROXIMITY_IN {
+ self.attach(tablet);
+
+ ffi::wlr_cursor_warp_absolute(
+ self.wlr_cursor,
+ (*(*tablet).device).wlr_device,
+ (*event).x,
+ (*event).y,
+ );
+
+ let xcursor_manager = (*(*(*tablet).device).seat).cursor.xcursor_manager;
+ ffi::wlr_cursor_set_xcursor(
+ self.wlr_cursor,
+ xcursor_manager,
+ b"pencil\0".as_ptr() as *const _,
+ );
+
+ self.passthrough(tablet);
+ self.detach(tablet);
+ } else {
+ ffi::wlr_tablet_v2_tablet_tool_notify_proximity_out(self.wp_tool);
+ ffi::wlr_cursor_unset_image(self.wlr_cursor);
+ }
+ }
+
+ pub unsafe fn tip(&mut self, tablet: *mut Tablet, event: *mut ffi::wlr_tablet_tool_tip_event) {
+ let state = (*event).state;
+ let is_down = ffi::river_wlr_tablet_v2_tablet_tool_get_is_down(self.wp_tool);
+ if state == ffi::wlr_tablet_tool_tip_state_WLR_TABLET_TOOL_TIP_DOWN {
+ if is_down {
+ return;
+ }
+ ffi::wlr_send_tablet_v2_tablet_tool_down(self.wp_tool);
+
+ let server = (*(*(*tablet).device).seat).server;
+ let lx = self.cursor_x();
+ let ly = self.cursor_y();
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if !result.surface.is_null() {
+ self.mode = TabletToolMode::Down {
+ lx,
+ ly,
+ sx: result.sx,
+ sy: result.sy,
+ };
+ }
+ }
+ } else {
+ if !is_down {
+ return;
+ }
+ ffi::wlr_send_tablet_v2_tablet_tool_up(self.wp_tool);
+ self.maybe_exit_down(tablet);
+ }
+ }
+
+ pub unsafe fn button(
+ &mut self,
+ tablet: *mut Tablet,
+ event: *mut ffi::wlr_tablet_tool_button_event,
+ ) {
+ ffi::wlr_tablet_v2_tablet_tool_notify_button(
+ self.wp_tool,
+ (*event).button,
+ (*event).state,
+ );
+ self.maybe_exit_down(tablet);
+ }
+
+ pub unsafe fn maybe_exit_down(&mut self, tablet: *mut Tablet) {
+ let is_down = ffi::river_wlr_tablet_v2_tablet_tool_get_is_down(self.wp_tool);
+ let num_buttons = ffi::river_wlr_tablet_v2_tablet_tool_get_num_buttons(self.wp_tool);
+ if !matches!(self.mode, TabletToolMode::Down { .. }) || is_down || num_buttons > 0 {
+ return;
+ }
+ self.mode = TabletToolMode::Passthrough;
+ self.passthrough(tablet);
+ }
+
+ pub unsafe fn passthrough(&mut self, tablet: *mut Tablet) {
+ let server = (*(*(*tablet).device).seat).server;
+ let lx = self.cursor_x();
+ let ly = self.cursor_y();
+
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if matches!(result.data, crate::scene_node_data::SceneNodeDataVal::LockSurface(_)) {
+ assert!((*server).lock_manager.state != crate::lock_manager::LockState::Unlocked);
+ } else {
+ assert!((*server).lock_manager.state != crate::lock_manager::LockState::Locked);
+ }
+
+ if !result.surface.is_null() {
+ ffi::wlr_send_tablet_v2_tablet_tool_proximity_in(self.wp_tool, (*tablet).wp_tablet, result.surface);
+ ffi::wlr_tablet_v2_tablet_tool_notify_motion(self.wp_tool, result.sx, result.sy);
+ return;
+ }
+ } else {
+ let xcursor_manager = (*(*(*tablet).device).seat).cursor.xcursor_manager;
+ ffi::wlr_cursor_set_xcursor(self.wlr_cursor, xcursor_manager, b"pencil\0".as_ptr() as *const _);
+ }
+
+ ffi::wlr_tablet_v2_tablet_tool_notify_proximity_out(self.wp_tool);
+ }
+}
+
+unsafe extern "C" fn handle_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let tool_ptr = crate::container_of!(listener, TabletTool, destroy_listener) as *mut TabletTool;
+ let tool = &mut *tool_ptr;
+
+ ffi::wlr_cursor_destroy(tool.wlr_cursor);
+
+ wl_listener_remove(&mut tool.destroy_listener);
+ wl_listener_remove(&mut tool.set_cursor_listener);
+
+ let _boxed = Box::from_raw(tool_ptr);
+}
+
+unsafe extern "C" fn handle_set_cursor(
+ listener: *mut ffi::wl_listener,
+ data: *mut std::ffi::c_void,
+) {
+ let tool = &mut *crate::container_of!(listener, TabletTool, set_cursor_listener);
+ let event = data as *mut ffi::wlr_tablet_v2_event_cursor;
+
+ if tool.allow_set_cursor((*event).seat_client, (*event).serial) {
+ ffi::wlr_cursor_set_surface(
+ tool.wlr_cursor,
+ (*event).surface,
+ (*event).hotspot_x,
+ (*event).hotspot_y,
+ );
+ }
+}
diff --git a/src/text_input.rs b/src/text_input.rs
new file mode 100644
index 0000000..23cab0e
--- /dev/null
+++ b/src/text_input.rs
@@ -0,0 +1,154 @@
+// SPDX-FileCopyrightText: © 2021 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{WlListener, wl_listener_remove, wl_signal_add, WlList};
+use crate::seat::Seat;
+
+#[repr(C)]
+pub struct TextInput {
+ pub link: ffi::wl_list,
+ pub wlr_text_input: *mut ffi::wlr_text_input_v3,
+
+ pub enable: ffi::wl_listener,
+ pub commit: ffi::wl_listener,
+ pub disable: ffi::wl_listener,
+ pub destroy: ffi::wl_listener,
+}
+
+unsafe fn connect_listener(
+ signal: *mut ffi::wl_signal,
+ listener: *mut ffi::wl_listener,
+ callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
+) {
+ let wl_lis = listener as *mut WlListener;
+ (*wl_lis).notify = Some(callback);
+ wl_signal_add(signal, listener);
+}
+
+unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
+ let prev = (*listener).link.prev;
+ let next = (*listener).link.next;
+ if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
+ ffi::wl_list_remove(&mut (*listener).link);
+ (*listener).link.prev = std::ptr::null_mut();
+ (*listener).link.next = std::ptr::null_mut();
+ }
+}
+
+impl TextInput {
+ pub unsafe fn create(wlr_text_input: *mut ffi::wlr_text_input_v3) -> Result<(), &'static str> {
+ let wlr_seat = (*wlr_text_input).seat;
+ let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat;
+ if seat.is_null() {
+ return Err("Seat is null");
+ }
+
+ let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name(wlr_seat))
+ .to_str()
+ .unwrap_or("unknown");
+ log::debug!("new text input on seat {}", seat_name);
+
+ let mut text_input = Box::new(TextInput {
+ link: std::mem::zeroed(),
+ wlr_text_input,
+ enable: std::mem::zeroed(),
+ commit: std::mem::zeroed(),
+ disable: std::mem::zeroed(),
+ destroy: std::mem::zeroed(),
+ });
+
+ ffi::wl_list_init(&mut text_input.link);
+
+ let raw = Box::into_raw(text_input);
+
+ // Append to seat.relay.text_inputs
+ let text_inputs_list = &mut (*seat).relay.text_inputs as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert((*text_inputs_list).prev, &mut (*raw).link as *mut ffi::wl_list as *mut WlList);
+
+ connect_listener(&mut (*wlr_text_input).events.enable, &mut (*raw).enable, handle_enable);
+ connect_listener(&mut (*wlr_text_input).events.commit, &mut (*raw).commit, handle_commit);
+ connect_listener(&mut (*wlr_text_input).events.disable, &mut (*raw).disable, handle_disable);
+ connect_listener(&mut (*wlr_text_input).events.destroy, &mut (*raw).destroy, handle_destroy);
+
+ Ok(())
+ }
+}
+
+unsafe extern "C" fn handle_enable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let text_input = crate::container_of!(listener, TextInput, enable);
+ let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+
+ if (*(*text_input).wlr_text_input).focused_surface.is_null() {
+ log::error!("client requested to enable text input without focus, ignoring request");
+ return;
+ }
+
+ if !(*seat).relay.text_input.is_null() {
+ if text_input != (*seat).relay.text_input {
+ log::error!("client requested to enable more than one text input on a single seat, ignoring request");
+ return;
+ }
+ }
+
+ (*seat).relay.text_input = text_input;
+
+ let input_method = (*seat).relay.input_method;
+ if !input_method.is_null() {
+ ffi::wlr_input_method_v2_send_activate(input_method);
+ (*seat).relay.send_input_method_state();
+ }
+}
+
+unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let text_input = crate::container_of!(listener, TextInput, commit);
+ let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+
+ if (*seat).relay.text_input != text_input {
+ log::error!("inactive text input tried to commit an update, client bug?");
+ return;
+ }
+
+ if !(*seat).relay.input_method.is_null() {
+ (*seat).relay.send_input_method_state();
+ }
+}
+
+unsafe extern "C" fn handle_disable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let text_input = crate::container_of!(listener, TextInput, disable);
+ let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+
+ if (*seat).relay.text_input == text_input {
+ (*seat).relay.disable_text_input();
+ }
+}
+
+unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
+ let text_input = crate::container_of!(listener, TextInput, destroy);
+ let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
+ if seat.is_null() {
+ return;
+ }
+
+ if (*seat).relay.text_input == text_input {
+ (*seat).relay.disable_text_input();
+ }
+
+ wl_listener_remove_safe(&mut (*text_input).enable);
+ wl_listener_remove_safe(&mut (*text_input).commit);
+ wl_listener_remove_safe(&mut (*text_input).disable);
+ wl_listener_remove_safe(&mut (*text_input).destroy);
+
+ ffi::wl_list_remove(&mut (*text_input).link);
+
+ let _ = Box::from_raw(text_input);
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..2eddfa4
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: © 2022 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+pub fn timestamp() -> libc::timespec {
+ let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
+ unsafe {
+ if libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) < 0 {
+ panic!("CLOCK_MONOTONIC not supported");
+ }
+ }
+ ts
+}
+
+pub fn msec_timestamp() -> u32 {
+ let now = timestamp();
+ // 2^32-1 milliseconds is ~50 days.
+ // Wrap it using wrapping arithmetic.
+ let secs_ms = (now.tv_sec as u64).wrapping_mul(1000);
+ let nsecs_ms = (now.tv_nsec as u64) / 1_000_000;
+ secs_ms.wrapping_add(nsecs_ms) as u32
+}
diff --git a/src/window.rs b/src/window.rs
new file mode 100644
index 0000000..15ecd5c
--- /dev/null
+++ b/src/window.rs
@@ -0,0 +1,2038 @@
+// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlList, wl_list_insert, wl_list_remove};
+use crate::wm_node::WmNode;
+use crate::xdg_toplevel::ConfigureState;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum WindowState {
+ Init,
+ Ready,
+ Initialized,
+ Mapped,
+ Closing,
+}
+
+#[derive(Clone, Copy)]
+pub enum WindowImpl {
+ Toplevel(*mut crate::xdg_toplevel::XdgToplevel),
+ Xwayland(*mut crate::xwayland_window::XwaylandWindow),
+ Destroying,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FullscreenRequest {
+ NoRequest,
+ Fullscreen(*mut crate::output::Output),
+ Exit,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum MaximizeRequest {
+ NoRequest,
+ Maximize,
+ Unmaximize,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Dimensions {
+ pub width: u32,
+ pub height: u32,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct DimensionsHint {
+ pub min_width: u32,
+ pub min_height: u32,
+ pub max_width: u32,
+ pub max_height: u32,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Edges {
+ pub top: bool,
+ pub bottom: bool,
+ pub left: bool,
+ pub right: bool,
+}
+
+impl Edges {
+ pub fn new() -> Self {
+ Self { top: false, bottom: false, left: false, right: false }
+ }
+ pub fn from_u32(val: u32) -> Self {
+ Self {
+ top: (val & 1) != 0,
+ bottom: (val & 2) != 0,
+ left: (val & 4) != 0,
+ right: (val & 8) != 0,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Border {
+ pub edges: Edges,
+ pub width: u32,
+ pub r: u32,
+ pub g: u32,
+ pub b: u32,
+ pub a: u32,
+}
+
+pub struct BorderRects {
+ pub left: *mut ffi::wlr_scene_rect,
+ pub right: *mut ffi::wlr_scene_rect,
+ pub top: *mut ffi::wlr_scene_rect,
+ pub bottom: *mut ffi::wlr_scene_rect,
+}
+
+pub struct ShowWindowMenuRequest {
+ pub x: i32,
+ pub y: i32,
+}
+
+pub struct PointerResizeRequest {
+ pub seat: *mut crate::seat::Seat,
+ pub edges: u32,
+}
+
+pub struct WmScheduledState {
+ pub dimensions_hint: DimensionsHint,
+ pub decoration_hint: ffi::river_window_v1_decoration_hint,
+ pub show_window_menu_requested: Option<ShowWindowMenuRequest>,
+ pub fullscreen_requested: FullscreenRequest,
+ pub maximize_requested: MaximizeRequest,
+ pub minimize_requested: bool,
+ pub dirty_app_id: bool,
+ pub dirty_title: bool,
+ pub pointer_move_requested: *mut crate::seat::Seat,
+ pub pointer_resize_requested: Option<PointerResizeRequest>,
+}
+
+pub struct WmSentState {
+ pub dimensions_hint: DimensionsHint,
+ pub decoration_hint: ffi::river_window_v1_decoration_hint,
+ pub parent: Option<crate::slotmap::Key>,
+}
+
+pub struct WmRequestedState {
+ pub dimensions: Option<Dimensions>,
+ pub bounds: Dimensions,
+ pub ssd: bool,
+ pub tiled: u32,
+ pub capabilities: u32,
+ pub resizing: bool,
+ pub maximized: bool,
+ pub fullscreen: *mut crate::output::Output,
+ pub inform_fullscreen: bool,
+ pub close: bool,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Configure {
+ pub width: Option<u32>,
+ pub height: Option<u32>,
+ pub bounds: Dimensions,
+ pub activated: bool,
+ pub ssd: bool,
+ pub tiled: u32,
+ pub capabilities: u32,
+ pub maximized: bool,
+ pub inform_fullscreen: bool,
+ pub resizing: bool,
+}
+
+impl Configure {
+ pub fn new() -> Self {
+ Self {
+ width: None,
+ height: None,
+ bounds: Dimensions { width: 0, height: 0 },
+ activated: false,
+ ssd: false,
+ tiled: 0,
+ capabilities: 0,
+ maximized: false,
+ inform_fullscreen: false,
+ resizing: false,
+ }
+ }
+}
+
+pub struct WindowRenderingScheduled {
+ pub width: u32,
+ pub height: u32,
+ pub resend_dimensions: bool,
+}
+
+pub struct WindowRenderingSent {
+ pub width: u32,
+ pub height: u32,
+ pub presentation_hint: ffi::river_output_v1_presentation_mode,
+}
+
+pub struct WindowRenderingRequested {
+ pub x: i32,
+ pub y: i32,
+ pub hidden: bool,
+ pub border: Border,
+ pub clip: ffi::wlr_box,
+ pub content_clip: ffi::wlr_box,
+}
+
+pub struct Window {
+ pub ref_key: crate::slotmap::Key,
+ pub server: *mut Server,
+ pub object: *mut ffi::wl_resource, // river_window_v1
+ pub node: WmNode,
+ pub state: WindowState,
+ pub impl_type: WindowImpl,
+
+ pub tree: *mut ffi::wlr_scene_tree,
+ pub fullscreen_background: *mut ffi::wlr_scene_rect,
+ pub decorations_below: ffi::wl_list,
+ pub decorations_below_tree: *mut ffi::wlr_scene_tree,
+ pub surfaces: crate::scene::SaveableSurfaces,
+ pub border: BorderRects,
+ pub decorations_above: ffi::wl_list,
+ pub decorations_above_tree: *mut ffi::wlr_scene_tree,
+ pub popup_tree: *mut ffi::wlr_scene_tree,
+ pub capture_scene: *mut ffi::wlr_scene,
+ pub capture_source: *mut ffi::wlr_ext_image_capture_source_v1,
+
+ pub wm_scheduled: WmScheduledState,
+ pub wm_sent: WmSentState,
+ pub wm_requested: WmRequestedState,
+ pub configure_scheduled: Configure,
+ pub configure_sent: Configure,
+ pub rendering_scheduled: WindowRenderingScheduled,
+ pub rendering_sent: WindowRenderingSent,
+ pub rendering_requested: WindowRenderingRequested,
+ pub box_geom: ffi::wlr_box,
+ pub foreign_toplevel_handle: *mut ffi::wlr_ext_foreign_toplevel_handle_v1,
+ pub wlr_toplevel_handle: *mut ffi::wlr_foreign_toplevel_handle_v1,
+}
+
+impl Window {
+ pub unsafe fn create(impl_type: WindowImpl, server: *mut Server) -> Result<*mut Self, &'static str> {
+ let hidden_tree = (*server).scene.hidden_tree;
+ let tree = ffi::wlr_scene_tree_create(hidden_tree);
+ if tree.is_null() {
+ return Err("Failed to create tree");
+ }
+
+ let popup_tree = ffi::wlr_scene_tree_create(hidden_tree);
+ if popup_tree.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ return Err("Failed to create popup_tree");
+ }
+
+ let capture_scene = ffi::wlr_scene_create();
+ if capture_scene.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
+ return Err("Failed to create capture_scene");
+ }
+ (*capture_scene).restack_xwayland_surfaces = false;
+
+ let black_color = [0.0f32, 0.0f32, 0.0f32, 1.0f32];
+ let fullscreen_background = ffi::wlr_scene_rect_create(tree, 0, 0, black_color.as_ptr());
+ if fullscreen_background.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(&mut (*capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node);
+ return Err("Failed to create fullscreen rect");
+ }
+
+ let clear_color = [0.0f32, 0.0f32, 0.0f32, 0.0f32];
+ let border_left = ffi::wlr_scene_rect_create(tree, 0, 0, clear_color.as_ptr());
+ let border_right = ffi::wlr_scene_rect_create(tree, 0, 0, clear_color.as_ptr());
+ let border_top = ffi::wlr_scene_rect_create(tree, 0, 0, clear_color.as_ptr());
+ let border_bottom = ffi::wlr_scene_rect_create(tree, 0, 0, clear_color.as_ptr());
+
+ let decorations_below_tree = ffi::wlr_scene_tree_create(tree);
+ let decorations_above_tree = ffi::wlr_scene_tree_create(tree);
+
+ let surfaces = match crate::scene::SaveableSurfaces::init(tree) {
+ Ok(s) => s,
+ Err(e) => {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(&mut (*capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node);
+ return Err(e);
+ }
+ };
+
+ let mut window = Box::new(Window {
+ ref_key: crate::slotmap::Key { generation: 0, index: 0 },
+ server,
+ object: std::ptr::null_mut(),
+ node: std::mem::zeroed(),
+ state: WindowState::Init,
+ impl_type,
+ tree,
+ fullscreen_background,
+ decorations_below: std::mem::zeroed(),
+ decorations_below_tree,
+ surfaces,
+ border: BorderRects {
+ left: border_left,
+ right: border_right,
+ top: border_top,
+ bottom: border_bottom,
+ },
+ decorations_above: std::mem::zeroed(),
+ decorations_above_tree,
+ popup_tree,
+ capture_scene,
+ capture_source: std::ptr::null_mut(),
+ wm_scheduled: WmScheduledState {
+ dimensions_hint: DimensionsHint { min_width: 0, min_height: 0, max_width: 0, max_height: 0 },
+ decoration_hint: ffi::river_window_v1_decoration_hint_RIVER_WINDOW_V1_DECORATION_HINT_ONLY_SUPPORTS_CSD,
+ show_window_menu_requested: None,
+ fullscreen_requested: FullscreenRequest::NoRequest,
+ maximize_requested: MaximizeRequest::NoRequest,
+ minimize_requested: false,
+ dirty_app_id: false,
+ dirty_title: false,
+ pointer_move_requested: std::ptr::null_mut(),
+ pointer_resize_requested: None,
+ },
+ wm_sent: WmSentState {
+ dimensions_hint: DimensionsHint { min_width: 0, min_height: 0, max_width: 0, max_height: 0 },
+ decoration_hint: ffi::river_window_v1_decoration_hint_RIVER_WINDOW_V1_DECORATION_HINT_ONLY_SUPPORTS_CSD,
+ parent: None,
+ },
+ wm_requested: WmRequestedState {
+ dimensions: None,
+ bounds: Dimensions { width: 0, height: 0 },
+ ssd: false,
+ tiled: 0,
+ capabilities: 1 | 2 | 4 | 8,
+ resizing: false,
+ maximized: false,
+ fullscreen: std::ptr::null_mut(),
+ inform_fullscreen: false,
+ close: false,
+ },
+ configure_scheduled: Configure::new(),
+ configure_sent: Configure::new(),
+ rendering_scheduled: WindowRenderingScheduled {
+ width: 0,
+ height: 0,
+ resend_dimensions: false,
+ },
+ rendering_sent: WindowRenderingSent {
+ width: 0,
+ height: 0,
+ presentation_hint: ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_VSYNC,
+ },
+ rendering_requested: WindowRenderingRequested {
+ x: 0,
+ y: 0,
+ hidden: false,
+ border: Border { edges: Edges::new(), width: 0, r: 0, g: 0, b: 0, a: 0 },
+ clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ content_clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ },
+ box_geom: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ foreign_toplevel_handle: std::ptr::null_mut(),
+ wlr_toplevel_handle: std::ptr::null_mut(),
+ });
+
+ ffi::wl_list_init(&mut window.decorations_below);
+ ffi::wl_list_init(&mut window.decorations_above);
+
+ let raw = Box::into_raw(window);
+ (*raw).node.init(crate::wm_node::WmNodeTag::Window);
+
+ ffi::wlr_scene_node_set_enabled(tree as *mut ffi::wlr_scene_node, false);
+ ffi::wlr_scene_node_set_enabled(popup_tree as *mut ffi::wlr_scene_node, false);
+ ffi::wlr_scene_node_set_enabled(fullscreen_background as *mut ffi::wlr_scene_node, false);
+
+ crate::scene_node_data::SceneNodeData::attach(
+ tree as *mut ffi::wlr_scene_node,
+ crate::scene_node_data::SceneNodeDataVal::Window(raw),
+ );
+ crate::scene_node_data::SceneNodeData::attach(
+ popup_tree as *mut ffi::wlr_scene_node,
+ crate::scene_node_data::SceneNodeDataVal::Window(raw),
+ );
+
+ Ok(raw)
+ }
+
+ pub unsafe fn set_impl(&mut self, impl_type: WindowImpl) {
+ self.impl_type = impl_type;
+ }
+
+ pub unsafe fn impl_destroying(&mut self) {
+ self.impl_type = WindowImpl::Destroying;
+ }
+
+ pub unsafe fn get_title(&self) -> *const libc::c_char {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ std::ptr::null()
+ } else {
+ ffi::river_wlr_xdg_toplevel_get_title((*toplevel).wlr_toplevel)
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() {
+ std::ptr::null()
+ } else {
+ (*(*xwindow).xsurface).title
+ }
+ }
+ WindowImpl::Destroying => std::ptr::null(),
+ }
+ }
+
+ pub unsafe fn get_app_id(&self) -> *const libc::c_char {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ std::ptr::null()
+ } else {
+ ffi::river_wlr_xdg_toplevel_get_app_id((*toplevel).wlr_toplevel)
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() {
+ std::ptr::null()
+ } else {
+ (*(*xwindow).xsurface).class
+ }
+ }
+ WindowImpl::Destroying => std::ptr::null(),
+ }
+ }
+
+ pub unsafe fn get_parent(&self) -> *mut Window {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let wlr_parent = ffi::river_wlr_xdg_toplevel_get_parent((*toplevel).wlr_toplevel);
+ if wlr_parent.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let base = ffi::river_wlr_xdg_toplevel_get_base(wlr_parent);
+ let parent_xdg = ffi::river_wlr_xdg_surface_get_data(base) as *mut crate::xdg_toplevel::XdgToplevel;
+ if parent_xdg.is_null() {
+ std::ptr::null_mut()
+ } else {
+ (*parent_xdg).window
+ }
+ }
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let parent_xsurface = (*(*xwindow).xsurface).parent;
+ if parent_xsurface.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let parent_data = (*parent_xsurface).data;
+ if parent_data.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let parent_xwindow = parent_data as *mut crate::xwayland_window::XwaylandWindow;
+ (*parent_xwindow).window
+ }
+ }
+ }
+ }
+ WindowImpl::Destroying => std::ptr::null_mut(),
+ }
+ }
+
+ pub unsafe fn unreliable_pid(&self) -> i32 {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ 0
+ } else {
+ let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
+ let surface = ffi::river_wlr_xdg_surface_get_surface(base);
+ if surface.is_null() {
+ 0
+ } else {
+ let res = ffi::river_wlr_surface_get_resource(surface);
+ if res.is_null() {
+ 0
+ } else {
+ let client = ffi::wl_resource_get_client(res);
+ if client.is_null() {
+ 0
+ } else {
+ let mut pid = 0;
+ let mut uid = 0;
+ let mut gid = 0;
+ ffi::wl_client_get_credentials(client, &mut pid, &mut uid, &mut gid);
+ pid
+ }
+ }
+ }
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() {
+ 0
+ } else {
+ (*(*xwindow).xsurface).pid
+ }
+ }
+ WindowImpl::Destroying => 0,
+ }
+ }
+
+ pub unsafe fn map(&mut self) -> Result<(), &'static str> {
+ log::debug!("window '{:?}' mapped", self.get_title());
+ assert!(!matches!(self.impl_type, WindowImpl::Destroying));
+ assert_eq!(self.state, WindowState::Initialized);
+ self.state = WindowState::Mapped;
+ Ok(())
+ }
+
+ pub unsafe fn unmap(&mut self) {
+ log::debug!("window '{:?}' unmapped", self.get_title());
+ self.surfaces.save();
+ assert!(!matches!(self.impl_type, WindowImpl::Destroying));
+ assert_eq!(self.state, WindowState::Mapped);
+ self.state = WindowState::Closing;
+ (*self.server).wm.dirty_windowing();
+
+ if !self.foreign_toplevel_handle.is_null() {
+ ffi::wlr_ext_foreign_toplevel_handle_v1_destroy(self.foreign_toplevel_handle);
+ self.foreign_toplevel_handle = std::ptr::null_mut();
+ }
+ if !self.wlr_toplevel_handle.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_destroy(self.wlr_toplevel_handle);
+ self.wlr_toplevel_handle = std::ptr::null_mut();
+ }
+ }
+
+ pub unsafe fn close(&mut self) {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if !toplevel.is_null() {
+ ffi::wlr_xdg_toplevel_send_close((*toplevel).wlr_toplevel);
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if !xwindow.is_null() {
+ ffi::wlr_xwayland_surface_close((*xwindow).xsurface);
+ }
+ }
+ WindowImpl::Destroying => {}
+ }
+ }
+
+ pub unsafe fn destroy(window: *mut Window) {
+ assert!(matches!((*window).impl_type, WindowImpl::Destroying));
+ match (*window).state {
+ WindowState::Init => {}
+ WindowState::Closing => {
+ (*(*window).server).wm.dirty_windowing();
+ return;
+ }
+ _ => unreachable!(),
+ }
+ assert!((*window).object.is_null());
+
+ let seats = &mut (*(*window).server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ if let crate::seat::Focus::Window(w) = (*seat).focused {
+ if w == window {
+ (*seat).focus(crate::seat::Focus::None);
+ }
+ }
+ curr = next;
+ }
+
+ // Destroy decorations
+ for decorations in [&mut (*window).decorations_above as *mut ffi::wl_list, &mut (*window).decorations_below as *mut ffi::wl_list] {
+ let list_head = decorations as *mut WlList;
+ let mut curr = (*list_head).next;
+ while curr != list_head {
+ let next = (*curr).next;
+ let dec = crate::container_of!(curr, Decoration, link);
+ (*dec).destroy();
+ curr = next;
+ }
+ }
+
+ ffi::wlr_scene_node_destroy((*window).tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy((*window).popup_tree as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_destroy(&mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node);
+
+ (*window).node.deinit();
+
+ let _ = Box::from_raw(window);
+ }
+
+ pub unsafe fn set_dimensions_hint(&mut self, hint: DimensionsHint) {
+ self.wm_scheduled.dimensions_hint = hint;
+ if self.wm_sent.dimensions_hint != hint {
+ (*self.server).wm.dirty_windowing();
+ }
+ }
+
+ pub unsafe fn set_dimensions(&mut self, width: u32, height: u32) {
+ self.rendering_scheduled.width = width;
+ self.rendering_scheduled.height = height;
+
+ if self.rendering_scheduled.resend_dimensions ||
+ self.rendering_scheduled.width != self.rendering_sent.width ||
+ self.rendering_scheduled.height != self.rendering_sent.height {
+ (*self.server).wm.dirty_rendering();
+ }
+ }
+
+ pub unsafe fn set_decoration_hint(&mut self, hint: ffi::river_window_v1_decoration_hint) {
+ self.wm_scheduled.decoration_hint = hint;
+ if hint != self.wm_sent.decoration_hint {
+ (*self.server).wm.dirty_windowing();
+ }
+ }
+
+ pub unsafe fn root_surface(&self) -> *mut ffi::wlr_surface {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
+ ffi::river_wlr_xdg_surface_get_surface(base)
+ }
+ }
+ _ => std::ptr::null_mut(),
+ }
+ }
+
+ pub unsafe fn send_frame_done(&self) {
+ assert_eq!(self.state, WindowState::Mapped);
+ if !matches!(self.impl_type, WindowImpl::Destroying) {
+ let mut now = std::mem::zeroed();
+ clock_gettime(libc::CLOCK_MONOTONIC, &mut now);
+ let now_ffi = ffi::timespec {
+ tv_sec: now.tv_sec as _,
+ tv_nsec: now.tv_nsec as _,
+ };
+ ffi::wlr_surface_send_frame_done(self.root_surface(), &now_ffi);
+ }
+ }
+
+ pub unsafe fn manage_start(&mut self) {
+ match self.state {
+ WindowState::Init => {}
+ WindowState::Closing => {
+ self.state = WindowState::Init;
+ self.wm_sent = WmSentState {
+ dimensions_hint: DimensionsHint { min_width: 0, min_height: 0, max_width: 0, max_height: 0 },
+ decoration_hint: ffi::river_window_v1_decoration_hint_RIVER_WINDOW_V1_DECORATION_HINT_ONLY_SUPPORTS_CSD,
+ parent: None,
+ };
+ self.wm_requested = WmRequestedState {
+ dimensions: None,
+ bounds: Dimensions { width: 0, height: 0 },
+ ssd: false,
+ tiled: 0,
+ capabilities: 1 | 2 | 4 | 8,
+ resizing: false,
+ maximized: false,
+ fullscreen: std::ptr::null_mut(),
+ inform_fullscreen: false,
+ close: false,
+ };
+ self.rendering_sent = WindowRenderingSent {
+ width: 0,
+ height: 0,
+ presentation_hint: ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_VSYNC,
+ };
+ self.rendering_requested = WindowRenderingRequested {
+ x: 0,
+ y: 0,
+ hidden: false,
+ border: Border { edges: Edges::new(), width: 0, r: 0, g: 0, b: 0, a: 0 },
+ clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ content_clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+ };
+
+ wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
+ self.node.link.prev = &mut self.node.link;
+ self.node.link.next = &mut self.node.link;
+
+ self.make_inert();
+ }
+ WindowState::Ready | WindowState::Initialized | WindowState::Mapped => {
+ let wm_v1 = (*self.server).wm.object;
+ if wm_v1.is_null() {
+ return;
+ }
+ let new_resource = self.object.is_null();
+ let window_v1 = if new_resource {
+ let client = ffi::wl_resource_get_client(wm_v1);
+ let res = ffi::wl_resource_create(client, &ffi::river_window_v1_interface, ffi::wl_resource_get_version(wm_v1), 0);
+ if res.is_null() {
+ log::error!("out of memory");
+ return;
+ }
+ self.object = res;
+ ffi::wl_resource_set_implementation(
+ res,
+ &WINDOW_INTERFACE as *const _ as *const _,
+ self as *mut Window as *mut _,
+ Some(handle_destroy_resource),
+ );
+
+ // Send window to manager
+ ffi::wl_resource_post_event(wm_v1, 0, res); // river_window_manager_v1.window
+
+ wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
+ let rendering_list = &mut (*self.server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*rendering_list).prev, &mut self.node.link as *mut ffi::wl_list as *mut WlList);
+
+ if self.foreign_toplevel_handle.is_null() {
+ let list = (*self.server).foreign_toplevel_list;
+ let title = self.get_title();
+ let app_id = self.get_app_id();
+ let state = ffi::wlr_ext_foreign_toplevel_handle_v1_state {
+ title,
+ app_id,
+ };
+ let handle = ffi::wlr_ext_foreign_toplevel_handle_v1_create(list, &state);
+ if !handle.is_null() {
+ self.foreign_toplevel_handle = handle;
+ (*handle).data = self as *mut Window as *mut _;
+ }
+ }
+
+ if self.wlr_toplevel_handle.is_null() {
+ let manager = (*self.server).wlr_foreign_toplevel_manager;
+ let handle = ffi::wlr_foreign_toplevel_handle_v1_create(manager);
+ if !handle.is_null() {
+ self.wlr_toplevel_handle = handle;
+ let title = self.get_title();
+ if !title.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_title(handle, title);
+ }
+ let app_id = self.get_app_id();
+ if !app_id.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_app_id(handle, app_id);
+ }
+ }
+ }
+
+ res
+ } else {
+ self.object
+ };
+
+ if new_resource {
+ let version = ffi::wl_resource_get_version(window_v1);
+ if version >= 2 {
+ ffi::wl_resource_post_event(window_v1, 14, self.unreliable_pid()); // sendUnreliablePid
+ }
+ if version >= 4 {
+ if !self.foreign_toplevel_handle.is_null() {
+ let identifier = (*self.foreign_toplevel_handle).identifier;
+ ffi::wl_resource_post_event(window_v1, 16, identifier);
+ }
+ }
+ }
+
+ if new_resource || self.wm_scheduled.dimensions_hint != self.wm_sent.dimensions_hint {
+ ffi::wl_resource_post_event(
+ window_v1,
+ 2, // sendDimensionsHint
+ self.wm_scheduled.dimensions_hint.min_width as i32,
+ self.wm_scheduled.dimensions_hint.min_height as i32,
+ self.wm_scheduled.dimensions_hint.max_width as i32,
+ self.wm_scheduled.dimensions_hint.max_height as i32,
+ );
+ self.wm_sent.dimensions_hint = self.wm_scheduled.dimensions_hint;
+ }
+
+ if new_resource || self.wm_scheduled.decoration_hint != self.wm_sent.decoration_hint {
+ ffi::wl_resource_post_event(window_v1, 1, self.wm_scheduled.decoration_hint); // sendDecorationHint
+ self.wm_sent.decoration_hint = self.wm_scheduled.decoration_hint;
+ }
+
+ if let Some(ref offset) = self.wm_scheduled.show_window_menu_requested {
+ ffi::wl_resource_post_event(window_v1, 3, offset.x, offset.y); // sendShowWindowMenuRequested
+ self.wm_scheduled.show_window_menu_requested = None;
+ }
+
+ match self.wm_scheduled.fullscreen_requested {
+ FullscreenRequest::NoRequest => {}
+ FullscreenRequest::Fullscreen(output) => {
+ let out_resource = if output.is_null() { std::ptr::null_mut() } else { (*output).object };
+ ffi::wl_resource_post_event(window_v1, 4, out_resource); // sendFullscreenRequested
+ }
+ FullscreenRequest::Exit => {
+ ffi::wl_resource_post_event(window_v1, 5); // sendExitFullscreenRequested
+ }
+ }
+ self.wm_scheduled.fullscreen_requested = FullscreenRequest::NoRequest;
+
+ match self.wm_scheduled.maximize_requested {
+ MaximizeRequest::NoRequest => {}
+ MaximizeRequest::Maximize => {
+ ffi::wl_resource_post_event(window_v1, 6); // sendMaximizeRequested
+ }
+ MaximizeRequest::Unmaximize => {
+ ffi::wl_resource_post_event(window_v1, 7); // sendUnmaximizeRequested
+ }
+ }
+ self.wm_scheduled.maximize_requested = MaximizeRequest::NoRequest;
+
+ if self.wm_scheduled.minimize_requested {
+ ffi::wl_resource_post_event(window_v1, 8); // sendMinimizeRequested
+ }
+ self.wm_scheduled.minimize_requested = false;
+
+ let parent = self.get_parent();
+ if !parent.is_null() {
+ let parent_ref = Some((*parent).ref_key);
+ if self.wm_sent.parent.is_none() || self.wm_sent.parent != parent_ref {
+ let parent_obj = (*parent).object;
+ ffi::wl_resource_post_event(window_v1, 11, parent_obj); // sendParent
+ self.wm_sent.parent = parent_ref;
+ }
+ } else if self.wm_sent.parent.is_some() {
+ ffi::wl_resource_post_event(window_v1, 11, std::ptr::null_mut::<ffi::wl_resource>()); // sendParent
+ self.wm_sent.parent = None;
+ }
+
+ if new_resource || self.wm_scheduled.dirty_app_id {
+ let app_id = self.get_app_id();
+ ffi::wl_resource_post_event(window_v1, 13, app_id); // sendAppId
+ self.wm_scheduled.dirty_app_id = false;
+ }
+
+ if new_resource || self.wm_scheduled.dirty_title {
+ let title = self.get_title();
+ ffi::wl_resource_post_event(window_v1, 12, title); // sendTitle
+ self.wm_scheduled.dirty_title = false;
+ }
+
+ if let Some(seat) = self.wm_scheduled.pointer_move_requested.as_mut() {
+ if !seat.object.is_null() {
+ ffi::wl_resource_post_event(window_v1, 9, seat.object); // sendPointerMoveRequested
+ }
+ }
+ self.wm_scheduled.pointer_move_requested = std::ptr::null_mut();
+
+ if let Some(ref data) = self.wm_scheduled.pointer_resize_requested {
+ if !(*data.seat).object.is_null() {
+ ffi::wl_resource_post_event(window_v1, 10, (*data.seat).object, data.edges); // sendPointerResizeRequested
+ }
+ }
+ self.wm_scheduled.pointer_resize_requested = None;
+ }
+ }
+ }
+
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 15); // sendClosed
+ ffi::wl_resource_set_implementation(
+ self.object,
+ &INERT_WINDOW_INTERFACE as *const _ as *const _,
+ std::ptr::null_mut(),
+ None,
+ );
+ self.object = std::ptr::null_mut();
+ (*self.server).wm.dirty_windowing();
+ self.node.make_inert();
+
+ for decorations in [&mut self.decorations_above as *mut ffi::wl_list, &mut self.decorations_below as *mut ffi::wl_list] {
+ let list_head = decorations as *mut WlList;
+ let mut curr = (*list_head).next;
+ while curr != list_head {
+ let next = (*curr).next;
+ let dec = crate::container_of!(curr, Decoration, link);
+ (*dec).make_inert();
+ curr = next;
+ }
+ }
+
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ if let crate::seat::Focus::Window(w) = (*seat).focused {
+ if w == self as *mut Window {
+ (*seat).focus(crate::seat::Focus::None);
+ }
+ }
+ curr = next;
+ }
+ }
+ }
+
+ pub unsafe fn manage_finish(&mut self) -> bool {
+ if matches!(self.impl_type, WindowImpl::Destroying) {
+ assert_eq!(self.state, WindowState::Closing);
+ return false;
+ }
+
+ match self.state {
+ WindowState::Init => unreachable!(),
+ WindowState::Ready => {
+ if self.wm_requested.dimensions.is_none() && self.wm_requested.fullscreen.is_null() {
+ return false;
+ }
+ self.state = WindowState::Initialized;
+ }
+ WindowState::Initialized | WindowState::Mapped => {}
+ WindowState::Closing => return false,
+ }
+
+ if self.wm_requested.close {
+ self.close();
+ self.wm_requested.close = false;
+ }
+
+ let mut activated = false;
+ let seats = &mut (*self.server).wm.sent.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link_sent);
+ if let crate::seat::Focus::Window(w) = (*seat).focused {
+ if w == self as *mut Window {
+ activated = true;
+ break;
+ }
+ }
+ curr = next;
+ }
+
+ if !self.wlr_toplevel_handle.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_activated(self.wlr_toplevel_handle, activated);
+ }
+
+ let (width, height) = if !self.wm_requested.fullscreen.is_null() {
+ let output = self.wm_requested.fullscreen;
+ let (w, h) = (*output).sent.dimensions();
+ if self.configure_sent.width != Some(w as u32) || self.configure_sent.height != Some(h as u32) {
+ self.configure_scheduled.width = Some(w as u32);
+ self.configure_scheduled.height = Some(h as u32);
+ self.rendering_scheduled.resend_dimensions = true;
+ (Some(w as u32), Some(h as u32))
+ } else {
+ (None, None)
+ }
+ } else if let Some(dimensions) = self.wm_requested.dimensions {
+ self.rendering_scheduled.resend_dimensions = true;
+ (Some(dimensions.width), Some(dimensions.height))
+ } else {
+ (None, None)
+ };
+ self.wm_requested.dimensions = None;
+
+ self.configure_scheduled = Configure {
+ width,
+ height,
+ bounds: self.wm_requested.bounds,
+ activated,
+ ssd: self.wm_requested.ssd,
+ tiled: self.wm_requested.tiled,
+ capabilities: self.wm_requested.capabilities,
+ maximized: self.wm_requested.maximized,
+ inform_fullscreen: self.wm_requested.inform_fullscreen,
+ resizing: self.wm_requested.resizing,
+ };
+
+ let track_configure = match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ false
+ } else {
+ (*toplevel).configure()
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() {
+ false
+ } else {
+ (*xwindow).configure()
+ }
+ }
+ WindowImpl::Destroying => unreachable!(),
+ };
+
+ if track_configure && matches!(self.state, WindowState::Mapped) {
+ self.surfaces.save();
+ self.send_frame_done();
+ }
+
+ track_configure
+ }
+
+ pub unsafe fn render_start(&mut self) {
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if !toplevel.is_null() {
+ match (*toplevel).configure_state {
+ ConfigureState::Inflight(serial) => {
+ (*toplevel).configure_state = ConfigureState::TimedOut(serial);
+ }
+ ConfigureState::Acked => {
+ (*toplevel).configure_state = ConfigureState::TimedOutAcked;
+ }
+ ConfigureState::Committed => {
+ (*toplevel).configure_state = ConfigureState::Idle;
+ }
+ _ => {}
+ }
+ self.rendering_scheduled.width = (*toplevel).geometry.width as u32;
+ self.rendering_scheduled.height = (*toplevel).geometry.height as u32;
+ }
+ }
+ WindowImpl::Xwayland(xwindow) => {
+ if !xwindow.is_null() {
+ self.rendering_scheduled.width = (*(*xwindow).xsurface).width as u32;
+ self.rendering_scheduled.height = (*(*xwindow).xsurface).height as u32;
+ }
+ }
+ WindowImpl::Destroying => {}
+ }
+
+ let presentation_hint = self.presentation_hint();
+ let sent = &mut self.rendering_sent;
+ let scheduled = &mut self.rendering_scheduled;
+
+ if matches!(self.state, WindowState::Mapped) &&
+ (scheduled.resend_dimensions ||
+ scheduled.width != sent.width || scheduled.height != sent.height) {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 0, scheduled.width as i32, scheduled.height as i32); // sendDimensions
+ scheduled.resend_dimensions = false;
+ }
+ }
+ sent.width = scheduled.width;
+ sent.height = scheduled.height;
+ if sent.presentation_hint != presentation_hint {
+ if !self.object.is_null() {
+ let version = ffi::wl_resource_get_version(self.object);
+ if version >= 4 {
+ ffi::wl_resource_post_event(self.object, 17, presentation_hint); // sendPresentationHint
+ }
+ }
+ sent.presentation_hint = presentation_hint;
+ }
+ }
+
+ pub unsafe fn presentation_hint(&self) -> ffi::river_output_v1_presentation_mode {
+ let root = self.root_surface();
+ if root.is_null() {
+ return ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_VSYNC;
+ }
+
+ // tearing control check stub:
+ // switch (server.tearing_control_manager.hintFromSurface(root)) {
+ // .async => .async,
+ // .vsync => .vsync,
+ // }
+ // For now, return VSYNC by default.
+ ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_VSYNC
+ }
+
+ pub unsafe fn notify_title(&mut self) {
+ self.wm_scheduled.dirty_title = true;
+ (*self.server).wm.dirty_windowing();
+
+ if !self.foreign_toplevel_handle.is_null() {
+ let title = self.get_title();
+ let app_id = self.get_app_id();
+ let state = ffi::wlr_ext_foreign_toplevel_handle_v1_state {
+ title,
+ app_id,
+ };
+ ffi::wlr_ext_foreign_toplevel_handle_v1_update_state(self.foreign_toplevel_handle, &state);
+ }
+
+ if !self.wlr_toplevel_handle.is_null() {
+ let title = self.get_title();
+ if !title.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_title(self.wlr_toplevel_handle, title);
+ }
+ }
+ }
+
+ pub unsafe fn notify_app_id(&mut self) {
+ self.wm_scheduled.dirty_app_id = true;
+ (*self.server).wm.dirty_windowing();
+
+ if !self.foreign_toplevel_handle.is_null() {
+ let title = self.get_title();
+ let app_id = self.get_app_id();
+ let state = ffi::wlr_ext_foreign_toplevel_handle_v1_state {
+ title,
+ app_id,
+ };
+ ffi::wlr_ext_foreign_toplevel_handle_v1_update_state(self.foreign_toplevel_handle, &state);
+ }
+
+ if !self.wlr_toplevel_handle.is_null() {
+ let app_id = self.get_app_id();
+ if !app_id.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_app_id(self.wlr_toplevel_handle, app_id);
+ }
+ }
+ }
+
+ pub unsafe fn render_finish(&mut self) {
+ let requested = &self.rendering_requested;
+ let enabled = !requested.hidden && (matches!(self.state, WindowState::Mapped) || matches!(self.state, WindowState::Closing));
+
+ ffi::wlr_scene_node_set_enabled(self.tree as *mut ffi::wlr_scene_node, enabled);
+ ffi::wlr_scene_node_set_enabled(self.popup_tree as *mut ffi::wlr_scene_node, enabled);
+
+ self.box_geom.width = self.rendering_sent.width as i32;
+ self.box_geom.height = self.rendering_sent.height as i32;
+
+ let mut clip = requested.clip;
+ let mut content_clip = requested.content_clip;
+
+ if !self.wm_requested.fullscreen.is_null() {
+ let output = self.wm_requested.fullscreen;
+ self.box_geom.x = (*output).sent.x;
+ self.box_geom.y = (*output).sent.y;
+ ffi::wlr_scene_node_set_enabled(self.fullscreen_background as *mut ffi::wlr_scene_node, true);
+ let (width, height) = (*output).sent.dimensions();
+ ffi::wlr_scene_rect_set_size(self.fullscreen_background, width as i32, height as i32);
+ clip = ffi::wlr_box { x: 0, y: 0, width: width as i32, height: height as i32 };
+ content_clip = ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 };
+
+ ffi::wlr_scene_node_set_enabled(self.border.left as *mut ffi::wlr_scene_node, false);
+ ffi::wlr_scene_node_set_enabled(self.border.right as *mut ffi::wlr_scene_node, false);
+ ffi::wlr_scene_node_set_enabled(self.border.top as *mut ffi::wlr_scene_node, false);
+ ffi::wlr_scene_node_set_enabled(self.border.bottom as *mut ffi::wlr_scene_node, false);
+ } else {
+ self.box_geom.x = requested.x;
+ self.box_geom.y = requested.y;
+ ffi::wlr_scene_node_set_enabled(self.fullscreen_background as *mut ffi::wlr_scene_node, false);
+ self.draw_borders();
+ }
+
+ ffi::wlr_scene_node_set_position(self.tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
+ ffi::wlr_scene_node_set_position(self.popup_tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
+
+ self.apply_surface_clip(&clip, &content_clip);
+
+ for decorations in [&mut self.decorations_above as *mut ffi::wl_list, &mut self.decorations_below as *mut ffi::wl_list] {
+ let list_head = decorations as *mut WlList;
+ let mut curr = (*list_head).next;
+ while curr != list_head {
+ let next = (*curr).next;
+ let dec = crate::container_of!(curr, Decoration, link);
+ (*dec).render_finish(&clip);
+ curr = next;
+ }
+ }
+
+ match self.impl_type {
+ WindowImpl::Xwayland(xwindow) => {
+ if !xwindow.is_null() {
+ (*xwindow).configure();
+ }
+ }
+ _ => {}
+ }
+ }
+
+ pub unsafe fn draw_borders(&mut self) {
+ let requested = &self.rendering_requested;
+ let content = ffi::wlr_box {
+ x: 0,
+ y: 0,
+ width: self.box_geom.width,
+ height: self.box_geom.height,
+ };
+
+ let mut intersect = std::mem::zeroed();
+ let clip_empty = requested.content_clip.width == 0 && requested.content_clip.height == 0;
+ if clip_empty || ffi::wlr_box_intersection(&mut intersect, &content, &requested.content_clip) {
+ let border = &requested.border;
+ let color: [f32; 4] = [
+ (border.r as f64 / u32::MAX as f64) as f32,
+ (border.g as f64 / u32::MAX as f64) as f32,
+ (border.b as f64 / u32::MAX as f64) as f32,
+ (border.a as f64 / u32::MAX as f64) as f32,
+ ];
+
+ let mut left = ffi::wlr_box {
+ x: -(border.width as i32),
+ y: 0,
+ width: border.width as i32,
+ height: content.height,
+ };
+ let mut right = ffi::wlr_box {
+ x: content.width,
+ y: 0,
+ width: border.width as i32,
+ height: content.height,
+ };
+ let mut top = ffi::wlr_box {
+ x: 0,
+ y: -(border.width as i32),
+ width: content.width,
+ height: border.width as i32,
+ };
+ let mut bottom = ffi::wlr_box {
+ x: 0,
+ y: content.height,
+ width: content.width,
+ height: border.width as i32,
+ };
+
+ if border.edges.top {
+ left.y -= border.width as i32;
+ left.height += border.width as i32;
+ right.y -= border.width as i32;
+ right.height += border.width as i32;
+ }
+ if border.edges.bottom {
+ left.height += border.width as i32;
+ right.height += border.width as i32;
+ }
+
+ let mut edges = [
+ ("left", &mut left, self.border.left, border.edges.left),
+ ("right", &mut right, self.border.right, border.edges.right),
+ ("top", &mut top, self.border.top, border.edges.top),
+ ("bottom", &mut bottom, self.border.bottom, border.edges.bottom),
+ ];
+
+ for (_, edge_box, rect, enabled) in &mut edges {
+ if requested.clip.width != 0 || requested.clip.height != 0 {
+ let mut clip_intersect = std::mem::zeroed();
+ ffi::wlr_box_intersection(&mut clip_intersect, *edge_box, &requested.clip);
+ **edge_box = clip_intersect;
+ }
+ ffi::wlr_scene_node_set_enabled(*rect as *mut ffi::wlr_scene_node, *enabled);
+ ffi::wlr_scene_node_set_position(*rect as *mut ffi::wlr_scene_node, (*edge_box).x, (*edge_box).y);
+ ffi::wlr_scene_rect_set_size(*rect, (*edge_box).width, (*edge_box).height);
+ ffi::wlr_scene_rect_set_color(*rect, color.as_ptr());
+ }
+ }
+ }
+
+ pub unsafe fn apply_surface_clip(&mut self, a: *const ffi::wlr_box, b: *const ffi::wlr_box) {
+ let mut surface_clip = std::mem::zeroed::<ffi::wlr_box>();
+ let a_empty = (*a).width == 0 && (*a).height == 0;
+ let b_empty = (*b).width == 0 && (*b).height == 0;
+
+ if !a_empty && !b_empty {
+ if !ffi::wlr_box_intersection(&mut surface_clip, a, b) {
+ self.surfaces.set_enabled(false);
+ return;
+ }
+ } else if !a_empty {
+ surface_clip = *a;
+ } else {
+ surface_clip = *b;
+ }
+
+ self.surfaces.set_enabled(true);
+ match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if !toplevel.is_null() {
+ surface_clip.x += (*toplevel).geometry.x;
+ surface_clip.y += (*toplevel).geometry.y;
+ }
+ }
+ _ => {}
+ }
+
+ let children_head = ffi::river_scene_tree_get_children(self.surfaces.tree) as *mut WlList;
+ if (*children_head).next != children_head {
+ ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, &surface_clip);
+ }
+ }
+}
+
+unsafe fn clock_gettime(clk_id: libc::clockid_t, tp: &mut libc::timespec) -> libc::c_int {
+ libc::clock_gettime(clk_id, tp)
+}
+
+unsafe extern "C" fn window_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn window_close(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.close = true;
+}
+
+unsafe extern "C" fn window_get_node(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ if !(*window).node.object.is_null() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_NODE_EXISTS,
+ b"window already has a node object\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).node.create_object(client, ffi::wl_resource_get_version(resource) as u32, id);
+}
+
+unsafe extern "C" fn window_propose_dimensions(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ width: i32,
+ height: i32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ if width < 0 || height < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_INVALID_DIMENSIONS,
+ b"dimensions must be greater than or equal to 0\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).wm_requested.dimensions = Some(Dimensions {
+ width: width as u32,
+ height: height as u32,
+ });
+}
+
+unsafe extern "C" fn window_hide(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ (*window).rendering_requested.hidden = true;
+}
+
+unsafe extern "C" fn window_show(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ (*window).rendering_requested.hidden = false;
+}
+
+unsafe extern "C" fn window_use_csd(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.ssd = false;
+}
+
+unsafe extern "C" fn window_use_ssd(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.ssd = true;
+}
+
+unsafe extern "C" fn window_set_borders(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ edges: u32,
+ width: i32,
+ r: u32,
+ g: u32,
+ b: u32,
+ a: u32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ if width < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_INVALID_BORDER,
+ b"border width must be greater than or equal to 0\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).rendering_requested.border = Border {
+ edges: Edges::from_u32(edges),
+ width: width as u32,
+ r,
+ g,
+ b,
+ a,
+ };
+}
+
+unsafe extern "C" fn window_set_tiled(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ edges: u32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.tiled = edges;
+}
+
+unsafe extern "C" fn window_get_decoration_above(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ wl_surface: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let wlr_surface = ffi::wlr_surface_from_resource(wl_surface);
+ let decoration = match Decoration::create(
+ client,
+ ffi::wl_resource_get_version(resource) as u32,
+ id,
+ wlr_surface,
+ (*window).decorations_above_tree,
+ window,
+ ) {
+ Ok(d) => d,
+ Err(e) => {
+ log::error!("Failed to create decoration: {}", e);
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+ };
+ let list_head = &mut (*window).decorations_above as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, &mut (*decoration).link as *mut ffi::wl_list as *mut WlList);
+}
+
+unsafe extern "C" fn window_get_decoration_below(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ wl_surface: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let wlr_surface = ffi::wlr_surface_from_resource(wl_surface);
+ let decoration = match Decoration::create(
+ client,
+ ffi::wl_resource_get_version(resource) as u32,
+ id,
+ wlr_surface,
+ (*window).decorations_below_tree,
+ window,
+ ) {
+ Ok(d) => d,
+ Err(e) => {
+ log::error!("Failed to create decoration: {}", e);
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+ };
+ let list_head = &mut (*window).decorations_below as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, &mut (*decoration).link as *mut ffi::wl_list as *mut WlList);
+}
+
+unsafe extern "C" fn window_inform_resize_start(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.resizing = true;
+}
+
+unsafe extern "C" fn window_inform_resize_end(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.resizing = false;
+}
+
+unsafe extern "C" fn window_set_capabilities(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ caps: u32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.capabilities = caps;
+}
+
+unsafe extern "C" fn window_inform_maximized(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.maximized = true;
+}
+
+unsafe extern "C" fn window_inform_unmaximized(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.maximized = false;
+}
+
+unsafe extern "C" fn window_inform_fullscreen(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.inform_fullscreen = true;
+}
+
+unsafe extern "C" fn window_inform_not_fullscreen(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.inform_fullscreen = false;
+}
+
+unsafe extern "C" fn window_fullscreen(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ output: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ let out = if output.is_null() {
+ std::ptr::null_mut()
+ } else {
+ let wlr_output = ffi::wlr_output_from_resource(output);
+ if wlr_output.is_null() {
+ std::ptr::null_mut()
+ } else {
+ ffi::river_wlr_output_get_data(wlr_output) as *mut crate::output::Output
+ }
+ };
+ (*window).wm_requested.fullscreen = out;
+}
+
+unsafe extern "C" fn window_exit_fullscreen(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ (*window).wm_requested.fullscreen = std::ptr::null_mut();
+}
+
+unsafe extern "C" fn window_set_clip_box(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ if width < 0 || height < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_INVALID_CLIP_BOX,
+ b"width/height must be greater than or equal to 0\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).rendering_requested.clip = ffi::wlr_box {
+ x,
+ y,
+ width,
+ height,
+ };
+}
+
+unsafe extern "C" fn window_set_content_clip_box(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ if width < 0 || height < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_INVALID_CLIP_BOX,
+ b"width/height must be greater than or equal to 0\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).rendering_requested.content_clip = ffi::wlr_box {
+ x,
+ y,
+ width,
+ height,
+ };
+}
+
+unsafe extern "C" fn window_set_dimension_bounds(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ max_width: i32,
+ max_height: i32,
+) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if window.is_null() {
+ return;
+ }
+ let server = (*window).server;
+ if !(*server).wm.ensure_windowing() {
+ return;
+ }
+ if max_width < 0 || max_height < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_v1_error_RIVER_WINDOW_V1_ERROR_INVALID_DIMENSIONS,
+ b"dimensions must be greater than or equal to 0\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*window).wm_requested.bounds = Dimensions {
+ width: max_width as u32,
+ height: max_height as u32,
+ };
+}
+
+// river_window_v1 implementation
+static WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
+ destroy: Some(window_destroy),
+ close: Some(window_close),
+ get_node: Some(window_get_node),
+ propose_dimensions: Some(window_propose_dimensions),
+ hide: Some(window_hide),
+ show: Some(window_show),
+ use_csd: Some(window_use_csd),
+ use_ssd: Some(window_use_ssd),
+ set_borders: Some(window_set_borders),
+ set_tiled: Some(window_set_tiled),
+ get_decoration_above: Some(window_get_decoration_above),
+ get_decoration_below: Some(window_get_decoration_below),
+ inform_resize_start: Some(window_inform_resize_start),
+ inform_resize_end: Some(window_inform_resize_end),
+ set_capabilities: Some(window_set_capabilities),
+ inform_maximized: Some(window_inform_maximized),
+ inform_unmaximized: Some(window_inform_unmaximized),
+ inform_fullscreen: Some(window_inform_fullscreen),
+ inform_not_fullscreen: Some(window_inform_not_fullscreen),
+ fullscreen: Some(window_fullscreen),
+ exit_fullscreen: Some(window_exit_fullscreen),
+ set_clip_box: Some(window_set_clip_box),
+ set_content_clip_box: Some(window_set_content_clip_box),
+ set_dimension_bounds: Some(window_set_dimension_bounds),
+};
+
+static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
+ destroy: Some(window_destroy),
+ close: None,
+ get_node: None,
+ propose_dimensions: None,
+ hide: None,
+ show: None,
+ use_csd: None,
+ use_ssd: None,
+ set_borders: None,
+ set_tiled: None,
+ get_decoration_above: None,
+ get_decoration_below: None,
+ inform_resize_start: None,
+ inform_resize_end: None,
+ set_capabilities: None,
+ inform_maximized: None,
+ inform_unmaximized: None,
+ inform_fullscreen: None,
+ inform_not_fullscreen: None,
+ fullscreen: None,
+ exit_fullscreen: None,
+ set_clip_box: None,
+ set_content_clip_box: None,
+ set_dimension_bounds: None,
+};
+
+unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
+ let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+ if !window.is_null() {
+ (*window).object = std::ptr::null_mut();
+ (*window).node.make_inert();
+
+ for decorations in [&mut (*window).decorations_above as *mut ffi::wl_list, &mut (*window).decorations_below as *mut ffi::wl_list] {
+ let list_head = decorations as *mut WlList;
+ let mut curr = (*list_head).next;
+ while curr != list_head {
+ let next = (*curr).next;
+ let dec = crate::container_of!(curr, Decoration, link);
+ (*dec).make_inert();
+ curr = next;
+ }
+ }
+ }
+}
+
+// river_decoration_v1 implementation
+pub struct DecorationRenderingRequested {
+ pub offset_x: i32,
+ pub offset_y: i32,
+ pub sync_next_commit: bool,
+}
+
+pub struct Decoration {
+ pub object: *mut ffi::wl_resource, // river_decoration_v1
+ pub surface: *mut ffi::wlr_surface,
+ pub tree: *mut ffi::wlr_scene_tree,
+ pub surfaces: crate::scene::SaveableSurfaces,
+ pub link: ffi::wl_list,
+ pub window: *mut Window,
+ pub rendering_requested: DecorationRenderingRequested,
+}
+
+impl Decoration {
+ pub unsafe fn create(
+ client: *mut ffi::wl_client,
+ version: u32,
+ id: u32,
+ surface: *mut ffi::wlr_surface,
+ parent: *mut ffi::wlr_scene_tree,
+ window: *mut Window,
+ ) -> Result<*mut Self, &'static str> {
+ let decoration_v1 = ffi::wl_resource_create(client, &ffi::river_decoration_v1_interface, version as i32, id);
+ if decoration_v1.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return Err("wl_resource_create failed");
+ }
+
+ if !ffi::wlr_surface_set_role(
+ surface,
+ &DECORATION_ROLE,
+ decoration_v1,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_ROLE,
+ ) {
+ return Err("wlr_surface_set_role failed");
+ }
+ ffi::river_wlr_surface_set_role_object(surface, decoration_v1);
+
+ let tree = ffi::wlr_scene_tree_create(parent);
+ if tree.is_null() {
+ return Err("wlr_scene_tree_create failed");
+ }
+
+ let surfaces = crate::scene::SaveableSurfaces::init(tree)?;
+ let subsurface_tree = ffi::wlr_scene_subsurface_tree_create(surfaces.tree, surface);
+ if subsurface_tree.is_null() {
+ ffi::wlr_scene_node_destroy(tree as *mut ffi::wlr_scene_node);
+ return Err("wlr_scene_subsurface_tree_create failed");
+ }
+
+ let dec = Box::new(Decoration {
+ object: decoration_v1,
+ surface,
+ tree,
+ surfaces,
+ link: std::mem::zeroed(),
+ window,
+ rendering_requested: DecorationRenderingRequested {
+ offset_x: 0,
+ offset_y: 0,
+ sync_next_commit: false,
+ },
+ });
+ let raw = Box::into_raw(dec);
+
+ ffi::wl_resource_set_implementation(
+ decoration_v1,
+ &DECORATION_INTERFACE as *const _ as *const _,
+ raw as *mut _,
+ Some(handle_dec_destroy_resource),
+ );
+
+ Ok(raw)
+ }
+
+ pub unsafe fn destroy(&mut self) {
+ assert!(self.object.is_null());
+ ffi::wlr_scene_node_destroy(self.tree as *mut ffi::wlr_scene_node);
+ wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(self);
+ }
+
+ pub unsafe fn make_inert(&mut self) {
+ if !self.object.is_null() {
+ ffi::wl_resource_set_implementation(
+ self.object,
+ &INERT_DECORATION_INTERFACE as *const _ as *const _,
+ std::ptr::null_mut(),
+ None,
+ );
+ self.object = std::ptr::null_mut();
+ }
+ self.surfaces.save();
+ }
+
+ pub unsafe fn render_finish(&mut self, window_clip: *const ffi::wlr_box) {
+ if self.rendering_requested.sync_next_commit {
+ self.rendering_requested.sync_next_commit = false;
+
+ if !self.surfaces.saved {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_error(
+ self.object,
+ ffi::river_decoration_v1_error_RIVER_DECORATION_V1_ERROR_NO_COMMIT,
+ b"no wl_surface.commit after sync_next_commit and before update_rendering_finish\0".as_ptr() as *const _,
+ );
+ }
+ }
+ }
+
+ self.surfaces.drop_saved();
+
+ ffi::wlr_scene_node_set_position(self.tree as *mut ffi::wlr_scene_node, self.rendering_requested.offset_x, self.rendering_requested.offset_y);
+
+ let children_head = ffi::river_scene_tree_get_children(self.surfaces.tree) as *mut WlList;
+ if (*children_head).next != children_head {
+ let mut clip = *window_clip;
+ clip.x -= self.rendering_requested.offset_x;
+ clip.y -= self.rendering_requested.offset_y;
+ ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, &clip);
+ }
+ }
+}
+
+pub unsafe fn decoration_from_wlr_surface(surface: *mut ffi::wlr_surface) -> *mut Decoration {
+ if surface.is_null() {
+ return std::ptr::null_mut();
+ }
+ let role_ptr = ffi::river_wlr_surface_get_role(surface);
+ if role_ptr != &DECORATION_ROLE as *const _ {
+ return std::ptr::null_mut();
+ }
+ let resource = ffi::river_wlr_surface_get_role_resource(surface);
+ if resource.is_null() {
+ return std::ptr::null_mut();
+ }
+ ffi::wl_resource_get_user_data(resource) as *mut Decoration
+}
+
+unsafe extern "C" fn dec_client_commit(surface: *mut ffi::wlr_surface) {
+ let dec = decoration_from_wlr_surface(surface);
+ if dec.is_null() {
+ return;
+ }
+ if (*dec).rendering_requested.sync_next_commit {
+ (*dec).surfaces.save();
+ }
+}
+
+unsafe extern "C" fn dec_commit(surface: *mut ffi::wlr_surface) {
+ if ffi::wlr_surface_has_buffer(surface) {
+ ffi::wlr_surface_map(surface);
+ }
+}
+
+unsafe extern "C" fn dec_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn dec_set_offset(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ x: i32,
+ y: i32,
+) {
+ let dec = ffi::wl_resource_get_user_data(resource) as *mut Decoration;
+ if dec.is_null() {
+ return;
+ }
+ let server = (*(*dec).window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ (*dec).rendering_requested.offset_x = x;
+ (*dec).rendering_requested.offset_y = y;
+}
+
+unsafe extern "C" fn dec_sync_next_commit(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let dec = ffi::wl_resource_get_user_data(resource) as *mut Decoration;
+ if dec.is_null() {
+ return;
+ }
+ let server = (*(*dec).window).server;
+ if !(*server).wm.ensure_rendering() {
+ return;
+ }
+ (*dec).rendering_requested.sync_next_commit = true;
+}
+
+static DECORATION_INTERFACE: ffi::river_decoration_v1_interface = ffi::river_decoration_v1_interface {
+ destroy: Some(dec_destroy),
+ set_offset: Some(dec_set_offset),
+ sync_next_commit: Some(dec_sync_next_commit),
+};
+
+static INERT_DECORATION_INTERFACE: ffi::river_decoration_v1_interface = ffi::river_decoration_v1_interface {
+ destroy: Some(dec_destroy),
+ set_offset: None,
+ sync_next_commit: None,
+};
+
+unsafe extern "C" fn handle_dec_destroy_resource(resource: *mut ffi::wl_resource) {
+ let dec = ffi::wl_resource_get_user_data(resource) as *mut Decoration;
+ if !dec.is_null() {
+ (*dec).object = std::ptr::null_mut();
+ (*dec).destroy();
+ }
+}
+
+#[no_mangle]
+pub static mut DECORATION_ROLE: ffi::wlr_surface_role = ffi::wlr_surface_role {
+ name: b"river_decoration_v1\0".as_ptr() as *const _,
+ no_object: false,
+ client_commit: Some(dec_client_commit),
+ commit: Some(dec_commit),
+ map: None,
+ unmap: None,
+ destroy: None,
+};
diff --git a/src/window_manager.rs b/src/window_manager.rs
new file mode 100644
index 0000000..1d08bbc
--- /dev/null
+++ b/src/window_manager.rs
@@ -0,0 +1,727 @@
+// SPDX-FileCopyrightText: © 2024 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::server::{Server, WlListener, WlList, wl_list_insert, wl_list_remove, wl_listener_remove};
+use crate::slotmap::{SlotMap, Key};
+use std::hash::{Hash, Hasher};
+
+pub use crate::window::Window;
+pub use crate::shell_surface::ShellSurface;
+
+pub use crate::xwayland_override_redirect::XwaylandOverrideRedirect;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum WindowManagerState {
+ Idle,
+ Manage,
+ InflightConfigures(u32),
+ Render,
+}
+
+pub struct WindowManagerScheduled {
+ pub dirty: bool,
+ pub dirty_lazy: bool,
+ pub output_config: *mut ffi::wlr_output_configuration_v1,
+}
+
+pub struct WindowManagerSent {
+ pub session_locked: bool,
+ pub outputs: ffi::wl_list,
+ pub output_config: *mut ffi::wlr_output_configuration_v1,
+ pub seats: ffi::wl_list,
+}
+
+pub struct WindowManagerRenderingScheduled {
+ pub dirty: bool,
+}
+
+pub struct WindowManagerRenderingRequested {
+ pub list: ffi::wl_list,
+ pub order_hash: u64,
+}
+
+pub struct WindowManager {
+ pub server: *mut Server,
+ pub global: *mut ffi::wl_global,
+ pub server_destroy: ffi::wl_listener,
+ pub object: *mut ffi::wl_resource,
+ pub state: WindowManagerState,
+ pub windows: SlotMap<*mut Window>,
+ pub scheduled: WindowManagerScheduled,
+ pub sent: WindowManagerSent,
+ pub rendering_scheduled: WindowManagerRenderingScheduled,
+ pub rendering_requested: WindowManagerRenderingRequested,
+ pub dirty_idle: *mut ffi::wl_event_source,
+ pub timeout: *mut ffi::wl_event_source,
+}
+
+impl WindowManager {
+ pub unsafe fn init(&mut self) -> Result<(), ()> {
+ // This is a stub for the 0-arg struct instantiation.
+ // We will call the real initialization with the server parameter.
+ ffi::wl_list_init(&mut self.sent.outputs);
+ self.scheduled.output_config = std::ptr::null_mut();
+ self.sent.output_config = std::ptr::null_mut();
+ Ok(())
+ }
+
+ pub unsafe fn init_with_server(&mut self, server: *mut Server) -> Result<(), &'static str> {
+ self.server = server;
+ self.global = std::ptr::null_mut();
+ self.object = std::ptr::null_mut();
+ self.state = WindowManagerState::Idle;
+ self.windows = SlotMap::new();
+ self.scheduled = WindowManagerScheduled {
+ dirty: false,
+ dirty_lazy: false,
+ output_config: std::ptr::null_mut(),
+ };
+ self.sent = WindowManagerSent {
+ session_locked: false,
+ outputs: std::mem::zeroed(),
+ output_config: std::ptr::null_mut(),
+ seats: std::mem::zeroed(),
+ };
+ self.rendering_scheduled = WindowManagerRenderingScheduled {
+ dirty: false,
+ };
+ self.rendering_requested = WindowManagerRenderingRequested {
+ list: std::mem::zeroed(),
+ order_hash: 0,
+ };
+ self.dirty_idle = std::ptr::null_mut();
+
+ ffi::wl_list_init(&mut self.sent.outputs);
+ ffi::wl_list_init(&mut self.sent.seats);
+ ffi::wl_list_init(&mut self.rendering_requested.list);
+
+ let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
+ self.timeout = ffi::wl_event_loop_add_timer(event_loop, Some(handle_timeout), self as *mut WindowManager as *mut _);
+ if self.timeout.is_null() {
+ return Err("Failed to create timer event source");
+ }
+
+ self.global = ffi::wl_global_create(
+ (*server).wl_server,
+ &ffi::river_window_manager_v1_interface,
+ 4,
+ self as *mut WindowManager as *mut _,
+ Some(bind),
+ );
+ if self.global.is_null() {
+ ffi::wl_event_source_remove(self.timeout);
+ return Err("Failed to create river_window_manager_v1 global");
+ }
+
+ let server_destroy_ptr = &mut self.server_destroy as *mut ffi::wl_listener as *mut WlListener;
+ (*server_destroy_ptr).notify = Some(handle_server_destroy);
+ ffi::wl_display_add_destroy_listener((*server).wl_server, &mut self.server_destroy);
+
+ Ok(())
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ if !self.global.is_null() {
+ ffi::wl_global_destroy(self.global);
+ self.global = std::ptr::null_mut();
+ }
+ if !self.timeout.is_null() {
+ ffi::wl_event_source_remove(self.timeout);
+ self.timeout = std::ptr::null_mut();
+ }
+ wl_listener_remove(&mut self.server_destroy);
+ }
+
+ pub unsafe fn ensure_windowing(&self) -> bool {
+ match self.state {
+ WindowManagerState::Manage => true,
+ _ => {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_error(
+ self.object,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_SEQUENCE_ORDER,
+ b"invalid modification of window management state\0".as_ptr() as *const _,
+ );
+ }
+ false
+ }
+ }
+ }
+
+ pub unsafe fn ensure_rendering(&self) -> bool {
+ match self.state {
+ WindowManagerState::Manage | WindowManagerState::InflightConfigures(_) | WindowManagerState::Render => true,
+ WindowManagerState::Idle => {
+ if !self.object.is_null() {
+ ffi::wl_resource_post_error(
+ self.object,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_SEQUENCE_ORDER,
+ b"invalid modification of rendering state\0".as_ptr() as *const _,
+ );
+ }
+ false
+ }
+ }
+ }
+
+ pub unsafe fn dirty_windowing(&mut self) {
+ self.scheduled.dirty = true;
+ self.add_dirty_idle();
+ }
+
+ pub unsafe fn dirty_windowing_lazy(&mut self) {
+ self.scheduled.dirty_lazy = true;
+ self.add_dirty_idle();
+ }
+
+ pub unsafe fn clean_windowing(&mut self) {
+ self.scheduled.dirty = false;
+ self.remove_dirty_idle();
+ }
+
+ pub unsafe fn dirty_rendering(&mut self) {
+ self.rendering_scheduled.dirty = true;
+ self.add_dirty_idle();
+ }
+
+ pub unsafe fn clean_rendering(&mut self) {
+ self.rendering_scheduled.dirty = false;
+ self.remove_dirty_idle();
+ }
+
+ unsafe fn add_dirty_idle(&mut self) {
+ if self.scheduled.dirty || self.scheduled.dirty_lazy || self.rendering_scheduled.dirty {
+ if self.dirty_idle.is_null() {
+ let event_loop = ffi::wl_display_get_event_loop((*self.server).wl_server);
+ self.dirty_idle = ffi::wl_event_loop_add_idle(
+ event_loop,
+ Some(dirty_idle_callback),
+ self as *mut WindowManager as *mut _,
+ );
+ }
+ }
+ }
+
+ unsafe fn remove_dirty_idle(&mut self) {
+ if !self.scheduled.dirty && !self.rendering_scheduled.dirty {
+ if !self.dirty_idle.is_null() {
+ ffi::wl_event_source_remove(self.dirty_idle);
+ self.dirty_idle = std::ptr::null_mut();
+ }
+ }
+ }
+
+ pub unsafe fn manage_start(&mut self) {
+ assert!(matches!(self.state, WindowManagerState::Idle));
+ assert!(self.scheduled.dirty);
+ self.clean_windowing();
+ self.state = WindowManagerState::Manage;
+
+ log::debug!("manage sequence start");
+
+ let session_locked = (*self.server).lock_manager.state == crate::lock_manager::LockState::Locked;
+ if session_locked != self.sent.session_locked {
+ if !self.object.is_null() {
+ if session_locked {
+ ffi::wl_resource_post_event(self.object, 4); // river_window_manager_v1.session_locked
+ } else {
+ ffi::wl_resource_post_event(self.object, 5); // river_window_manager_v1.session_unlocked
+ }
+ }
+ self.sent.session_locked = session_locked;
+ }
+
+ (*self.server).om.auto_layout();
+
+ let outputs = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs).next;
+ while curr != outputs {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, crate::output::Output, link);
+ (*output).layer_shell.manage_start(output);
+ curr = next;
+ }
+
+ assert!(self.sent.output_config.is_null());
+ self.sent.output_config = self.scheduled.output_config;
+ self.scheduled.output_config = std::ptr::null_mut();
+
+ for &win_ptr in self.windows.iter() {
+ (*win_ptr).manage_start();
+ }
+
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).manage_start();
+ curr = next;
+ }
+
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 2); // river_window_manager_v1.manage_start
+ self.start_timeout_timer(3000);
+ } else {
+ self.manage_finish();
+ }
+ }
+
+ pub unsafe fn manage_finish(&mut self) {
+ assert!(matches!(self.state, WindowManagerState::Manage));
+ self.cancel_timeout_timer();
+
+ log::debug!("manage sequence finish");
+
+ let seats = &mut self.sent.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link_sent);
+ (*seat).manage_finish();
+ curr = next;
+ }
+
+ self.state = WindowManagerState::InflightConfigures(0);
+
+ let render_list = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*render_list).next;
+ while curr != render_list {
+ let next = (*curr).next;
+ let node = crate::container_of!(curr, crate::wm_node::WmNode, link);
+ match (*node).get() {
+ crate::wm_node::WmNodeType::Window(window) => {
+ if (*window).manage_finish() {
+ if let WindowManagerState::InflightConfigures(ref mut count) = self.state {
+ *count += 1;
+ }
+ }
+ }
+ _ => {}
+ }
+ curr = next;
+ }
+
+ if let WindowManagerState::InflightConfigures(count) = self.state {
+ log::debug!("sent {} tracked configure(s)", count);
+ if count > 0 {
+ self.start_timeout_timer(100);
+ } else {
+ self.render_start();
+ }
+ }
+ }
+
+ unsafe fn start_timeout_timer(&mut self, ms: u32) {
+ if !self.timeout.is_null() {
+ ffi::wl_event_source_timer_update(self.timeout, ms as i32);
+ }
+ }
+
+ unsafe fn cancel_timeout_timer(&mut self) {
+ if !self.timeout.is_null() {
+ ffi::wl_event_source_timer_update(self.timeout, 0);
+ }
+ }
+
+ pub unsafe fn notify_configured(&mut self) {
+ if let WindowManagerState::InflightConfigures(ref mut count) = self.state {
+ *count -= 1;
+ if *count == 0 {
+ self.cancel_timeout_timer();
+ self.render_start();
+ }
+ }
+ }
+
+ pub unsafe fn render_start(&mut self) {
+ assert!(matches!(self.state, WindowManagerState::InflightConfigures(0)) ||
+ (matches!(self.state, WindowManagerState::Idle) && self.rendering_scheduled.dirty));
+ self.state = WindowManagerState::Render;
+ self.clean_rendering();
+
+ log::debug!("render sequence start");
+
+ let render_list = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*render_list).next;
+ while curr != render_list {
+ let next = (*curr).next;
+ let node = crate::container_of!(curr, crate::wm_node::WmNode, link);
+ match (*node).get() {
+ crate::wm_node::WmNodeType::Window(window) => {
+ (*window).render_start();
+ }
+ _ => {}
+ }
+ curr = next;
+ }
+
+ if !self.object.is_null() {
+ ffi::wl_resource_post_event(self.object, 3); // river_window_manager_v1.render_start
+ self.start_timeout_timer(3000);
+ } else {
+ self.render_finish();
+ }
+ }
+
+ pub unsafe fn render_finish(&mut self) {
+ assert!(matches!(self.state, WindowManagerState::Render));
+ self.state = WindowManagerState::Idle;
+ self.cancel_timeout_timer();
+
+ log::debug!("render sequence finish");
+
+ for &window in self.windows.iter() {
+ if !matches!((*window).state, crate::window::WindowState::Closing) {
+ (*window).surfaces.drop_saved();
+ }
+ if matches!((*window).state, crate::window::WindowState::Init) {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut ffi::wlr_scene_node, (*self.server).scene.hidden_tree);
+ }
+ if let crate::window::WindowImpl::Destroying = (*window).impl_type {
+ Window::destroy(window);
+ }
+ }
+
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
+ let render_list = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*render_list).next;
+ while curr != render_list {
+ let next = (*curr).next;
+ let node = crate::container_of!(curr, crate::wm_node::WmNode, link);
+ match (*node).get() {
+ crate::wm_node::WmNodeType::Window(window) => {
+ (*window).ref_key.hash(&mut hasher);
+ rendered_fullscreen(window).hash(&mut hasher);
+ }
+ crate::wm_node::WmNodeType::ShellSurface(shell_surface) => {
+ (shell_surface as usize).hash(&mut hasher);
+ }
+ }
+ curr = next;
+ }
+ let new_order_hash = hasher.finish();
+ let reorder = self.rendering_requested.order_hash != new_order_hash;
+ self.rendering_requested.order_hash = new_order_hash;
+
+ let mut found_fullscreen = false;
+ curr = (*render_list).next;
+ while curr != render_list {
+ let next = (*curr).next;
+ let node = crate::container_of!(curr, crate::wm_node::WmNode, link);
+ match (*node).get() {
+ crate::wm_node::WmNodeType::Window(window) => {
+ (*window).render_finish();
+ if reorder {
+ ffi::wlr_scene_node_reparent((*window).popup_tree as *mut _, (*self.server).scene.layers.popups);
+ if rendered_fullscreen(window) {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.fullscreen);
+ ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ found_fullscreen = true;
+ } else {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.wm);
+ ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ }
+ }
+ }
+ crate::wm_node::WmNodeType::ShellSurface(shell_surface) => {
+ (*shell_surface).render_finish();
+ if reorder {
+ ffi::wlr_scene_node_reparent((*shell_surface).popup_tree as *mut _, (*self.server).scene.layers.popups);
+ if found_fullscreen {
+ ffi::wlr_scene_node_reparent((*shell_surface).tree as *mut _, (*self.server).scene.layers.fullscreen);
+ } else {
+ ffi::wlr_scene_node_reparent((*shell_surface).tree as *mut _, (*self.server).scene.layers.wm);
+ }
+ ffi::wlr_scene_node_raise_to_top((*shell_surface).tree as *mut _);
+ }
+ }
+ }
+ curr = next;
+ }
+
+ (*self.server).om.commit_output_state(self.server);
+
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).cursor.update_hovered();
+ curr = next;
+ }
+
+ (*self.server).idle_inhibit_manager.check_active();
+
+ log::debug!("finished committing transaction");
+
+ if self.scheduled.dirty || self.scheduled.dirty_lazy || self.rendering_scheduled.dirty {
+ self.add_dirty_idle();
+ }
+ }
+}
+
+// Deprecated sent_outputs that is part of structural layout compatibility
+pub struct WindowManagerScheduledCompat {
+ pub output_config: *mut ffi::wlr_output_configuration_v1,
+}
+pub struct WindowManagerSentCompat {
+ pub outputs: ffi::wl_list,
+ pub output_config: *mut ffi::wlr_output_configuration_v1,
+}
+impl WindowManager {
+ // Add legacy fields so structural offsets are preserved if layout-based code is compiled
+ pub fn sent_outputs_compat(&self) {}
+}
+
+unsafe fn rendered_fullscreen(window: *mut Window) -> bool {
+ !(*window).wm_requested.fullscreen.is_null() && !(*window).rendering_requested.hidden
+}
+
+unsafe extern "C" fn dirty_idle_callback(data: *mut std::ffi::c_void) {
+ let wm = data as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ (*wm).dirty_idle = std::ptr::null_mut();
+ match (*wm).state {
+ WindowManagerState::Idle => {
+ if (*wm).rendering_scheduled.dirty {
+ (*wm).render_start();
+ } else {
+ assert!((*wm).scheduled.dirty || (*wm).scheduled.dirty_lazy);
+ (*wm).scheduled.dirty = true;
+ (*wm).scheduled.dirty_lazy = false;
+ (*wm).manage_start();
+ }
+ }
+ _ => {}
+ }
+}
+
+unsafe extern "C" fn handle_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
+ let wm = data as *mut WindowManager;
+ if wm.is_null() {
+ return 0;
+ }
+
+ match (*wm).state {
+ WindowManagerState::InflightConfigures(count) => {
+ log::error!("timeout occurred, some imperfect frames may be shown");
+ assert!(count > 0);
+ (*wm).state = WindowManagerState::InflightConfigures(0);
+ (*wm).render_start();
+ }
+ WindowManagerState::Manage | WindowManagerState::Render => {
+ if !(*wm).object.is_null() {
+ log::error!("window manager unresponsive for more than 3 seconds, disconnecting");
+ ffi::wl_resource_post_error(
+ (*wm).object,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_UNRESPONSIVE,
+ b"unresponsive for more than 3 seconds\0".as_ptr() as *const _,
+ );
+ let client = ffi::wl_resource_get_client((*wm).object);
+ ffi::wl_client_destroy(client);
+ }
+ }
+ WindowManagerState::Idle => {}
+ }
+ 0
+}
+
+unsafe extern "C" fn handle_server_destroy(
+ listener: *mut ffi::wl_listener,
+ _data: *mut std::ffi::c_void,
+) {
+ let wm = crate::container_of!(listener, WindowManager, server_destroy);
+ (*wm).deinit();
+}
+
+// WM request handlers
+unsafe extern "C" fn wm_stop(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if !wm.is_null() {
+ (*wm).object = std::ptr::null_mut();
+ ffi::wl_resource_post_event(resource, 1); // river_window_manager_v1.finished (opcode 1)
+ ffi::wl_resource_set_implementation(
+ resource,
+ &INERT_WM_INTERFACE as *const _ as *const _,
+ std::ptr::null_mut(),
+ None,
+ );
+ }
+}
+
+unsafe extern "C" fn wm_destroy(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn wm_manage_finish(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ if !matches!((*wm).state, WindowManagerState::Manage) {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_SEQUENCE_ORDER,
+ b"manage_finish request does not match manage_start\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*wm).manage_finish();
+}
+
+unsafe extern "C" fn wm_manage_dirty(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ (*wm).scheduled.dirty_lazy = true;
+ (*wm).add_dirty_idle();
+}
+
+unsafe extern "C" fn wm_render_finish(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ if !matches!((*wm).state, WindowManagerState::Render) {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_window_manager_v1_error_RIVER_WINDOW_MANAGER_V1_ERROR_SEQUENCE_ORDER,
+ b"render_finish request does not match render_start\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*wm).render_finish();
+}
+
+unsafe extern "C" fn wm_get_shell_surface(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ id: u32,
+ surface_resource: *mut ffi::wl_resource,
+) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ let surface = ffi::wlr_surface_from_resource(surface_resource);
+ let version = ffi::wl_resource_get_version(resource) as u32;
+ if let Err(e) = crate::shell_surface::ShellSurface::create(client, version, id, surface, (*wm).server) {
+ log::error!("Failed to create shell surface: {}", e);
+ ffi::wl_client_post_no_memory(client);
+ }
+}
+
+unsafe extern "C" fn wm_exit_session(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ log::info!("window manager requested to exit session");
+ ffi::wl_display_terminate((*(*wm).server).wl_server);
+}
+
+static WM_INTERFACE: ffi::river_window_manager_v1_interface = ffi::river_window_manager_v1_interface {
+ stop: Some(wm_stop),
+ destroy: Some(wm_destroy),
+ manage_finish: Some(wm_manage_finish),
+ manage_dirty: Some(wm_manage_dirty),
+ render_finish: Some(wm_render_finish),
+ get_shell_surface: Some(wm_get_shell_surface),
+ exit_session: Some(wm_exit_session),
+};
+
+static INERT_WM_INTERFACE: ffi::river_window_manager_v1_interface = ffi::river_window_manager_v1_interface {
+ stop: None,
+ destroy: Some(wm_destroy),
+ manage_finish: None,
+ manage_dirty: None,
+ render_finish: None,
+ get_shell_surface: None,
+ exit_session: None,
+};
+
+unsafe extern "C" fn bind(
+ client: *mut ffi::wl_client,
+ data: *mut std::ffi::c_void,
+ version: u32,
+ id: u32,
+) {
+ let wm = data as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+
+ let resource = ffi::wl_resource_create(client, &ffi::river_window_manager_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ log::error!("out of memory binding river_window_manager_v1");
+ return;
+ }
+
+ if !(*wm).object.is_null() {
+ ffi::wl_resource_post_event(resource, 0); // river_window_manager_v1.unavailable
+ ffi::wl_resource_set_implementation(
+ resource,
+ &INERT_WM_INTERFACE as *const _ as *const _,
+ std::ptr::null_mut(),
+ None,
+ );
+ return;
+ }
+
+ (*wm).object = resource;
+ ffi::wl_resource_set_implementation(
+ resource,
+ &WM_INTERFACE as *const _ as *const _,
+ wm as *mut _,
+ Some(handle_destroy_wm_resource),
+ );
+ (*wm).dirty_windowing();
+}
+
+unsafe extern "C" fn handle_destroy_wm_resource(resource: *mut ffi::wl_resource) {
+ let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
+ if wm.is_null() {
+ return;
+ }
+ log::debug!("active river_window_manager_v1 destroyed");
+ (*wm).object = std::ptr::null_mut();
+
+ let server = (*wm).server;
+
+ // Iterate over outputs and make inert
+ let outputs_list = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs_list).next;
+ while curr != outputs_list {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, crate::output::Output, link);
+ (*output).make_inert();
+ curr = next;
+ }
+
+ // Iterate over seats and make inert
+ let seats_list = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ curr = (*seats_list).next;
+ while curr != seats_list {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).make_inert();
+ curr = next;
+ }
+
+ // Iterate over windows and make inert
+ for &window in (*wm).windows.iter() {
+ (*window).make_inert();
+ }
+
+ match (*wm).state {
+ WindowManagerState::Idle | WindowManagerState::InflightConfigures(_) => {}
+ WindowManagerState::Manage => (*wm).manage_finish(),
+ WindowManagerState::Render => (*wm).render_finish(),
+ }
+}
diff --git a/src/wlroots_log_wrapper.c b/src/wlroots_log_wrapper.c
new file mode 100644
index 0000000..0e83f5a
--- /dev/null
+++ b/src/wlroots_log_wrapper.c
@@ -0,0 +1,650 @@
+// SPDX-FileCopyrightText: © 2021 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+#define _POSIX_C_SOURCE 199309L
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <wlr/util/log.h>
+
+#define BUFFER_SIZE 1024
+
+void river_wlroots_log_callback(enum wlr_log_importance importance, const char *ptr, size_t len);
+
+static void callback(enum wlr_log_importance importance, const char *fmt, va_list args) {
+ char buffer[BUFFER_SIZE];
+
+ // Need to make a copy of the args in case our buffer isn't big
+ // enough and we need to use them again.
+ va_list args_copy;
+ va_copy(args_copy, args);
+
+ const int length = vsnprintf(buffer, BUFFER_SIZE, fmt, args);
+ // Need to add one for the terminating 0 byte
+ if (length + 1 <= BUFFER_SIZE) {
+ // The formatted string fit within our buffer, pass it on to river
+ river_wlroots_log_callback(importance, buffer, length);
+ } else {
+ // The formatted string did not fit in our buffer, we need
+ // to allocate enough memory to hold it.
+ char *allocated_buffer = malloc(length + 1);
+ if (allocated_buffer != NULL) {
+ const int length2 = vsnprintf(allocated_buffer, length + 1, fmt, args_copy);
+ assert(length2 == length);
+ river_wlroots_log_callback(importance, allocated_buffer, length);
+ free(allocated_buffer);
+ }
+ }
+
+ va_end(args_copy);
+}
+
+void river_init_wlroots_log(enum wlr_log_importance importance) {
+ wlr_log_init(importance, callback);
+}
+
+#include <time.h>
+#include <wlr/types/wlr_scene.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/backend.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_data_device.h>
+
+struct wlr_surface *river_scene_node_get_surface(struct wlr_scene_node *node) {
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer);
+ if (scene_surface) {
+ return scene_surface->surface;
+ }
+ }
+ return NULL;
+}
+
+enum wlr_scene_node_type river_scene_node_get_type(struct wlr_scene_node *node) {
+ return node->type;
+}
+
+struct wlr_scene_tree *river_scene_node_get_parent(struct wlr_scene_node *node) {
+ return node->parent;
+}
+
+int river_scene_node_get_x(struct wlr_scene_node *node) {
+ return node->x;
+}
+
+int river_scene_node_get_y(struct wlr_scene_node *node) {
+ return node->y;
+}
+
+void *river_scene_node_get_data(struct wlr_scene_node *node) {
+ return node->data;
+}
+
+void river_scene_node_set_data(struct wlr_scene_node *node, void *data) {
+ node->data = data;
+}
+
+struct wl_signal *river_scene_node_get_destroy_signal(struct wlr_scene_node *node) {
+ return &node->events.destroy;
+}
+
+static void save_surface_tree_iter(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) {
+ struct wlr_scene_tree *saved_tree = user_data;
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create(saved_tree, buffer->buffer);
+ if (!scene_buffer) {
+ return;
+ }
+ wlr_scene_node_set_position(&scene_buffer->node, sx, sy);
+ wlr_scene_buffer_set_dest_size(scene_buffer, buffer->dst_width, buffer->dst_height);
+ wlr_scene_buffer_set_source_box(scene_buffer, &buffer->src_box);
+ wlr_scene_buffer_set_transform(scene_buffer, buffer->transform);
+}
+
+void river_scene_tree_save_buffers(struct wlr_scene_tree *tree, struct wlr_scene_tree *saved_tree) {
+ wlr_scene_node_for_each_buffer(&tree->node, save_surface_tree_iter, saved_tree);
+}
+
+void river_scene_tree_clear_children(struct wlr_scene_tree *tree) {
+ struct wlr_scene_node *child, *tmp;
+ wl_list_for_each_safe(child, tmp, &tree->children, link) {
+ wlr_scene_node_destroy(child);
+ }
+}
+
+struct wlr_scene_node *river_scene_node_from_children_link(struct wl_list *link) {
+ return wl_container_of(link, (struct wlr_scene_node *)NULL, link);
+}
+
+struct wl_signal *river_wlr_output_get_destroy_signal(struct wlr_output *output) {
+ return &output->events.destroy;
+}
+
+struct wl_signal *river_wlr_output_get_request_state_signal(struct wlr_output *output) {
+ return &output->events.request_state;
+}
+
+struct wl_signal *river_wlr_output_get_frame_signal(struct wlr_output *output) {
+ return &output->events.frame;
+}
+
+struct wl_signal *river_wlr_output_get_present_signal(struct wlr_output *output) {
+ return &output->events.present;
+}
+
+void *river_wlr_output_get_data(struct wlr_output *output) {
+ return output->data;
+}
+
+void river_wlr_output_set_data(struct wlr_output *output, void *data) {
+ output->data = data;
+}
+
+const char *river_wlr_output_get_name(struct wlr_output *output) {
+ return output->name;
+}
+
+enum wlr_output_adaptive_sync_status river_wlr_output_get_adaptive_sync_status(struct wlr_output *output) {
+ return output->adaptive_sync_status;
+}
+
+bool river_wlr_output_get_enabled(struct wlr_output *output) {
+ return output->enabled;
+}
+
+struct wlr_output_mode *river_wlr_output_get_current_mode(struct wlr_output *output) {
+ return output->current_mode;
+}
+
+int32_t river_wlr_output_get_width(struct wlr_output *output) {
+ return output->width;
+}
+
+int32_t river_wlr_output_get_height(struct wlr_output *output) {
+ return output->height;
+}
+
+int32_t river_wlr_output_get_refresh(struct wlr_output *output) {
+ return output->refresh;
+}
+
+struct wl_global *river_wlr_output_get_global(struct wlr_output *output) {
+ return output->global;
+}
+
+void *river_wlr_surface_get_data(struct wlr_surface *surface) {
+ return surface->data;
+}
+
+void river_wlr_surface_set_data(struct wlr_surface *surface, void *data) {
+ surface->data = data;
+}
+
+struct wl_signal *river_wlr_surface_get_commit_signal(struct wlr_surface *surface) {
+ return &surface->events.commit;
+}
+
+enum wlr_input_device_type river_wlr_input_device_get_type(struct wlr_input_device *dev) {
+ return dev->type;
+}
+
+const char *river_wlr_input_device_get_name(struct wlr_input_device *dev) {
+ return dev->name;
+}
+
+struct wl_signal *river_wlr_input_device_get_destroy_signal(struct wlr_input_device *dev) {
+ return &dev->events.destroy;
+}
+
+void *river_wlr_input_device_get_data(struct wlr_input_device *dev) {
+ return dev->data;
+}
+
+void river_wlr_input_device_set_data(struct wlr_input_device *dev, void *data) {
+ dev->data = data;
+}
+
+struct wl_signal *river_wlr_keyboard_get_key_signal(struct wlr_keyboard *kbd) {
+ return &kbd->events.key;
+}
+
+struct wl_signal *river_wlr_keyboard_get_modifiers_signal(struct wlr_keyboard *kbd) {
+ return &kbd->events.modifiers;
+}
+
+struct wl_signal *river_wlr_keyboard_get_keymap_signal(struct wlr_keyboard *kbd) {
+ return &kbd->events.keymap;
+}
+
+struct xkb_keymap *river_wlr_keyboard_get_keymap(struct wlr_keyboard *kbd) {
+ return kbd->keymap;
+}
+
+struct wlr_keyboard_modifiers *river_wlr_keyboard_get_modifiers(struct wlr_keyboard *kbd) {
+ return &kbd->modifiers;
+}
+
+void *river_wlr_keyboard_get_data(struct wlr_keyboard *kbd) {
+ return kbd->data;
+}
+
+void river_wlr_keyboard_set_data(struct wlr_keyboard *kbd, void *data) {
+ kbd->data = data;
+}
+
+double river_wlr_cursor_get_x(struct wlr_cursor *cursor) {
+ return cursor->x;
+}
+
+double river_wlr_cursor_get_y(struct wlr_cursor *cursor) {
+ return cursor->y;
+}
+
+struct wl_signal *river_wlr_cursor_get_motion_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.motion;
+}
+
+struct wl_signal *river_wlr_cursor_get_motion_absolute_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.motion_absolute;
+}
+
+struct wl_signal *river_wlr_cursor_get_button_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.button;
+}
+
+struct wl_signal *river_wlr_cursor_get_axis_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.axis;
+}
+
+struct wl_signal *river_wlr_cursor_get_frame_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.frame;
+}
+
+struct wl_signal *river_wlr_cursor_get_swipe_begin_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.swipe_begin;
+}
+
+struct wl_signal *river_wlr_cursor_get_swipe_update_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.swipe_update;
+}
+
+struct wl_signal *river_wlr_cursor_get_swipe_end_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.swipe_end;
+}
+
+struct wl_signal *river_wlr_cursor_get_pinch_begin_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.pinch_begin;
+}
+
+struct wl_signal *river_wlr_cursor_get_pinch_update_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.pinch_update;
+}
+
+struct wl_signal *river_wlr_cursor_get_pinch_end_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.pinch_end;
+}
+
+struct wl_signal *river_wlr_cursor_get_hold_begin_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.hold_begin;
+}
+
+struct wl_signal *river_wlr_cursor_get_hold_end_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.hold_end;
+}
+
+struct wl_signal *river_wlr_cursor_get_touch_down_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.touch_down;
+}
+
+struct wl_signal *river_wlr_cursor_get_touch_motion_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.touch_motion;
+}
+
+struct wl_signal *river_wlr_cursor_get_touch_up_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.touch_up;
+}
+
+struct wl_signal *river_wlr_cursor_get_touch_cancel_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.touch_cancel;
+}
+
+struct wl_signal *river_wlr_cursor_get_touch_frame_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.touch_frame;
+}
+
+struct wl_signal *river_wlr_cursor_get_tablet_tool_axis_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.tablet_tool_axis;
+}
+
+struct wl_signal *river_wlr_cursor_get_tablet_tool_proximity_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.tablet_tool_proximity;
+}
+
+struct wl_signal *river_wlr_cursor_get_tablet_tool_tip_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.tablet_tool_tip;
+}
+
+struct wl_signal *river_wlr_cursor_get_tablet_tool_button_signal(struct wlr_cursor *cursor) {
+ return &cursor->events.tablet_tool_button;
+}
+
+const char *river_wlr_seat_get_name(struct wlr_seat *seat) {
+ return seat->name;
+}
+
+void *river_wlr_seat_get_data(struct wlr_seat *seat) {
+ return seat->data;
+}
+
+void river_wlr_seat_set_data(struct wlr_seat *seat, void *data) {
+ seat->data = data;
+}
+
+struct wl_signal *river_wlr_seat_get_request_set_cursor_signal(struct wlr_seat *seat) {
+ return &seat->events.request_set_cursor;
+}
+
+struct wl_signal *river_wlr_seat_get_request_set_selection_signal(struct wlr_seat *seat) {
+ return &seat->events.request_set_selection;
+}
+
+struct wl_signal *river_wlr_seat_get_request_start_drag_signal(struct wlr_seat *seat) {
+ return &seat->events.request_start_drag;
+}
+
+struct wl_signal *river_wlr_seat_get_start_drag_signal(struct wlr_seat *seat) {
+ return &seat->events.start_drag;
+}
+
+struct wl_signal *river_wlr_seat_get_request_set_primary_selection_signal(struct wlr_seat *seat) {
+ return &seat->events.request_set_primary_selection;
+}
+
+struct wlr_seat_client *river_wlr_seat_get_pointer_focused_client(struct wlr_seat *seat) {
+ return seat->pointer_state.focused_client;
+}
+
+struct wl_client *river_wlr_seat_client_get_client(struct wlr_seat_client *client) {
+ return client->client;
+}
+
+struct wlr_keyboard *river_wlr_seat_get_keyboard(struct wlr_seat *seat) {
+ return seat->keyboard_state.keyboard;
+}
+
+struct wl_global *river_wlr_seat_get_global(struct wlr_seat *seat) {
+ return seat->global;
+}
+
+struct wl_signal *river_wlr_backend_get_new_input_signal(struct wlr_backend *backend) {
+ return &backend->events.new_input;
+}
+
+const struct wlr_surface_role *river_wlr_surface_get_role(struct wlr_surface *surface) {
+ return surface->role;
+}
+
+struct wl_resource *river_wlr_surface_get_role_resource(struct wlr_surface *surface) {
+ return surface->role_resource;
+}
+
+void river_wlr_surface_set_role_object(struct wlr_surface *surface, struct wl_resource *role_resource) {
+ surface->role_resource = role_resource;
+}
+
+struct wlr_surface *river_wlr_xdg_surface_get_surface(struct wlr_xdg_surface *xdg_surface) {
+ return xdg_surface->surface;
+}
+
+struct wl_signal *river_wlr_xdg_surface_get_ack_configure_signal(struct wlr_xdg_surface *xdg_surface) {
+ return &xdg_surface->events.ack_configure;
+}
+
+struct wl_signal *river_wlr_xdg_surface_get_new_popup_signal(struct wlr_xdg_surface *xdg_surface) {
+ return &xdg_surface->events.new_popup;
+}
+
+void river_wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *xdg_surface, struct wlr_box *box) {
+ *box = xdg_surface->geometry;
+}
+
+bool river_wlr_xdg_surface_get_initial_commit(struct wlr_xdg_surface *xdg_surface) {
+ return xdg_surface->initial_commit;
+}
+
+bool river_wlr_xdg_surface_get_initialized(struct wlr_xdg_surface *xdg_surface) {
+ return xdg_surface->initialized;
+}
+
+struct wlr_xdg_surface *river_wlr_xdg_toplevel_get_base(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->base;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_destroy_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.destroy;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_show_window_menu_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_show_window_menu;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_fullscreen_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_fullscreen;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_maximize_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_maximize;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_minimize_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_minimize;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_move_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_move;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_request_resize_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.request_resize;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_set_parent_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.set_parent;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_set_title_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.set_title;
+}
+
+struct wl_signal *river_wlr_xdg_toplevel_get_set_app_id_signal(struct wlr_xdg_toplevel *toplevel) {
+ return &toplevel->events.set_app_id;
+}
+
+const char *river_wlr_xdg_toplevel_get_title(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->title;
+}
+
+const char *river_wlr_xdg_toplevel_get_app_id(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->app_id;
+}
+
+struct wlr_xdg_toplevel *river_wlr_xdg_toplevel_get_parent(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->parent;
+}
+
+bool river_wlr_xdg_toplevel_get_requested_fullscreen(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->requested.fullscreen;
+}
+
+struct wlr_output *river_wlr_xdg_toplevel_get_requested_fullscreen_output(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->requested.fullscreen_output;
+}
+
+bool river_wlr_xdg_toplevel_get_requested_maximized(struct wlr_xdg_toplevel *toplevel) {
+ return toplevel->requested.maximized;
+}
+
+void river_wlr_xdg_toplevel_get_requested_min_max_size(struct wlr_xdg_toplevel *toplevel, int *min_w, int *min_h, int *max_w, int *max_h) {
+ *min_w = toplevel->current.min_width;
+ *min_h = toplevel->current.min_height;
+ *max_w = toplevel->current.max_width;
+ *max_h = toplevel->current.max_height;
+}
+
+struct wlr_xdg_surface *river_wlr_xdg_popup_get_base(struct wlr_xdg_popup *popup) {
+ return popup->base;
+}
+
+struct wl_signal *river_wlr_xdg_popup_get_destroy_signal(struct wlr_xdg_popup *popup) {
+ return &popup->events.destroy;
+}
+
+struct wl_signal *river_wlr_xdg_popup_get_reposition_signal(struct wlr_xdg_popup *popup) {
+ return &popup->events.reposition;
+}
+
+void river_wlr_xdg_popup_get_anchor_rect(struct wlr_xdg_popup *popup, struct wlr_box *box) {
+ *box = popup->scheduled.rules.anchor_rect;
+}
+
+void *river_wlr_xdg_surface_get_data(struct wlr_xdg_surface *xdg_surface) {
+ return xdg_surface->data;
+}
+
+void river_wlr_xdg_surface_set_data(struct wlr_xdg_surface *xdg_surface, void *data) {
+ xdg_surface->data = data;
+}
+
+struct wl_list *river_wlr_xdg_surface_get_popups(struct wlr_xdg_surface *xdg_surface) {
+ return &xdg_surface->popups;
+}
+
+struct wlr_scene_tree *river_wlr_scene_tree_get_parent(struct wlr_scene_tree *tree) {
+ return tree->node.parent;
+}
+
+struct wl_list *river_scene_tree_get_children(struct wlr_scene_tree *tree) {
+ return &tree->children;
+}
+
+struct wl_signal *river_wlr_surface_get_map_signal(struct wlr_surface *surface) {
+ return &surface->events.map;
+}
+
+struct wl_signal *river_wlr_surface_get_unmap_signal(struct wlr_surface *surface) {
+ return &surface->events.unmap;
+}
+
+struct wl_resource *river_wlr_surface_get_resource(struct wlr_surface *surface) {
+ return surface->resource;
+}
+
+bool river
diff truncated