Initial commit

This commit is contained in:
Shawn Wallace 2024-04-29 00:27:22 -04:00
commit 85b940e427
21 changed files with 6825 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

747
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,10 @@
[workspace]
members = [
"satellite",
"testwl" ,
"wl_drm"
]
resolver = "2"

373
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
View 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);
}

View 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);

View 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
View 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,
}

View 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(
&registry.obj,
bind_req(name, WlCompositor::interface(), version),
));
}
x if x == WlShm::interface().name => {
ret.shm = Some(TestObject::from_request(
&registry.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
View 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()
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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");
}