diff options
| author | MaxVerevkin <maxxverrr@gmail.com> | 2023-01-02 20:23:35 +0200 |
|---|---|---|
| committer | Max Verevkin <34583604+MaxVerevkin@users.noreply.github.com> | 2023-01-02 20:44:14 +0200 |
| commit | eebeed1f2774146c3d7df90c7a982f58428d4cff (patch) | |
| tree | c038e06100fbd8b4debf29c5b077d51a56a5f98b | |
| parent | 2570f39b5daf6e2a13b3973d47aa7c67fbb457fb (diff) | |
| download | i3bar-mango-eebeed1f2774146c3d7df90c7a982f58428d4cff.tar.gz | |
rewrite using wayrs
| -rw-r--r-- | Cargo.lock | 365 | ||||
| -rw-r--r-- | Cargo.toml | 14 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | protocols/wlr-layer-shell-unstable-v1.xml | 391 | ||||
| -rw-r--r-- | protocols/xdg-shell.xml | 1352 | ||||
| -rw-r--r-- | src/bar.rs | 160 | ||||
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/cursor.rs | 109 | ||||
| -rw-r--r-- | src/main.rs | 43 | ||||
| -rw-r--r-- | src/protocol.rs | 8 | ||||
| -rw-r--r-- | src/river_protocols.rs | 4 | ||||
| -rw-r--r-- | src/river_protocols/control.rs | 112 | ||||
| -rw-r--r-- | src/river_protocols/status.rs | 144 | ||||
| -rw-r--r-- | src/shared_state.rs | 14 | ||||
| -rw-r--r-- | src/state.rs | 713 | ||||
| -rw-r--r-- | src/status_cmd.rs | 86 |
16 files changed, 2503 insertions, 1015 deletions
@@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "atty" @@ -41,10 +41,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] name = "cairo-rs" -version = "0.16.3" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247e1183fa769ac22121f92276dae52f89acaf297f24b1320019f439b6e3b46f" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" dependencies = [ "bitflags", "cairo-sys-rs", @@ -66,12 +72,6 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" - -[[package]] name = "cfg-expr" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -87,6 +87,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -108,21 +117,6 @@ dependencies = [ ] [[package]] -name = "dlib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" -dependencies = [ - "libloading", -] - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -211,9 +205,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.16.3" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4a17d999e6e4e05d87c2bb05b7140d47769bc53211711a33e2f91536458714" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" dependencies = [ "bitflags", "futures-channel", @@ -244,9 +238,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.16.4" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5204a4217749b385cefbfb7bf3a2fcde83e4ce6d0945f64440a1f5bd4010305" +checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" dependencies = [ "bitflags", "futures-channel", @@ -333,51 +327,26 @@ dependencies = [ "pangocairo", "serde", "serde_json", - "smithay-client-toolkit", + "thiserror", "tokio", "toml", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f65dae1d3aa98e6877917ab4e6fdbfdfb00e95885ea7c4f4f29e3a5dfc08fdf" -dependencies = [ - "libc", - "windows-sys 0.36.1", + "wayrs-client", + "wayrs-scanner", + "wayrs-shm-alloc", + "xcursor", ] [[package]] name = "itoa" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" - -[[package]] -name = "lazy_static" -version = "1.4.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "log" @@ -405,9 +374,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -427,20 +396,20 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "nix" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" dependencies = [ - "autocfg", "bitflags", "cfg-if", "libc", "memoffset", + "static_assertions", ] [[package]] @@ -461,9 +430,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "pango" -version = "0.16.3" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6a83cd4015382dbb0f4fcf3ab7b277d4885711a62b2f2c1e6582a120094edad" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", "gio", @@ -567,27 +536,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.23.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -631,30 +600,24 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "scoped-tls" -version = "1.0.1" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -663,9 +626,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -673,6 +636,24 @@ dependencies = [ ] [[package]] +name = "shmemfdrs" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07caaa5573b8fa1142d19783d30ba2999c6a221169784f3bb7e2b9b308bcbdb" +dependencies = [ + "libc", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] name = "slab" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -688,27 +669,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] -name = "smithay-client-toolkit" -version = "0.16.0" -source = "git+https://github.com/Smithay/client-toolkit#638ca60e3ae8277d645991cc986be3df5981fbb6" -dependencies = [ - "bitflags", - "dlib", - "lazy_static", - "log", - "memmap2", - "nix", - "pkg-config", - "thiserror", - "wayland-backend", - "wayland-client", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", -] - -[[package]] name = "socket2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -719,10 +679,16 @@ dependencies = [ ] [[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] name = "syn" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -753,18 +719,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -773,24 +739,27 @@ dependencies = [ [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", + "bytes", "libc", + "memchr", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -799,18 +768,24 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "version-compare" @@ -831,77 +806,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wayland-backend" -version = "0.1.0-beta.13" +name = "wayrs-client" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f96c52ca34b33e6bb55497327428be54dad648973aa5d3d1e02982d2fcc298" +checksum = "36735e4fa01ade0a22aa5366c0245e49e955b7088379127d86b8bb14f2427eac" dependencies = [ - "cc", - "downcast-rs", - "io-lifetimes", - "nix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.30.0-beta.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6ab5911cc8a28e9d781933c1afe0f97ffa6885ad8941294784d025f06e5ad9" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", "nix", "thiserror", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-cursor" -version = "0.30.0-beta.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5ce1bbc13a3d806648974cba1d53481fea2ac29483eb147e4cce1250fc5263" -dependencies = [ - "nix", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.30.0-beta.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32104ba51988754f4429dbb59c7b7fd2ccf1feaa0009bb59e49a5e2f91e985d" -dependencies = [ - "bitflags", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.1.0-beta.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130613a48629204e4ab50ea4b5de2efa2e17c58f71a5af15b39535f99421f8c3" -dependencies = [ - "bitflags", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", + "tokio", + "wayrs-scanner", ] [[package]] -name = "wayland-scanner" -version = "0.30.0-beta.13" +name = "wayrs-scanner" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb55ca28710dcc155078db4e2bd29aacf24bd311261c48750f38c6c5430bf1f" +checksum = "3957115661161e3f6efcadc6f09c894e3ed8536374baeffedc5e6c80a680b517" dependencies = [ + "convert_case", "proc-macro2", "quick-xml", "quote", @@ -909,14 +831,14 @@ dependencies = [ ] [[package]] -name = "wayland-sys" -version = "0.30.0-beta.13" +name = "wayrs-shm-alloc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882bda56e8397384ad66ec0ebe763e0b3b53153a6327344f7d74b85981a271e1" +checksum = "7e3811c51fb48e8bd025ea8005f9a724e97ef955912eef050eedd257507c2c78" dependencies = [ - "dlib", - "log", - "pkg-config", + "memmap2", + "shmemfdrs", + "wayrs-client", ] [[package]] @@ -952,30 +874,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] @@ -986,48 +895,24 @@ checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" @@ -1040,12 +925,6 @@ checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" @@ -13,17 +13,17 @@ pangocairo = "0.16" serde = { version = "1", features = ["derive"] } serde_json = "1" memchr = "2" -wayland-client = "0.30.0-beta.13" -wayland-scanner = "0.30.0-beta.13" -wayland-backend = "0.1.0-beta.13" +thiserror = "1.0.38" -[dependencies.smithay-client-toolkit] -git = "https://github.com/Smithay/client-toolkit" -default-features = false +wayrs-client = { version = "0.2.1", features = ["tokio"] } +wayrs-scanner = "0.2" +wayrs-shm-alloc = "0.2" + +xcursor = "0.3.4" [dependencies.tokio] version = "1" -features = ["macros", "net", "rt", "sync"] +features = ["macros", "net", "rt", "sync", "io-util", "process"] [profile.release] lto = "fat" @@ -58,6 +58,7 @@ tags_r = 6 blocks_r = 6 blocks_overlap = 0 command = "i3status-rs" +show_layout_name = false ``` ## How progressive short mode and rounded corners work diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..b701bd3 --- /dev/null +++ b/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,391 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_layer_shell_unstable_v1"> + <copyright> + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that 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> + + <interface name="zwlr_layer_shell_v1" version="4"> + <description summary="create surfaces that are layers of the desktop"> + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + </description> + + <request name="get_layer_surface"> + <description summary="create a layer_surface from a surface"> + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + </description> + <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> + <arg name="namespace" type="string" summary="namespace for the layer surface"/> + </request> + + <enum name="error"> + <entry name="role" value="0" summary="wl_surface has another role"/> + <entry name="invalid_layer" value="1" summary="layer value is invalid"/> + <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> + </enum> + + <enum name="layer"> + <description summary="available layers for surfaces"> + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + </description> + + <entry name="background" value="0"/> + <entry name="bottom" value="1"/> + <entry name="top" value="2"/> + <entry name="overlay" value="3"/> + </enum> + + <!-- Version 3 additions --> + + <request name="destroy" type="destructor" since="3"> + <description summary="destroy the layer_shell object"> + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + </description> + </request> + </interface> + + <interface name="zwlr_layer_surface_v1" version="4"> + <description summary="layer metadata interface"> + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + </description> + + <request name="set_size"> + <description summary="sets the size of the surface"> + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + </description> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </request> + + <request name="set_anchor"> + <description summary="configures the anchor point of the surface"> + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + </description> + <arg name="anchor" type="uint" enum="anchor"/> + </request> + + <request name="set_exclusive_zone"> + <description summary="configures the exclusive geometry of this surface"> + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + </description> + <arg name="zone" type="int"/> + </request> + + <request name="set_margin"> + <description summary="sets a margin from the anchor point"> + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + </description> + <arg name="top" type="int"/> + <arg name="right" type="int"/> + <arg name="bottom" type="int"/> + <arg name="left" type="int"/> + </request> + + <enum name="keyboard_interactivity"> + <description summary="types of keyboard interaction possible for a layer shell surface"> + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + </description> + + <entry name="none" value="0"> + <description summary="no keyboard focus is possible"> + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + </description> + </entry> + <entry name="exclusive" value="1"> + <description summary="request exclusive keyboard focus"> + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + </description> + </entry> + <entry name="on_demand" value="2" since="4"> + <description summary="request regular keyboard focus semantics"> + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + </description> + </entry> + </enum> + + <request name="set_keyboard_interactivity"> + <description summary="requests keyboard events"> + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + </description> + <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/> + </request> + + <request name="get_popup"> + <description summary="assign this layer_surface as an xdg_popup parent"> + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="popup" type="object" interface="xdg_popup"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the layer_surface"> + This request destroys the layer surface. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + </description> + <arg name="serial" type="uint"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + + <event name="closed"> + <description summary="surface should be closed"> + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + </description> + </event> + + <enum name="error"> + <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> + <entry name="invalid_size" value="1" summary="size is invalid"/> + <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> + <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/> + </enum> + + <enum name="anchor" bitfield="true"> + <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> + <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> + <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> + <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> + </enum> + + <!-- Version 2 additions --> + + <request name="set_layer" since="2"> + <description summary="change the layer of the surface"> + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + </description> + <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/> + </request> + </interface> +</protocol> + diff --git a/protocols/xdg-shell.xml b/protocols/xdg-shell.xml new file mode 100644 index 0000000..cc2b439 --- /dev/null +++ b/protocols/xdg-shell.xml @@ -0,0 +1,1352 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xdg_shell"> + + <copyright> + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="xdg_wm_base" version="5"> + <description summary="create desktop-style surfaces"> + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + </description> + + <enum name="error"> + <entry name="role" value="0" summary="given wl_surface has another role"/> + <entry name="defunct_surfaces" value="1" + summary="xdg_wm_base was destroyed before children"/> + <entry name="not_the_topmost_popup" value="2" + summary="the client tried to map or destroy a non-topmost popup"/> + <entry name="invalid_popup_parent" value="3" + summary="the client specified an invalid popup parent surface"/> + <entry name="invalid_surface_state" value="4" + summary="the client provided an invalid surface state"/> + <entry name="invalid_positioner" value="5" + summary="the client provided an invalid positioner"/> + <entry name="unresponsive" value="6" + summary="the client didn’t respond to a ping event in time"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy xdg_wm_base"> + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + </description> + </request> + + <request name="create_positioner"> + <description summary="create a positioner object"> + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + </description> + <arg name="id" type="new_id" interface="xdg_positioner"/> + </request> + + <request name="get_xdg_surface"> + <description summary="create a shell surface from a surface"> + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_surface"/> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + + <request name="pong"> + <description summary="respond to a ping event"> + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + </description> + <arg name="serial" type="uint" summary="serial of the ping event"/> + </request> + + <event name="ping"> + <description summary="check if the client is alive"> + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + </description> + <arg name="serial" type="uint" summary="pass this to the pong request"/> + </event> + </interface> + + <interface name="xdg_positioner" version="5"> + <description summary="child surface positioner"> + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + </description> + + <enum name="error"> + <entry name="invalid_input" value="0" summary="invalid input provided"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_positioner object"> + Notify the compositor that the xdg_positioner will no longer be used. + </description> + </request> + + <request name="set_size"> + <description summary="set the size of the to-be positioned rectangle"> + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + </description> + <arg name="width" type="int" summary="width of positioned rectangle"/> + <arg name="height" type="int" summary="height of positioned rectangle"/> + </request> + + <request name="set_anchor_rect"> + <description summary="set the anchor rectangle within the parent surface"> + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + </description> + <arg name="x" type="int" summary="x position of anchor rectangle"/> + <arg name="y" type="int" summary="y position of anchor rectangle"/> + <arg name="width" type="int" summary="width of anchor rectangle"/> + <arg name="height" type="int" summary="height of anchor rectangle"/> + </request> + + <enum name="anchor"> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="3"/> + <entry name="right" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="top_right" value="7"/> + <entry name="bottom_right" value="8"/> + </enum> + + <request name="set_anchor"> + <description summary="set anchor rectangle anchor"> + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + </description> + <arg name="anchor" type="uint" enum="anchor" + summary="anchor"/> + </request> + + <enum name="gravity"> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="3"/> + <entry name="right" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="top_right" value="7"/> + <entry name="bottom_right" value="8"/> + </enum> + + <request name="set_gravity"> + <description summary="set child surface gravity"> + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + </description> + <arg name="gravity" type="uint" enum="gravity" + summary="gravity direction"/> + </request> + + <enum name="constraint_adjustment" bitfield="true"> + <description summary="constraint adjustments"> + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + </description> + <entry name="none" value="0"> + <description summary="don't move the child surface when constrained"> + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + </description> + </entry> + <entry name="slide_x" value="1"> + <description summary="move along the x axis until unconstrained"> + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + </description> + </entry> + <entry name="slide_y" value="2"> + <description summary="move along the y axis until unconstrained"> + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + </description> + </entry> + <entry name="flip_x" value="4"> + <description summary="invert the anchor and gravity on the x axis"> + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + </description> + </entry> + <entry name="flip_y" value="8"> + <description summary="invert the anchor and gravity on the y axis"> + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + </description> + </entry> + <entry name="resize_x" value="16"> + <description summary="horizontally resize the surface"> + Resize the surface horizontally so that it is completely + unconstrained. + </description> + </entry> + <entry name="resize_y" value="32"> + <description summary="vertically resize the surface"> + Resize the surface vertically so that it is completely unconstrained. + </description> + </entry> + </enum> + + <request name="set_constraint_adjustment"> + <description summary="set the adjustment to be done when constrained"> + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + </description> + <arg name="constraint_adjustment" type="uint" + summary="bit mask of constraint adjustments"/> + </request> + + <request name="set_offset"> + <description summary="set surface position offset"> + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + </description> + <arg name="x" type="int" summary="surface position x offset"/> + <arg name="y" type="int" summary="surface position y offset"/> + </request> + + <!-- Version 3 additions --> + + <request name="set_reactive" since="3"> + <description summary="continuously reconstrain the surface"> + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + </description> + </request> + + <request name="set_parent_size" since="3"> + <description summary=""> + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + </description> + <arg name="parent_width" type="int" + summary="future window geometry width of parent"/> + <arg name="parent_height" type="int" + summary="future window geometry height of parent"/> + </request> + + <request name="set_parent_configure" since="3"> + <description summary="set parent configure this is a response to"> + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + </description> + <arg name="serial" type="uint" + summary="serial of parent configure event"/> + </request> + </interface> + + <interface name="xdg_surface" version="5"> + <description summary="desktop user interface surface base interface"> + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up, the client must + perform an initial commit without any buffer attached. The compositor + will reply with an xdg_surface.configure event. The client must + acknowledge it and is then allowed to attach a buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + </description> + + <enum name="error"> + <entry name="not_constructed" value="1" + summary="Surface was not fully constructed"/> + <entry name="already_constructed" value="2" + summary="Surface was already constructed"/> + <entry name="unconfigured_buffer" value="3" + summary="Attaching a buffer to an unconfigured surface"/> + <entry name="invalid_serial" value="4" + summary="Invalid serial number when acking a configure event"/> + <entry name="invalid_size" value="5" + summary="Width or height was zero or negative"/> + <entry name="defunct_role_object" value="6" + summary="Surface was destroyed before its role object"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_surface"> + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + </description> + </request> + + <request name="get_toplevel"> + <description summary="assign the xdg_toplevel surface role"> + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_toplevel"/> + </request> + + <request name="get_popup"> + <description summary="assign the xdg_popup surface role"> + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_popup"/> + <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/> + <arg name="positioner" type="object" interface="xdg_positioner"/> + </request> + + <request name="set_window_geometry"> + <description summary="set the new window geometry"> + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an invalid_size error. When applied, the effective window + geometry will be the set window geometry clamped to the bounding + rectangle of the combined geometry of the surface of the xdg_surface and + the associated subsurfaces. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + </description> + <arg name="serial" type="uint" summary="serial of the configure event"/> + </event> + + </interface> + + <interface name="xdg_toplevel" version="5"> + <description summary="toplevel surface"> + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by perfoming a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_toplevel"> + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + </description> + </request> + + <enum name="error"> + <entry name="invalid_resize_edge" value="0" summary="provided value is + not a valid variant of the resize_edge enum"/> + <entry name="invalid_parent" value="1" + summary="invalid parent toplevel"/> + <entry name="invalid_size" value="2" + summary="client provided an invalid min or max size"/> + </enum> + + <request name="set_parent"> + <description summary="set the parent of this surface"> + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + </description> + <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/> + </request> + + <request name="set_title"> + <description summary="set surface title"> + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + </description> + <arg name="title" type="string"/> + </request> + + <request name="set_app_id"> + <description summary="set application ID"> + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + </description> + <arg name="app_id" type="string"/> + </request> + + <request name="show_window_menu"> + <description summary="show the window menu"> + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + <arg name="x" type="int" summary="the x position to pop up the window menu at"/> + <arg name="y" type="int" summary="the y position to pop up the window menu at"/> + </request> + + <request name="move"> + <description summary="start an interactive move"> + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + </request> + + <enum name="resize_edge"> + <description summary="edge values for resizing"> + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + </description> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="right" value="8"/> + <entry name="top_right" value="9"/> + <entry name="bottom_right" value="10"/> + </enum> + + <request name="resize"> + <description summary="start an interactive resize"> + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause a protocol error. The compositor + may use this information to update the surface position for example + when dragging the top left corner. The compositor may also use + this information to adapt its behavior, e.g. choose an appropriate + cursor image. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/> + </request> + + <enum name="state"> + <description summary="types of state on the surface"> + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + </description> + <entry name="maximized" value="1" summary="the surface is maximized"> + <description summary="the surface is maximized"> + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + The client should draw without shadow or other + decoration outside of the window geometry. + </description> + </entry> + <entry name="fullscreen" value="2" summary="the surface is fullscreen"> + <description summary="the surface is fullscreen"> + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + </description> + </entry> + <entry name="resizing" value="3" summary="the surface is being resized"> + <description summary="the surface is being resized"> + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + </description> + </entry> + <entry name="activated" value="4" summary="the surface is now activated"> + <description summary="the surface is now activated"> + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + </description> + </entry> + <entry name="tiled_left" value="5" since="2"> + <description summary="the surface’s left edge is tiled"> + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + </description> + </entry> + <entry name="tiled_right" value="6" since="2"> + <description summary="the surface’s right edge is tiled"> + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + </description> + </entry> + <entry name="tiled_top" value="7" since="2"> + <description summary="the surface’s top edge is tiled"> + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + </description> + </entry> + <entry name="tiled_bottom" value="8" since="2"> + <description summary="the surface’s bottom edge is tiled"> + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + </description> + </entry> + </enum> + + <request name="set_max_size"> + <description summary="set the maximum size"> + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="set_min_size"> + <description summary="set the minimum size"> + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="set_maximized"> + <description summary="maximize the window"> + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + </description> + </request> + + <request name="unset_maximized"> + <description summary="unmaximize the window"> + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + </description> + </request> + + <request name="set_fullscreen"> + <description summary="set the window as fullscreen on an output"> + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + </description> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + </request> + + <request name="unset_fullscreen"> + <description summary="unset the window as fullscreen"> + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + </description> + </request> + + <request name="set_minimized"> + <description summary="set the window as minimized"> + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="states" type="array"/> + </event> + + <event name="close"> + <description summary="surface wants to be closed"> + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + </description> + </event> + + <!-- Version 4 additions --> + + <event name="configure_bounds" since="4"> + <description summary="recommended window geometry bounds"> + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </event> + + <!-- Version 5 additions --> + + <enum name="wm_capabilities" since="5"> + <entry name="window_menu" value="1" summary="show_window_menu is available"/> + <entry name="maximize" value="2" summary="set_maximized and unset_maximized are available"/> + <entry name="fullscreen" value="3" summary="set_fullscreen and unset_fullscreen are available"/> + <entry name="minimize" value="4" summary="set_minimized is available"/> + </enum> + + <event name="wm_capabilities" since="5"> + <description summary="compositor capabilities"> + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + </description> + <arg name="capabilities" type="array" summary="array of 32-bit capabilities"/> + </event> + </interface> + + <interface name="xdg_popup" version="5"> + <description summary="short-lived, popup surfaces for menus"> + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + </description> + + <enum name="error"> + <entry name="invalid_grab" value="0" + summary="tried to grab after being mapped"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="remove xdg_popup interface"> + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + </description> + </request> + + <request name="grab"> + <description summary="make the popup take an explicit grab"> + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + </description> + <arg name="seat" type="object" interface="wl_seat" + summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + </request> + + <event name="configure"> + <description summary="configure the popup surface"> + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + </description> + <arg name="x" type="int" + summary="x position relative to parent surface window geometry"/> + <arg name="y" type="int" + summary="y position relative to parent surface window geometry"/> + <arg name="width" type="int" summary="window geometry width"/> + <arg name="height" type="int" summary="window geometry height"/> + </event> + + <event name="popup_done"> + <description summary="popup interaction is done"> + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + </description> + </event> + + <!-- Version 3 additions --> + + <request name="reposition" since="3"> + <description summary="recalculate the popup's location"> + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + </description> + <arg name="positioner" type="object" interface="xdg_positioner"/> + <arg name="token" type="uint" summary="reposition request token"/> + </request> + + <event name="repositioned" since="3"> + <description summary="signal the completion of a repositioned request"> + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + </description> + <arg name="token" type="uint" summary="reposition request token"/> + </event> + + </interface> +</protocol> + @@ -1,34 +1,34 @@ use std::collections::BinaryHeap; +use std::ffi::CString; use pangocairo::cairo; -use smithay_client_toolkit::{ - reexports::client::protocol::{wl_output, wl_seat, wl_shm}, - shell::layer::LayerSurface, -}; -use crate::{ - button_manager::ButtonManager, - config::Config, - i3bar_protocol::{self, Block, MinWidth}, - ord_adaptor::DefaultLess, - pointer_btn::PointerBtn, - river_protocols::{control::RiverControlState, status::RiverOutputStatus}, - shared_state::SharedState, - state::ComputedBlock, - tags::{compute_tag_label, TagState, TagsInfo}, - text::{self, ComputedText, RenderOptions}, -}; +use wayrs_client::connection::Connection; + +use crate::button_manager::ButtonManager; +use crate::config::Config; +use crate::i3bar_protocol::{self, Block, MinWidth}; +use crate::ord_adaptor::DefaultLess; +use crate::pointer_btn::PointerBtn; +use crate::protocol::*; +use crate::shared_state::SharedState; +use crate::state::{ComputedBlock, State}; +use crate::tags::{compute_tag_label, TagState, TagsInfo}; +use crate::text::{self, ComputedText, RenderOptions}; pub struct Bar { - pub output: wl_output::WlOutput, + pub output: WlOutput, + pub output_reg_name: u32, pub configured: bool, + pub frame_cb: Option<WlCallback>, pub width: u32, pub height: u32, pub scale: i32, - pub layer: LayerSurface, + pub surface: WlSurface, + pub layer_surface: ZwlrLayerSurfaceV1, pub blocks_btns: ButtonManager<(Option<String>, Option<String>)>, - pub river_output_status: Option<RiverOutputStatus>, - pub river_control: Option<RiverControlState>, + pub river_output_status: Option<ZriverOutputStatusV1>, + pub river_control: Option<ZriverControlV1>, pub layout_name: Option<String>, pub layout_name_computed: Option<ComputedText>, pub tags_btns: ButtonManager, @@ -37,16 +37,6 @@ pub struct Bar { } impl Bar { - pub fn configure(&mut self, ss: &mut SharedState, width: u32) { - if self.width != width && width != 0 { - self.width = width; - self.layer.set_exclusive_zone(self.height as i32); - } - - self.configured = true; - self.draw(ss); - } - pub fn has_tags_provider(&self) -> bool { // TODO: add more tags providers self.river_output_status.is_some() @@ -59,20 +49,23 @@ impl Bar { pub fn click( &mut self, + conn: &mut Connection<State>, ss: &mut SharedState, button: PointerBtn, - seat: &wl_seat::WlSeat, + seat: WlSeat, x: f64, _y: f64, ) -> anyhow::Result<()> { if let Some(tag) = self.tags_btns.click(x) { if let Some(river_control) = &self.river_control { let cmd = match button { - PointerBtn::Left => "set-focused-tags", - PointerBtn::Right => "toggle-focused-tags", + PointerBtn::Left => wayrs_client::cstr!("set-focused-tags"), + PointerBtn::Right => wayrs_client::cstr!("toggle-focused-tags"), _ => return Ok(()), }; - river_control.run_command(&ss.qh, seat, [cmd.into(), (1u32 << tag).to_string()]); + river_control.add_argument(conn, cmd.into()); + river_control.add_argument(conn, CString::new((1u32 << tag).to_string()).unwrap()); + river_control.run_command(conn, seat); } } else if let Some((name, instance)) = self.blocks_btns.click(x) { if let Some(cmd) = &mut ss.status_cmd { @@ -87,10 +80,8 @@ impl Bar { Ok(()) } - pub fn draw(&mut self, ss: &mut SharedState) { - if !self.configured { - return; - } + pub fn frame(&mut self, conn: &mut Connection<State>, ss: &mut SharedState) { + assert!(self.configured); let stride = 4 * self.width as i32; let width = self.width as i32; @@ -98,15 +89,13 @@ impl Bar { let width_f = width as f64; let height_f = height as f64; - let (buffer, canvas) = ss - .pool - .create_buffer( - width * self.scale, - height * self.scale, - stride * self.scale, - wl_shm::Format::Argb8888, - ) - .expect("create buffer"); + let (buffer, canvas) = ss.shm.alloc_buffer( + conn, + width * self.scale, + height * self.scale, + stride * self.scale, + wl_shm::Format::Argb8888, + ); let cairo_surf = unsafe { cairo::ImageSurface::create_for_data_unsafe( @@ -121,7 +110,7 @@ impl Bar { let cairo_ctx = cairo::Context::new(&cairo_surf).expect("cairo context"); cairo_ctx.scale(self.scale as f64, self.scale as f64); - self.layer.wl_surface().set_buffer_scale(self.scale); + self.surface.set_buffer_scale(conn, self.scale); // Background cairo_ctx.save().unwrap(); @@ -181,34 +170,36 @@ impl Bar { } // Display layout name - if let Some(layout_name) = &self.layout_name { - let text = self.layout_name_computed.get_or_insert_with(|| { - ComputedText::new( - layout_name, - text::Attributes { - font: &ss.config.font, - padding_left: 25.0, - padding_right: 25.0, - min_width: None, - align: Default::default(), - markup: false, - }, + if ss.config.show_layout_name { + if let Some(layout_name) = &self.layout_name { + let text = self.layout_name_computed.get_or_insert_with(|| { + ComputedText::new( + layout_name, + text::Attributes { + font: &ss.config.font, + padding_left: 25.0, + padding_right: 25.0, + min_width: None, + align: Default::default(), + markup: false, + }, + &cairo_ctx, + ) + }); + text.render( &cairo_ctx, - ) - }); - text.render( - &cairo_ctx, - RenderOptions { - x_offset: offset_left, - bar_height: height_f, - fg_color: ss.config.tag_inactive_fg, - bg_color: None, - r_left: 0.0, - r_right: 0.0, - overlap: 0.0, - }, - ); - offset_left += text.width; + RenderOptions { + x_offset: offset_left, + bar_height: height_f, + fg_color: ss.config.tag_inactive_fg, + bg_color: None, + r_left: 0.0, + r_right: 0.0, + overlap: 0.0, + }, + ); + offset_left += text.width; + } } // Display the blocks @@ -223,16 +214,17 @@ impl Bar { height_f, ); - // Attach the buffer to the surface and mark the entire surface as damaged - buffer - .attach_to(self.layer.wl_surface()) - .expect("attaching buffer"); - self.layer - .wl_surface() - .damage_buffer(0, 0, width * self.scale, height * self.scale); + self.surface.attach(conn, buffer, 0, 0); + self.surface + .damage_buffer(conn, 0, 0, width * self.scale, height * self.scale); + self.surface.commit(conn); + } - // Finally, commit the surface - self.layer.wl_surface().commit(); + pub fn request_frame(&mut self, conn: &mut Connection<State>) { + if self.configured && self.frame_cb.is_none() { + self.frame_cb = Some(self.surface.frame(conn)); + self.surface.commit(conn); + } } } diff --git a/src/config.rs b/src/config.rs index 2af9af0..a5b05d0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,6 +34,7 @@ pub struct Config { // misc pub hide_inactive_tags: bool, pub invert_touchpad_scrolling: bool, + pub show_layout_name: bool, } impl Default for Config { @@ -60,6 +61,7 @@ impl Default for Config { command: None, hide_inactive_tags: true, invert_touchpad_scrolling: true, + show_layout_name: true, } } } diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..57db8bb --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,109 @@ +use std::io; + +use wayrs_client::connection::Connection; +use wayrs_client::protocol::wl_compositor::WlCompositor; +use wayrs_client::protocol::wl_pointer::WlPointer; +use wayrs_client::protocol::wl_shm::Format; +use wayrs_client::protocol::wl_surface::WlSurface; + +use wayrs_shm_alloc::ShmAlloc; + +use xcursor::parser::Image; + +use crate::state::State; + +#[derive(Debug, thiserror::Error)] +pub enum CursorError { + #[error("theme not found")] + ThemeNotFound, + #[error("theme could not be parsed")] + ThemeParseError, + #[error(transparent)] + ReadError(#[from] io::Error), +} + +pub struct Cursor { + pub surface: WlSurface, + pub images: Vec<Image>, +} + +impl Cursor { + pub fn new( + conn: &mut Connection<State>, + compositor: WlCompositor, + ) -> Result<Self, CursorError> { + let theme_name = std::env::var("XCURSOR_THEME").ok(); + let theme_name = theme_name.as_deref().unwrap_or("default"); + + let theme_path = xcursor::CursorTheme::load(theme_name) + .load_icon("default") + .ok_or(CursorError::ThemeNotFound)?; + let raw_theme = std::fs::read(theme_path)?; + + let mut images = + xcursor::parser::parse_xcursor(&raw_theme).ok_or(CursorError::ThemeParseError)?; + images.sort_unstable_by_key(|img| img.size); + + let surface = compositor.create_surface(conn); + + Ok(Cursor { surface, images }) + } + + pub fn set( + &self, + conn: &mut Connection<State>, + serial: u32, + pointer: WlPointer, + scale: u32, + shm: &mut ShmAlloc, + ) { + let target_size = std::env::var("XCURSOR_SIZE") + .ok() + .and_then(|x| x.parse().ok()) + .unwrap_or(24) + * scale; + + let image = match self + .images + .binary_search_by_key(&target_size, |img| img.size) + { + Ok(indx) => &self.images[indx], + Err(indx) if indx == 0 => self.images.first().unwrap(), + Err(indx) if indx >= self.images.len() => self.images.last().unwrap(), + Err(indx) => { + let a = &self.images[indx - 1]; + let b = &self.images[indx]; + if target_size - a.size < b.size - target_size { + a + } else { + b + } + } + }; + + let (buffer, canvas) = shm.alloc_buffer( + conn, + image.width as i32, + image.height as i32, + (image.width * 4) as i32, + Format::Argb8888, + ); + + assert_eq!(image.pixels_argb.len(), canvas.len(),); + canvas.copy_from_slice(&image.pixels_rgba); + + self.surface.attach(conn, buffer, 0, 0); + self.surface + .damage_buffer(conn, 0, 0, image.width as i32, image.height as i32); + self.surface.set_buffer_scale(conn, scale as i32); + self.surface.commit(conn); + + pointer.set_cursor( + conn, + serial, + self.surface, + (image.xhot / scale) as i32, + (image.yhot / scale) as i32, + ); + } +} diff --git a/src/main.rs b/src/main.rs index 3dad693..73c2d6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_component_path_imports)] + #[macro_use] extern crate anyhow; #[macro_use] @@ -7,10 +9,11 @@ mod bar; mod button_manager; mod color; mod config; +mod cursor; mod i3bar_protocol; mod ord_adaptor; mod pointer_btn; -mod river_protocols; +mod protocol; mod shared_state; mod state; mod status_cmd; @@ -18,45 +21,35 @@ mod tags; mod text; mod utils; -use std::os::unix::io::AsRawFd; - -use smithay_client_toolkit::reexports::client::Connection; -use tokio::io::{unix::AsyncFd, Interest}; +use wayrs_client::connection::Connection; use state::State; -use wayland_client::globals::registry_queue_init; #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { env_logger::init(); - let conn = Connection::connect_to_env()?; - let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); - let mut state = State::new(&mut event_queue, &globals); - - let async_fd = AsyncFd::with_interest( - event_queue.prepare_read()?.connection_fd().as_raw_fd(), - Interest::READABLE, - )?; + let mut conn = Connection::connect()?; + let globals = conn.async_collect_initial_globals().await?; + let mut state = State::new(&mut conn, &globals); + conn.async_flush().await?; loop { tokio::select! { - readable = async_fd.readable() => { - readable?.clear_ready(); - event_queue.prepare_read()?.read()?; - event_queue.dispatch_pending(&mut state)?; - event_queue.flush()?; + recv_events = conn.async_recv_events() => { + recv_events?; + conn.dispatch_events(&mut state)?; + conn.async_flush().await?; } - readable = state.wait_for_status_cmd() => { - readable?.clear_ready(); - if let Err(e) = state.notify_available() { + reat_res = state.status_cmd_read() => { + reat_res?; + if let Err(e) = state.status_cmd_notify_available(&mut conn) { if let Some(mut status_cmd) = state.shared_state.status_cmd.take() { let _ = status_cmd.child.kill(); } - state.set_error(e.to_string()); - state.draw_all(); + state.set_error(&mut conn, e.to_string()); } - event_queue.flush()?; + conn.async_flush().await?; } } } diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..ac8b2c1 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,8 @@ +#![allow(clippy::enum_variant_names)] + +use wayrs_client; +pub use wayrs_client::protocol::*; +wayrs_scanner::generate!("protocols/xdg-shell.xml"); +wayrs_scanner::generate!("protocols/wlr-layer-shell-unstable-v1.xml"); +wayrs_scanner::generate!("protocols/river-status-unstable-v1.xml"); +wayrs_scanner::generate!("protocols/river-control-unstable-v1.xml"); diff --git a/src/river_protocols.rs b/src/river_protocols.rs deleted file mode 100644 index 0a2cf91..0000000 --- a/src/river_protocols.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![allow(clippy::enum_variant_names)] - -pub mod control; -pub mod status; diff --git a/src/river_protocols/control.rs b/src/river_protocols/control.rs deleted file mode 100644 index 7cce23d..0000000 --- a/src/river_protocols/control.rs +++ /dev/null @@ -1,112 +0,0 @@ -use wayland_client::globals::{BindError, GlobalList}; -use wayland_client::{protocol::wl_seat, Connection, Dispatch, QueueHandle}; - -use protocol::{zriver_command_callback_v1, zriver_control_v1}; - -#[derive(Debug, Clone)] -pub struct RiverControlState { - control: zriver_control_v1::ZriverControlV1, -} - -impl RiverControlState { - pub fn new<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Result<Self, BindError> - where - D: Dispatch<zriver_control_v1::ZriverControlV1, (), D> + 'static, - { - let control = globals.bind(qh, 1..=1, ())?; - Ok(Self { control }) - } - - pub fn run_command<D, Args, Arg>(&self, qh: &QueueHandle<D>, seat: &wl_seat::WlSeat, args: Args) - where - D: Dispatch<zriver_command_callback_v1::ZriverCommandCallbackV1, RiverCommandCallbackData> - + 'static, - Args: IntoIterator<Item = Arg>, - Arg: Into<String>, - { - for arg in args { - self.control.add_argument(arg.into()); - } - self.control - .run_command(seat, qh, RiverCommandCallbackData {}); - } -} - -pub trait RiverControlHandler: Sized { - fn river_control_state(&mut self) -> &mut RiverControlState; - - fn command_failure(&mut self, conn: &Connection, qh: &QueueHandle<Self>, message: String); - - fn command_success(&mut self, conn: &Connection, qh: &QueueHandle<Self>, message: String); -} - -#[derive(Debug)] -pub struct RiverCommandCallbackData { - // This is empty right now, but may be populated in the future. -} - -#[macro_export] -macro_rules! delegate_river_control { - ($ty: ty) => { - ::smithay_client_toolkit::reexports::client::delegate_dispatch!($ty: [ - $crate::river_protocols::control::protocol::zriver_control_v1::ZriverControlV1: () - ] => $crate::river_protocols::control::RiverControlState); - ::smithay_client_toolkit::reexports::client::delegate_dispatch!($ty: [ - $crate::river_protocols::control::protocol::zriver_command_callback_v1::ZriverCommandCallbackV1: $crate::river_protocols::control::RiverCommandCallbackData - ] => $crate::river_protocols::control::RiverControlState); - }; -} - -impl<D> Dispatch<zriver_control_v1::ZriverControlV1, (), D> for RiverControlState -where - D: Dispatch<zriver_control_v1::ZriverControlV1, ()> + RiverControlHandler + 'static, -{ - fn event( - _: &mut D, - _: &zriver_control_v1::ZriverControlV1, - _: zriver_control_v1::Event, - _: &(), - _: &Connection, - _: &QueueHandle<D>, - ) { - unreachable!("zriver_control_v1 has no events") - } -} - -impl<D> Dispatch<zriver_command_callback_v1::ZriverCommandCallbackV1, RiverCommandCallbackData, D> - for RiverControlState -where - D: Dispatch<zriver_command_callback_v1::ZriverCommandCallbackV1, RiverCommandCallbackData> - + RiverControlHandler - + 'static, -{ - fn event( - data: &mut D, - _callback: &zriver_command_callback_v1::ZriverCommandCallbackV1, - event: zriver_command_callback_v1::Event, - _udata: &RiverCommandCallbackData, - conn: &Connection, - qh: &QueueHandle<D>, - ) { - use zriver_command_callback_v1::Event; - match event { - Event::Success { output } => data.command_success(conn, qh, output), - Event::Failure { failure_message } => data.command_success(conn, qh, failure_message), - } - } -} - -pub mod protocol { - #![allow(non_upper_case_globals)] - - use smithay_client_toolkit::reexports::client as wayland_client; - use smithay_client_toolkit::reexports::client::protocol::*; - - pub mod __interfaces { - use smithay_client_toolkit::reexports::client::protocol::__interfaces::*; - wayland_scanner::generate_interfaces!("protocols/river-control-unstable-v1.xml"); - } - use self::__interfaces::*; - - wayland_scanner::generate_client_code!("protocols/river-control-unstable-v1.xml"); -} diff --git a/src/river_protocols/status.rs b/src/river_protocols/status.rs deleted file mode 100644 index ad93279..0000000 --- a/src/river_protocols/status.rs +++ /dev/null @@ -1,144 +0,0 @@ -use wayland_client::{ - globals::{BindError, GlobalList}, - protocol::wl_output, - Connection, Dispatch, QueueHandle, -}; - -use protocol::{zriver_output_status_v1, zriver_status_manager_v1}; - -#[derive(Debug, Clone)] -pub struct RiverStatusState { - status_manager: zriver_status_manager_v1::ZriverStatusManagerV1, -} - -impl RiverStatusState { - pub fn new<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Result<Self, BindError> - where - D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, (), D> + 'static, - { - let status_manager = globals.bind(qh, 1..=4, ())?; - Ok(Self { status_manager }) - } - - #[must_use = "The output status is destroyed if dropped"] - pub fn new_output_status<D>( - &mut self, - qh: &QueueHandle<D>, - output: &wl_output::WlOutput, - ) -> RiverOutputStatus - where - D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, ()> - + Dispatch<zriver_output_status_v1::ZriverOutputStatusV1, RiverStatusData> - + 'static, - { - RiverOutputStatus(self.status_manager.get_river_output_status( - output, - qh, - RiverStatusData { - output: output.clone(), - }, - )) - } -} - -pub trait RiverStatusHandler: Sized { - fn river_status_state(&mut self) -> &mut RiverStatusState; - - fn focused_tags_updated(&mut self, output: &wl_output::WlOutput, focused: u32); - - fn urgent_tags_updated(&mut self, output: &wl_output::WlOutput, urgent: u32); - - fn views_tags_updated(&mut self, output: &wl_output::WlOutput, tags: Vec<u32>); - - fn layout_name_updated(&mut self, output: &wl_output::WlOutput, layout_name: Option<String>); -} - -#[derive(Debug)] -pub struct RiverOutputStatus(zriver_output_status_v1::ZriverOutputStatusV1); - -impl Drop for RiverOutputStatus { - fn drop(&mut self) { - self.0.destroy(); - } -} - -#[macro_export] -macro_rules! delegate_river_status { - ($ty: ty) => { - ::smithay_client_toolkit::reexports::client::delegate_dispatch!($ty: [ - $crate::river_protocols::status::protocol::zriver_status_manager_v1::ZriverStatusManagerV1: () - ] => $crate::river_protocols::status::RiverStatusState); - ::smithay_client_toolkit::reexports::client::delegate_dispatch!($ty: [ - $crate::river_protocols::status::protocol::zriver_output_status_v1::ZriverOutputStatusV1: $crate::river_protocols::status::RiverStatusData - ] => $crate::river_protocols::status::RiverStatusState); - }; -} - -#[derive(Debug)] -pub struct RiverStatusData { - output: wl_output::WlOutput, -} - -impl<D> Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, (), D> for RiverStatusState -where - D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, ()> + RiverStatusHandler + 'static, -{ - fn event( - _: &mut D, - _: &zriver_status_manager_v1::ZriverStatusManagerV1, - _: zriver_status_manager_v1::Event, - _: &(), - _: &Connection, - _: &QueueHandle<D>, - ) { - unreachable!("zriver_status_manager_v1 has no events") - } -} - -impl<D> Dispatch<zriver_output_status_v1::ZriverOutputStatusV1, RiverStatusData, D> - for RiverStatusState -where - D: Dispatch<zriver_output_status_v1::ZriverOutputStatusV1, RiverStatusData> - + RiverStatusHandler - + 'static, -{ - fn event( - data: &mut D, - _: &zriver_output_status_v1::ZriverOutputStatusV1, - event: zriver_output_status_v1::Event, - udata: &RiverStatusData, - _: &Connection, - _: &QueueHandle<D>, - ) { - use zriver_output_status_v1::Event; - - match event { - Event::FocusedTags { tags } => data.focused_tags_updated(&udata.output, tags), - Event::UrgentTags { tags } => data.urgent_tags_updated(&udata.output, tags), - Event::ViewTags { tags } => { - let tags = tags - .chunks_exact(4) - .map(|bytes| u32::from_ne_bytes(bytes.try_into().unwrap())) - .collect(); - data.views_tags_updated(&udata.output, tags); - } - Event::LayoutName { name } => data.layout_name_updated(&udata.output, Some(name)), - Event::LayoutNameClear => data.layout_name_updated(&udata.output, None), - } - } -} - -pub mod protocol { - #![allow(non_upper_case_globals)] - - use smithay_client_toolkit::reexports::client as wayland_client; - use smithay_client_toolkit::reexports::client::protocol::*; - - pub mod __interfaces { - use smithay_client_toolkit::reexports::client::protocol::__interfaces::*; - wayland_scanner::generate_interfaces!("protocols/river-status-unstable-v1.xml"); - } - use self::__interfaces::*; - - wayland_scanner::generate_client_code!("protocols/river-status-unstable-v1.xml"); -} diff --git a/src/shared_state.rs b/src/shared_state.rs index c4ec9d6..dd18606 100644 --- a/src/shared_state.rs +++ b/src/shared_state.rs @@ -1,16 +1,8 @@ -use smithay_client_toolkit::{reexports::client::QueueHandle, shm::slot::SlotPool}; +use crate::{config::Config, i3bar_protocol::Block, state::ComputedBlock, status_cmd::StatusCmd}; +use wayrs_shm_alloc::ShmAlloc; -use crate::{ - config::Config, - i3bar_protocol::Block, - state::{ComputedBlock, State}, - status_cmd::StatusCmd, -}; - -#[derive(Debug)] pub struct SharedState { - pub qh: QueueHandle<State>, - pub pool: SlotPool, + pub shm: ShmAlloc, pub config: Config, pub status_cmd: Option<StatusCmd>, pub blocks: Vec<Block>, diff --git a/src/state.rs b/src/state.rs index e2e57ee..dbe73fd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,103 +1,76 @@ -use std::{future::pending, io}; +use crate::cursor::Cursor; +use crate::protocol::*; -use smithay_client_toolkit::{ - compositor::{CompositorHandler, CompositorState}, - delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry, - delegate_seat, delegate_shm, - output::{OutputHandler, OutputState}, - reexports::client::{ - protocol::{wl_output, wl_pointer, wl_seat, wl_surface}, - Connection, EventQueue, QueueHandle, - }, - registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, - seat::{ - pointer::{PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer}, - Capability, SeatHandler, SeatState, - }, - shell::layer::{ - Anchor, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, - }, - shm::{slot::SlotPool, ShmHandler, ShmState}, -}; -use tokio::io::unix::AsyncFdReadyGuard; -use wayland_client::globals::GlobalList; +use std::convert::Infallible; +use std::future::pending; + +use wayrs_client::connection::Connection; +use wayrs_client::global::{Global, GlobalExt, Globals, GlobalsExt}; +use wayrs_client::proxy::{Dispatch, Dispatcher, Proxy}; +use wayrs_shm_alloc::ShmAlloc; use crate::{ - bar::Bar, - config::Config, - delegate_river_control, delegate_river_status, - i3bar_protocol::Block, - pointer_btn::PointerBtn, - river_protocols::{ - control::{RiverControlHandler, RiverControlState}, - status::{RiverStatusHandler, RiverStatusState}, - }, - shared_state::SharedState, - status_cmd::StatusCmd, - text::ComputedText, + bar::Bar, config::Config, i3bar_protocol::Block, pointer_btn::PointerBtn, + shared_state::SharedState, status_cmd::StatusCmd, text::ComputedText, }; pub struct State { - registry_state: RegistryState, - shm_state: ShmState, - seat_state: SeatState, - output_state: OutputState, - compositor_state: CompositorState, - layer_state: LayerShell, - river_status_state: Option<RiverStatusState>, - river_control_state: Option<RiverControlState>, + wl_compositor: WlCompositor, + layer_shell: ZwlrLayerShellV1, + + river_status_manager: Option<ZriverStatusManagerV1>, + river_control: Option<ZriverControlV1>, seats: Vec<Seat>, bars: Vec<Bar>, + pub shared_state: SharedState, } struct Seat { - seat: wl_seat::WlSeat, - pointer: wl_pointer::WlPointer, - themed_pointer: ThemedPointer, - pointer_surface: wl_surface::WlSurface, - finger_scroll: f64, + seat: WlSeat, + reg_name: u32, + pointer: Option<WlPointer>, + cursor: Cursor, + cur_surface: Option<WlSurface>, + x: f64, + y: f64, + pending_button: Option<PointerBtn>, + pending_scroll: f64, + scroll_frame: ScrollFrame, } impl State { - pub fn new(event_queue: &mut EventQueue<Self>, globals: &GlobalList) -> Self { + pub fn new(conn: &mut Connection<Self>, globals: &Globals) -> Self { let mut error = Ok(()); - let qh = event_queue.handle(); - let config = Config::new() .map_err(|e| error = Err(e)) .unwrap_or_default(); - let status_cmd = match &error { - Err(_) => None, - Ok(()) => config.command.as_ref().and_then(|cmd| { - StatusCmd::new(cmd) - .map_err(|e| error = Err(anyhow!(e))) - .ok() - }), - }; + let status_cmd = config + .command + .as_ref() + .and_then(|cmd| StatusCmd::new(cmd).map_err(|e| error = Err(e)).ok()); - let shm_state = ShmState::bind(globals, &qh).unwrap(); - let pool = SlotPool::new(1024, &shm_state).expect("Failed to create pool"); + let shm = globals.bind(conn, 1..=1).expect("could not bind wl_shm"); let mut this = Self { - registry_state: RegistryState::new(globals), - shm_state, - seat_state: SeatState::new(globals, &qh), - output_state: OutputState::new(globals, &qh), - compositor_state: CompositorState::bind(globals, &qh).unwrap(), - layer_state: LayerShell::bind(globals, &qh).unwrap(), - river_status_state: RiverStatusState::new(globals, &qh).ok(), - river_control_state: RiverControlState::new(globals, &qh).ok(), + wl_compositor: globals + .bind(conn, 4..=5) + .expect("could not bind wl_compositor"), + layer_shell: globals + .bind(conn, 1..=4) + .expect("could not bind layer_shell"), + + river_status_manager: globals.bind(conn, 1..=4).ok(), + river_control: globals.bind(conn, 1..=1).ok(), seats: Vec::new(), bars: Vec::new(), + shared_state: SharedState { - qh: event_queue.handle(), - pool, + shm: ShmAlloc::new(conn, shm, 1024), config, status_cmd, blocks: Vec::new(), @@ -105,113 +78,102 @@ impl State { }, }; + globals + .iter() + .filter(|g| g.is::<WlSeat>()) + .for_each(|g| this.bind_seat(conn, g)); + globals + .iter() + .filter(|g| g.is::<WlOutput>()) + .for_each(|g| this.bind_output(conn, g)); + if let Err(e) = error { - this.set_error(e.to_string()); + this.set_error(conn, e.to_string()); } this } - pub fn set_blocks(&mut self, blocks: Vec<Block>) { + pub fn set_blocks(&mut self, conn: &mut Connection<Self>, blocks: Vec<Block>) { self.shared_state.blocks = blocks; - self.draw_all(); + self.draw_all(conn); } - pub fn set_error(&mut self, error: impl Into<String>) { - self.set_blocks(vec![Block { - full_text: error.into(), - ..Default::default() - }]); + pub fn set_error(&mut self, conn: &mut Connection<Self>, error: impl Into<String>) { + self.set_blocks( + conn, + vec![Block { + full_text: error.into(), + ..Default::default() + }], + ); } - pub fn notify_available(&mut self) -> anyhow::Result<()> { - if let Some(cmd) = &mut self.shared_state.status_cmd { - if let Some(blocks) = cmd.notify_available()? { - self.set_blocks(blocks); - } - } - Ok(()) - } - - pub fn draw_all(&mut self) { + pub fn draw_all(&mut self, conn: &mut Connection<Self>) { for bar in &mut self.bars { - bar.draw(&mut self.shared_state); + bar.request_frame(conn); } } - pub async fn wait_for_status_cmd(&self) -> io::Result<AsyncFdReadyGuard<i32>> { - match &self.shared_state.status_cmd { - Some(cmd) => cmd.async_fd.readable().await, + pub async fn status_cmd_read(&mut self) -> anyhow::Result<()> { + match &mut self.shared_state.status_cmd { + Some(cmd) => cmd.read().await, None => { pending::<()>().await; unreachable!() } } } -} -impl CompositorHandler for State { - fn scale_factor_changed( + pub fn status_cmd_notify_available( &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - surface: &wl_surface::WlSurface, - scale: i32, - ) { - if let Some(bar) = self - .bars - .iter_mut() - .find(|bar| bar.layer.wl_surface() == surface) - { - bar.scale = scale; - bar.draw(&mut self.shared_state); + conn: &mut Connection<Self>, + ) -> anyhow::Result<()> { + if let Some(cmd) = &mut self.shared_state.status_cmd { + if let Some(blocks) = cmd.notify_available()? { + self.set_blocks(conn, blocks); + } } + Ok(()) } - fn frame( - &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - _surface: &wl_surface::WlSurface, - _time: u32, - ) { - } -} + fn bind_output(&mut self, conn: &mut Connection<Self>, global: &Global) { + let output = global.bind(conn, 2..=4).expect("could not bind wl_output"); + let surface = self.wl_compositor.create_surface(conn); -impl OutputHandler for State { - fn output_state(&mut self) -> &mut OutputState { - &mut self.output_state - } + use zwlr_layer_shell_v1::Layer; + use zwlr_layer_surface_v1::Anchor; + let layer_surface = self.layer_shell.get_layer_surface( + conn, + surface, + output, + Layer::Top, + wayrs_client::cstr!("i3bar-river").into(), + ); + layer_surface.set_size(conn, 0, self.shared_state.config.height); + layer_surface.set_anchor(conn, Anchor::Top | Anchor::Left | Anchor::Right); // Top + Left + Right + layer_surface.set_exclusive_zone(conn, self.shared_state.config.height as i32 + 5); // TODO: make the margin configurable + + // Note: layer_surface is commited when we receive the scale factor of this output - fn new_output( - &mut self, - _conn: &Connection, - qh: &QueueHandle<Self>, - output: wl_output::WlOutput, - ) { - let height = self.shared_state.config.height; - let surface = self.compositor_state.create_surface(qh); - let layer = LayerSurface::builder() - .output(&output) - .size((0, height)) - .anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT) - .namespace("i3bar-river") - .map(qh, &self.layer_state, surface, Layer::Top) - .expect("layer surface creation"); let river_output_status = self - .river_status_state - .as_mut() - .map(|s| s.new_output_status(qh, &output)); + .river_status_manager + .as_ref() + .map(|s| s.get_river_output_status(conn, output)); + self.bars.push(Bar { output, + output_reg_name: global.name, configured: false, + frame_cb: None, width: 0, - height, + height: self.shared_state.config.height, scale: 1, - layer, + surface, + layer_surface, blocks_btns: Default::default(), river_output_status, - river_control: self.river_control_state.clone(), + river_control: self.river_control, layout_name: None, layout_name_computed: None, tags_btns: Default::default(), @@ -220,238 +182,318 @@ impl OutputHandler for State { }); } - fn update_output( - &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - _output: wl_output::WlOutput, - ) { + fn bind_seat(&mut self, conn: &mut Connection<Self>, global: &Global) { + let seat: WlSeat = global.bind(conn, 5..=8).unwrap(); + let cursor = Cursor::new(conn, self.wl_compositor).unwrap(); + self.seats.push(Seat { + seat, + reg_name: global.name, + pointer: None, + cursor, + cur_surface: None, + x: 0.0, + y: 0.0, + pending_button: None, + pending_scroll: 0.0, + scroll_frame: ScrollFrame::default(), + }); } - fn output_destroyed( - &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - _output: wl_output::WlOutput, - ) { + fn drop_bar(&mut self, conn: &mut Connection<Self>, bar_index: usize) { + let bar = self.bars.swap_remove(bar_index); + bar.surface.destroy(conn); + bar.layer_surface.destroy(conn); + if let Some(output_status) = bar.river_output_status { + output_status.destroy(conn); + } + if bar.output.version() >= 3 { + bar.output.release(conn); + } } -} -impl LayerShellHandler for State { - fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, layer: &LayerSurface) { - self.bars.retain(|b| &b.layer != layer) + fn drop_seat(&mut self, conn: &mut Connection<Self>, seat_index: usize) { + let seat = self.seats.swap_remove(seat_index); + if let Some(pointer) = seat.pointer { + if pointer.version() >= 3 { + pointer.release(conn); + } + } + if seat.seat.version() >= 5 { + seat.seat.release(conn); + } } +} - fn configure( - &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - layer: &LayerSurface, - configure: LayerSurfaceConfigure, - _serial: u32, - ) { - self.bars - .iter_mut() - .find(|b| &b.layer == layer) - .unwrap() - .configure(&mut self.shared_state, configure.new_size.0); - } +impl Dispatcher for State { + type Error = Infallible; } -impl SeatHandler for State { - fn seat_state(&mut self) -> &mut SeatState { - &mut self.seat_state +impl Dispatch<WlRegistry> for State { + fn event(&mut self, conn: &mut Connection<Self>, _: WlRegistry, event: wl_registry::Event) { + match event { + wl_registry::Event::Global(global) if global.is::<WlOutput>() => { + self.bind_output(conn, &global); + } + wl_registry::Event::Global(global) if global.is::<WlSeat>() => { + self.bind_seat(conn, &global); + } + wl_registry::Event::GlobalRemove(name) => { + if let Some(bar_index) = + self.bars.iter().position(|bar| bar.output_reg_name == name) + { + self.drop_bar(conn, bar_index); + } else if let Some(seat_index) = + self.seats.iter().position(|seat| seat.reg_name == name) + { + self.drop_seat(conn, seat_index); + } + } + _ => (), + } } +} - fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {} +impl Dispatch<WlCallback> for State { + fn event(&mut self, conn: &mut Connection<Self>, cb: WlCallback, event: wl_callback::Event) { + let wl_callback::Event::Done(_) = event; + if let Some(bar) = self.bars.iter_mut().find(|bar| bar.frame_cb == Some(cb)) { + bar.frame_cb = None; + bar.frame(conn, &mut self.shared_state); + } + } +} - fn new_capability( - &mut self, - _conn: &Connection, - qh: &QueueHandle<Self>, - seat: wl_seat::WlSeat, - capability: Capability, - ) { - if capability == Capability::Pointer { - let (pointer, themed_pointer) = self - .seat_state - .get_pointer_with_theme(qh, &seat, ThemeSpec::System, 1) - .expect("Failed to create pointer"); - self.seats.push(Seat { - seat, - pointer, - themed_pointer, - pointer_surface: self.compositor_state.create_surface(qh), - finger_scroll: 0.0, - }); +impl Dispatch<WlOutput> for State { + fn event(&mut self, conn: &mut Connection<Self>, output: WlOutput, event: wl_output::Event) { + if let wl_output::Event::Scale(scale) = event { + let bar = self + .bars + .iter_mut() + .find(|bar| bar.output == output) + .unwrap(); + bar.scale = scale; + // If bar is not configured yet, it is because we were waiting for the "scale" event + // before commiting the surface. Otherwise, there is no need to do any redrawing (we'll + // do that after "configure" event). + if !bar.configured { + bar.surface.commit(conn); + } } } +} - fn remove_capability( +impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1> for State { + fn event( &mut self, - _conn: &Connection, - _qh: &QueueHandle<Self>, - seat: wl_seat::WlSeat, - capability: Capability, + conn: &mut Connection<Self>, + layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: zwlr_layer_surface_v1::Event, ) { - if capability == Capability::Pointer { - self.seats.retain(|p| p.seat == seat); + match event { + zwlr_layer_surface_v1::Event::Configure(args) => { + let bar = self + .bars + .iter_mut() + .find(|bar| bar.layer_surface == layer_surface) + .unwrap(); + assert_ne!(args.width, 0); + bar.width = args.width; + bar.configured = true; + bar.layer_surface.ack_configure(conn, args.serial); + bar.frame(conn, &mut self.shared_state); + } + zwlr_layer_surface_v1::Event::Closed => { + let bar_index = self + .bars + .iter() + .position(|bar| bar.layer_surface == layer_surface) + .unwrap(); + self.drop_bar(conn, bar_index); + } } } +} - fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {} +impl Dispatch<WlBuffer> for State { + fn event(&mut self, _: &mut Connection<Self>, buffer: WlBuffer, event: wl_buffer::Event) { + let wl_buffer::Event::Release = event; + self.shared_state.shm.free_buffer(buffer); + } } -impl PointerHandler for State { - fn pointer_frame( - &mut self, - conn: &Connection, - _qh: &QueueHandle<Self>, - pointer: &wl_pointer::WlPointer, - events: &[PointerEvent], - ) { +impl Dispatch<WlSeat> for State { + fn event(&mut self, conn: &mut Connection<Self>, seat: WlSeat, event: wl_seat::Event) { + if let wl_seat::Event::Capabilities(capabilities) = event { + let seat = self.seats.iter_mut().find(|s| s.seat == seat).unwrap(); + match &seat.pointer { + Some(pointer) if !capabilities.contains(wl_seat::Capability::Pointer) => { + if pointer.version() >= 3 { + pointer.release(conn); + } + seat.pointer = None; + } + None if capabilities.contains(wl_seat::Capability::Pointer) => { + let pointer = seat.seat.get_pointer(conn); + seat.pointer = Some(pointer); + } + _ => (), + } + } + } +} + +impl Dispatch<WlPointer> for State { + fn event(&mut self, conn: &mut Connection<Self>, pointer: WlPointer, event: wl_pointer::Event) { let seat = self .seats .iter_mut() - .find(|p| &p.pointer == pointer) + .find(|s| s.pointer == Some(pointer)) .unwrap(); - for event in events { - if let Some(bar) = self - .bars - .iter_mut() - .find(|b| b.layer.wl_surface() == &event.surface) - { - match event.kind { - PointerEventKind::Enter { .. } => { - seat.themed_pointer - .set_cursor( - conn, - "default", - self.shm_state.wl_shm(), - &seat.pointer_surface, - ) + + use wl_pointer::Event; + match event { + Event::Frame => { + let btn = seat.pending_button.take(); + let scroll = seat.scroll_frame.finalize(); + if let Some(surface) = seat.cur_surface { + let bar = self + .bars + .iter_mut() + .find(|bar| bar.surface == surface) + .unwrap(); + + if let Some(btn) = btn { + bar.click(conn, &mut self.shared_state, btn, seat.seat, seat.x, seat.y) .unwrap(); } - PointerEventKind::Press { button, .. } => { - bar.click( - &mut self.shared_state, - button.into(), - &seat.seat, - event.position.0, - event.position.1, - ) - .unwrap(); + + if scroll.is_finder && self.shared_state.config.invert_touchpad_scrolling { + seat.pending_scroll -= scroll.absolute; + } else { + seat.pending_scroll += scroll.absolute; + } + + if scroll.stop { + seat.pending_scroll = 0.0; } - PointerEventKind::Axis { - vertical, source, .. - } => { - if source == Some(wl_pointer::AxisSource::Finger) { - if self.shared_state.config.invert_touchpad_scrolling { - seat.finger_scroll -= vertical.absolute; - } else { - seat.finger_scroll += vertical.absolute; - } - if vertical.stop { - seat.finger_scroll = 0.0; - } - } - let btn = if vertical.discrete > 0 { - PointerBtn::WheelDown - } else if vertical.discrete < 0 { - PointerBtn::WheelUp - } else if seat.finger_scroll >= 15.0 { - seat.finger_scroll = 0.0; - PointerBtn::WheelDown - } else if seat.finger_scroll <= -15.0 { - seat.finger_scroll = 0.0; - PointerBtn::WheelUp - } else { - continue; - }; + let btn = if seat.pending_scroll >= 15.0 { + seat.pending_scroll = 0.0; + Some(PointerBtn::WheelDown) + } else if seat.pending_scroll <= -15.0 { + seat.pending_scroll = 0.0; + Some(PointerBtn::WheelUp) + } else { + None + }; - bar.click( - &mut self.shared_state, - btn, - &seat.seat, - event.position.0, - event.position.1, - ) - .unwrap(); + if let Some(btn) = btn { + bar.click(conn, &mut self.shared_state, btn, seat.seat, seat.x, seat.y) + .unwrap(); } - _ => (), } } + Event::Enter(args) => { + let bar = self + .bars + .iter() + .find(|bar| bar.surface.id() == args.surface) + .unwrap(); + seat.cur_surface = Some(bar.surface); + seat.x = args.surface_x.as_f64(); + seat.y = args.surface_y.as_f64(); + seat.cursor.set( + conn, + args.serial, + pointer, + bar.scale as u32, + &mut self.shared_state.shm, + ); + } + Event::Leave(_) => seat.cur_surface = None, + Event::Motion(args) => { + seat.x = args.surface_x.as_f64(); + seat.y = args.surface_y.as_f64(); + } + Event::Button(args) => { + if args.state == wl_pointer::ButtonState::Pressed { + seat.pending_button = Some(args.button.into()); + } + } + Event::Axis(args) => { + if args.axis == wl_pointer::Axis::VerticalScroll { + seat.scroll_frame.absolute += args.value.as_f64(); + } + } + Event::AxisSource(source) => { + seat.scroll_frame.is_finder = source == wl_pointer::AxisSource::Finger; + } + Event::AxisStop(args) => { + if args.axis == wl_pointer::Axis::VerticalScroll { + seat.scroll_frame.stop = true; + } + } + Event::AxisDiscrete(_) | Event::AxisValue120(_) => (), } } } -impl ShmHandler for State { - fn shm_state(&mut self) -> &mut ShmState { - &mut self.shm_state - } -} - -impl RiverStatusHandler for State { - fn river_status_state(&mut self) -> &mut RiverStatusState { - self.river_status_state.as_mut().unwrap() - } - - fn focused_tags_updated(&mut self, output: &wl_output::WlOutput, focused: u32) { - let bar = self.bars.iter_mut().find(|b| &b.output == output).unwrap(); - bar.tags_info.focused = focused; - bar.draw(&mut self.shared_state); - } - - fn urgent_tags_updated(&mut self, output: &wl_output::WlOutput, urgent: u32) { - let bar = self.bars.iter_mut().find(|b| &b.output == output).unwrap(); - bar.tags_info.urgent = urgent; - bar.draw(&mut self.shared_state); - } +impl Dispatch<ZriverOutputStatusV1> for State { + fn event( + &mut self, + conn: &mut Connection<Self>, + status: ZriverOutputStatusV1, + event: zriver_output_status_v1::Event, + ) { + let bar = self + .bars + .iter_mut() + .find(|b| b.river_output_status == Some(status)) + .unwrap(); - fn views_tags_updated(&mut self, output: &wl_output::WlOutput, tags: Vec<u32>) { - let bar = self.bars.iter_mut().find(|b| &b.output == output).unwrap(); - bar.tags_info.active = tags.into_iter().fold(0, |a, b| a | b); - bar.draw(&mut self.shared_state); - } + use zriver_output_status_v1::Event; + match event { + Event::FocusedTags(tags) => bar.tags_info.focused = tags, + Event::UrgentTags(tags) => bar.tags_info.urgent = tags, + Event::LayoutName(name) => bar.set_layout_name(Some(name.into_string().unwrap())), + Event::LayoutNameClear => bar.set_layout_name(None), + Event::ViewTags(vt) => { + bar.tags_info.active = vt + .chunks_exact(4) + .map(|bytes| u32::from_ne_bytes(bytes.try_into().unwrap())) + .fold(0, |a, b| a | b); + } + } - fn layout_name_updated(&mut self, output: &wl_output::WlOutput, layout_name: Option<String>) { - let bar = self.bars.iter_mut().find(|b| &b.output == output).unwrap(); - bar.set_layout_name(layout_name); - bar.draw(&mut self.shared_state); + bar.request_frame(conn); } } -impl RiverControlHandler for State { - fn river_control_state(&mut self) -> &mut RiverControlState { - self.river_control_state.as_mut().unwrap() - } - - fn command_failure(&mut self, _: &Connection, _: &QueueHandle<Self>, message: String) { - self.set_error(format!("[river_control] {message}")); - } - - fn command_success(&mut self, _: &Connection, _: &QueueHandle<Self>, message: String) { - info!("river_control: {message}"); +impl Dispatch<ZriverCommandCallbackV1> for State { + fn event( + &mut self, + conn: &mut Connection<Self>, + _: ZriverCommandCallbackV1, + event: zriver_command_callback_v1::Event, + ) { + use zriver_command_callback_v1::Event; + match event { + Event::Success(msg) => info!("river_control: {msg:?}"), + Event::Failure(msg) => self.set_error(conn, msg.into_string().unwrap()), + } } } -delegate_compositor!(State); -delegate_output!(State); -delegate_shm!(State); -delegate_seat!(State); -delegate_pointer!(State); -delegate_layer!(State); -delegate_registry!(State); -delegate_river_status!(State); -delegate_river_control!(State); +// Interfaces with no events +impl Dispatch<WlCompositor> for State {} +impl Dispatch<ZwlrLayerShellV1> for State {} +impl Dispatch<ZriverStatusManagerV1> for State {} +impl Dispatch<ZriverControlV1> for State {} +impl Dispatch<WlShmPool> for State {} -impl ProvidesRegistryState for State { - fn registry(&mut self) -> &mut RegistryState { - &mut self.registry_state - } - registry_handlers![OutputState,]; -} +// Dont care +impl Dispatch<WlShm> for State {} +impl Dispatch<WlSurface> for State {} #[derive(Debug)] pub struct ComputedBlock { @@ -460,3 +502,18 @@ pub struct ComputedBlock { pub short: Option<ComputedText>, pub min_width: Option<f64>, } + +#[derive(Debug, Default, Clone, Copy)] +pub struct ScrollFrame { + stop: bool, + absolute: f64, + is_finder: bool, +} + +impl ScrollFrame { + fn finalize(&mut self) -> Self { + let copy = *self; + *self = Self::default(); + copy + } +} diff --git a/src/status_cmd.rs b/src/status_cmd.rs index 4d9c453..f6c9c2c 100644 --- a/src/status_cmd.rs +++ b/src/status_cmd.rs @@ -1,22 +1,22 @@ -use std::io::{BufRead, BufReader, Write}; -use std::os::unix::prelude::AsRawFd; -use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio}; +use std::io::Write; +use std::process::{Child, ChildStdin, Command, Stdio}; use anyhow::Result; -use tokio::io::unix::AsyncFd; -use tokio::io::Interest; +use tokio::io::AsyncReadExt; +use tokio::process::ChildStdout; use crate::i3bar_protocol::{Block, Event, Protocol}; +const INITIAL_BUF_CAPACITY: usize = 4096; + #[derive(Debug)] pub struct StatusCmd { pub child: Child, - output: BufReader<ChildStdout>, + pub output: ChildStdout, input: ChildStdin, protocol: Protocol, buf: Vec<u8>, - pub async_fd: AsyncFd<i32>, } impl StatusCmd { @@ -28,73 +28,45 @@ impl StatusCmd { .spawn()?; let output = child.stdout.take().unwrap(); let input = child.stdin.take().unwrap(); - let async_fd = AsyncFd::with_interest(output.as_raw_fd(), Interest::READABLE)?; Ok(Self { child, - output: BufReader::new(output), + output: ChildStdout::from_std(output)?, input, protocol: Protocol::Unknown, - buf: Vec::new(), - async_fd, + buf: Vec::with_capacity(INITIAL_BUF_CAPACITY), }) } pub fn notify_available(&mut self) -> Result<Option<Vec<Block>>> { - let buf = self.output.fill_buf()?; - if buf.is_empty() { - bail!("status command exited"); - } - if self.buf.is_empty() { - let rem = self.protocol.process_new_bytes(buf)?; - if !rem.is_empty() { - self.buf.extend_from_slice(rem); - } + let rem = self.protocol.process_new_bytes(&self.buf)?; + if rem.is_empty() { + self.buf.clear(); } else { - self.buf.extend_from_slice(buf); - let rem = self.protocol.process_new_bytes(&self.buf)?; - if rem.is_empty() { - self.buf.clear(); - } else { - let used = self.buf.len() - rem.len(); - self.buf.drain(..used); - } + let used = self.buf.len() - rem.len(); + self.buf.drain(..used); } - let consumed = buf.len(); - self.output.consume(consumed); Ok(self.protocol.get_blocks()) } + pub async fn read(&mut self) -> Result<()> { + assert!(self.buf.capacity() >= INITIAL_BUF_CAPACITY); + if self.buf.capacity() == self.buf.len() { + // Double the capacity + self.buf.reserve(self.buf.len()); + } + + let read_len = self.output.read_buf(&mut self.buf).await?; + if read_len == 0 { + bail!("status command exited"); + } + + Ok(()) + } + pub fn send_click_event(&mut self, event: &Event) -> Result<()> { if self.protocol.supports_clicks() { writeln!(self.input, "{}", serde_json::to_string(event).unwrap())?; } Ok(()) } - - // pub fn quick_insert(&self, handle: LoopHandle<BarState>) { - // handle - // .insert_source( - // calloop::generic::Generic::new( - // self.output.get_ref().as_raw_fd(), - // calloop::Interest { - // readable: true, - // writable: false, - // }, - // calloop::Mode::Level, - // ), - // move |ready, _, bar_state| { - // if ready.readable { - // bar_state.notify_available()?; - // Ok(calloop::PostAction::Continue) - // } else { - // bar_state.set_error("error reading from status command"); - // if let Some(mut child) = bar_state.status_cmd.take() { - // let _ = child.child.kill(); - // } - // Ok(calloop::PostAction::Remove) - // } - // }, - // ) - // .expect("failed to inser calloop source"); - // } } |
