Initial commit
This commit is contained in:
commit
85b940e427
21 changed files with 6825 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
747
Cargo.lock
generated
Normal file
747
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,747 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.64.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
|
||||
|
||||
[[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.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[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.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[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.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testwl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wayland-protocols",
|
||||
"wayland-server",
|
||||
"wl_drm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.31.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-server"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e6e4d5c285bc24ba4ed2d5a4bd4febd5fd904451f465973225c8e99772fdb7"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"downcast-rs",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[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 = "winapi-util"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
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.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "wl_drm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e75181b5a62b6eeaa72f303d3cef7dbb841e22885bf6d3e66fe23e88c55dc6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"quick-xml 0.30.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb-util-cursor"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cca04fb82324e278cdcc920289ab454635d17f2d3036e2aec1c320b435b036"
|
||||
dependencies = [
|
||||
"xcb",
|
||||
"xcb-util-cursor-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb-util-cursor-sys"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b13724e3af85816d1bbd617cc899b9ff7a55ca53413c4cc5c269e8c62bcc1702"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xwayland-satellite"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"env_logger 0.11.3",
|
||||
"libc",
|
||||
"log",
|
||||
"paste",
|
||||
"pretty_env_logger",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"slotmap",
|
||||
"testwl",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
"wl_drm",
|
||||
"xcb",
|
||||
"xcb-util-cursor",
|
||||
]
|
||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"satellite",
|
||||
"testwl" ,
|
||||
"wl_drm"
|
||||
]
|
||||
|
||||
resolver = "2"
|
||||
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
13
README.md
Normal file
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# xwayland-satellite
|
||||
xwayland-satellite grants rootless Xwayland integration to any Wayland compositor implementing xdg_wm_base.
|
||||
This is particularly useful for compositors that (understandably) do not want to go through implementing support for rootless Xwayland themselves.
|
||||
|
||||
## Usage
|
||||
Run `xwayland-satellite`. You can specify an X display to use (i.e. `:12`). Be sure to set the same `DISPLAY` environment variable for any X11 clients.
|
||||
Because xwayland-satellite is a Wayland client (in addition to being a Wayland compositor), it will need to launch after your compositor launches, but obviously before any X11 applications.
|
||||
|
||||
## Building
|
||||
```
|
||||
cargo build
|
||||
cargo run
|
||||
```
|
||||
31
satellite/Cargo.toml
Normal file
31
satellite/Cargo.toml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "xwayland-satellite"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.5.0"
|
||||
paste = "1.0.14"
|
||||
rustix = { version = "0.38.31", features = ["event"] }
|
||||
wayland-client = "0.31.2"
|
||||
wayland-protocols = { version = "0.31.2", features = ["client", "server", "staging"] }
|
||||
wayland-scanner = "0.31.1"
|
||||
wayland-server = "0.31.1"
|
||||
xcb = { version = "1.3.0", features = ["composite"] }
|
||||
wl_drm = { path = "../wl_drm" }
|
||||
signal-hook = "0.3.17"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
env_logger = "0.11.3"
|
||||
pretty_env_logger = "0.5.0"
|
||||
slotmap = "1.0.7"
|
||||
xcb-util-cursor = "0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rustix = { version = "0.38.31", features = ["fs"] }
|
||||
testwl = { path = "../testwl" }
|
||||
200
satellite/src/clientside.rs
Normal file
200
satellite/src/clientside.rs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
use crate::server::{ObjectEvent, ObjectKey};
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::mpsc;
|
||||
use wayland_client::protocol::{
|
||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
|
||||
};
|
||||
use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, Proxy, QueueHandle};
|
||||
use wayland_protocols::wp::relative_pointer::zv1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::client::{
|
||||
self as dmabuf,
|
||||
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as DmabufFeedback,
|
||||
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
},
|
||||
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{
|
||||
xdg_popup::XdgPopup, xdg_positioner::XdgPositioner, xdg_surface::XdgSurface,
|
||||
xdg_toplevel::XdgToplevel, xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
xdg_output::zv1::client::{
|
||||
zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1 as XdgOutput,
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol as server;
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalData {
|
||||
pub name: u32,
|
||||
pub interface: String,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Globals {
|
||||
pub(crate) events: Vec<(ObjectKey, ObjectEvent)>,
|
||||
pub new_globals: Vec<GlobalData>,
|
||||
}
|
||||
|
||||
pub type ClientQueueHandle = QueueHandle<Globals>;
|
||||
|
||||
pub struct ClientShmPool {
|
||||
pub pool: WlShmPool,
|
||||
pub fd: OwnedFd,
|
||||
}
|
||||
|
||||
pub struct ClientState {
|
||||
pub connection: Connection,
|
||||
pub queue: EventQueue<Globals>,
|
||||
pub qh: ClientQueueHandle,
|
||||
pub globals: Globals,
|
||||
pub registry: WlRegistry,
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
pub fn new(server_connection: Option<UnixStream>) -> Self {
|
||||
let connection = if let Some(stream) = server_connection {
|
||||
Connection::from_socket(stream)
|
||||
} else {
|
||||
Connection::connect_to_env()
|
||||
}
|
||||
.unwrap();
|
||||
let mut queue = connection.new_event_queue::<Globals>();
|
||||
let qh = queue.handle();
|
||||
let mut globals = Globals::default();
|
||||
|
||||
let registry = connection.display().get_registry(&qh, ());
|
||||
// Get initial globals
|
||||
queue.roundtrip(&mut globals).unwrap();
|
||||
|
||||
Self {
|
||||
connection,
|
||||
queue,
|
||||
qh,
|
||||
globals,
|
||||
registry,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Event<T> = <T as Proxy>::Event;
|
||||
|
||||
delegate_noop!(Globals: WlCompositor);
|
||||
delegate_noop!(Globals: ignore WlShm);
|
||||
delegate_noop!(Globals: ignore ZwpLinuxDmabufV1);
|
||||
delegate_noop!(Globals: ZwpRelativePointerManagerV1);
|
||||
delegate_noop!(Globals: ignore dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
|
||||
delegate_noop!(Globals: XdgPositioner);
|
||||
delegate_noop!(Globals: WlShmPool);
|
||||
delegate_noop!(Globals: WpViewporter);
|
||||
delegate_noop!(Globals: WpViewport);
|
||||
delegate_noop!(Globals: ZxdgOutputManagerV1);
|
||||
|
||||
impl Dispatch<WlRegistry, ()> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &WlRegistry,
|
||||
event: <WlRegistry as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<WlRegistry>::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
state.new_globals.push(GlobalData {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgWmBase, ()> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
base: &XdgWmBase,
|
||||
event: <XdgWmBase as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<XdgWmBase>::Ping { serial } = event {
|
||||
base.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlCallback, server::wl_callback::WlCallback> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlCallback,
|
||||
event: <WlCallback as Proxy>::Event,
|
||||
s_callback: &server::wl_callback::WlCallback,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let Event::<WlCallback>::Done { callback_data } = event {
|
||||
s_callback.done(callback_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlOutput, mpsc::Sender<Event<WlOutput>>> for Globals {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlOutput,
|
||||
event: <WlOutput as Proxy>::Event,
|
||||
data: &mpsc::Sender<Event<WlOutput>>,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
let _ = data.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! push_events {
|
||||
($type:ident) => {
|
||||
impl Dispatch<$type, ObjectKey> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &$type,
|
||||
event: <$type as Proxy>::Event,
|
||||
key: &ObjectKey,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
state.events.push((*key, event.into()));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
push_events!(WlSurface);
|
||||
push_events!(WlBuffer);
|
||||
push_events!(XdgSurface);
|
||||
push_events!(XdgToplevel);
|
||||
push_events!(XdgPopup);
|
||||
push_events!(WlSeat);
|
||||
push_events!(WlPointer);
|
||||
push_events!(WlOutput);
|
||||
push_events!(WlKeyboard);
|
||||
push_events!(ZwpRelativePointerV1);
|
||||
push_events!(WlDrm);
|
||||
push_events!(DmabufFeedback);
|
||||
push_events!(XdgOutput);
|
||||
197
satellite/src/lib.rs
Normal file
197
satellite/src/lib.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
mod clientside;
|
||||
mod server;
|
||||
mod xstate;
|
||||
|
||||
use crate::server::{PendingSurfaceState, ServerState};
|
||||
use crate::xstate::XState;
|
||||
use log::{error, info};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use signal_hook::consts::*;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
|
||||
use std::os::unix::{net::UnixStream, process::CommandExt};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::Sender,
|
||||
Arc,
|
||||
};
|
||||
use wayland_server::{Display, ListeningSocket};
|
||||
use xcb::x;
|
||||
|
||||
pub trait XConnection: Sized + 'static {
|
||||
type ExtraData: FromServerState<Self>;
|
||||
|
||||
fn root_window(&self) -> x::Window;
|
||||
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
|
||||
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, data: Self::ExtraData);
|
||||
fn focus_window(&mut self, window: x::Window, data: Self::ExtraData);
|
||||
fn close_window(&mut self, window: x::Window, data: Self::ExtraData);
|
||||
}
|
||||
|
||||
pub trait FromServerState<C: XConnection> {
|
||||
fn create(state: &ServerState<C>) -> Self;
|
||||
}
|
||||
|
||||
type RealServerState = ServerState<Arc<xcb::Connection>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StateEvent {
|
||||
CreatedServer,
|
||||
ConnectedServer,
|
||||
XwaylandReady,
|
||||
}
|
||||
|
||||
pub fn main(comp: Option<UnixStream>, state_updater: Option<Sender<StateEvent>>) -> Option<()> {
|
||||
let display_arg = get_display()?;
|
||||
|
||||
let socket = ListeningSocket::bind_auto("wayland", 1..=128).unwrap();
|
||||
let mut display = Display::<RealServerState>::new().unwrap();
|
||||
let dh = display.handle();
|
||||
|
||||
let mut server_state = RealServerState::new(dh, comp);
|
||||
if let Some(ref s) = state_updater {
|
||||
s.send(StateEvent::CreatedServer).unwrap();
|
||||
}
|
||||
|
||||
let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap();
|
||||
// Prevent creation of new Xwayland command from closing fd
|
||||
rustix::io::fcntl_setfd(&xsock_xwl, rustix::io::FdFlags::empty()).unwrap();
|
||||
|
||||
// Flag when Xwayland is ready to accept our connection
|
||||
let ready = Arc::new(AtomicBool::new(false));
|
||||
signal_hook::flag::register(SIGUSR1, ready.clone()).unwrap();
|
||||
|
||||
let mut xwayland = Command::new("Xwayland");
|
||||
let mut xwayland = unsafe {
|
||||
xwayland.pre_exec(|| {
|
||||
// Set SIGUSR1 to SIG_IGN for Xwayland to get SIGUSR1 to our main process,
|
||||
// which signifies that the X server is ready to accept connections
|
||||
let mut sa_mask = MaybeUninit::uninit();
|
||||
libc::sigemptyset(sa_mask.as_mut_ptr());
|
||||
let sa_mask = sa_mask.assume_init();
|
||||
let act = libc::sigaction {
|
||||
sa_sigaction: libc::SIG_IGN,
|
||||
sa_mask,
|
||||
sa_flags: 0,
|
||||
sa_restorer: None,
|
||||
};
|
||||
libc::sigaction(SIGUSR1, &act, std::ptr::null_mut());
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
.env("WAYLAND_DISPLAY", socket.socket_name().unwrap())
|
||||
//.env("WAYLAND_DEBUG", "1")
|
||||
.args([
|
||||
&display_arg,
|
||||
"-rootless",
|
||||
"-wm",
|
||||
&format!("{}", &xsock_xwl.as_raw_fd()),
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let (mut finish_tx, mut finish_rx) = UnixStream::pair().unwrap();
|
||||
let stderr = xwayland.stderr.take().unwrap();
|
||||
std::thread::spawn(move || {
|
||||
let reader = BufReader::new(stderr);
|
||||
for line in reader.lines() {
|
||||
let line = line.unwrap();
|
||||
info!(target: "xwayland_process", "{line}");
|
||||
}
|
||||
let status = Box::new(xwayland.wait().unwrap());
|
||||
let status = Box::into_raw(status) as usize;
|
||||
finish_tx.write_all(&status.to_ne_bytes()).unwrap();
|
||||
});
|
||||
|
||||
let mut ready_fds = [
|
||||
PollFd::new(&socket, PollFlags::IN),
|
||||
PollFd::new(&finish_rx, PollFlags::IN),
|
||||
];
|
||||
|
||||
let connection = match poll(&mut ready_fds, -1) {
|
||||
Ok(_) => {
|
||||
if !ready_fds[1].revents().is_empty() {
|
||||
let mut data = [0; (usize::BITS / 8) as usize];
|
||||
finish_rx.read_exact(&mut data).unwrap();
|
||||
let data = usize::from_ne_bytes(data);
|
||||
let status: Box<std::process::ExitStatus> =
|
||||
unsafe { Box::from_raw(data as *mut _) };
|
||||
|
||||
error!("Xwayland exited early with {status}");
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(ref s) = state_updater {
|
||||
s.send(StateEvent::ConnectedServer).unwrap();
|
||||
}
|
||||
socket.accept().unwrap().unwrap()
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("first poll failed: {e:?}")
|
||||
}
|
||||
};
|
||||
drop(finish_rx);
|
||||
|
||||
server_state.connect(connection);
|
||||
server_state.run();
|
||||
|
||||
let mut xstate: Option<XState> = None;
|
||||
|
||||
// Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for
|
||||
// the rest of our program anyway
|
||||
let server_fd = unsafe { BorrowedFd::borrow_raw(server_state.clientside_fd().as_raw_fd()) };
|
||||
let display_fd = unsafe { BorrowedFd::borrow_raw(display.backend().poll_fd().as_raw_fd()) };
|
||||
|
||||
let mut fds = [
|
||||
PollFd::from_borrowed_fd(server_fd, PollFlags::IN),
|
||||
PollFd::new(&xsock_wl, PollFlags::IN),
|
||||
PollFd::from_borrowed_fd(display_fd, PollFlags::IN),
|
||||
];
|
||||
|
||||
loop {
|
||||
match poll(&mut fds, -1) {
|
||||
Ok(_) => {}
|
||||
Err(rustix::io::Errno::INTR) => {
|
||||
// Typically caused by SIGUSR1
|
||||
if !ready.load(Ordering::Relaxed) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(other) => panic!("Poll failed: {other:?}"),
|
||||
}
|
||||
|
||||
if xstate.is_none() && ready.load(Ordering::Relaxed) {
|
||||
let xstate = xstate.insert(XState::new(xsock_wl.as_fd()));
|
||||
info!("Connected to Xwayland on {display_arg}");
|
||||
if let Some(ref s) = state_updater {
|
||||
s.send(StateEvent::XwaylandReady).unwrap();
|
||||
}
|
||||
server_state.set_x_connection(xstate.connection.clone());
|
||||
server_state.atoms = Some(xstate.atoms.clone());
|
||||
}
|
||||
|
||||
if let Some(state) = &mut xstate {
|
||||
state.handle_events(&mut server_state);
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut server_state).unwrap();
|
||||
server_state.run();
|
||||
display.flush_clients().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display() -> Option<String> {
|
||||
let mut args: Vec<_> = std::env::args().collect();
|
||||
if args.len() > 2 {
|
||||
error!("Unexpected arguments: {:?}", &args[2..]);
|
||||
return None;
|
||||
}
|
||||
if args.len() == 1 {
|
||||
Some(":0".into())
|
||||
} else {
|
||||
Some(args.swap_remove(1))
|
||||
}
|
||||
}
|
||||
7
satellite/src/main.rs
Normal file
7
satellite/src/main.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fn main() {
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.parse_default_env()
|
||||
.init();
|
||||
xwayland_satellite::main(None, None);
|
||||
}
|
||||
867
satellite/src/server/dispatch.rs
Normal file
867
satellite/src/server/dispatch.rs
Normal file
|
|
@ -0,0 +1,867 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManClient,
|
||||
server::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
},
|
||||
viewporter::{client as c_vp, server as s_vp},
|
||||
},
|
||||
xdg::xdg_output::zv1::{
|
||||
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
|
||||
server::{
|
||||
zxdg_output_manager_v1::{
|
||||
self as s_output_man, ZxdgOutputManagerV1 as OutputManServer,
|
||||
},
|
||||
zxdg_output_v1::{self as s_xdgo, ZxdgOutputV1 as XdgOutputServer},
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
|
||||
},
|
||||
Dispatch, DisplayHandle, GlobalDispatch, Resource,
|
||||
};
|
||||
|
||||
// noop
|
||||
impl<C: XConnection> Dispatch<WlCallback, ()> for ServerState<C> {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCallback,
|
||||
_: <WlCallback as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSurface, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSurface,
|
||||
request: <WlSurface as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let surface: &SurfaceData = state.objects[*key].as_ref();
|
||||
let configured =
|
||||
surface.role.is_none() || surface.xdg().is_none() || surface.xdg().unwrap().configured;
|
||||
|
||||
match request {
|
||||
Request::<WlSurface>::Attach { buffer, x, y } => {
|
||||
if buffer.is_none() {
|
||||
trace!("xwayland attached null buffer to {:?}", surface.client);
|
||||
}
|
||||
let buffer = buffer.as_ref().map(|b| {
|
||||
let key: &ObjectKey = b.data().unwrap();
|
||||
let data: &Buffer = state.objects[*key].as_ref();
|
||||
&data.client
|
||||
});
|
||||
|
||||
if configured {
|
||||
surface.client.attach(buffer, x, y);
|
||||
} else {
|
||||
let buffer = buffer.cloned();
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.attach = Some(SurfaceAttach { buffer, x, y });
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::DamageBuffer {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if configured {
|
||||
surface.client.damage_buffer(x, y, width, height);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Frame { callback } => {
|
||||
let cb = data_init.init(callback, ());
|
||||
if configured {
|
||||
surface.client.frame(&state.qh, cb);
|
||||
} else {
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.frame_callback = Some(cb);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Commit => {
|
||||
if configured {
|
||||
surface.client.commit();
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Destroy => {
|
||||
let mut object = state.objects.remove(*key).unwrap();
|
||||
let surface: &mut SurfaceData = object.as_mut();
|
||||
surface.destroy_role();
|
||||
surface.client.destroy();
|
||||
debug!("deleting key: {:?}", key);
|
||||
}
|
||||
Request::<WlSurface>::SetBufferScale { scale } => {
|
||||
surface.client.set_buffer_scale(scale);
|
||||
}
|
||||
other => warn!("unhandled surface request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<WlCompositor, ClientGlobalWrapper<client::wl_compositor::WlCompositor>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCompositor,
|
||||
request: <WlCompositor as wayland_server::Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_compositor::WlCompositor>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlCompositor>::CreateSurface { id } => {
|
||||
let mut surface_id = None;
|
||||
|
||||
let key = state.objects.insert_with_key(|key| {
|
||||
debug!("new surface with key {key:?}");
|
||||
let client = client.create_surface(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
surface_id = Some(server.id().protocol_id());
|
||||
|
||||
SurfaceData {
|
||||
client,
|
||||
server,
|
||||
key,
|
||||
attach: None,
|
||||
frame_callback: None,
|
||||
role: None,
|
||||
}
|
||||
.into()
|
||||
});
|
||||
|
||||
let surface_id = surface_id.unwrap();
|
||||
|
||||
if let Some((win, window_data)) =
|
||||
state.windows.iter_mut().find_map(|(win, data)| {
|
||||
Some(*win).zip((data.surface_id == surface_id).then_some(data))
|
||||
})
|
||||
{
|
||||
window_data.surface_key = Some(key);
|
||||
state.associated_windows.insert(key, win);
|
||||
debug!("associate surface {surface_id} with window {win:?}");
|
||||
if window_data.mapped {
|
||||
state.create_role_window(win, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled wlcompositor request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlBuffer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlBuffer,
|
||||
request: <WlBuffer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
assert!(matches!(request, Request::<WlBuffer>::Destroy));
|
||||
|
||||
let buf: &Buffer = state.objects[*key].as_ref();
|
||||
buf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShmPool, ClientShmPool> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShmPool,
|
||||
request: <WlShmPool as Resource>::Request,
|
||||
c_pool: &ClientShmPool,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShmPool>::CreateBuffer {
|
||||
id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_pool.pool.create_buffer(
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
convert_wenum(format),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Request::<WlShmPool>::Destroy => {
|
||||
c_pool.pool.destroy();
|
||||
}
|
||||
other => warn!("unhandled shmpool request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShm, ClientGlobalWrapper<client::wl_shm::WlShm>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShm,
|
||||
request: <WlShm as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_shm::WlShm>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShm>::CreatePool { id, fd, size } => {
|
||||
let c_pool = client.create_pool(fd.as_fd(), size, &state.qh, ());
|
||||
let c_pool = ClientShmPool { pool: c_pool, fd };
|
||||
data_init.init(id, c_pool);
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled shm pool request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlPointer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlPointer,
|
||||
request: <WlPointer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let Pointer {
|
||||
client: c_pointer, ..
|
||||
}: &Pointer = state.objects[*key].as_ref();
|
||||
|
||||
match request {
|
||||
Request::<WlPointer>::SetCursor {
|
||||
serial,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
surface,
|
||||
} => {
|
||||
let c_surface = surface.map(|s| state.get_client_surface_from_server(s));
|
||||
c_pointer.set_cursor(serial, c_surface, hotspot_x, hotspot_y);
|
||||
}
|
||||
Request::<WlPointer>::Release => {
|
||||
c_pointer.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => warn!("unhandled cursor request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlKeyboard, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlKeyboard,
|
||||
request: <WlKeyboard as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlKeyboard>::Release => {
|
||||
let Keyboard { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSeat,
|
||||
request: <WlSeat as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlSeat>::GetPointer { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_pointer(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
trace!("new pointer: {server:?}");
|
||||
Pointer::new(server, client).into()
|
||||
});
|
||||
}
|
||||
Request::<WlSeat>::GetKeyboard { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_keyboard(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
Keyboard { client, server }.into()
|
||||
});
|
||||
}
|
||||
other => warn!("unhandled seat request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<RelativePointerServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerServer,
|
||||
request: <RelativePointerServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
if let Request::<RelativePointerServer>::Destroy = request {
|
||||
let obj: &RelativePointer = state.objects[*key].as_ref();
|
||||
obj.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<RelativePointerManServer, ClientGlobalWrapper<RelativePointerManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerManServer,
|
||||
request: <RelativePointerManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<RelativePointerManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<RelativePointerManServer>::GetRelativePointer { id, pointer } => {
|
||||
let p_key: ObjectKey = pointer.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([p_key], |[pointer_obj], key| {
|
||||
let pointer: &Pointer = pointer_obj.try_into().unwrap();
|
||||
let client = client.get_relative_pointer(&pointer.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
RelativePointer { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled relative pointer request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlOutput, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlOutput,
|
||||
request: <WlOutput as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wayland_server::protocol::wl_output::Request::Release => {
|
||||
let Output { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
todo!("handle wloutput destruction");
|
||||
}
|
||||
_ => warn!("unhandled output request {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ObjectKey>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_feedback_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
let dmabuf: &DmabufFeedback = state.objects[*key].as_ref();
|
||||
dmabuf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
request: <s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 as Resource>::Request,
|
||||
c_params: &c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_buffer_params_v1::Request::*;
|
||||
match request {
|
||||
// TODO: Xwayland doesn't actually seem to use the Create request, and I don't feel like implementing it...
|
||||
Create { .. } => todo!(),
|
||||
CreateImmed {
|
||||
buffer_id,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
flags,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_params.create_immed(
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
convert_wenum(flags),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(buffer_id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Add {
|
||||
fd,
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
} => {
|
||||
c_params.add(
|
||||
fd.as_fd(),
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
);
|
||||
}
|
||||
Destroy => {
|
||||
c_params.destroy();
|
||||
}
|
||||
_ => warn!("unhandled params request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
CreateParams { params_id } => {
|
||||
let c_params = client.create_params(&state.qh, ());
|
||||
data_init.init(params_id, c_params);
|
||||
}
|
||||
GetDefaultFeedback { id } => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = client.get_default_feedback(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
GetSurfaceFeedback { id, surface } => {
|
||||
let surf_key: ObjectKey = surface.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([surf_key], |[surface_obj], key| {
|
||||
let SurfaceData {
|
||||
client: c_surface, ..
|
||||
}: &SurfaceData = surface_obj.try_into().unwrap();
|
||||
let client = client.get_surface_feedback(c_surface, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled dmabuf request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlDrmServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlDrmServer,
|
||||
request: <WlDrmServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wl_drm::server::wl_drm::Request::*;
|
||||
|
||||
type DrmFn = dyn FnOnce(
|
||||
&wl_drm::client::wl_drm::WlDrm,
|
||||
ObjectKey,
|
||||
&ClientQueueHandle,
|
||||
) -> client::wl_buffer::WlBuffer;
|
||||
|
||||
let mut bufs: Option<(Box<DrmFn>, wayland_server::New<WlBuffer>)> = None;
|
||||
match request {
|
||||
CreateBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_buffer(name, width, height, stride, format, qh, key)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePlanarBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_planar_buffer(
|
||||
name, width, height, format, offset0, stride0, offset1, stride1,
|
||||
offset2, stride2, qh, key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePrimeBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_prime_buffer(
|
||||
name.as_fd(),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
qh,
|
||||
key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
Authenticate { id } => {
|
||||
let drm: &Drm = state.objects[*key].as_ref();
|
||||
drm.client.authenticate(id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
if let Some((buf_create, id)) = bufs {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[drm_obj], key| {
|
||||
let drm: &Drm = drm_obj.try_into().unwrap();
|
||||
let client = buf_create(&drm.client, key, &state.qh);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { client, server }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<s_vp::wp_viewport::WpViewport, c_vp::wp_viewport::WpViewport>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewport::WpViewport,
|
||||
request: <s_vp::wp_viewport::WpViewport as Resource>::Request,
|
||||
c_viewport: &c_vp::wp_viewport::WpViewport,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
simple_event_shunt! {
|
||||
c_viewport, request: s_vp::wp_viewport::Request => [
|
||||
SetSource { x, y, width, height },
|
||||
SetDestination { width, height },
|
||||
Destroy
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewporter::WpViewporter,
|
||||
request: <s_vp::wp_viewporter::WpViewporter as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_vp::wp_viewporter;
|
||||
match request {
|
||||
wp_viewporter::Request::GetViewport { id, surface } => {
|
||||
let c_surface = state.get_client_surface_from_server(surface);
|
||||
let c_viewport = client.get_viewport(c_surface, &state.qh, ());
|
||||
data_init.init(id, c_viewport);
|
||||
}
|
||||
wp_viewporter::Request::Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &XdgOutputServer,
|
||||
request: <XdgOutputServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let s_xdgo::Request::Destroy = request else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let output: &XdgOutput = state.objects[*key].as_ref();
|
||||
output.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<OutputManServer, ClientGlobalWrapper<OutputManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &OutputManServer,
|
||||
request: <OutputManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<OutputManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
s_output_man::Request::GetXdgOutput { id, output } => {
|
||||
let output_key: ObjectKey = output.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([output_key], |[output_obj], key| {
|
||||
let output: &Output = output_obj.try_into().unwrap();
|
||||
let client = client.get_xdg_output(&output.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
XdgOutput { server, client }.into()
|
||||
});
|
||||
}
|
||||
s_output_man::Request::Destroy => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientGlobalWrapper<T: Proxy>(Arc<OnceLock<T>>);
|
||||
impl<T: Proxy> std::ops::Deref for ClientGlobalWrapper<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for ClientGlobalWrapper<T> {
|
||||
fn default() -> Self {
|
||||
Self(Arc::default())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_no_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
ServerState<C>: Dispatch<$server, ClientGlobalWrapper<$client>>,
|
||||
Globals: wayland_client::Dispatch<$client, ()>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let client = ClientGlobalWrapper::<$client>::default();
|
||||
let server = data_init.init(resource, client.clone());
|
||||
client
|
||||
.0
|
||||
.set(
|
||||
state
|
||||
.clientside
|
||||
.registry
|
||||
.bind(data.name, server.version(), &state.qh, ()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_with_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
$server: Resource,
|
||||
$client: Proxy,
|
||||
ServerState<C>: Dispatch<$server, ObjectKey>,
|
||||
Globals: wayland_client::Dispatch<$client, ObjectKey>,
|
||||
GenericObject<$server, $client>: Into<Object>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let server = data_init.init(resource, key);
|
||||
let client = state.clientside.registry.bind::<$client, _, _>(
|
||||
data.name,
|
||||
server.version(),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
GenericObject { server, client }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
global_dispatch_no_events!(WlShm, client::wl_shm::WlShm);
|
||||
global_dispatch_no_events!(WlCompositor, client::wl_compositor::WlCompositor);
|
||||
global_dispatch_no_events!(RelativePointerManServer, RelativePointerManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
|
||||
);
|
||||
global_dispatch_no_events!(OutputManServer, OutputManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
c_vp::wp_viewporter::WpViewporter
|
||||
);
|
||||
|
||||
global_dispatch_with_events!(WlSeat, client::wl_seat::WlSeat);
|
||||
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput);
|
||||
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
||||
605
satellite/src/server/event.rs
Normal file
605
satellite/src/server/event.rs
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::os::fd::AsFd;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1 as RelativePointerClient},
|
||||
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
||||
xdg_output::zv1::{
|
||||
client::zxdg_output_v1::{self, ZxdgOutputV1 as ClientXdgOutput},
|
||||
server::zxdg_output_v1::ZxdgOutputV1 as ServerXdgOutput,
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol::{
|
||||
wl_buffer::WlBuffer, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer,
|
||||
wl_seat::WlSeat,
|
||||
};
|
||||
|
||||
/// Lord forgive me, I am a sinner, who's probably gonna sin again
|
||||
/// This macro takes an enum variant name and a list of the field names of the enum
|
||||
/// and/or closures that take an argument that must be named the same as the field name,
|
||||
/// and converts that into a destructured enum
|
||||
/// shunt_helper_enum!(Foo [a, |b| b.do_thing(), c]) -> Foo {a, b, c}
|
||||
macro_rules! shunt_helper_enum {
|
||||
// No fields
|
||||
($variant:ident) => { $variant };
|
||||
// Starting state: variant destructure
|
||||
($variant:ident $([$($body:tt)+])?) => {
|
||||
shunt_helper_enum!($variant [$($($body)+)?] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($variant:ident [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure field to list
|
||||
($variant:ident [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Finalize into enum variant
|
||||
($variant:ident [] -> [,$($body:tt)+]) => { $variant { $($body)+ } };
|
||||
}
|
||||
|
||||
/// This does the same thing as shunt_helper_enum, except it transforms the fields into the given
|
||||
/// function/method call.
|
||||
/// shunt_helper_fn!({obj.foo} [a, |b| b.do_thing(), c]) -> obj.foo(a, b.do_thing(), c)
|
||||
macro_rules! shunt_helper_fn {
|
||||
// No fields
|
||||
({$($fn:tt)+}) => { $($fn)+() };
|
||||
// Starting state
|
||||
($fn:tt [$($body:tt)+]) => {
|
||||
shunt_helper_fn!($fn [$($body)+] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($fn:tt [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure expression to list
|
||||
($fn:tt [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $conv])
|
||||
};
|
||||
// Finalize into function call
|
||||
({$($fn:tt)+} [] -> [,$($body:tt)+]) => { $($fn)+($($body)+) };
|
||||
}
|
||||
|
||||
/// Takes an object, the name of a variable holding an event, the event type, and a list of the
|
||||
/// variants with their fields, and converts them into function calls on their arguments
|
||||
/// Event { field1, field2 } => obj.event(field1, field2)
|
||||
macro_rules! simple_event_shunt {
|
||||
($obj:expr, $event:ident: $event_type:path => [
|
||||
$( $variant:ident $({ $($fields:tt)* })? ),+
|
||||
]) => {
|
||||
{
|
||||
use $event_type::*;
|
||||
match $event {
|
||||
$(
|
||||
shunt_helper_enum!( $variant $( [ $($fields)* ] )? ) => {
|
||||
paste::paste! {
|
||||
shunt_helper_fn!( { $obj.[<$variant:snake>] } $( [ $($fields)* ] )? )
|
||||
}
|
||||
}
|
||||
)+
|
||||
_ => log::warn!(concat!("unhandled", stringify!($event_type), ": {:?}"), $event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use shunt_helper_enum;
|
||||
pub(crate) use shunt_helper_fn;
|
||||
pub(crate) use simple_event_shunt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SurfaceEvents {
|
||||
WlSurface(client::wl_surface::Event),
|
||||
XdgSurface(xdg_surface::Event),
|
||||
Toplevel(xdg_toplevel::Event),
|
||||
Popup(xdg_popup::Event),
|
||||
}
|
||||
macro_rules! impl_from {
|
||||
($type:ty, $variant:ident) => {
|
||||
impl From<$type> for ObjectEvent {
|
||||
fn from(value: $type) -> Self {
|
||||
Self::Surface(SurfaceEvents::$variant(value))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from!(client::wl_surface::Event, WlSurface);
|
||||
impl_from!(xdg_surface::Event, XdgSurface);
|
||||
impl_from!(xdg_toplevel::Event, Toplevel);
|
||||
impl_from!(xdg_popup::Event, Popup);
|
||||
|
||||
impl HandleEvent for SurfaceData {
|
||||
type Event = SurfaceEvents;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match event {
|
||||
SurfaceEvents::WlSurface(event) => self.surface_event(event, state),
|
||||
SurfaceEvents::XdgSurface(event) => self.xdg_event(event, state),
|
||||
SurfaceEvents::Toplevel(event) => self.toplevel_event(event, state),
|
||||
SurfaceEvents::Popup(event) => self.popup_event(event, state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn surface_event<C: XConnection>(
|
||||
&self,
|
||||
event: client::wl_surface::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
let surface = &self.server;
|
||||
simple_event_shunt! {
|
||||
surface, event: client::wl_surface::Event => [
|
||||
Enter { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
Leave { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
PreferredBufferScale { factor }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_event<C: XConnection>(&mut self, event: xdg_surface::Event, state: &mut ServerState<C>) {
|
||||
let connection = state.connection.as_mut().unwrap();
|
||||
let xdg_surface::Event::Configure { serial } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let xdg = self.xdg_mut().unwrap();
|
||||
xdg.surface.ack_configure(serial);
|
||||
xdg.configured = true;
|
||||
|
||||
if let Some(pending) = xdg.pending.take() {
|
||||
let window = state.associated_windows[self.key];
|
||||
let window = state.windows.get_mut(&window).unwrap();
|
||||
let width = if pending.width > 0 {
|
||||
pending.width as _
|
||||
} else {
|
||||
window.dims.width
|
||||
};
|
||||
let height = if pending.height > 0 {
|
||||
pending.height as _
|
||||
} else {
|
||||
window.dims.height
|
||||
};
|
||||
debug!(
|
||||
"configuring {:?}: {}x{}, {width}x{height}",
|
||||
window.window, pending.x, pending.y
|
||||
);
|
||||
connection.set_window_dims(
|
||||
window.window,
|
||||
PendingSurfaceState {
|
||||
x: pending.x,
|
||||
y: pending.y,
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
},
|
||||
);
|
||||
window.dims = WindowDims {
|
||||
x: pending.x as _,
|
||||
y: pending.y as _,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
|
||||
self.client.attach(buffer.as_ref(), x, y);
|
||||
}
|
||||
if let Some(cb) = self.frame_callback.take() {
|
||||
self.client.frame(&state.qh, cb);
|
||||
}
|
||||
self.client.commit();
|
||||
}
|
||||
|
||||
fn toplevel_event<C: XConnection>(
|
||||
&mut self,
|
||||
event: xdg_toplevel::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
states,
|
||||
} => {
|
||||
debug!("configuring toplevel {width}x{height}, {states:?}");
|
||||
let activated = states.contains(&(u32::from(xdg_toplevel::State::Activated) as u8));
|
||||
|
||||
if activated {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.to_focus = Some(window);
|
||||
}
|
||||
|
||||
if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
|
||||
let prev_fs = toplevel.fullscreen;
|
||||
toplevel.fullscreen =
|
||||
states.contains(&(u32::from(xdg_toplevel::State::Fullscreen) as u8));
|
||||
if toplevel.fullscreen != prev_fs {
|
||||
let window = state.associated_windows[self.key];
|
||||
let data = C::ExtraData::create(state);
|
||||
state.connection.as_mut().unwrap().set_fullscreen(
|
||||
window,
|
||||
toplevel.fullscreen,
|
||||
data,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.close_x_window(window);
|
||||
}
|
||||
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn popup_event<C: XConnection>(&mut self, event: xdg_popup::Event, _: &mut ServerState<C>) {
|
||||
match event {
|
||||
xdg_popup::Event::Configure {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
trace!("popup configure: {x}x{y}, {width}x{height}");
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
xdg_popup::Event::Repositioned { .. } => {}
|
||||
other => todo!("{other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GenericObject<Server: Resource, Client: Proxy> {
|
||||
pub server: Server,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
pub type Buffer = GenericObject<WlBuffer, client::wl_buffer::WlBuffer>;
|
||||
impl HandleEvent for Buffer {
|
||||
type Event = client::wl_buffer::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, _: Self::Event, _: &mut ServerState<C>) {
|
||||
// The only event from a buffer would be the release.
|
||||
self.server.release();
|
||||
}
|
||||
}
|
||||
|
||||
pub type XdgOutput = GenericObject<ServerXdgOutput, ClientXdgOutput>;
|
||||
impl HandleEvent for XdgOutput {
|
||||
type Event = zxdg_output_v1::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: zxdg_output_v1::Event => [
|
||||
LogicalPosition { x, y },
|
||||
LogicalSize { width, height },
|
||||
Done,
|
||||
Name { name },
|
||||
Description { description }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Seat = GenericObject<WlSeat, client::wl_seat::WlSeat>;
|
||||
impl HandleEvent for Seat {
|
||||
type Event = client::wl_seat::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_seat::Event => [
|
||||
Capabilities { |capabilities| convert_wenum(capabilities) },
|
||||
Name { name }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pointer {
|
||||
server: WlPointer,
|
||||
pub client: client::wl_pointer::WlPointer,
|
||||
pending_enter: PendingEnter,
|
||||
}
|
||||
|
||||
impl Pointer {
|
||||
pub fn new(server: WlPointer, client: client::wl_pointer::WlPointer) -> Self {
|
||||
Self {
|
||||
server,
|
||||
client,
|
||||
pending_enter: PendingEnter(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingEnter(Option<client::wl_pointer::Event>);
|
||||
|
||||
impl HandleEvent for Pointer {
|
||||
type Event = client::wl_pointer::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
// Workaround GTK (stupidly) autoclosing popups if it receives an wl_pointer.enter
|
||||
// event shortly after creation.
|
||||
// When Niri creates a popup, it immediately sends wl_pointer.enter on the new surface,
|
||||
// generating an EnterNotify event, and Xwayland will send a release button event.
|
||||
// In its menu implementation, GTK treats EnterNotify "this menu is now active" and will
|
||||
// destroy the menu if this occurs within a 500 ms interval (which it always does with
|
||||
// Niri). Other compositors do not run into this problem because they appear to not send
|
||||
// wl_pointer.enter until the user actually moves the mouse in the popup.
|
||||
let mut process_event = Vec::new();
|
||||
match event {
|
||||
client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
ref surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
let do_enter = || {
|
||||
debug!("entering surface ({serial})");
|
||||
let surface = state.get_server_surface_from_client(surface.clone());
|
||||
self.server.enter(serial, surface, surface_x, surface_y);
|
||||
};
|
||||
let surface_key: ObjectKey = surface.data().copied().unwrap();
|
||||
let surface_data: &SurfaceData = state.objects[surface_key].as_ref();
|
||||
|
||||
if matches!(surface_data.role, Some(SurfaceRole::Popup(_))) {
|
||||
match self.pending_enter.0.take() {
|
||||
Some(e) => {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial: pending_serial,
|
||||
..
|
||||
} = e
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
if serial == pending_serial {
|
||||
do_enter();
|
||||
} else {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.pending_enter.0.take();
|
||||
do_enter();
|
||||
}
|
||||
}
|
||||
client::wl_pointer::Event::Leave { serial, surface } => {
|
||||
debug!("leaving surface ({serial})");
|
||||
self.pending_enter.0.take();
|
||||
self.server
|
||||
.leave(serial, state.get_server_surface_from_client(surface));
|
||||
}
|
||||
client::wl_pointer::Event::Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
if let Some(p) = &self.pending_enter.0 {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} = p
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
process_event.push(client::wl_pointer::Event::Enter {
|
||||
serial: *serial,
|
||||
surface: surface.clone(),
|
||||
surface_x: *surface_x,
|
||||
surface_y: *surface_y,
|
||||
});
|
||||
process_event.push(event);
|
||||
trace!("resending enter ({serial}) before motion");
|
||||
} else {
|
||||
self.server.motion(time, surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
_ => simple_event_shunt! {
|
||||
self.server, event: client::wl_pointer::Event => [
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Frame,
|
||||
Button {
|
||||
serial,
|
||||
time,
|
||||
button,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Axis {
|
||||
time,
|
||||
|axis| convert_wenum(axis),
|
||||
value
|
||||
},
|
||||
AxisSource {
|
||||
|axis_source| convert_wenum(axis_source)
|
||||
},
|
||||
AxisStop {
|
||||
time,
|
||||
|axis| convert_wenum(axis)
|
||||
},
|
||||
AxisDiscrete {
|
||||
|axis| convert_wenum(axis),
|
||||
discrete
|
||||
},
|
||||
AxisValue120 {
|
||||
|axis| convert_wenum(axis),
|
||||
value120
|
||||
},
|
||||
AxisRelativeDirection {
|
||||
|axis| convert_wenum(axis),
|
||||
|direction| convert_wenum(direction)
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
for event in process_event {
|
||||
self.handle_event(event, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
|
||||
impl HandleEvent for Keyboard {
|
||||
type Event = client::wl_keyboard::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|format| convert_wenum(format),
|
||||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
keys
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group
|
||||
},
|
||||
RepeatInfo {
|
||||
rate,
|
||||
delay
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Output = GenericObject<WlOutput, client::wl_output::WlOutput>;
|
||||
impl HandleEvent for Output {
|
||||
type Event = client::wl_output::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_output::Event => [
|
||||
Name { name },
|
||||
Description { description },
|
||||
Mode {
|
||||
|flags| convert_wenum(flags),
|
||||
width,
|
||||
height,
|
||||
refresh
|
||||
},
|
||||
Scale { factor },
|
||||
Geometry {
|
||||
x,
|
||||
y,
|
||||
physical_width,
|
||||
physical_height,
|
||||
|subpixel| convert_wenum(subpixel),
|
||||
make,
|
||||
model,
|
||||
|transform| convert_wenum(transform)
|
||||
},
|
||||
Done
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Drm = GenericObject<WlDrmServer, WlDrmClient>;
|
||||
impl HandleEvent for Drm {
|
||||
type Event = wl_drm::client::wl_drm::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: wl_drm::client::wl_drm::Event => [
|
||||
Device { name },
|
||||
Format { format },
|
||||
Authenticated,
|
||||
Capabilities { value }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DmabufFeedback = GenericObject<
|
||||
s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
>;
|
||||
impl HandleEvent for DmabufFeedback {
|
||||
type Event = c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event => [
|
||||
Done,
|
||||
FormatTable { |fd| fd.as_fd(), size },
|
||||
MainDevice { device },
|
||||
TrancheDone,
|
||||
TrancheTargetDevice { device },
|
||||
TrancheFormats { indices },
|
||||
TrancheFlags { |flags| convert_wenum(flags) }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type RelativePointer = GenericObject<RelativePointerServer, RelativePointerClient>;
|
||||
impl HandleEvent for RelativePointer {
|
||||
type Event = zwp_relative_pointer_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: rp::client::zwp_relative_pointer_v1::Event => [
|
||||
RelativeMotion {
|
||||
utime_hi,
|
||||
utime_lo,
|
||||
dx,
|
||||
dy,
|
||||
dx_unaccel,
|
||||
dy_unaccel
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
768
satellite/src/server/mod.rs
Normal file
768
satellite/src/server/mod.rs
Normal file
|
|
@ -0,0 +1,768 @@
|
|||
mod dispatch;
|
||||
mod event;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use self::event::*;
|
||||
use super::FromServerState;
|
||||
use crate::clientside::*;
|
||||
use crate::xstate::{Atoms, WindowDims, WmNormalHints};
|
||||
use crate::XConnection;
|
||||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
self as rp, server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
},
|
||||
viewporter::server as s_vp,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{
|
||||
xdg_popup::XdgPopup,
|
||||
xdg_positioner::{Anchor, Gravity, XdgPositioner},
|
||||
xdg_surface::XdgSurface,
|
||||
xdg_toplevel::XdgToplevel,
|
||||
xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_surface::WlSurface,
|
||||
},
|
||||
DisplayHandle, Resource, WEnum,
|
||||
};
|
||||
use wl_drm::{client::wl_drm::WlDrm as WlDrmClient, server::wl_drm::WlDrm as WlDrmServer};
|
||||
use xcb::x;
|
||||
|
||||
impl From<&x::CreateNotifyEvent> for WindowDims {
|
||||
fn from(value: &x::CreateNotifyEvent) -> Self {
|
||||
Self {
|
||||
x: value.x(),
|
||||
y: value.y(),
|
||||
width: value.width(),
|
||||
height: value.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Request<T> = <T as Resource>::Request;
|
||||
|
||||
/// Converts a WEnum from its client side version to its server side version
|
||||
fn convert_wenum<Client, Server>(wenum: WEnum<Client>) -> Server
|
||||
where
|
||||
u32: From<WEnum<Client>>,
|
||||
Server: TryFrom<u32>,
|
||||
<Server as TryFrom<u32>>::Error: std::fmt::Debug,
|
||||
{
|
||||
u32::from(wenum).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
window: x::Window,
|
||||
surface_key: Option<ObjectKey>,
|
||||
mapped: bool,
|
||||
surface_id: u32,
|
||||
popup_for: Option<x::Window>,
|
||||
dims: WindowDims,
|
||||
hints: Option<WmNormalHints>,
|
||||
override_redirect: bool,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
surface_key: None,
|
||||
mapped: false,
|
||||
popup_for: parent,
|
||||
surface_id: 0,
|
||||
dims,
|
||||
hints: None,
|
||||
override_redirect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SurfaceAttach {
|
||||
buffer: Option<client::wl_buffer::WlBuffer>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
pub struct SurfaceData {
|
||||
client: client::wl_surface::WlSurface,
|
||||
server: WlSurface,
|
||||
key: ObjectKey,
|
||||
frame_callback: Option<WlCallback>,
|
||||
attach: Option<SurfaceAttach>,
|
||||
role: Option<SurfaceRole>,
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn xdg(&self) -> Option<&XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_ref()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref t) => t.as_ref().map(|t| &t.xdg),
|
||||
SurfaceRole::Popup(ref p) => p.as_ref().map(|p| &p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_mut(&mut self) -> Option<&mut XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_mut()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref mut t) => t.as_mut().map(|t| &mut t.xdg),
|
||||
SurfaceRole::Popup(ref mut p) => p.as_mut().map(|p| &mut p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_role(&mut self) {
|
||||
if let Some(role) = self.role.take() {
|
||||
match role {
|
||||
SurfaceRole::Toplevel(Some(t)) => {
|
||||
t.toplevel.destroy();
|
||||
t.xdg.surface.destroy();
|
||||
}
|
||||
SurfaceRole::Popup(Some(p)) => {
|
||||
p.positioner.destroy();
|
||||
p.popup.destroy();
|
||||
p.xdg.surface.destroy();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SurfaceRole {
|
||||
Toplevel(Option<ToplevelData>),
|
||||
Popup(Option<PopupData>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct XdgSurfaceData {
|
||||
surface: XdgSurface,
|
||||
configured: bool,
|
||||
pending: Option<PendingSurfaceState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ToplevelData {
|
||||
toplevel: XdgToplevel,
|
||||
xdg: XdgSurfaceData,
|
||||
fullscreen: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PopupData {
|
||||
popup: XdgPopup,
|
||||
positioner: XdgPositioner,
|
||||
xdg: XdgSurfaceData,
|
||||
}
|
||||
|
||||
pub(crate) trait HandleEvent {
|
||||
type Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>);
|
||||
}
|
||||
|
||||
macro_rules! enum_try_from {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $enum:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
$pub enum $enum {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
|
||||
$(
|
||||
impl TryFrom<$enum> for $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a $enum> for &'a $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a mut $enum> for &'a mut $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a mut $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ty> for $enum {
|
||||
fn from(value: $ty) -> Self {
|
||||
$enum::$variant(value)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
(@variant_match $value:ident $enum:ident $variant:ident) => {
|
||||
match $value {
|
||||
$enum::$variant(obj) => Ok(obj),
|
||||
other => Err(format!("wrong variant type: {}", std::any::type_name_of_val(&other)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement HandleEvent for our enum
|
||||
macro_rules! handle_event_enum {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $name:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
enum_try_from! {
|
||||
$(#[$meta])*
|
||||
$pub enum $name {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
enum_try_from! {
|
||||
#[derive(Debug)]
|
||||
$pub enum [<$name Event>] {
|
||||
$( $variant(<$ty as HandleEvent>::Event) ),+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleEvent for $name {
|
||||
paste::paste! {
|
||||
type Event = [<$name Event>];
|
||||
}
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant(v) => {
|
||||
let Self::Event::$variant(event) = event else {
|
||||
unreachable!();
|
||||
};
|
||||
v.handle_event(event, state)
|
||||
}
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_event_enum! {
|
||||
|
||||
/// Objects that generate client side events that we will have to process.
|
||||
pub(crate) enum Object {
|
||||
Surface(SurfaceData),
|
||||
Buffer(Buffer),
|
||||
Seat(Seat),
|
||||
Pointer(Pointer),
|
||||
Keyboard(Keyboard),
|
||||
Output(Output),
|
||||
RelativePointer(RelativePointer),
|
||||
DmabufFeedback(DmabufFeedback),
|
||||
Drm(Drm),
|
||||
XdgOutput(XdgOutput)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct WrappedObject(Option<Object>);
|
||||
|
||||
impl<T> From<T> for WrappedObject
|
||||
where
|
||||
T: Into<Object>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(Some(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
<&T>::try_from(self.0.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a mut T: TryFrom<&'a mut Object, Error = String>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
<&mut T>::try_from(self.0.as_mut().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
type ObjectMap = HopSlotMap<ObjectKey, WrappedObject>;
|
||||
trait ObjectMapExt {
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object;
|
||||
}
|
||||
|
||||
impl ObjectMapExt for ObjectMap {
|
||||
/// Insert an object into our map that needs some other values from our map as well
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object,
|
||||
{
|
||||
let objects = keys.each_ref().map(|key| self[*key].0.take().unwrap());
|
||||
let key = self.insert(WrappedObject(None));
|
||||
let obj = insert_fn(objects.each_ref(), key);
|
||||
debug_assert!(self[key].0.replace(obj).is_none());
|
||||
for (object, key) in objects.into_iter().zip(keys.into_iter()) {
|
||||
debug_assert!(self[key].0.replace(object).is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_key_type! {
|
||||
pub struct ObjectKey;
|
||||
}
|
||||
pub struct ServerState<C: XConnection> {
|
||||
pub atoms: Option<Atoms>,
|
||||
dh: DisplayHandle,
|
||||
clientside: ClientState,
|
||||
objects: ObjectMap,
|
||||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||
windows: HashMap<x::Window, WindowData>,
|
||||
|
||||
xdg_wm_base: XdgWmBase,
|
||||
qh: ClientQueueHandle,
|
||||
to_focus: Option<x::Window>,
|
||||
last_focused_toplevel: Option<x::Window>,
|
||||
connection: Option<C>,
|
||||
}
|
||||
|
||||
const XDG_WM_BASE_VERSION: u32 = 2;
|
||||
|
||||
impl<C: XConnection> ServerState<C> {
|
||||
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
|
||||
let mut clientside = ClientState::new(server_connection);
|
||||
let qh = clientside.qh.clone();
|
||||
|
||||
let xdg_pos = clientside
|
||||
.globals
|
||||
.new_globals
|
||||
.iter()
|
||||
.position(|g| g.interface == XdgWmBase::interface().name)
|
||||
.expect("Did not get an xdg_wm_base global");
|
||||
|
||||
let data = clientside.globals.new_globals.swap_remove(xdg_pos);
|
||||
|
||||
assert!(
|
||||
data.version >= XDG_WM_BASE_VERSION,
|
||||
"xdg_wm_base older than version {XDG_WM_BASE_VERSION}"
|
||||
);
|
||||
|
||||
let xdg_wm_base =
|
||||
clientside
|
||||
.registry
|
||||
.bind::<XdgWmBase, _, _>(data.name, XDG_WM_BASE_VERSION, &qh, ());
|
||||
|
||||
let mut ret = Self {
|
||||
windows: HashMap::new(),
|
||||
clientside,
|
||||
atoms: None,
|
||||
qh,
|
||||
dh,
|
||||
to_focus: None,
|
||||
last_focused_toplevel: None,
|
||||
connection: None,
|
||||
objects: Default::default(),
|
||||
associated_windows: Default::default(),
|
||||
xdg_wm_base,
|
||||
};
|
||||
ret.handle_new_globals();
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn clientside_fd(&self) -> BorrowedFd<'_> {
|
||||
self.clientside.queue.as_fd()
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, connection: UnixStream) {
|
||||
self.dh
|
||||
.insert_client(connection, std::sync::Arc::new(()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_x_connection(&mut self, connection: C) {
|
||||
self.connection = Some(connection);
|
||||
}
|
||||
|
||||
fn handle_new_globals(&mut self) {
|
||||
let globals = std::mem::take(&mut self.clientside.globals.new_globals);
|
||||
for data in globals {
|
||||
macro_rules! server_global {
|
||||
($($global:ty),+) => {
|
||||
match data.interface {
|
||||
$(
|
||||
ref x if x == <$global>::interface().name => {
|
||||
self.dh.create_global::<Self, $global, GlobalData>(data.version, data);
|
||||
}
|
||||
)+
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_global![
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlSeat,
|
||||
WlOutput,
|
||||
ZwpRelativePointerManagerV1,
|
||||
WlDrmServer,
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ZxdgOutputManagerV1,
|
||||
s_vp::wp_viewporter::WpViewporter
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
fn get_object_from_client_object<T, P: Proxy>(&self, proxy: &P) -> &T
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
Globals: wayland_client::Dispatch<P, ObjectKey>,
|
||||
{
|
||||
let key: ObjectKey = proxy.data().copied().unwrap();
|
||||
self.objects[key].as_ref()
|
||||
}
|
||||
|
||||
pub fn new_window(
|
||||
&mut self,
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) {
|
||||
self.windows.insert(
|
||||
window,
|
||||
WindowData::new(window, override_redirect, dims, parent),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
if win.hints.is_none() || *win.hints.as_ref().unwrap() != hints {
|
||||
debug!("setting {window:?} hints {hints:?}");
|
||||
if let Some(surface) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[surface].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
if let Some(min_size) = &hints.min_size {
|
||||
data.toplevel.set_min_size(min_size.width, min_size.height);
|
||||
}
|
||||
if let Some(max_size) = &hints.max_size {
|
||||
data.toplevel.set_max_size(max_size.width, max_size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
win.hints = Some(hints);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associate_window(&mut self, window: x::Window, surface_id: u32) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
win.surface_id = surface_id;
|
||||
|
||||
if let Some(key) = self
|
||||
.objects
|
||||
.iter_mut()
|
||||
.filter_map(|(key, obj)| {
|
||||
Some(key).zip(<&mut SurfaceData>::try_from(obj.0.as_mut().unwrap()).ok())
|
||||
})
|
||||
.find_map(|(key, surface)| {
|
||||
(surface_id == surface.server.id().protocol_id()).then_some(key)
|
||||
})
|
||||
{
|
||||
win.surface_key = Some(key);
|
||||
self.associated_windows.insert(key, window);
|
||||
debug!("associate {:?} with surface {surface_id}", window);
|
||||
if win.mapped {
|
||||
self.create_role_window(window, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
|
||||
let win = self.windows.get_mut(&event.window()).unwrap();
|
||||
win.dims = WindowDims {
|
||||
x: event.x(),
|
||||
y: event.y(),
|
||||
width: event.width(),
|
||||
height: event.height(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn map_window(&mut self, window: x::Window) {
|
||||
debug!("mapping {window:?}");
|
||||
|
||||
let window = self.windows.get_mut(&window).unwrap();
|
||||
window.mapped = true;
|
||||
}
|
||||
|
||||
pub fn unmap_window(&mut self, window: x::Window) {
|
||||
let Some(win) = self.windows.get_mut(&window) else {
|
||||
return;
|
||||
};
|
||||
if !win.mapped {
|
||||
return;
|
||||
}
|
||||
debug!("unmapping {window:?}");
|
||||
|
||||
if matches!(self.last_focused_toplevel, Some(x) if x == window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
win.mapped = false;
|
||||
|
||||
if let Some(key) = win.surface_key.take() {
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
surface.destroy_role();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: x::Window, state: super::xstate::SetState) {
|
||||
let win = self.windows.get(&window).unwrap();
|
||||
let Some(key) = win.surface_key else {
|
||||
warn!("Tried to set window without surface fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
let Some(SurfaceRole::Toplevel(Some(ref toplevel))) = surface.role else {
|
||||
warn!("Tried to set an unmapped toplevel or non toplevel fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
match state {
|
||||
crate::xstate::SetState::Add => toplevel.toplevel.set_fullscreen(None),
|
||||
crate::xstate::SetState::Remove => toplevel.toplevel.unset_fullscreen(),
|
||||
crate::xstate::SetState::Toggle => {
|
||||
if toplevel.fullscreen {
|
||||
toplevel.toplevel.unset_fullscreen()
|
||||
} else {
|
||||
toplevel.toplevel.set_fullscreen(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window(&mut self, window: x::Window) {
|
||||
let _ = self.windows.remove(&window);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
if let Some(r) = self.clientside.queue.prepare_read() {
|
||||
let fd = r.connection_fd();
|
||||
let pollfd = PollFd::new(&fd, PollFlags::IN);
|
||||
if poll(&mut [pollfd], 0).unwrap() > 0 {
|
||||
let _ = r.read();
|
||||
}
|
||||
}
|
||||
self.clientside
|
||||
.queue
|
||||
.dispatch_pending(&mut self.clientside.globals)
|
||||
.unwrap();
|
||||
self.handle_clientside_events();
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_clientside_events(&mut self) {
|
||||
self.handle_new_globals();
|
||||
|
||||
let client_events = std::mem::take(&mut self.clientside.globals.events);
|
||||
for (key, event) in client_events {
|
||||
let object = &mut self.objects[key];
|
||||
let mut object = object.0.take().unwrap();
|
||||
object.handle_event(event, self);
|
||||
debug_assert!(self.objects[key].0.replace(object).is_none());
|
||||
}
|
||||
|
||||
{
|
||||
if let Some(win) = self.to_focus.take() {
|
||||
let data = C::ExtraData::create(self);
|
||||
let conn = self.connection.as_mut().unwrap();
|
||||
debug!("focusing window {win:?}");
|
||||
conn.focus_window(win, data);
|
||||
self.last_focused_toplevel = Some(win);
|
||||
}
|
||||
}
|
||||
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
||||
let surface: &SurfaceData = self.objects[surface_key].as_ref();
|
||||
let client = &surface.client;
|
||||
client.attach(None, 0, 0);
|
||||
client.commit();
|
||||
|
||||
let xdg_surface = self
|
||||
.xdg_wm_base
|
||||
.get_xdg_surface(client, &self.qh, surface_key);
|
||||
|
||||
let window_data = self.windows.get_mut(&window).unwrap();
|
||||
if window_data.override_redirect {
|
||||
// Override redirect is hard to convert to Wayland!
|
||||
// We will just make them be popups for the last focused toplevel.
|
||||
if let Some(win) = self.last_focused_toplevel {
|
||||
window_data.popup_for = Some(win)
|
||||
}
|
||||
}
|
||||
let window = self.windows.get(&window).unwrap();
|
||||
|
||||
let role = if let Some(parent) = window.popup_for {
|
||||
debug!(
|
||||
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
|
||||
window.window,
|
||||
parent,
|
||||
window.dims,
|
||||
surface.client.id()
|
||||
);
|
||||
|
||||
let parent_window = self.windows.get(&parent).unwrap();
|
||||
let parent_surface: &SurfaceData =
|
||||
self.objects[parent_window.surface_key.unwrap()].as_ref();
|
||||
|
||||
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
|
||||
positioner.set_size(window.dims.width as _, window.dims.height as _);
|
||||
positioner.set_offset(window.dims.x as i32, window.dims.y as i32);
|
||||
positioner.set_anchor(Anchor::TopLeft);
|
||||
positioner.set_gravity(Gravity::BottomRight);
|
||||
positioner.set_anchor_rect(
|
||||
0,
|
||||
0,
|
||||
parent_window.dims.width as _,
|
||||
parent_window.dims.height as _,
|
||||
);
|
||||
let popup = xdg_surface.get_popup(
|
||||
Some(&parent_surface.xdg().unwrap().surface),
|
||||
&positioner,
|
||||
&self.qh,
|
||||
surface_key,
|
||||
);
|
||||
let popup = PopupData {
|
||||
popup,
|
||||
positioner,
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg_surface,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
};
|
||||
SurfaceRole::Popup(Some(popup))
|
||||
} else {
|
||||
let data = self.create_toplevel(window, surface_key, xdg_surface);
|
||||
SurfaceRole::Toplevel(Some(data))
|
||||
};
|
||||
|
||||
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
|
||||
|
||||
let new_role_type = std::mem::discriminant(&role);
|
||||
let prev = surface.role.replace(role);
|
||||
if let Some(role) = prev {
|
||||
let old_role_type = std::mem::discriminant(&role);
|
||||
assert_eq!(
|
||||
new_role_type, old_role_type,
|
||||
"Surface for {:?} already had a role: {:?}",
|
||||
window.window, role
|
||||
);
|
||||
}
|
||||
|
||||
surface.client.commit();
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&self,
|
||||
window: &WindowData,
|
||||
surface_key: ObjectKey,
|
||||
xdg: XdgSurface,
|
||||
) -> ToplevelData {
|
||||
debug!("creating toplevel for {:?}", window.window);
|
||||
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
||||
if let Some(hints) = &window.hints {
|
||||
if let Some(min) = &hints.min_size {
|
||||
toplevel.set_min_size(min.width, min.height);
|
||||
}
|
||||
if let Some(max) = &hints.max_size {
|
||||
toplevel.set_max_size(max.width, max.height);
|
||||
}
|
||||
}
|
||||
|
||||
ToplevelData {
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
toplevel,
|
||||
fullscreen: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_surface_from_client(&self, surface: client::wl_surface::WlSurface) -> &WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.server
|
||||
}
|
||||
|
||||
fn get_client_surface_from_server(&self, surface: WlSurface) -> &client::wl_surface::WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.client
|
||||
}
|
||||
|
||||
fn close_x_window(&mut self, window: x::Window) {
|
||||
debug!("sending close request to {window:?}");
|
||||
let data = C::ExtraData::create(self);
|
||||
self.connection.as_mut().unwrap().close_window(window, data);
|
||||
if self.last_focused_toplevel == Some(window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PendingSurfaceState {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
866
satellite/src/server/tests.rs
Normal file
866
satellite/src/server/tests.rs
Normal file
|
|
@ -0,0 +1,866 @@
|
|||
use super::{ServerState, WindowDims};
|
||||
use crate::xstate::SetState;
|
||||
use paste::paste;
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::{
|
||||
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer,
|
||||
wl_compositor::WlCompositor,
|
||||
wl_display::WlDisplay,
|
||||
wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat,
|
||||
wl_shm::{Format, WlShm},
|
||||
wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Connection, Proxy, WEnum,
|
||||
};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
viewporter::client::wp_viewporter::WpViewporter,
|
||||
},
|
||||
xdg::{
|
||||
shell::server::{xdg_positioner, xdg_toplevel},
|
||||
xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{protocol as s_proto, Display, Resource};
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
use xcb::x::Window;
|
||||
|
||||
use xcb::XidNew;
|
||||
|
||||
macro_rules! with_optional {
|
||||
(
|
||||
$( #[$attr:meta] )?
|
||||
struct $name:ident$(<$($lifetimes:lifetime),+>)? {
|
||||
$(
|
||||
$field:ident: $type:ty
|
||||
),+$(,)?
|
||||
}
|
||||
) => {
|
||||
$( #[$attr] )?
|
||||
struct $name$(<$($lifetimes),+>)? {
|
||||
$(
|
||||
$field: $type
|
||||
),+
|
||||
}
|
||||
|
||||
paste! {
|
||||
#[derive(Default)]
|
||||
struct [< $name Optional >] {
|
||||
$(
|
||||
$field: Option<$type>
|
||||
),+
|
||||
}
|
||||
}
|
||||
|
||||
paste! {
|
||||
impl From<[<$name Optional>]> for $name {
|
||||
fn from(opt: [<$name Optional>]) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: opt.$field.expect(concat!("uninitialized field ", stringify!($field)))
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with_optional! {
|
||||
|
||||
struct Compositor {
|
||||
compositor: TestObject<WlCompositor>,
|
||||
shm: TestObject<WlShm>,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
fn create_surface(&self) -> (TestObject<WlBuffer>, TestObject<WlSurface>) {
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(0) };
|
||||
let pool = TestObject::<WlShmPool>::from_request(
|
||||
&self.shm.obj,
|
||||
Req::<WlShm>::CreatePool { fd, size: 1024 },
|
||||
);
|
||||
let buffer = TestObject::<WlBuffer>::from_request(
|
||||
&pool.obj,
|
||||
Req::<WlShmPool>::CreateBuffer {
|
||||
offset: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
stride: 1,
|
||||
format: WEnum::Value(Format::Xrgb8888A8),
|
||||
},
|
||||
);
|
||||
let surface = TestObject::<WlSurface>::from_request(
|
||||
&self.compositor.obj,
|
||||
Req::<WlCompositor>::CreateSurface {},
|
||||
);
|
||||
surface
|
||||
.send_request(Req::<WlSurface>::Attach {
|
||||
buffer: Some(buffer.obj.clone()),
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(buffer, surface)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
mapped: bool,
|
||||
fullscreen: bool,
|
||||
dims: WindowDims,
|
||||
}
|
||||
struct FakeXConnection {
|
||||
root: Window,
|
||||
focused_window: Option<Window>,
|
||||
windows: HashMap<Window, WindowData>,
|
||||
}
|
||||
|
||||
impl FakeXConnection {
|
||||
#[track_caller]
|
||||
fn window(&mut self, window: Window) -> &mut WindowData {
|
||||
self.windows
|
||||
.get_mut(&window)
|
||||
.expect("Unknown window: {window:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FakeXConnection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: unsafe { Window::new(9001) },
|
||||
focused_window: None,
|
||||
windows: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::FromServerState<FakeXConnection> for () {
|
||||
fn create(_: &FakeServerState) -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for FakeXConnection {
|
||||
type ExtraData = ();
|
||||
fn root_window(&self) -> Window {
|
||||
self.root
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn close_window(&mut self, window: Window, _: ()) {
|
||||
log::debug!("closing window {window:?}");
|
||||
self.window(window).mapped = false;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_fullscreen(&mut self, window: xcb::x::Window, fullscreen: bool, _: ()) {
|
||||
self.window(window).fullscreen = fullscreen;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
|
||||
self.window(window).dims = WindowDims {
|
||||
x: state.x as _,
|
||||
y: state.y as _,
|
||||
width: state.width as _,
|
||||
height: state.height as _,
|
||||
};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn focus_window(&mut self, window: Window, _: ()) {
|
||||
assert!(
|
||||
self.windows.contains_key(&window),
|
||||
"Unknown window: {window:?}"
|
||||
);
|
||||
self.focused_window = window.into();
|
||||
}
|
||||
}
|
||||
|
||||
type FakeServerState = ServerState<FakeXConnection>;
|
||||
|
||||
struct TestFixture {
|
||||
testwl: testwl::Server,
|
||||
exwayland: FakeServerState,
|
||||
/// Our connection to exwayland - i.e., where Xwayland sends requests to
|
||||
exwl_connection: Arc<Connection>,
|
||||
/// Exwayland's display - must dispatch this for our server state to advance
|
||||
exwl_display: Display<FakeServerState>,
|
||||
}
|
||||
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
impl TestFixture {
|
||||
fn new() -> Self {
|
||||
INIT.call_once(|| {
|
||||
env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Trace)
|
||||
.init()
|
||||
});
|
||||
|
||||
let (client_s, server_s) = UnixStream::pair().unwrap();
|
||||
let mut testwl = testwl::Server::new(true);
|
||||
let display = Display::<FakeServerState>::new().unwrap();
|
||||
testwl.connect(server_s);
|
||||
// Handle initial globals roundtrip setup requirement
|
||||
let thread = std::thread::spawn(move || {
|
||||
let mut pollfd = [PollFd::from_borrowed_fd(testwl.poll_fd(), PollFlags::IN)];
|
||||
if poll(&mut pollfd, 1000).unwrap() == 0 {
|
||||
panic!("Did not get events for testwl!");
|
||||
}
|
||||
testwl.dispatch();
|
||||
testwl
|
||||
});
|
||||
let mut exwayland = FakeServerState::new(display.handle(), Some(client_s));
|
||||
let testwl = thread.join().unwrap();
|
||||
|
||||
let (fake_client, ex_server) = UnixStream::pair().unwrap();
|
||||
exwayland.connect(ex_server);
|
||||
|
||||
exwayland.set_x_connection(FakeXConnection::default());
|
||||
let mut f = TestFixture {
|
||||
testwl,
|
||||
exwayland,
|
||||
exwl_connection: Connection::from_socket(fake_client).unwrap().into(),
|
||||
exwl_display: display,
|
||||
};
|
||||
f.run();
|
||||
f
|
||||
}
|
||||
|
||||
fn new_with_compositor() -> (Self, Compositor) {
|
||||
let mut f = Self::new();
|
||||
let compositor = f.compositor();
|
||||
(f, compositor)
|
||||
}
|
||||
|
||||
fn connection(&self) -> &FakeXConnection {
|
||||
self.exwayland.connection.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn compositor(&mut self) -> Compositor {
|
||||
let mut ret = CompositorOptional::default();
|
||||
let wl_display = self.exwl_connection.display();
|
||||
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&wl_display, Req::<WlDisplay>::GetRegistry {});
|
||||
self.run();
|
||||
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
|
||||
let bind_req = |name, interface, version| Req::<WlRegistry>::Bind {
|
||||
name,
|
||||
id: (interface, version),
|
||||
};
|
||||
|
||||
for event in events {
|
||||
if let Ev::<WlRegistry>::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
match interface {
|
||||
x if x == WlCompositor::interface().name => {
|
||||
ret.compositor = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlCompositor::interface(), version),
|
||||
));
|
||||
}
|
||||
x if x == WlShm::interface().name => {
|
||||
ret.shm = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlShm::interface(), version),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.into()
|
||||
}
|
||||
|
||||
/// Cascade our requests/events through exwayland and testwl
|
||||
fn run(&mut self) {
|
||||
// Flush our requests to exwayland
|
||||
self.exwl_connection.flush().unwrap();
|
||||
|
||||
// Have exwayland dispatch our requests
|
||||
self.exwl_display
|
||||
.dispatch_clients(&mut self.exwayland)
|
||||
.unwrap();
|
||||
self.exwl_display.flush_clients().unwrap();
|
||||
|
||||
// Dispatch any clientside requests
|
||||
self.exwayland.run();
|
||||
|
||||
// Have testwl dispatch the clientside requests
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Handle clientside events
|
||||
self.exwayland.handle_clientside_events();
|
||||
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Get our events
|
||||
let res = self.exwl_connection.prepare_read().unwrap().read();
|
||||
if res.is_err()
|
||||
&& !matches!(res, Err(WaylandError::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock)
|
||||
{
|
||||
panic!("Read failed: {res:?}")
|
||||
}
|
||||
}
|
||||
|
||||
fn register_window(&mut self, window: Window, data: WindowData) {
|
||||
self.exwayland
|
||||
.connection
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.windows
|
||||
.insert(window, data);
|
||||
}
|
||||
|
||||
fn create_and_map_window(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
override_redirect: bool,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland
|
||||
.new_window(window, override_redirect, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Surface not created");
|
||||
|
||||
assert!(self.testwl.get_surface_data(testwl_id).is_some());
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, false, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Toplevel surface not created");
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(testwl_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"surface role: {:?}",
|
||||
surface_data.role
|
||||
);
|
||||
}
|
||||
|
||||
self.testwl
|
||||
.configure_toplevel(testwl_id, 100, 100, vec![xdg_toplevel::State::Activated]);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
let win_data = self.connection().windows.get(&window).map(|d| &d.dims);
|
||||
assert!(
|
||||
matches!(
|
||||
win_data,
|
||||
Some(&super::WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
),
|
||||
"Incorrect window geometry: {win_data:?}"
|
||||
);
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_popup(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
parent_id: testwl::SurfaceId,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_, popup_surface) = comp.create_surface();
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
let dims = data.dims;
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, true, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, popup_surface.id().protocol_id());
|
||||
self.run();
|
||||
let popup_id = self.testwl.last_created_surface_id().unwrap();
|
||||
assert_ne!(popup_id, parent_id);
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(popup_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Popup(_))),
|
||||
"surface was not a popup (role: {:?})",
|
||||
surface_data.role
|
||||
);
|
||||
|
||||
let toplevel_xdg = &self
|
||||
.testwl
|
||||
.get_surface_data(parent_id)
|
||||
.unwrap()
|
||||
.xdg()
|
||||
.surface;
|
||||
assert_eq!(&surface_data.popup().parent, toplevel_xdg);
|
||||
|
||||
let pos = &surface_data.popup().positioner_state;
|
||||
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
|
||||
assert_eq!(
|
||||
pos.anchor_rect.as_ref().unwrap(),
|
||||
&testwl::Rect {
|
||||
size: testwl::Vec2 { x: 100, y: 100 },
|
||||
offset: testwl::Vec2::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
pos.offset,
|
||||
testwl::Vec2 {
|
||||
x: dims.x as _,
|
||||
y: dims.y as _
|
||||
}
|
||||
);
|
||||
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
|
||||
assert_eq!(pos.gravity, xdg_positioner::Gravity::BottomRight);
|
||||
}
|
||||
|
||||
self.testwl.configure_popup(popup_id);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
(popup_surface, popup_id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestObjectData<T: Proxy> {
|
||||
events: Mutex<Vec<T::Event>>,
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for TestObjectData<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Send + Sync + 'static> ObjectData for TestObjectData<T>
|
||||
where
|
||||
T::Event: Send + Sync + std::fmt::Debug,
|
||||
{
|
||||
fn event(
|
||||
self: Arc<Self>,
|
||||
backend: &Backend,
|
||||
msg: Message<ObjectId, std::os::fd::OwnedFd>,
|
||||
) -> Option<Arc<dyn ObjectData>> {
|
||||
let connection = Connection::from_backend(backend.clone());
|
||||
let event = T::parse_event(&connection, msg).unwrap().1;
|
||||
self.events.lock().unwrap().push(event);
|
||||
None
|
||||
}
|
||||
|
||||
fn destroyed(&self, _: ObjectId) {}
|
||||
}
|
||||
|
||||
struct TestObject<T: Proxy> {
|
||||
obj: T,
|
||||
data: Arc<TestObjectData<T>>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> std::ops::Deref for TestObject<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.obj
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Sync + Send + 'static> TestObject<T>
|
||||
where
|
||||
T::Event: Sync + Send + std::fmt::Debug,
|
||||
{
|
||||
fn from_request<P: Proxy>(object: &P, request: P::Request<'_>) -> Self {
|
||||
let data = Arc::<TestObjectData<T>>::default();
|
||||
let obj: T = P::send_constructor(object, request, data.clone()).unwrap();
|
||||
Self { obj, data }
|
||||
}
|
||||
}
|
||||
|
||||
type Req<'a, T> = <T as Proxy>::Request<'a>;
|
||||
type Ev<T> = <T as Proxy>::Event;
|
||||
|
||||
// TODO: tests to add
|
||||
// - destroy window before surface
|
||||
// - destroy surface before window
|
||||
// - destroy popup and reassociate with new surface
|
||||
// - reconfigure window (popup) before mapping
|
||||
// - associate window after surface is already created
|
||||
|
||||
// Matches Xwayland flow.
|
||||
#[test]
|
||||
fn toplevel_flow() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let window = unsafe { Window::new(1) };
|
||||
let (surface, testwl_id) = f.create_toplevel(&compositor, window);
|
||||
{
|
||||
let surface_data = f.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
f.testwl.close_toplevel(testwl_id);
|
||||
f.run();
|
||||
|
||||
assert!(!f.exwayland.connection.as_ref().unwrap().windows[&window].mapped);
|
||||
|
||||
assert!(
|
||||
f.testwl.get_surface_data(testwl_id).is_some(),
|
||||
"Surface should still exist for closed toplevel"
|
||||
);
|
||||
assert!(surface.obj.is_alive());
|
||||
|
||||
// For some reason, we can get two UnmapNotify events
|
||||
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.destroy_window(window);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(testwl_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_flow_simple() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let win_toplevel = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
|
||||
|
||||
let win_popup = unsafe { Window::new(2) };
|
||||
let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win_popup);
|
||||
f.exwayland.destroy_window(win_popup);
|
||||
popup_surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(popup_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_through_globals() {
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
|
||||
let mut f = TestFixture::new();
|
||||
|
||||
const fn check<T: Proxy>() {}
|
||||
|
||||
macro_rules! globals_struct {
|
||||
($($field:ident),+) => {
|
||||
$( check::<$field>(); )+
|
||||
#[derive(Default)]
|
||||
#[allow(non_snake_case)]
|
||||
struct SupportedGlobals {
|
||||
$( $field: bool ),+
|
||||
}
|
||||
|
||||
impl SupportedGlobals {
|
||||
fn check_globals(&self) {
|
||||
$( assert!(self.$field, "Missing global {}", stringify!($field)); )+
|
||||
}
|
||||
|
||||
fn global_found(&mut self, interface: String) {
|
||||
match interface {
|
||||
$(
|
||||
x if x == $field::interface().name => {
|
||||
self.$field = true;
|
||||
}
|
||||
)+
|
||||
_ => panic!("Found an unhandled global: {interface}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New globals need to be added here and in testwl.
|
||||
globals_struct! {
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlOutput,
|
||||
WlSeat,
|
||||
ZwpLinuxDmabufV1,
|
||||
ZwpRelativePointerManagerV1,
|
||||
ZxdgOutputManagerV1,
|
||||
WpViewporter,
|
||||
WlDrm
|
||||
}
|
||||
|
||||
let mut globals = SupportedGlobals::default();
|
||||
let display = f.exwl_connection.display();
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
|
||||
f.run();
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
for event in events {
|
||||
let Ev::<WlRegistry>::Global { interface, .. } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
globals.global_found(interface);
|
||||
}
|
||||
|
||||
globals.check_globals();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_activated_toplevel_is_focused() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
|
||||
let (_surface1, id1) = f.create_toplevel(&comp, win1);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win1),
|
||||
"new toplevel's window is not focused"
|
||||
);
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let _data2 = f.create_toplevel(&comp, win2);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not switch"
|
||||
);
|
||||
|
||||
f.testwl.configure_toplevel(id1, 100, 100, vec![]);
|
||||
f.run();
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not stay the same"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_window_changes_surface() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let t_win = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
|
||||
|
||||
let win = unsafe { Window::new(2) };
|
||||
let (surface, old_id) = f.create_popup(&comp, win, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(old_id).is_none());
|
||||
|
||||
let (_, surface) = comp.create_surface();
|
||||
f.run();
|
||||
let id = f
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("No surface created");
|
||||
|
||||
assert_ne!(old_id, id);
|
||||
assert!(f.testwl.get_surface_data(id).is_some());
|
||||
|
||||
f.exwayland.map_window(win);
|
||||
f.exwayland
|
||||
.associate_window(win, surface.id().protocol_id());
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
|
||||
f.testwl.configure_popup(id);
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn override_redirect_window_after_toplevel_close() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
let (obj, first) = f.create_toplevel(&comp, win1);
|
||||
f.testwl.close_toplevel(first);
|
||||
f.run();
|
||||
|
||||
f.exwayland.unmap_window(win1);
|
||||
f.exwayland.destroy_window(win1);
|
||||
obj.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(first).is_none());
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let (_, second) = f.create_and_map_window(&comp, win2, true);
|
||||
let data = f.testwl.get_surface_data(second).unwrap();
|
||||
assert!(
|
||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"wrong role: {:?}",
|
||||
data.role
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_, id) = f.create_toplevel(&comp, win);
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Add);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Remove);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
}
|
||||
|
||||
/// See Pointer::handle_event for an explanation.
|
||||
#[test]
|
||||
fn popup_pointer_motion_workaround() {}
|
||||
535
satellite/src/xstate.rs
Normal file
535
satellite/src/xstate.rs
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
use bitflags::bitflags;
|
||||
use log::{debug, trace, warn};
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::sync::Arc;
|
||||
use xcb::{x, Xid, XidNew};
|
||||
use xcb_util_cursor::{Cursor, CursorContext};
|
||||
|
||||
pub struct XState {
|
||||
pub connection: Arc<xcb::Connection>,
|
||||
root: x::Window,
|
||||
pub atoms: Atoms,
|
||||
window_types: WindowTypes,
|
||||
}
|
||||
|
||||
impl XState {
|
||||
pub fn new(fd: BorrowedFd) -> Self {
|
||||
let connection = Arc::new(xcb::Connection::connect_to_fd(fd.as_raw_fd(), None).unwrap());
|
||||
let setup = connection.get_setup();
|
||||
let screen = setup.roots().next().unwrap();
|
||||
let root = screen.root();
|
||||
|
||||
connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: root,
|
||||
value_list: &[x::Cw::EventMask(
|
||||
x::EventMask::SUBSTRUCTURE_REDIRECT // To have Xwayland send us WL_SURFACE_ID
|
||||
| x::EventMask::SUBSTRUCTURE_NOTIFY // To get notified whenever new windows are created
|
||||
| x::EventMask::RESIZE_REDIRECT,
|
||||
)],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let atoms = Atoms::intern_all(&connection).unwrap();
|
||||
trace!("atoms: {atoms:#?}");
|
||||
let window_types = WindowTypes::new(&connection);
|
||||
|
||||
// This makes Xwayland spit out damage tracking
|
||||
connection
|
||||
.send_and_check_request(&xcb::composite::RedirectSubwindows {
|
||||
window: screen.root(),
|
||||
update: xcb::composite::Redirect::Manual,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Setup default cursor theme
|
||||
let ctx = CursorContext::new(&connection, screen).unwrap();
|
||||
let left_ptr = ctx.load_cursor(Cursor::LeftPtr);
|
||||
connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: root,
|
||||
value_list: &[x::Cw::Cursor(left_ptr)],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut r = Self {
|
||||
connection,
|
||||
root,
|
||||
atoms,
|
||||
window_types,
|
||||
};
|
||||
r.create_ewmh_window();
|
||||
r
|
||||
}
|
||||
|
||||
fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) {
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: self.root,
|
||||
property,
|
||||
r#type,
|
||||
data,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn create_ewmh_window(&mut self) {
|
||||
let window = self.connection.generate_id();
|
||||
self.connection
|
||||
.send_and_check_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: window,
|
||||
parent: self.root,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOnly,
|
||||
visual: x::COPY_FROM_PARENT,
|
||||
value_list: &[],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
|
||||
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
|
||||
self.set_root_property(
|
||||
self.atoms.supported,
|
||||
x::ATOM_ATOM,
|
||||
&[self.atoms.active_win, self.atoms.client_list],
|
||||
);
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: self.atoms.wm_check,
|
||||
r#type: x::ATOM_WINDOW,
|
||||
data: &[window],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: self.atoms.wm_name,
|
||||
r#type: x::ATOM_STRING,
|
||||
data: b"exwayland wm",
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
|
||||
while let Some(event) = self.connection.poll_for_event().unwrap() {
|
||||
trace!("x11 event: {event:?}");
|
||||
match event {
|
||||
xcb::Event::X(x::Event::CreateNotify(e)) => {
|
||||
debug!("new window: {:?}", e);
|
||||
match self
|
||||
.connection
|
||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window: e.window(),
|
||||
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
|
||||
}) {
|
||||
// This can sometimes fail if the window was created and then immediately
|
||||
// destroyed.
|
||||
Ok(()) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
|
||||
Err(other) => {
|
||||
panic!("error subscribing to property change on new window: {other:?}")
|
||||
}
|
||||
}
|
||||
|
||||
let parent = e.parent();
|
||||
let parent = if parent.is_none() || parent == self.root {
|
||||
None
|
||||
} else {
|
||||
Some(parent)
|
||||
};
|
||||
server_state.new_window(e.window(), e.override_redirect(), (&e).into(), parent);
|
||||
}
|
||||
xcb::Event::X(x::Event::MapRequest(e)) => {
|
||||
debug!("requested to map {:?}", e.window());
|
||||
self.connection
|
||||
.send_and_check_request(&x::MapWindow { window: e.window() })
|
||||
.unwrap();
|
||||
}
|
||||
xcb::Event::X(x::Event::MapNotify(e)) => {
|
||||
server_state.map_window(e.window());
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(e)) => {
|
||||
server_state.reconfigure_window(e);
|
||||
}
|
||||
xcb::Event::X(x::Event::UnmapNotify(e)) => {
|
||||
trace!("unmap event: {:?}", e.event());
|
||||
server_state.unmap_window(e.window());
|
||||
}
|
||||
xcb::Event::X(x::Event::DestroyNotify(e)) => {
|
||||
debug!("destroying window {:?}", e.window());
|
||||
server_state.destroy_window(e.window());
|
||||
}
|
||||
xcb::Event::X(x::Event::PropertyNotify(e)) => {
|
||||
self.handle_property_change(e, server_state);
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureRequest(e)) => {
|
||||
debug!("{:?} request: {:?}", e.window(), e.value_mask());
|
||||
let mut list = Vec::new();
|
||||
let mask = e.value_mask();
|
||||
|
||||
if mask.contains(x::ConfigWindowMask::X) {
|
||||
list.push(x::ConfigWindow::X(e.x().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::Y) {
|
||||
list.push(x::ConfigWindow::Y(e.y().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::WIDTH) {
|
||||
list.push(x::ConfigWindow::Width(e.width().into()));
|
||||
}
|
||||
if mask.contains(x::ConfigWindowMask::HEIGHT) {
|
||||
list.push(x::ConfigWindow::Height(e.height().into()));
|
||||
}
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ConfigureWindow {
|
||||
window: e.window(),
|
||||
value_list: &list,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
|
||||
x if x == self.atoms.wl_surface_id => {
|
||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||
unreachable!();
|
||||
};
|
||||
let id: u32 = (data[0] as u64 | ((data[1] as u64) << 32)) as u32;
|
||||
server_state.associate_window(e.window(), id);
|
||||
}
|
||||
x if x == self.atoms.net_wm_state => {
|
||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||
unreachable!();
|
||||
};
|
||||
let Ok(action) = SetState::try_from(data[0]) else {
|
||||
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
|
||||
continue;
|
||||
};
|
||||
let prop1 = unsafe { x::Atom::new(data[1]) };
|
||||
let prop2 = unsafe { x::Atom::new(data[2]) };
|
||||
|
||||
trace!("_NET_WM_STATE ({action:?}) props: {prop1:?} {prop2:?}");
|
||||
|
||||
for prop in [prop1, prop2] {
|
||||
match prop {
|
||||
x if x == self.atoms.wm_fullscreen => {
|
||||
server_state.set_fullscreen(e.window(), action);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
t => warn!("unrecognized message: {t:?}"),
|
||||
},
|
||||
xcb::Event::X(x::Event::MappingNotify(_)) => {}
|
||||
other => {
|
||||
warn!("unhandled event: {other:?}");
|
||||
}
|
||||
}
|
||||
|
||||
server_state.run();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_property_change(
|
||||
&self,
|
||||
event: x::PropertyNotifyEvent,
|
||||
server_state: &mut super::RealServerState,
|
||||
) {
|
||||
let get_prop = |r#type, long_length| {
|
||||
self.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
window: event.window(),
|
||||
property: event.atom(),
|
||||
r#type,
|
||||
long_offset: 0,
|
||||
long_length,
|
||||
delete: false,
|
||||
}))
|
||||
};
|
||||
if event.state() != x::Property::NewValue {
|
||||
return;
|
||||
}
|
||||
|
||||
match event.atom() {
|
||||
x if x == self.atoms.wm_window_type => {
|
||||
let Ok(prop) = get_prop(x::ATOM_ATOM, 8) else {
|
||||
return;
|
||||
};
|
||||
let types: &[x::Atom] = prop.value();
|
||||
let win_type = types.iter().find_map(|a| self.window_types.get_type(*a));
|
||||
debug!(
|
||||
"set {:?} type to {} ({})",
|
||||
event.window(),
|
||||
win_type.unwrap_or("[Unknown/Unrecognized]".to_string()),
|
||||
types.len()
|
||||
);
|
||||
}
|
||||
x if x == x::ATOM_WM_NORMAL_HINTS => {
|
||||
let Ok(prop) = get_prop(x::ATOM_WM_SIZE_HINTS, 9) else {
|
||||
return;
|
||||
};
|
||||
let data: &[u32] = prop.value();
|
||||
let hints = WmNormalHints::from(data);
|
||||
server_state.set_win_hints(event.window(), hints);
|
||||
}
|
||||
_ => {
|
||||
let prop = self
|
||||
.connection
|
||||
.wait_for_reply(
|
||||
self.connection
|
||||
.send_request(&x::GetAtomName { atom: event.atom() }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
debug!(
|
||||
"changed property {:?} for {:?}",
|
||||
prop.name(),
|
||||
event.window()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Atoms {
|
||||
pub wl_surface_id => b"WL_SURFACE_ID" only_if_exists = false,
|
||||
pub wm_protocols => b"WM_PROTOCOLS" only_if_exists = false,
|
||||
pub wm_delete_window => b"WM_DELETE_WINDOW" only_if_exists = false,
|
||||
pub wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
||||
pub wm_hints => b"WM_HINTS" only_if_exists = false,
|
||||
pub wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
||||
pub wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
||||
pub wm_window_type => b"_NET_WM_WINDOW_TYPE" only_if_exists = false,
|
||||
pub wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
||||
pub net_wm_state => b"_NET_WM_STATE" only_if_exists = false,
|
||||
pub wm_fullscreen => b"_NET_WM_STATE_FULLSCREEN" only_if_exists = false,
|
||||
pub active_win => b"_NET_ACTIVE_WINDOW" only_if_exists = false,
|
||||
pub client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
|
||||
pub supported => b"_NET_SUPPORTED" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
pub struct WindowTypes {
|
||||
pub normal => b"_NET_WM_WINDOW_TYPE_NORMAL" only_if_exists = false,
|
||||
pub dialog => b"_NET_WM_WINDOW_TYPE_DIALOG" only_if_exists = false,
|
||||
pub splash => b"_NET_WM_WINDOW_TYPE_SPLASH" only_if_exists = false,
|
||||
pub menu => b"_NET_WM_WINDOW_TYPE_MENU" only_if_exists = false,
|
||||
pub utility => b"_NET_WM_WINDOW_TYPE_UTILITY" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowTypes {
|
||||
pub fn new(connection: &xcb::Connection) -> Self {
|
||||
let r = Self::intern_all(connection).unwrap();
|
||||
assert_ne!(r.normal, x::ATOM_NONE);
|
||||
assert_ne!(r.dialog, x::ATOM_NONE);
|
||||
assert_ne!(r.utility, x::ATOM_NONE);
|
||||
r
|
||||
}
|
||||
pub fn get_type(&self, atom: x::Atom) -> Option<String> {
|
||||
match atom {
|
||||
x if x == self.normal => Some("Normal".to_string()),
|
||||
x if x == self.dialog => Some("Dialog".to_string()),
|
||||
x if x == self.utility => Some("Utility".to_string()),
|
||||
x if x == self.menu => Some("Menu".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct WindowDims {
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// From ICCCM spec.
|
||||
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
|
||||
pub struct WmSizeHintsFlags: u32 {
|
||||
const UserPosition = 1;
|
||||
const UserSize = 2;
|
||||
const ProgramPosition = 4;
|
||||
const ProgramSize = 8;
|
||||
const ProgramMinSize = 16;
|
||||
const ProgramMaxSize = 32;
|
||||
const ProgramResizeIncrement = 64;
|
||||
const ProgramAspect = 128;
|
||||
const ProgramBaseSize = 256;
|
||||
const ProgramWinGravity = 512;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct WinSize {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct WmNormalHints {
|
||||
pub min_size: Option<WinSize>,
|
||||
pub max_size: Option<WinSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SetState {
|
||||
Remove,
|
||||
Add,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for SetState {
|
||||
type Error = ();
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Remove),
|
||||
1 => Ok(Self::Add),
|
||||
2 => Ok(Self::Toggle),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmNormalHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmSizeHintsFlags::from_bits(value[0]).unwrap();
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMinSize) {
|
||||
ret.min_size = Some(WinSize {
|
||||
width: value[5] as _,
|
||||
height: value[6] as _,
|
||||
});
|
||||
}
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMaxSize) {
|
||||
ret.max_size = Some(WinSize {
|
||||
width: value[7] as _,
|
||||
height: value[8] as _,
|
||||
});
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for Arc<xcb::Connection> {
|
||||
type ExtraData = Atoms;
|
||||
|
||||
fn root_window(&self) -> x::Window {
|
||||
self.get_setup().roots().next().unwrap().root()
|
||||
}
|
||||
|
||||
fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
|
||||
trace!("reconfiguring window {window:?}");
|
||||
self.send_and_check_request(&x::ConfigureWindow {
|
||||
window,
|
||||
value_list: &[
|
||||
x::ConfigWindow::X(dims.x),
|
||||
x::ConfigWindow::Y(dims.y),
|
||||
x::ConfigWindow::Width(dims.width as _),
|
||||
x::ConfigWindow::Height(dims.height as _),
|
||||
],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, atoms: Self::ExtraData) {
|
||||
let data = if fullscreen {
|
||||
std::slice::from_ref(&atoms.wm_fullscreen)
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
self.send_and_check_request(&x::ChangeProperty::<x::Atom> {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: atoms.net_wm_state,
|
||||
r#type: x::ATOM_ATOM,
|
||||
data,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn focus_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
|
||||
let prop = self
|
||||
.wait_for_reply(self.send_request(&x::GetProperty {
|
||||
delete: false,
|
||||
window,
|
||||
property: atoms.wm_hints,
|
||||
r#type: atoms.wm_hints,
|
||||
long_offset: 0,
|
||||
long_length: 8,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let fields: &[u32] = prop.value();
|
||||
let mut input = false;
|
||||
if !fields.is_empty() {
|
||||
let flags = fields[0];
|
||||
if (flags & 0x1) > 0 {
|
||||
input = fields[1] > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if input {
|
||||
// might fail if window is not visible but who cares
|
||||
let _ = self.send_and_check_request(&x::SetInputFocus {
|
||||
focus: window,
|
||||
revert_to: x::InputFocus::None,
|
||||
time: x::CURRENT_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
self.send_and_check_request(&x::ConfigureWindow {
|
||||
window,
|
||||
value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
|
||||
})
|
||||
.unwrap();
|
||||
self.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: self.root_window(),
|
||||
property: atoms.active_win,
|
||||
r#type: x::ATOM_WINDOW,
|
||||
data: &[window],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn close_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
|
||||
let data = [atoms.wm_delete_window.resource_id(), 0, 0, 0, 0];
|
||||
let event = &x::ClientMessageEvent::new(
|
||||
window,
|
||||
atoms.wm_protocols,
|
||||
x::ClientMessageData::Data32(data),
|
||||
);
|
||||
|
||||
self.send_and_check_request(&x::SendEvent {
|
||||
destination: x::SendEventDest::Window(window),
|
||||
propagate: false,
|
||||
event_mask: x::EventMask::empty(),
|
||||
event,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl super::FromServerState<Arc<xcb::Connection>> for Atoms {
|
||||
fn create(state: &super::RealServerState) -> Self {
|
||||
state.atoms.as_ref().unwrap().clone()
|
||||
}
|
||||
}
|
||||
269
satellite/tests/integration.rs
Normal file
269
satellite/tests/integration.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Once;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use wayland_server::Resource;
|
||||
use xcb::{x, Xid};
|
||||
use xwayland_satellite as xwls;
|
||||
|
||||
struct Fixture {
|
||||
testwl: testwl::Server,
|
||||
thread: ManuallyDrop<JoinHandle<Option<()>>>,
|
||||
pollfd: PollFd<'static>,
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let thread = unsafe { ManuallyDrop::take(&mut self.thread) };
|
||||
if thread.is_finished() {
|
||||
thread.join().expect("Main thread panicked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
struct Atoms {
|
||||
wm_protocols => b"WM_PROTOCOLS",
|
||||
wm_delete_window => b"WM_DELETE_WINDOW",
|
||||
}
|
||||
}
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
impl Fixture {
|
||||
#[track_caller]
|
||||
fn new() -> Self {
|
||||
INIT.call_once(|| {
|
||||
env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
});
|
||||
|
||||
let (a, b) = UnixStream::pair().unwrap();
|
||||
let mut testwl = testwl::Server::new(false);
|
||||
testwl.connect(a);
|
||||
|
||||
let (send, recv) = mpsc::channel();
|
||||
let thread = std::thread::spawn(move || xwls::main(Some(b), Some(send)));
|
||||
|
||||
// wait for connection
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(testwl.poll_fd().as_raw_fd()) };
|
||||
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
||||
assert!(poll(&mut [pollfd.clone()], 100).unwrap() > 0);
|
||||
testwl.dispatch();
|
||||
|
||||
let wait = Duration::from_secs(1);
|
||||
assert_eq!(
|
||||
recv.recv_timeout(wait),
|
||||
Ok(xwls::StateEvent::CreatedServer),
|
||||
"creating server"
|
||||
);
|
||||
assert_eq!(
|
||||
recv.recv_timeout(wait),
|
||||
Ok(xwls::StateEvent::ConnectedServer),
|
||||
"connecting to server"
|
||||
);
|
||||
|
||||
let mut f = [pollfd.clone()];
|
||||
let start = std::time::Instant::now();
|
||||
// Give Xwayland time to do its thing
|
||||
while start.elapsed() < Duration::from_millis(500) {
|
||||
let n = poll(&mut f, 100).unwrap();
|
||||
if n > 0 {
|
||||
testwl.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
recv.try_recv(),
|
||||
Ok(xwls::StateEvent::XwaylandReady),
|
||||
"connecting to xwayland"
|
||||
);
|
||||
|
||||
Self {
|
||||
testwl,
|
||||
thread: ManuallyDrop::new(thread),
|
||||
pollfd,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn wait_and_dispatch(&mut self) {
|
||||
let mut pollfd = [self.pollfd.clone()];
|
||||
assert!(
|
||||
poll(&mut pollfd, 50).unwrap() > 0,
|
||||
"Did not receive any events"
|
||||
);
|
||||
self.pollfd.clear_revents();
|
||||
self.testwl.dispatch();
|
||||
|
||||
while poll(&mut pollfd, 50).unwrap() > 0 {
|
||||
self.testwl.dispatch();
|
||||
self.pollfd.clear_revents();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
&mut self,
|
||||
connection: &xcb::Connection,
|
||||
override_redirect: bool,
|
||||
x: i16,
|
||||
y: i16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> (x::Window, testwl::SurfaceId) {
|
||||
let screen = connection.get_setup().roots().next().unwrap();
|
||||
let wid = connection.generate_id();
|
||||
let req = x::CreateWindow {
|
||||
depth: x::COPY_FROM_PARENT as _,
|
||||
wid,
|
||||
parent: screen.root(),
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOutput,
|
||||
visual: screen.root_visual(),
|
||||
value_list: &[
|
||||
x::Cw::BackPixel(screen.white_pixel()),
|
||||
x::Cw::OverrideRedirect(override_redirect),
|
||||
],
|
||||
};
|
||||
connection.send_and_check_request(&req).unwrap();
|
||||
|
||||
let req = x::MapWindow { window: wid };
|
||||
connection.send_and_check_request(&req).unwrap();
|
||||
self.wait_and_dispatch();
|
||||
|
||||
let id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("No surface created for window");
|
||||
|
||||
(wid, id)
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&mut self,
|
||||
connection: &xcb::Connection,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> (x::Window, testwl::SurfaceId) {
|
||||
let (window, surface) = self.create_window(connection, false, 0, 0, width, height);
|
||||
let data = self
|
||||
.testwl
|
||||
.get_surface_data(surface)
|
||||
.expect("No surface data");
|
||||
assert!(
|
||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"surface role was wrong: {:?}",
|
||||
data.role
|
||||
);
|
||||
|
||||
self.testwl
|
||||
.configure_toplevel(surface, 100, 100, vec![xdg_toplevel::State::Activated]);
|
||||
self.wait_and_dispatch();
|
||||
let geometry = connection
|
||||
.wait_for_reply(connection.send_request(&x::GetGeometry {
|
||||
drawable: x::Drawable::Window(window),
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(geometry.x(), 0);
|
||||
assert_eq!(geometry.y(), 0);
|
||||
assert_eq!(geometry.width(), 100);
|
||||
assert_eq!(geometry.height(), 100);
|
||||
|
||||
(window, surface)
|
||||
}
|
||||
|
||||
/// Triggers a Wayland side toplevel Close event and processes the corresponding
|
||||
/// X11 side WM_DELETE_WINDOW client message
|
||||
fn close_toplevel(
|
||||
&mut self,
|
||||
connection: &mut Connection,
|
||||
window: x::Window,
|
||||
surface: testwl::SurfaceId,
|
||||
) {
|
||||
self.testwl.close_toplevel(surface);
|
||||
connection.await_event();
|
||||
let event = connection
|
||||
.inner
|
||||
.poll_for_event()
|
||||
.unwrap()
|
||||
.expect("No close event");
|
||||
|
||||
let xcb::Event::X(x::Event::ClientMessage(event)) = event else {
|
||||
panic!("Expected ClientMessage event, got {event:?}");
|
||||
};
|
||||
|
||||
assert_eq!(event.window(), window);
|
||||
assert_eq!(event.format(), 32);
|
||||
assert_eq!(event.r#type(), connection.atoms.wm_protocols);
|
||||
match event.data() {
|
||||
x::ClientMessageData::Data32(d) => {
|
||||
assert_eq!(d[0], connection.atoms.wm_delete_window.resource_id())
|
||||
}
|
||||
other => panic!("wrong data type: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Connection {
|
||||
inner: xcb::Connection,
|
||||
pollfd: PollFd<'static>,
|
||||
atoms: Atoms,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Connection {
|
||||
type Target = xcb::Connection;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
fn new() -> Self {
|
||||
// TODO: this will not work if there is an Xserver at 1024, or whenever we add multiple
|
||||
// tests.
|
||||
let (inner, _) = xcb::Connection::connect(Some(":0")).unwrap();
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(inner.as_raw_fd()) };
|
||||
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
||||
let atoms = Atoms::intern_all(&inner).unwrap();
|
||||
|
||||
Self {
|
||||
inner,
|
||||
pollfd,
|
||||
atoms,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn await_event(&mut self) {
|
||||
assert!(
|
||||
poll(&mut [self.pollfd.clone()], 100).expect("poll failed") > 0,
|
||||
"Did not get any X11 events"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toplevel_flow() {
|
||||
let mut f = Fixture::new();
|
||||
let mut connection = Connection::new();
|
||||
let (window, surface) = f.create_toplevel(&connection.inner, 200, 200);
|
||||
f.close_toplevel(&mut connection, window, surface);
|
||||
|
||||
// Simulate killing client
|
||||
drop(connection);
|
||||
f.wait_and_dispatch();
|
||||
|
||||
let data = f.testwl.get_surface_data(surface).expect("No surface data");
|
||||
assert!(!data.toplevel().toplevel.is_alive());
|
||||
}
|
||||
273
testwl/Cargo.lock
generated
Normal file
273
testwl/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "testwl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-server"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e6e4d5c285bc24ba4ed2d5a4bd4febd5fd904451f465973225c8e99772fdb7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"downcast-rs",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
9
testwl/Cargo.toml
Normal file
9
testwl/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "testwl"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
wayland-protocols = { version = "0.31.2", features = ["server", "unstable"] }
|
||||
wayland-server = "0.31.1"
|
||||
wl_drm = { path = "../wl_drm" }
|
||||
839
testwl/src/lib.rs
Normal file
839
testwl/src/lib.rs
Normal file
|
|
@ -0,0 +1,839 @@
|
|||
use std::collections::{hash_map, HashMap, HashSet};
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::time::Instant;
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
viewporter::server::wp_viewporter::WpViewporter,
|
||||
},
|
||||
xdg::{
|
||||
shell::server::{
|
||||
xdg_popup::{self, XdgPopup},
|
||||
xdg_positioner::{self, XdgPositioner},
|
||||
xdg_surface::XdgSurface,
|
||||
xdg_toplevel::{self, XdgToplevel},
|
||||
xdg_wm_base::{self, XdgWmBase},
|
||||
},
|
||||
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
backend::protocol::ProtocolError,
|
||||
protocol::{
|
||||
self as proto,
|
||||
wl_buffer::WlBuffer,
|
||||
wl_callback::WlCallback,
|
||||
wl_compositor::WlCompositor,
|
||||
wl_output::WlOutput,
|
||||
wl_pointer::{self, WlPointer},
|
||||
wl_seat::{self, WlSeat},
|
||||
wl_shm::WlShm,
|
||||
wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Client, Dispatch, Display, DisplayHandle, GlobalDispatch, Resource,
|
||||
};
|
||||
use wl_drm::server::wl_drm::WlDrm;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BufferDamage {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SurfaceData {
|
||||
pub surface: WlSurface,
|
||||
pub buffer: Option<WlBuffer>,
|
||||
pub last_damage: Option<BufferDamage>,
|
||||
pub role: Option<SurfaceRole>,
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
pub fn xdg(&self) -> &XdgSurfaceData {
|
||||
match self.role.as_ref().expect("Surface missing role") {
|
||||
SurfaceRole::Toplevel(ref t) => &t.xdg,
|
||||
SurfaceRole::Popup(ref p) => &p.xdg,
|
||||
SurfaceRole::Cursor => panic!("cursor surface doesn't have an XdgSurface"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel(&self) -> &Toplevel {
|
||||
match self.role.as_ref().expect("Surface missing role") {
|
||||
SurfaceRole::Toplevel(ref t) => t,
|
||||
other => panic!("Surface role was not toplevel: {other:?}"),
|
||||
}
|
||||
}
|
||||
pub fn popup(&self) -> &Popup {
|
||||
match self.role.as_ref().expect("Surface missing role") {
|
||||
SurfaceRole::Popup(ref p) => p,
|
||||
other => panic!("Surface role was not popup: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SurfaceRole {
|
||||
Toplevel(Toplevel),
|
||||
Popup(Popup),
|
||||
Cursor,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Toplevel {
|
||||
pub xdg: XdgSurfaceData,
|
||||
pub toplevel: XdgToplevel,
|
||||
pub min_size: Option<Vec2>,
|
||||
pub max_size: Option<Vec2>,
|
||||
pub states: Vec<xdg_toplevel::State>,
|
||||
pub closed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Popup {
|
||||
pub xdg: XdgSurfaceData,
|
||||
pub parent: XdgSurface,
|
||||
pub popup: XdgPopup,
|
||||
pub positioner_state: PositionerState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Vec2 {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct XdgSurfaceData {
|
||||
pub surface: XdgSurface,
|
||||
pub last_configure_serial: u32,
|
||||
}
|
||||
|
||||
impl XdgSurfaceData {
|
||||
fn new(surface: XdgSurface) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
last_configure_serial: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn configure(&mut self, serial: u32) {
|
||||
self.surface.configure(serial);
|
||||
self.last_configure_serial = serial;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct SurfaceId(u32);
|
||||
|
||||
#[derive(Hash, Clone, Copy, Eq, PartialEq)]
|
||||
struct PositionerId(u32);
|
||||
|
||||
struct State {
|
||||
surfaces: HashMap<SurfaceId, SurfaceData>,
|
||||
positioners: HashMap<PositionerId, PositionerState>,
|
||||
buffers: HashSet<WlBuffer>,
|
||||
begin: Instant,
|
||||
last_surface_id: Option<SurfaceId>,
|
||||
callbacks: Vec<WlCallback>,
|
||||
pointer: Option<WlPointer>,
|
||||
configure_serial: u32,
|
||||
}
|
||||
|
||||
impl State {
|
||||
#[track_caller]
|
||||
fn configure_toplevel(
|
||||
&mut self,
|
||||
surface_id: SurfaceId,
|
||||
width: i32,
|
||||
height: i32,
|
||||
states: Vec<xdg_toplevel::State>,
|
||||
) {
|
||||
let last_serial = self.configure_serial;
|
||||
let toplevel = self.get_toplevel(surface_id);
|
||||
toplevel.states = states.clone();
|
||||
let states = states
|
||||
.into_iter()
|
||||
.map(|state| u32::from(state) as u8)
|
||||
.collect();
|
||||
toplevel.toplevel.configure(width, height, states);
|
||||
toplevel.xdg.configure(last_serial);
|
||||
self.configure_serial += 1;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn get_toplevel(&mut self, surface_id: SurfaceId) -> &mut Toplevel {
|
||||
let surface = self.surfaces.get_mut(&surface_id).unwrap();
|
||||
match &mut surface.role {
|
||||
Some(SurfaceRole::Toplevel(t)) => t,
|
||||
other => panic!("Surface does not have toplevel role: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
surfaces: Default::default(),
|
||||
buffers: Default::default(),
|
||||
positioners: Default::default(),
|
||||
begin: Instant::now(),
|
||||
last_surface_id: None,
|
||||
callbacks: Vec::new(),
|
||||
pointer: None,
|
||||
configure_serial: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! simple_global_dispatch {
|
||||
($type:ty) => {
|
||||
impl GlobalDispatch<$type, ()> for State {
|
||||
fn bind(
|
||||
_: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$type>,
|
||||
_: &(),
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
data_init.init(resource, ());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
display: Display<State>,
|
||||
dh: DisplayHandle,
|
||||
state: State,
|
||||
client: Option<Client>,
|
||||
configure_serial: u32,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(noops: bool) -> Self {
|
||||
let display = Display::new().unwrap();
|
||||
let dh = display.handle();
|
||||
|
||||
macro_rules! global_noop {
|
||||
($type:ty) => {
|
||||
if noops {
|
||||
dh.create_global::<State, $type, _>(1, ());
|
||||
}
|
||||
simple_global_dispatch!($type);
|
||||
impl Dispatch<$type, ()> for State {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &Client,
|
||||
_: &$type,
|
||||
_: <$type as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
todo!("Dispatch for {} is no-op", stringify!($type));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
dh.create_global::<State, WlCompositor, _>(6, ());
|
||||
dh.create_global::<State, WlShm, _>(1, ());
|
||||
dh.create_global::<State, XdgWmBase, _>(6, ());
|
||||
dh.create_global::<State, WlSeat, _>(5, ());
|
||||
global_noop!(WlOutput);
|
||||
global_noop!(ZwpLinuxDmabufV1);
|
||||
global_noop!(ZwpRelativePointerManagerV1);
|
||||
global_noop!(ZxdgOutputManagerV1);
|
||||
global_noop!(WpViewporter);
|
||||
global_noop!(WlDrm);
|
||||
|
||||
Self {
|
||||
display,
|
||||
dh,
|
||||
state: State::default(),
|
||||
client: None,
|
||||
configure_serial: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_fd(&mut self) -> BorrowedFd<'_> {
|
||||
self.display.backend().poll_fd()
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, stream: UnixStream) {
|
||||
let client = self
|
||||
.dh
|
||||
.insert_client(stream, std::sync::Arc::new(()))
|
||||
.unwrap();
|
||||
assert!(
|
||||
self.client.replace(client).is_none(),
|
||||
"Client already connected to test server"
|
||||
);
|
||||
//self.dispatch();
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self) {
|
||||
self.display.dispatch_clients(&mut self.state).unwrap();
|
||||
for callback in std::mem::take(&mut self.state.callbacks) {
|
||||
callback.done(self.state.begin.elapsed().as_millis().try_into().unwrap());
|
||||
}
|
||||
self.display.flush_clients().unwrap();
|
||||
}
|
||||
|
||||
pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> {
|
||||
self.state.surfaces.get(&surface_id)
|
||||
}
|
||||
|
||||
pub fn last_created_surface_id(&self) -> Option<SurfaceId> {
|
||||
self.state.last_surface_id
|
||||
}
|
||||
|
||||
pub fn get_object<T: Resource + 'static>(
|
||||
&self,
|
||||
id: SurfaceId,
|
||||
) -> Result<T, wayland_server::backend::InvalidId> {
|
||||
let client = self.client.as_ref().unwrap();
|
||||
client.object_from_protocol_id::<T>(&self.display.handle(), id.0)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn configure_toplevel(
|
||||
&mut self,
|
||||
surface_id: SurfaceId,
|
||||
width: i32,
|
||||
height: i32,
|
||||
states: Vec<xdg_toplevel::State>,
|
||||
) {
|
||||
self.state
|
||||
.configure_toplevel(surface_id, width, height, states);
|
||||
self.display.flush_clients().unwrap();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn configure_popup(&mut self, surface_id: SurfaceId) {
|
||||
let surface = self.state.surfaces.get_mut(&surface_id).unwrap();
|
||||
let Some(SurfaceRole::Popup(p)) = &mut surface.role else {
|
||||
panic!("Surface does not have popup role: {:?}", surface.role);
|
||||
};
|
||||
let PositionerState { size, offset, .. } = &p.positioner_state;
|
||||
let size = size.unwrap();
|
||||
p.popup.configure(offset.x, offset.y, size.x, size.y);
|
||||
p.xdg.configure(self.configure_serial);
|
||||
self.configure_serial += 1;
|
||||
self.dispatch();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn close_toplevel(&mut self, surface_id: SurfaceId) {
|
||||
let toplevel = self.state.get_toplevel(surface_id);
|
||||
toplevel.toplevel.close();
|
||||
self.dispatch();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn pointer(&self) -> &WlPointer {
|
||||
self.state.pointer.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
simple_global_dispatch!(WlShm);
|
||||
simple_global_dispatch!(WlCompositor);
|
||||
simple_global_dispatch!(XdgWmBase);
|
||||
|
||||
impl GlobalDispatch<WlSeat, ()> for State {
|
||||
fn bind(
|
||||
_: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &Client,
|
||||
resource: wayland_server::New<WlSeat>,
|
||||
_: &(),
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let seat = data_init.init(resource, ());
|
||||
seat.capabilities(wl_seat::Capability::Pointer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlSeat, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
_: &WlSeat,
|
||||
request: <WlSeat as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wl_seat::Request::GetPointer { id } => {
|
||||
state.pointer = Some(data_init.init(id, ()));
|
||||
}
|
||||
wl_seat::Request::Release => {}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlPointer, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
_: &WlPointer,
|
||||
request: <WlPointer as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wl_pointer::Request::SetCursor { surface, .. } => {
|
||||
if let Some(surface) = surface {
|
||||
let data = state
|
||||
.surfaces
|
||||
.get_mut(&SurfaceId(surface.id().protocol_id()))
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
data.role.replace(SurfaceRole::Cursor).is_none(),
|
||||
"Surface already had a role!"
|
||||
);
|
||||
}
|
||||
}
|
||||
wl_pointer::Request::Release => {
|
||||
state.pointer.take();
|
||||
}
|
||||
other => todo!("unhandled pointer request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgPopup, SurfaceId> for State {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &Client,
|
||||
_: &XdgPopup,
|
||||
request: <XdgPopup as Resource>::Request,
|
||||
_: &SurfaceId,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
xdg_popup::Request::Destroy => {}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgToplevel, SurfaceId> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &XdgToplevel,
|
||||
request: <XdgToplevel as Resource>::Request,
|
||||
surface_id: &SurfaceId,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
xdg_toplevel::Request::SetMinSize { width, height } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
toplevel.min_size = Some(Vec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
});
|
||||
}
|
||||
xdg_toplevel::Request::SetMaxSize { width, height } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
toplevel.max_size = Some(Vec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
});
|
||||
}
|
||||
xdg_toplevel::Request::SetFullscreen { .. } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
toplevel.states.push(xdg_toplevel::State::Fullscreen);
|
||||
let states = toplevel.states.clone();
|
||||
state.configure_toplevel(*surface_id, 100, 100, states);
|
||||
}
|
||||
xdg_toplevel::Request::UnsetFullscreen { .. } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
let Some(pos) = toplevel
|
||||
.states
|
||||
.iter()
|
||||
.copied()
|
||||
.position(|p| p == xdg_toplevel::State::Fullscreen)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
toplevel.states.swap_remove(pos);
|
||||
let states = toplevel.states.clone();
|
||||
state.configure_toplevel(*surface_id, 100, 100, states);
|
||||
}
|
||||
xdg_toplevel::Request::Destroy => {}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgSurface, SurfaceId> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
client: &wayland_server::Client,
|
||||
resource: &XdgSurface,
|
||||
request: <XdgSurface as Resource>::Request,
|
||||
surface_id: &SurfaceId,
|
||||
dh: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wayland_protocols::xdg::shell::server::xdg_surface;
|
||||
|
||||
match request {
|
||||
xdg_surface::Request::GetToplevel { id } => {
|
||||
let toplevel = data_init.init(id, *surface_id);
|
||||
let t = Toplevel {
|
||||
xdg: XdgSurfaceData::new(resource.clone()),
|
||||
toplevel,
|
||||
min_size: None,
|
||||
max_size: None,
|
||||
states: Vec::new(),
|
||||
closed: false,
|
||||
};
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
data.role = Some(SurfaceRole::Toplevel(t));
|
||||
}
|
||||
xdg_surface::Request::GetPopup {
|
||||
id,
|
||||
parent,
|
||||
positioner,
|
||||
} => {
|
||||
let popup = data_init.init(id, *surface_id);
|
||||
let p = Popup {
|
||||
xdg: XdgSurfaceData::new(resource.clone()),
|
||||
popup,
|
||||
parent: parent.unwrap(),
|
||||
positioner_state: state.positioners
|
||||
[&PositionerId(positioner.id().protocol_id())]
|
||||
.clone(),
|
||||
};
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
data.role = Some(SurfaceRole::Popup(p));
|
||||
}
|
||||
xdg_surface::Request::AckConfigure { serial } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
assert_eq!(data.xdg().last_configure_serial, serial);
|
||||
}
|
||||
xdg_surface::Request::Destroy => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let role_alive = data.role.is_none()
|
||||
|| match data.role.as_ref().unwrap() {
|
||||
SurfaceRole::Toplevel(t) => t.toplevel.is_alive(),
|
||||
SurfaceRole::Popup(p) => p.popup.is_alive(),
|
||||
SurfaceRole::Cursor => false,
|
||||
};
|
||||
if role_alive {
|
||||
client.kill(
|
||||
dh,
|
||||
ProtocolError {
|
||||
code: xdg_surface::Error::DefunctRoleObject.into(),
|
||||
object_id: resource.id().protocol_id(),
|
||||
object_interface: XdgSurface::interface().name.to_string(),
|
||||
message: "destroyed xdg surface before role".to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
pub size: Vec2,
|
||||
pub offset: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PositionerState {
|
||||
pub size: Option<Vec2>,
|
||||
pub anchor_rect: Option<Rect>,
|
||||
pub offset: Vec2,
|
||||
pub anchor: xdg_positioner::Anchor,
|
||||
pub gravity: xdg_positioner::Gravity,
|
||||
}
|
||||
|
||||
impl Default for PositionerState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: None,
|
||||
anchor_rect: None,
|
||||
offset: Vec2 { x: 0, y: 0 },
|
||||
anchor: xdg_positioner::Anchor::None,
|
||||
gravity: xdg_positioner::Gravity::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgPositioner, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &Client,
|
||||
resource: &XdgPositioner,
|
||||
request: <XdgPositioner as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let hash_map::Entry::Occupied(mut data) = state
|
||||
.positioners
|
||||
.entry(PositionerId(resource.id().protocol_id()))
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
match request {
|
||||
xdg_positioner::Request::SetSize { width, height } => {
|
||||
data.get_mut().size = Some(Vec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
});
|
||||
}
|
||||
xdg_positioner::Request::SetAnchorRect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
data.get_mut().anchor_rect = Some(Rect {
|
||||
size: Vec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
},
|
||||
offset: Vec2 { x, y },
|
||||
});
|
||||
}
|
||||
xdg_positioner::Request::SetOffset { x, y } => {
|
||||
data.get_mut().offset = Vec2 { x, y };
|
||||
}
|
||||
xdg_positioner::Request::SetAnchor { anchor } => {
|
||||
data.get_mut().anchor = anchor.into_result().unwrap();
|
||||
}
|
||||
xdg_positioner::Request::SetGravity { gravity } => {
|
||||
data.get_mut().gravity = gravity.into_result().unwrap();
|
||||
}
|
||||
xdg_positioner::Request::Destroy => {
|
||||
data.remove();
|
||||
}
|
||||
other => todo!("unhandled positioner request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgWmBase, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
client: &wayland_server::Client,
|
||||
_: &XdgWmBase,
|
||||
request: <XdgWmBase as Resource>::Request,
|
||||
_: &(),
|
||||
dhandle: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
|
||||
let surface_id = SurfaceId(surface.id().protocol_id());
|
||||
let data = state.surfaces.get(&surface_id).unwrap();
|
||||
if data.buffer.is_some() {
|
||||
client.kill(
|
||||
dhandle,
|
||||
ProtocolError {
|
||||
code: xdg_wm_base::Error::InvalidSurfaceState.into(),
|
||||
object_id: surface_id.0,
|
||||
object_interface: XdgWmBase::interface().name.to_string(),
|
||||
message: "Buffer already attached to surface".to_string(),
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
data_init.init(id, surface_id);
|
||||
}
|
||||
xdg_wm_base::Request::CreatePositioner { id } => {
|
||||
let pos = data_init.init(id, ());
|
||||
state.positioners.insert(
|
||||
PositionerId(pos.id().protocol_id()),
|
||||
PositionerState::default(),
|
||||
);
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlShm, ()> for State {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShm,
|
||||
request: <WlShm as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
proto::wl_shm::Request::CreatePool { id, .. } => {
|
||||
data_init.init(id, ());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlShmPool, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShmPool,
|
||||
request: <WlShmPool as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use proto::wl_shm_pool::Request::*;
|
||||
match request {
|
||||
CreateBuffer { id, .. } => {
|
||||
let buf = data_init.init(id, ());
|
||||
state.buffers.insert(buf);
|
||||
}
|
||||
Destroy => {}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlBuffer, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
resource: &WlBuffer,
|
||||
request: <WlBuffer as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
proto::wl_buffer::Request::Destroy => {
|
||||
state.buffers.remove(resource);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlCompositor, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCompositor,
|
||||
request: <WlCompositor as wayland_server::Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
proto::wl_compositor::Request::CreateSurface { id } => {
|
||||
let surface = data_init.init(id, ());
|
||||
let id = surface.id().protocol_id();
|
||||
state.surfaces.insert(
|
||||
SurfaceId(id),
|
||||
SurfaceData {
|
||||
surface,
|
||||
buffer: None,
|
||||
last_damage: None,
|
||||
role: None,
|
||||
},
|
||||
);
|
||||
state.last_surface_id = Some(SurfaceId(id));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlSurface, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
resource: &WlSurface,
|
||||
request: <WlSurface as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use proto::wl_surface::Request::*;
|
||||
|
||||
let data = state
|
||||
.surfaces
|
||||
.get_mut(&SurfaceId(resource.id().protocol_id()))
|
||||
.unwrap_or_else(|| panic!("{:?} missing from surface map", resource));
|
||||
|
||||
match request {
|
||||
Attach { buffer, .. } => {
|
||||
data.buffer = buffer;
|
||||
}
|
||||
Frame { callback } => {
|
||||
// XXX: calling done immediately will cause wayland_backend to panic,
|
||||
// report upstream
|
||||
state.callbacks.push(data_init.init(callback, ()));
|
||||
}
|
||||
DamageBuffer {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
data.last_damage = Some(BufferDamage {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
Commit => {}
|
||||
Destroy => {
|
||||
state
|
||||
.surfaces
|
||||
.remove(&SurfaceId(resource.id().protocol_id()));
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlCallback, ()> for State {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCallback,
|
||||
_: <WlCallback as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
11
wl_drm/Cargo.toml
Normal file
11
wl_drm/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "wl_drm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wayland-client = "0.31.2"
|
||||
wayland-scanner = "0.31.1"
|
||||
wayland-server = "0.31.1"
|
||||
185
wl_drm/src/drm.xml
Normal file
185
wl_drm/src/drm.xml
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="drm">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2008-2011 Kristian Høgsberg
|
||||
Copyright © 2010-2011 Intel Corporation
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that\n the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<!-- drm support. This object is created by the server and published
|
||||
using the display's global event. -->
|
||||
<interface name="wl_drm" version="2">
|
||||
<enum name="error">
|
||||
<entry name="authenticate_fail" value="0"/>
|
||||
<entry name="invalid_format" value="1"/>
|
||||
<entry name="invalid_name" value="2"/>
|
||||
</enum>
|
||||
|
||||
<enum name="format">
|
||||
<!-- The drm format codes match the #defines in drm_fourcc.h.
|
||||
The formats actually supported by the compositor will be
|
||||
reported by the format event. -->
|
||||
<entry name="c8" value="0x20203843"/>
|
||||
<entry name="rgb332" value="0x38424752"/>
|
||||
<entry name="bgr233" value="0x38524742"/>
|
||||
<entry name="xrgb4444" value="0x32315258"/>
|
||||
<entry name="xbgr4444" value="0x32314258"/>
|
||||
<entry name="rgbx4444" value="0x32315852"/>
|
||||
<entry name="bgrx4444" value="0x32315842"/>
|
||||
<entry name="argb4444" value="0x32315241"/>
|
||||
<entry name="abgr4444" value="0x32314241"/>
|
||||
<entry name="rgba4444" value="0x32314152"/>
|
||||
<entry name="bgra4444" value="0x32314142"/>
|
||||
<entry name="xrgb1555" value="0x35315258"/>
|
||||
<entry name="xbgr1555" value="0x35314258"/>
|
||||
<entry name="rgbx5551" value="0x35315852"/>
|
||||
<entry name="bgrx5551" value="0x35315842"/>
|
||||
<entry name="argb1555" value="0x35315241"/>
|
||||
<entry name="abgr1555" value="0x35314241"/>
|
||||
<entry name="rgba5551" value="0x35314152"/>
|
||||
<entry name="bgra5551" value="0x35314142"/>
|
||||
<entry name="rgb565" value="0x36314752"/>
|
||||
<entry name="bgr565" value="0x36314742"/>
|
||||
<entry name="rgb888" value="0x34324752"/>
|
||||
<entry name="bgr888" value="0x34324742"/>
|
||||
<entry name="xrgb8888" value="0x34325258"/>
|
||||
<entry name="xbgr8888" value="0x34324258"/>
|
||||
<entry name="rgbx8888" value="0x34325852"/>
|
||||
<entry name="bgrx8888" value="0x34325842"/>
|
||||
<entry name="argb8888" value="0x34325241"/>
|
||||
<entry name="abgr8888" value="0x34324241"/>
|
||||
<entry name="rgba8888" value="0x34324152"/>
|
||||
<entry name="bgra8888" value="0x34324142"/>
|
||||
<entry name="xrgb2101010" value="0x30335258"/>
|
||||
<entry name="xbgr2101010" value="0x30334258"/>
|
||||
<entry name="rgbx1010102" value="0x30335852"/>
|
||||
<entry name="bgrx1010102" value="0x30335842"/>
|
||||
<entry name="argb2101010" value="0x30335241"/>
|
||||
<entry name="abgr2101010" value="0x30334241"/>
|
||||
<entry name="rgba1010102" value="0x30334152"/>
|
||||
<entry name="bgra1010102" value="0x30334142"/>
|
||||
<entry name="yuyv" value="0x56595559"/>
|
||||
<entry name="yvyu" value="0x55595659"/>
|
||||
<entry name="uyvy" value="0x59565955"/>
|
||||
<entry name="vyuy" value="0x59555956"/>
|
||||
<entry name="ayuv" value="0x56555941"/>
|
||||
<entry name="nv12" value="0x3231564e"/>
|
||||
<entry name="nv21" value="0x3132564e"/>
|
||||
<entry name="nv16" value="0x3631564e"/>
|
||||
<entry name="nv61" value="0x3136564e"/>
|
||||
<entry name="yuv410" value="0x39565559"/>
|
||||
<entry name="yvu410" value="0x39555659"/>
|
||||
<entry name="yuv411" value="0x31315559"/>
|
||||
<entry name="yvu411" value="0x31315659"/>
|
||||
<entry name="yuv420" value="0x32315559"/>
|
||||
<entry name="yvu420" value="0x32315659"/>
|
||||
<entry name="yuv422" value="0x36315559"/>
|
||||
<entry name="yvu422" value="0x36315659"/>
|
||||
<entry name="yuv444" value="0x34325559"/>
|
||||
<entry name="yvu444" value="0x34325659"/>
|
||||
</enum>
|
||||
|
||||
<!-- Call this request with the magic received from drmGetMagic().
|
||||
It will be passed on to the drmAuthMagic() or
|
||||
DRIAuthConnection() call. This authentication must be
|
||||
completed before create_buffer could be used. -->
|
||||
<request name="authenticate">
|
||||
<arg name="id" type="uint"/>
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="uint"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="stride" type="uint"/>
|
||||
<arg name="format" type="uint"/>
|
||||
</request>
|
||||
|
||||
<!-- Create a wayland buffer for the named DRM buffer. The DRM
|
||||
surface must have a name using the flink ioctl -->
|
||||
<request name="create_planar_buffer">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="uint"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="format" type="uint"/>
|
||||
<arg name="offset0" type="int"/>
|
||||
<arg name="stride0" type="int"/>
|
||||
<arg name="offset1" type="int"/>
|
||||
<arg name="stride1" type="int"/>
|
||||
<arg name="offset2" type="int"/>
|
||||
<arg name="stride2" type="int"/>
|
||||
</request>
|
||||
|
||||
<!-- Notification of the path of the drm device which is used by
|
||||
the server. The client should use this device for creating
|
||||
local buffers. Only buffers created from this device should
|
||||
be be passed to the server using this drm object's
|
||||
create_buffer request. -->
|
||||
<event name="device">
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="format">
|
||||
<arg name="format" type="uint"/>
|
||||
</event>
|
||||
|
||||
<!-- Raised if the authenticate request succeeded -->
|
||||
<event name="authenticated"/>
|
||||
|
||||
<enum name="capability" since="2">
|
||||
<description summary="wl_drm capability bitmask">
|
||||
Bitmask of capabilities.
|
||||
</description>
|
||||
<entry name="prime" value="1" summary="wl_drm prime available"/>
|
||||
</enum>
|
||||
|
||||
<event name="capabilities">
|
||||
<arg name="value" type="uint"/>
|
||||
</event>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
|
||||
buffers. Pass 0 for offset and stride for unused planes. -->
|
||||
<request name="create_prime_buffer" since="2">
|
||||
<arg name="id" type="new_id" interface="wl_buffer"/>
|
||||
<arg name="name" type="fd"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
<arg name="format" type="uint"/>
|
||||
<arg name="offset0" type="int"/>
|
||||
<arg name="stride0" type="int"/>
|
||||
<arg name="offset1" type="int"/>
|
||||
<arg name="stride1" type="int"/>
|
||||
<arg name="offset2" type="int"/>
|
||||
<arg name="stride2" type="int"/>
|
||||
</request>
|
||||
|
||||
</interface>
|
||||
|
||||
</protocol>
|
||||
19
wl_drm/src/lib.rs
Normal file
19
wl_drm/src/lib.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#![allow(non_camel_case_types, non_upper_case_globals)]
|
||||
pub mod client {
|
||||
use wayland_client::{self, protocol::*};
|
||||
pub mod __interfaces {
|
||||
use wayland_client::protocol::__interfaces::*;
|
||||
use wayland_client::backend as wayland_backend;
|
||||
wayland_scanner::generate_interfaces!("src/drm.xml");
|
||||
}
|
||||
use self::__interfaces::*;
|
||||
wayland_scanner::generate_client_code!("src/drm.xml");
|
||||
}
|
||||
|
||||
pub mod server {
|
||||
use wayland_server::{self, protocol::*};
|
||||
pub use super::client::__interfaces;
|
||||
use self::__interfaces::*;
|
||||
wayland_scanner::generate_server_code!("src/drm.xml");
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue