diff options
| author | MaxVerevkin <maxxverrr@gmail.com> | 2022-08-08 23:12:23 +0300 |
|---|---|---|
| committer | MaxVerevkin <maxxverrr@gmail.com> | 2022-08-08 23:59:59 +0300 |
| commit | 1851c56e407cac72ca104cc5e75692cd9c648986 (patch) | |
| tree | 31473532e01ee6ee1571974a6abf59463e3265ed | |
| parent | 9ddde2d2ee9833412e39afbd1ed907cdea22f08f (diff) | |
| download | i3bar-mango-1851c56e407cac72ca104cc5e75692cd9c648986.tar.gz | |
rewrite using latest walayd libs & implement HiDPI
| -rw-r--r-- | .github/workflows/rust.yml | 8 | ||||
| -rw-r--r-- | Cargo.lock | 265 | ||||
| -rw-r--r-- | Cargo.toml | 31 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | build.rs | 20 | ||||
| -rw-r--r-- | src/bar.rs | 412 | ||||
| -rw-r--r-- | src/bar_state.rs | 651 | ||||
| -rw-r--r-- | src/config.rs | 5 | ||||
| -rw-r--r-- | src/i3bar_protocol.rs | 3 | ||||
| -rw-r--r-- | src/main.rs | 198 | ||||
| -rw-r--r-- | src/river_protocols.rs | 38 | ||||
| -rw-r--r-- | src/river_protocols/control.rs | 89 | ||||
| -rw-r--r-- | src/river_protocols/control/dispatch.rs | 70 | ||||
| -rw-r--r-- | src/river_protocols/status.rs | 134 | ||||
| -rw-r--r-- | src/river_protocols/status/dispatch.rs | 86 | ||||
| -rw-r--r-- | src/shared_state.rs | 18 | ||||
| -rw-r--r-- | src/state.rs | 430 | ||||
| -rw-r--r-- | src/status_cmd.rs | 71 |
18 files changed, 1514 insertions, 1017 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a4f6073..ecf9bac 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get required packages - run: sudo apt-get install libpango1.0-dev + run: sudo apt-get install libpango1.0-dev libxkbcommon-dev - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get required packages - run: sudo apt-get install libpango1.0-dev + run: sudo apt-get install libpango1.0-dev libxkbcommon-dev - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get required packages - run: sudo apt-get install libpango1.0-dev + run: sudo apt-get install libpango1.0-dev libxkbcommon-dev - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get required packages - run: sudo apt-get install libpango1.0-dev + run: sudo apt-get install libpango1.0-dev libxkbcommon-dev - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" [[package]] name = "atty" @@ -65,19 +65,6 @@ dependencies = [ ] [[package]] -name = "calloop" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" -dependencies = [ - "log", - "nix 0.24.2", - "slotmap", - "thiserror", - "vec_map", -] - -[[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -293,16 +280,18 @@ dependencies = [ "serde", "serde_json", "smithay-client-toolkit", + "tokio", "toml", - "wayland-commons", + "wayland-backend", + "wayland-client", "wayland-scanner", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "lazy_static" @@ -312,9 +301,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" [[package]] name = "libloading" @@ -366,16 +355,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "nix" -version = "0.22.3" +name = "mio" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ - "bitflags", - "cc", - "cfg-if", "libc", - "memoffset", + "log", + "wasi", + "windows-sys", ] [[package]] @@ -478,10 +466,11 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -512,27 +501,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -567,9 +556,9 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "scoped-tls" @@ -579,18 +568,18 @@ checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "serde" -version = "1.0.139" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" dependencies = [ "proc-macro2", "quote", @@ -599,9 +588,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -610,17 +599,11 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "slotmap" -version = "1.0.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ - "version_check", + "autocfg", ] [[package]] @@ -632,27 +615,38 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay-client-toolkit" version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +source = "git+https://github.com/Smithay/client-toolkit#32bb7a6521d621baf278dd0f3abb0618cb3d2234" dependencies = [ "bitflags", - "calloop", "dlib", "lazy_static", "log", "memmap2", - "nix 0.24.2", + "nix", "pkg-config", + "thiserror", + "wayland-backend", "wayland-client", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", ] [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -683,18 +677,45 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "libc", + "mio", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -712,15 +733,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "version-compare" @@ -741,75 +756,90 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wayland-client" -version = "0.29.4" +name = "wayland-backend" +version = "0.1.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +checksum = "0ee8e77c63b0cdc68bfc7b407b862b0fe2718949ce060b32d4f94ef1ea9607a4" dependencies = [ - "bitflags", + "cc", "downcast-rs", - "libc", - "nix 0.22.3", + "nix", "scoped-tls", - "wayland-commons", - "wayland-scanner", + "smallvec", "wayland-sys", ] [[package]] -name = "wayland-commons" -version = "0.29.4" +name = "wayland-client" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +checksum = "0f9e0d862c23f07b2c4b49de66b0680948af5dd1d2def17f1ddc16520352bf14" dependencies = [ - "nix 0.22.3", - "once_cell", - "smallvec", - "wayland-sys", + "bitflags", + "futures-channel", + "futures-core", + "nix", + "thiserror", + "wayland-backend", + "wayland-scanner", ] [[package]] name = "wayland-cursor" -version = "0.29.4" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +checksum = "53732b42facd4f5ba891ed5d5f16fc02231ca543a0eb5bd16233947aa5900027" dependencies = [ - "nix 0.22.3", + "nix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.4" +version = "0.30.0-beta.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47c45a60d531d5a513601f47f51a4743901836778ddae208ae9124606be1719" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +checksum = "d0b477d16e0c1d7512f11799403c8dbb1964e756667208569ec0ea2bd1abbccb" dependencies = [ "bitflags", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.4" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +checksum = "87933ccc3df4f6335cf240aca0647aa34319fdd693dda503f645ca4df4e10386" dependencies = [ "proc-macro2", "quote", + "syn", "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.4" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +checksum = "beca223ed017df1b356ff181d4d6e7f2b135418c4888df5bb02df7a563f02ab0" dependencies = [ "dlib", - "lazy_static", + "log", "pkg-config", ] @@ -845,6 +875,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[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_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[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_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] name = "xcursor" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4,22 +4,27 @@ version = "0.1.0" edition = "2021" [dependencies] -smithay-client-toolkit = "0.16" -wayland-commons = "0.29" -pangocairo = "0.15" -anyhow = "1.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -toml = "0.5" -dirs-next = "2.0" log = "0.4" env_logger = "0.9" -memchr = "2.4" +anyhow = "1" +dirs-next = "2" +toml = "0.5" +pangocairo = "0.15" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +memchr = "2" +wayland-client = "0.30.0-beta.8" +wayland-scanner = "0.30.0-beta.8" +wayland-backend = "0.1.0-beta.8" + +[dependencies.smithay-client-toolkit] +git = "https://github.com/Smithay/client-toolkit" +default-features = false -[build-dependencies] -wayland-scanner = "0.29" +[dependencies.tokio] +version = "1" +features = ["macros", "net", "rt", "sync"] [profile.release] -lto = "thin" +lto = "fat" strip = true -debug = 1 @@ -9,8 +9,6 @@ I've tested [`i3status-rs`](https://github.com/greshake/i3status-rust), [`bumble A list of things that are missing (for now): - `border[_top|_right|_bottom|_left]` - Click events lack some info (IDK if anyone actually relies on `x`, `y`, `width`, etc.) -- Multiple seat support (implemented in `next` branch) -- HiDPI (implemented in `next` branch) - Tray icons ## Advantages diff --git a/build.rs b/build.rs deleted file mode 100644 index 1dda7a6..0000000 --- a/build.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::env::var; -use std::path::Path; -use wayland_scanner::{generate_code, Side}; - -fn main() { - let out_dir = var("OUT_DIR").unwrap(); - let out_dir = Path::new(&out_dir); - - generate_code( - "./protocols/river-status-unstable-v1.xml", - out_dir.join("river-status-unstable-v1.rs"), - Side::Client, - ); - - generate_code( - "./protocols/river-control-unstable-v1.xml", - out_dir.join("river-control-unstable-v1.rs"), - Side::Client, - ); -} diff --git a/src/bar.rs b/src/bar.rs new file mode 100644 index 0000000..f3dc95c --- /dev/null +++ b/src/bar.rs @@ -0,0 +1,412 @@ +use std::collections::BinaryHeap; + +use pangocairo::cairo; +use smithay_client_toolkit::{ + reexports::client::protocol::{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}, +}; + +pub struct Bar { + pub first_configure: bool, + pub width: u32, + pub height: u32, + pub scale: i32, + pub layer: LayerSurface, + pub blocks_btns: ButtonManager<(Option<String>, Option<String>)>, + pub river_output_status: Option<RiverOutputStatus>, + pub river_control: RiverControlState, + pub tags_btns: ButtonManager, + pub tags_info: TagsInfo, + pub tags_computed: Vec<ComputedText>, +} + +impl Bar { + pub fn configure(&mut self, ss: &mut SharedState, width: u32) { + if self.width != width { + self.width = width; + self.layer.set_exclusive_zone(self.height as i32); + } + + self.first_configure = false; + self.draw(ss); + } + + pub fn has_tags_provider(&self) -> bool { + // TODO: add more tags providers + self.river_output_status.is_some() + } + + pub fn click( + &mut self, + ss: &mut SharedState, + button: PointerBtn, + seat: &wl_seat::WlSeat, + x: f64, + _y: f64, + ) -> anyhow::Result<()> { + if let Some(tag) = self.tags_btns.click(x) { + if self.river_control.is_available() { + let cmd = match button { + PointerBtn::Left => "set-focused-tags", + PointerBtn::Right => "toggle-focused-tags", + _ => return Ok(()), + }; + self.river_control.run_command( + &ss.qh, + seat, + [cmd.into(), (1u32 << tag).to_string()], + )?; + } + } else if let Some((name, instance)) = self.blocks_btns.click(x) { + if let Some(cmd) = &mut ss.status_cmd { + cmd.send_click_event(&i3bar_protocol::Event { + name: name.as_deref(), + instance: instance.as_deref(), + button, + ..Default::default() + })?; + } + } + Ok(()) + } + + pub fn draw(&mut self, ss: &mut SharedState) { + if self.first_configure { + return; + } + + let stride = 4 * self.width as i32; + let width = self.width as i32; + let height = self.height as i32; + 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 cairo_surf = unsafe { + cairo::ImageSurface::create_for_data_unsafe( + canvas.as_mut_ptr(), + cairo::Format::ARgb32, + width * self.scale, + height * self.scale, + stride * self.scale, + ) + .expect("cairo surface") + }; + + 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); + + // Background + ss.config.background.apply(&cairo_ctx); + cairo_ctx.paint().expect("cairo paint"); + + // Display tags + let mut offset_left = 0.0; + if self.has_tags_provider() { + if self.tags_computed.is_empty() { + let mut x_offset = 0.0; + // TODO make configurable + for (id, text) in ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + .iter() + .enumerate() + { + let tag = + compute_tag_label(text.to_string(), ss.config.font.clone(), &cairo_ctx); + self.tags_btns.push(x_offset, tag.width, id); + x_offset += tag.width; + self.tags_computed.push(tag); + } + } + for (i, label) in self.tags_computed.iter().enumerate() { + let state = self.tags_info.get_state(i); + let (bg, fg) = match state { + TagState::Focused => (ss.config.tag_focused_bg, ss.config.tag_focused_fg), + TagState::Inactive => (ss.config.tag_bg, ss.config.tag_fg), + TagState::Urgent => (ss.config.tag_urgent_bg, ss.config.tag_urgent_fg), + }; + label.render( + &cairo_ctx, + RenderOptions { + x_offset: offset_left, + bar_height: height_f, + fg_color: fg, + bg_color: Some(bg), + r_left: if i == 0 || self.tags_info.get_state(i.saturating_sub(1)) != state + { + ss.config.tags_r + } else { + 0.0 + }, + r_right: if i == 8 || self.tags_info.get_state(i + 1) != state { + ss.config.tags_r + } else { + 0.0 + }, + overlap: 0.0, + }, + ); + offset_left += label.width; + } + } + + // Display the blocks + render_blocks( + &cairo_ctx, + &ss.config, + &ss.blocks, + &mut ss.blocks_cache, + &mut self.blocks_btns, + offset_left, + width_f, + 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); + + // Finally, commit the surface + self.layer.wl_surface().commit(); + } +} + +#[allow(clippy::too_many_arguments)] +fn render_blocks( + context: &cairo::Context, + config: &Config, + blocks: &[Block], + blocks_cache: &mut Vec<ComputedBlock>, + buttons: &mut ButtonManager<(Option<String>, Option<String>)>, + offset_left: f64, + full_width: f64, + full_height: f64, +) { + let comp_min_width = |block: &Block| { + let markup = block.markup.as_deref() == Some("pango"); + match &block.min_width { + Some(MinWidth::Pixels(p)) => Some(*p as f64), + Some(MinWidth::Text(t)) => Some(text::width_of(t, context, markup, &config.font.0)), + None => None, + } + }; + let comp_full = |block: &Block, min_width: Option<f64>| { + let markup = block.markup.as_deref() == Some("pango"); + text::Text { + text: block.full_text.clone(), + attr: text::Attributes { + font: config.font.clone(), + padding_left: 0.0, + padding_right: 0.0, + min_width, + align: block.align.unwrap_or_default(), + markup, + }, + } + .compute(context) + }; + let comp_short = |block: &Block, min_width: Option<f64>| { + let markup = block.markup.as_deref() == Some("pango"); + block.short_text.as_ref().map(|short_text| { + text::Text { + text: short_text.clone(), + attr: text::Attributes { + font: config.font.clone(), + padding_left: 0.0, + padding_right: 0.0, + min_width, + align: block.align.unwrap_or_default(), + markup, + }, + } + .compute(context) + }) + }; + + // update cashe + if blocks.len() != blocks_cache.len() { + blocks_cache.clear(); + for block in blocks { + let mw = comp_min_width(block); + blocks_cache.push(ComputedBlock { + block: block.clone(), + full: comp_full(block, mw), + short: comp_short(block, mw), + min_width: mw, + }); + } + } else { + for (block, computed) in blocks.iter().zip(blocks_cache.iter_mut()) { + if block.min_width != computed.block.min_width || block.markup != computed.block.markup + { + let mw = comp_min_width(block); + *computed = ComputedBlock { + block: block.clone(), + full: comp_full(block, mw), + short: comp_short(block, mw), + min_width: mw, + }; + } else { + if block.full_text != computed.block.full_text { + computed.block.full_text = block.full_text.clone(); + computed.full = comp_full(block, computed.min_width); + } + if block.short_text != computed.block.short_text { + computed.block.full_text = block.full_text.clone(); + computed.short = comp_short(block, computed.min_width); + } + computed.block.color = block.color; + computed.block.background = block.background; + computed.block.align = block.align; + computed.block.name = block.name.clone(); + computed.block.instance = block.instance.clone(); + computed.block.separator = block.separator; + computed.block.separator_block_width = block.separator_block_width; + } + } + } + + #[derive(Debug)] + struct LogialBlock<'a> { + blocks: Vec<(&'a ComputedBlock, bool)>, + delta: f64, + separator: bool, + separator_block_width: u8, + } + + let mut blocks_computed = Vec::new(); + let mut blocks_width = 0.0; + let mut s_start = 0; + while s_start < blocks.len() { + let mut s_end = s_start + 1; + let series_name = &blocks[s_start].name; + while s_end < blocks.len() + && blocks[s_end - 1].separator_block_width == 0 + && &blocks[s_end].name == series_name + { + s_end += 1; + } + + let mut series = LogialBlock { + blocks: Vec::with_capacity(s_end - s_start), + delta: 0.0, + separator: blocks[s_end - 1].separator, + separator_block_width: blocks[s_end - 1].separator_block_width, + }; + + for comp in &blocks_cache[s_start..s_end] { + blocks_width += comp.full.width; + if let Some(short) = &comp.short { + series.delta += comp.full.width - short.width; + } + series.blocks.push((comp, false)); + } + if s_end != blocks.len() { + blocks_width += series.separator_block_width as f64; + } + blocks_computed.push(series); + s_start = s_end; + } + + // Progressively switch to short mode + if offset_left + blocks_width > full_width { + let mut heap = BinaryHeap::new(); + for (i, b) in blocks_computed.iter().enumerate() { + if b.delta > 0.0 { + heap.push((DefaultLess(b.delta), i)); + } + } + while let Some((DefaultLess(delta), to_switch)) = heap.pop() { + for comp in &mut blocks_computed[to_switch].blocks { + comp.1 = true; + } + blocks_width -= delta; + if offset_left + blocks_width <= full_width { + break; + } + } + } + + // Remove all the empy blocks + for s in &mut blocks_computed { + s.blocks.retain(|(text, is_short)| { + (*is_short + && text + .short + .as_ref() + .map_or(text.full.width > 0.0, |s| s.width > 0.0)) + || (!is_short && text.full.width > 0.0) + }); + } + + // Render blocks + buttons.clear(); + let mut j = 0; + for series in blocks_computed { + let s_len = series.blocks.len(); + for (i, (computed, is_short)) in series.blocks.into_iter().enumerate() { + let block = &computed.block; + let to_render = if is_short { + computed.short.as_ref().unwrap_or(&computed.full) + } else { + &computed.full + }; + j += 1; + to_render.render( + context, + RenderOptions { + x_offset: full_width - blocks_width, + bar_height: full_height, + fg_color: block.color.unwrap_or(config.color), + bg_color: block.background, + r_left: if i == 0 { config.blocks_r } else { 0.0 }, + r_right: if i + 1 == s_len { config.blocks_r } else { 0.0 }, + overlap: config.blocks_overlap, + }, + ); + buttons.push( + full_width - blocks_width, + to_render.width, + (block.name.clone(), block.instance.clone()), + ); + blocks_width -= to_render.width; + } + if j != blocks.len() && series.separator_block_width > 0 { + let w = series.separator_block_width as f64; + if series.separator && config.separator_width > 0.0 { + config.separator.apply(context); + context.set_line_width(config.separator_width); + context.move_to(full_width - blocks_width + w * 0.5, full_height * 0.1); + context.line_to(full_width - blocks_width + w * 0.5, full_height * 0.9); + context.stroke().unwrap(); + } + blocks_width -= w; + } + } +} diff --git a/src/bar_state.rs b/src/bar_state.rs deleted file mode 100644 index 55705da..0000000 --- a/src/bar_state.rs +++ /dev/null @@ -1,651 +0,0 @@ -use std::cell::RefCell; -use std::collections::BinaryHeap; -use std::io; -use std::rc::Rc; - -use smithay_client_toolkit::{ - environment::Environment, - reexports::{ - client::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_shm, wl_surface::WlSurface}, - client::{Attached, Main}, - protocols::wlr::unstable::layer_shell::v1::client::{ - zwlr_layer_shell_v1, zwlr_layer_surface_v1, - }, - }, - shm::AutoMemPool, -}; - -use pangocairo::cairo; - -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::river_protocols::zriver_command_callback_v1; -use crate::river_protocols::zriver_control_v1; -use crate::river_protocols::zriver_output_status_v1; -use crate::river_protocols::zriver_status_manager_v1; -use crate::status_cmd::StatusCmd; -use crate::tags::{compute_tag_label, TagState, TagsInfo}; -use crate::text; -use crate::text::{ComputedText, RenderOptions}; -use crate::Env; - -pub struct BarState { - pub status_cmd: Option<StatusCmd>, - config: Rc<RefCell<Config>>, - blocks: Vec<Block>, - blocks_cache: Vec<ComputedBlock>, - surfaces: Vec<Surface>, - layer_shell: Attached<zwlr_layer_shell_v1::ZwlrLayerShellV1>, - river_status: Option<Attached<zriver_status_manager_v1::ZriverStatusManagerV1>>, - river_control: Option<Attached<zriver_control_v1::ZriverControlV1>>, -} - -impl BarState { - pub fn new(env: &Environment<Env>) -> Self { - let mut error = Ok(()); - - let config = Config::new() - .map_err(|e| error = Err(e)) - .unwrap_or_default(); - - let status_cmd = match &error { - Err(_) => None, - Ok(()) => config.command.as_deref().and_then(|cmd| { - StatusCmd::new(cmd) - .map_err(|e| error = Err(anyhow!(e))) - .ok() - }), - }; - - let mut s = Self { - status_cmd, - config: Rc::new(RefCell::new(config)), - blocks: Vec::new(), - blocks_cache: Vec::new(), - surfaces: Vec::default(), - layer_shell: env.require_global::<zwlr_layer_shell_v1::ZwlrLayerShellV1>(), - river_status: env.get_global::<zriver_status_manager_v1::ZriverStatusManagerV1>(), - river_control: env.get_global::<zriver_control_v1::ZriverControlV1>(), - }; - - if let Err(e) = error { - s.set_error(e.to_string()); - } - - s - } - - pub fn set_blocks(&mut self, blocks: Vec<Block>) { - self.blocks = blocks; - for s in &mut self.surfaces { - s.dirty = true; - } - } - - pub fn set_error(&mut self, error: impl Into<String>) { - self.set_blocks(vec![Block { - full_text: error.into(), - ..Default::default() - }]); - } - - pub fn remove_surface(&mut self, output_id: u32) { - self.surfaces.retain(|s| s.output_id != output_id); - } - - pub fn notify_available(&mut self) -> io::Result<()> { - if let Some(cmd) = &mut self.status_cmd { - if let Some(blocks) = cmd.notify_available()? { - self.set_blocks(blocks); - } - } - Ok(()) - } - - pub fn handle_click( - &mut self, - surface: &WlSurface, - seat: &WlSeat, - x: f64, - y: f64, - btn: PointerBtn, - ) { - if let Some(s) = self - .surfaces - .iter() - .find(|s| &s.surface.detach() == surface) - { - if let Some(event) = s.click(btn, seat, x, y) { - if let Some(cmd) = &mut self.status_cmd { - if let Err(e) = cmd.send_click_event(&event) { - self.set_error(e.to_string()); - } - } - } - } - } - - pub fn add_surface(&mut self, output: &WlOutput, output_id: u32, env: &Environment<Env>) { - let surface = env.create_surface(); - let pool = env - .create_auto_pool() - .expect("Failed to create a memory pool!"); - - let layer_surface = self.layer_shell.get_layer_surface( - &surface, - Some(output), - zwlr_layer_shell_v1::Layer::Top, - "i3bar-river".to_owned(), - ); - - // Set the height - layer_surface.set_size(0, self.config.borrow().height); - // Anchor to the top - layer_surface.set_anchor( - zwlr_layer_surface_v1::Anchor::Top - | zwlr_layer_surface_v1::Anchor::Left - | zwlr_layer_surface_v1::Anchor::Right, - ); - - layer_surface.quick_assign(move |layer_surface, event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - match event { - zwlr_layer_surface_v1::Event::Closed => { - bar_state.remove_surface(output_id); - } - zwlr_layer_surface_v1::Event::Configure { - serial, - width, - height, - } => { - layer_surface.ack_configure(serial); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - if s.configure(width, height) { - s.draw(&bar_state.blocks, &mut bar_state.blocks_cache); - } - } - } - _ => (), - } - }); - - // Commit so that the server will send a configure event - surface.commit(); - - let (river_output_status, tags_info) = if let Some(river_status) = &self.river_status { - let river_output_status = river_status.get_river_output_status(output); - river_output_status.quick_assign(move |_, event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - match event { - zriver_output_status_v1::Event::FocusedTags { tags } => { - s.tags_info.as_mut().unwrap().focused = tags; - } - zriver_output_status_v1::Event::UrgentTags { tags } => { - s.tags_info.as_mut().unwrap().urgent = tags; - } - _ => (), - } - s.dirty = true; - } - }); - (Some(river_output_status), Some(TagsInfo::default())) - } else { - (None, None) - }; - - self.surfaces.push(Surface { - output_id, - surface, - layer_surface, - pool, - dimensions: (0, 0), - config: self.config.clone(), - dirty: true, - frame_scheduled: false, - river_output_status, - river_control: self.river_control.clone(), - tags_info, - tags_computed: Vec::with_capacity(9), - tags_btns: Default::default(), - blocks_btns: Default::default(), - }); - } -} - -pub struct Surface { - output_id: u32, - surface: Attached<WlSurface>, - layer_surface: Main<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>, - pool: AutoMemPool, - dimensions: (u32, u32), - config: Rc<RefCell<Config>>, - dirty: bool, - frame_scheduled: bool, - // river stuff - river_output_status: Option<Main<zriver_output_status_v1::ZriverOutputStatusV1>>, - river_control: Option<Attached<zriver_control_v1::ZriverControlV1>>, - // tags - tags_info: Option<TagsInfo>, - tags_computed: Vec<ComputedText>, - // buttons - tags_btns: ButtonManager, - blocks_btns: ButtonManager<(Option<String>, Option<String>)>, -} - -impl Surface { - /// Handle `configure` event. Returns `true` if configuration changed. - fn configure(&mut self, width: u32, height: u32) -> bool { - if self.dimensions != (width, height) { - self.dimensions = (width, height); - self.layer_surface.set_exclusive_zone(height as i32); - self.dirty = true; - true - } else { - false - } - } - - fn click( - &self, - button: PointerBtn, - seat: &WlSeat, - x: f64, - _y: f64, - ) -> Option<i3bar_protocol::Event> { - if let Some((id, river_control)) = self.tags_btns.click(x).zip(self.river_control.as_ref()) - { - let cmd = match button { - PointerBtn::Left => "set-focused-tags", - PointerBtn::Right => "toggle-focused-tags", - _ => return None, - }; - river_control.add_argument(cmd.into()); - river_control.add_argument((1u32 << id).to_string()); - let result = river_control.run_command(seat); - result.quick_assign(|_, event, _| match event { - zriver_command_callback_v1::Event::Success { output } => { - info!("River cmd output: '{output}'"); - } - zriver_command_callback_v1::Event::Failure { failure_message: f } => { - error!("River error: '{f}'"); - } - }); - } else if let Some((name, instance)) = self.blocks_btns.click(x) { - return Some(i3bar_protocol::Event { - name: name.as_deref(), - instance: instance.as_deref(), - button, - ..Default::default() - }); - } - None - } - - fn draw(&mut self, blocks: &[Block], blocks_cache: &mut Vec<ComputedBlock>) { - if self.dirty { - let config = self.config.borrow(); - - let stride = 4 * self.dimensions.0 as i32; - let width = self.dimensions.0 as i32; - let height = self.dimensions.1 as i32; - let width_f = width as f64; - let height_f = height as f64; - - let (canvas, buffer) = self - .pool - .buffer(width, height, stride, wl_shm::Format::Argb8888) - .unwrap(); - - let cairo_surf = unsafe { - cairo::ImageSurface::create_for_data_unsafe( - canvas.as_mut_ptr(), - cairo::Format::ARgb32, - width, - height, - stride, - ) - .expect("cairo surface") - }; - - let cairo_ctx = cairo::Context::new(&cairo_surf).expect("cairo context"); - let pango_layout = pangocairo::create_layout(&cairo_ctx).expect("pango layout"); - pango_layout.set_font_description(Some(&config.font)); - pango_layout.set_height(height); - - // Background - config.background.apply(&cairo_ctx); - cairo_ctx.paint().expect("cairo paint"); - - // Display tags - let mut offset_left = 0.0; - if let Some(tags_info) = &self.tags_info { - if self.tags_computed.is_empty() { - let mut x_offset = 0.0; - // TODO make configurable - for (id, text) in ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - .iter() - .enumerate() - { - let tag = - compute_tag_label(text.to_string(), config.font.clone(), &cairo_ctx); - self.tags_btns.push(x_offset, tag.width, id); - x_offset += tag.width; - self.tags_computed.push(tag); - } - } - for (i, label) in self.tags_computed.iter().enumerate() { - let state = tags_info.get_state(i); - let (bg, fg) = match state { - TagState::Focused => (config.tag_focused_bg, config.tag_focused_fg), - TagState::Inactive => (config.tag_bg, config.tag_fg), - TagState::Urgent => (config.tag_urgent_bg, config.tag_urgent_fg), - }; - label.render( - &cairo_ctx, - RenderOptions { - x_offset: offset_left, - bar_height: height_f, - fg_color: fg, - bg_color: Some(bg), - r_left: if i == 0 || tags_info.get_state(i.saturating_sub(1)) != state { - config.tags_r - } else { - 0.0 - }, - r_right: if i == 8 || tags_info.get_state(i + 1) != state { - config.tags_r - } else { - 0.0 - }, - overlap: 0.0, - }, - ); - offset_left += label.width; - } - } - - // Display the blocks - render_blocks( - &cairo_ctx, - &*config, - blocks, - blocks_cache, - &mut self.blocks_btns, - offset_left, - width_f, - height_f, - ); - - // Attach the buffer to the surface and mark the entire surface as damaged - self.surface.attach(Some(&buffer), 0, 0); - self.surface - .damage_buffer(0, 0, width as i32, height as i32); - } - - // Schedule next frame - if !self.frame_scheduled { - self.frame_scheduled = true; - let output_id = self.output_id; - let frame = self.surface.frame(); - frame.quick_assign(move |_callback, _event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - s.frame_scheduled = false; - s.draw(&bar_state.blocks, &mut bar_state.blocks_cache); - } - }); - } - - // Finally, commit the surface - self.surface.commit(); - self.dirty = false; - } -} - -impl Drop for Surface { - fn drop(&mut self) { - self.layer_surface.destroy(); - self.surface.destroy(); - if let Some(ros) = &self.river_output_status { - ros.destroy(); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn render_blocks( - context: &cairo::Context, - config: &Config, - blocks: &[Block], - blocks_cache: &mut Vec<ComputedBlock>, - buttons: &mut ButtonManager<(Option<String>, Option<String>)>, - offset_left: f64, - full_width: f64, - full_height: f64, -) { - let comp_min_width = |block: &Block| { - let markup = block.markup.as_deref() == Some("pango"); - match &block.min_width { - Some(MinWidth::Pixels(p)) => Some(*p as f64), - Some(MinWidth::Text(t)) => Some(text::width_of(t, context, markup, &config.font.0)), - None => None, - } - }; - let comp_full = |block: &Block, min_width: Option<f64>| { - let markup = block.markup.as_deref() == Some("pango"); - text::Text { - text: block.full_text.clone(), - attr: text::Attributes { - font: config.font.clone(), - padding_left: 0.0, - padding_right: 0.0, - min_width, - align: block.align.unwrap_or_default(), - markup, - }, - } - .compute(context) - }; - let comp_short = |block: &Block, min_width: Option<f64>| { - let markup = block.markup.as_deref() == Some("pango"); - block.short_text.as_ref().map(|short_text| { - text::Text { - text: short_text.clone(), - attr: text::Attributes { - font: config.font.clone(), - padding_left: 0.0, - padding_right: 0.0, - min_width, - align: block.align.unwrap_or_default(), - markup, - }, - } - .compute(context) - }) - }; - - // update cashe - if blocks.len() != blocks_cache.len() { - blocks_cache.clear(); - for block in blocks { - let mw = comp_min_width(block); - blocks_cache.push(ComputedBlock { - block: block.clone(), - full: comp_full(block, mw), - short: comp_short(block, mw), - min_width: mw, - }); - } - } else { - for (block, computed) in blocks.iter().zip(blocks_cache.iter_mut()) { - if block.min_width != computed.block.min_width || block.markup != computed.block.markup - { - let mw = comp_min_width(block); - *computed = ComputedBlock { - block: block.clone(), - full: comp_full(block, mw), - short: comp_short(block, mw), - min_width: mw, - }; - } else { - if block.full_text != computed.block.full_text { - computed.block.full_text = block.full_text.clone(); - computed.full = comp_full(block, computed.min_width); - } - if block.short_text != computed.block.short_text { - computed.block.full_text = block.full_text.clone(); - computed.short = comp_short(block, computed.min_width); - } - computed.block.color = block.color; - computed.block.background = block.background; - computed.block.align = block.align; - computed.block.name = block.name.clone(); - computed.block.instance = block.instance.clone(); - computed.block.separator = block.separator; - computed.block.separator_block_width = block.separator_block_width; - } - } - } - - #[derive(Debug)] - struct LogialBlock<'a> { - blocks: Vec<(&'a ComputedBlock, bool)>, - delta: f64, - separator: bool, - separator_block_width: u8, - } - - let mut blocks_computed = Vec::new(); - let mut blocks_width = 0.0; - let mut s_start = 0; - while s_start < blocks.len() { - let mut s_end = s_start + 1; - let series_name = &blocks[s_start].name; - while s_end < blocks.len() - && blocks[s_end - 1].separator_block_width == 0 - && &blocks[s_end].name == series_name - { - s_end += 1; - } - - let mut series = LogialBlock { - blocks: Vec::with_capacity(s_end - s_start), - delta: 0.0, - separator: blocks[s_end - 1].separator, - separator_block_width: blocks[s_end - 1].separator_block_width, - }; - - for comp in &blocks_cache[s_start..s_end] { - blocks_width += comp.full.width; - if let Some(short) = &comp.short { - series.delta += comp.full.width - short.width; - } - series.blocks.push((comp, false)); - } - if s_end != blocks.len() { - blocks_width += series.separator_block_width as f64; - } - blocks_computed.push(series); - s_start = s_end; - } - - // Progressively switch to short mode - if offset_left + blocks_width > full_width { - let mut heap = BinaryHeap::new(); - for (i, b) in blocks_computed.iter().enumerate() { - if b.delta > 0.0 { - heap.push((DefaultLess(b.delta), i)); - } - } - while let Some((DefaultLess(delta), to_switch)) = heap.pop() { - for comp in &mut blocks_computed[to_switch].blocks { - comp.1 = true; - } - blocks_width -= delta; - if offset_left + blocks_width <= full_width { - break; - } - } - } - - // Remove all the empy blocks - for s in &mut blocks_computed { - s.blocks.retain(|(text, is_short)| { - (*is_short - && text - .short - .as_ref() - .map_or(text.full.width > 0.0, |s| s.width > 0.0)) - || (!is_short && text.full.width > 0.0) - }); - } - - // Render blocks - buttons.clear(); - let mut j = 0; - for series in blocks_computed { - let s_len = series.blocks.len(); - for (i, (computed, is_short)) in series.blocks.into_iter().enumerate() { - let block = &computed.block; - let to_render = if is_short { - computed.short.as_ref().unwrap_or(&computed.full) - } else { - &computed.full - }; - j += 1; - to_render.render( - context, - RenderOptions { - x_offset: full_width - blocks_width, - bar_height: full_height, - fg_color: block.color.unwrap_or(config.color), - bg_color: block.background, - r_left: if i == 0 { config.blocks_r } else { 0.0 }, - r_right: if i + 1 == s_len { config.blocks_r } else { 0.0 }, - overlap: config.blocks_overlap, - }, - ); - buttons.push( - full_width - blocks_width, - to_render.width, - (block.name.clone(), block.instance.clone()), - ); - blocks_width -= to_render.width; - } - if j != blocks.len() && series.separator_block_width > 0 { - let w = series.separator_block_width as f64; - if series.separator && config.separator_width > 0.0 { - config.separator.apply(context); - context.set_line_width(config.separator_width); - context.move_to(full_width - blocks_width + w * 0.5, full_height * 0.1); - context.line_to(full_width - blocks_width + w * 0.5, full_height * 0.9); - context.stroke().unwrap(); - } - blocks_width -= w; - } - } -} - -#[derive(Debug)] -struct ComputedBlock { - block: Block, - full: ComputedText, - short: Option<ComputedText>, - min_width: Option<f64>, -} diff --git a/src/config.rs b/src/config.rs index 66b8c8a..bd7b91c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,8 +7,8 @@ use std::fmt; use std::fs::read_to_string; use std::ops::Deref; -#[derive(Deserialize)] -#[serde(default)] +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields, default)] pub struct Config { // colors pub background: Color, @@ -79,6 +79,7 @@ impl Config { } } +#[derive(Debug)] pub struct Font(pub FontDescription); impl Font { diff --git a/src/i3bar_protocol.rs b/src/i3bar_protocol.rs index 54ebd93..67e03f1 100644 --- a/src/i3bar_protocol.rs +++ b/src/i3bar_protocol.rs @@ -64,7 +64,7 @@ pub struct Event<'a> { pub height: u8, } -#[derive(Deserialize, Clone, Copy)] +#[derive(Deserialize, Clone, Copy, Debug)] #[serde(deny_unknown_fields)] pub struct JsonHeader { version: u8, @@ -78,6 +78,7 @@ pub struct JsonHeader { click_events: bool, } +#[derive(Debug)] pub enum Protocol { Unknown, PlainText { diff --git a/src/main.rs b/src/main.rs index 730edf6..b158c91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,9 @@ -#![allow(clippy::explicit_auto_deref)] // TODO: remove once https://github.com/rust-lang/rust-clippy/issues/9101 is resloved - -#[macro_use] -extern crate log; #[macro_use] extern crate anyhow; +#[macro_use] +extern crate log; -use smithay_client_toolkit::{ - self as sctk, default_environment, - environment::SimpleGlobal, - new_default_environment, - output::{with_output_info, OutputInfo}, - reexports::{ - calloop, - client::protocol::{wl_output::WlOutput, wl_pointer}, - protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1, - }, - seat::pointer, - WaylandSource, -}; - -mod bar_state; +mod bar; mod button_manager; mod color; mod config; @@ -27,163 +11,53 @@ mod i3bar_protocol; mod ord_adaptor; mod pointer_btn; mod river_protocols; +mod shared_state; +mod state; mod status_cmd; mod tags; mod text; mod utils; -use bar_state::BarState; -use pointer_btn::PointerBtn; -use river_protocols::zriver_control_v1; -use river_protocols::zriver_status_manager_v1; +use smithay_client_toolkit::reexports::client::Connection; +use tokio::io::{unix::AsyncFd, Interest}; -default_environment!(Env, - fields = [ - layer_shell: SimpleGlobal<zwlr_layer_shell_v1::ZwlrLayerShellV1>, - river_status_manager: SimpleGlobal<zriver_status_manager_v1::ZriverStatusManagerV1>, - river_control: SimpleGlobal<zriver_control_v1::ZriverControlV1>, - ], - singles = [ - zwlr_layer_shell_v1::ZwlrLayerShellV1 => layer_shell, - zriver_status_manager_v1::ZriverStatusManagerV1 => river_status_manager, - zriver_control_v1::ZriverControlV1 => river_control, - ], -); +use state::State; -fn main() { +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { env_logger::init(); - let (env, _display, queue) = new_default_environment!( - Env, - fields = [ - layer_shell: SimpleGlobal::new(), - river_status_manager: SimpleGlobal::new(), - river_control: SimpleGlobal::new(), - ] - ) - .expect("Initial roundtrip failed!"); - - let mut bar_state = BarState::new(&env); + let conn = Connection::connect_to_env()?; + let mut event_queue = conn.new_event_queue(); + let mut state = State::new(&conn, &mut event_queue); - let env_handle = env.clone(); - let output_handler = move |output: WlOutput, info: &OutputInfo, bar_state: &mut BarState| { - if info.obsolete { - info!("Output {} removed", info.id); - output.release(); - } else { - info!("Output {} detected", info.id); - bar_state.add_surface(&output, info.id, &env_handle); - } - }; - - // Process currently existing outputs - for output in env.get_all_outputs() { - if let Some(info) = with_output_info(&output, Clone::clone) { - output_handler(output, &info, &mut bar_state); - } - } + let async_fd = AsyncFd::with_interest( + event_queue.prepare_read()?.connection_fd(), + Interest::READABLE, + )?; - // Setup a listener for changes - // The listener will live for as long as we keep this handle alive - let _listner_handle = env.listen_for_outputs(move |output, info, mut data| { - output_handler(output, info, data.get().unwrap()) - }); + event_queue.roundtrip(&mut state)?; + event_queue.flush()?; - // Right now river only supports one seat - let cursor_theme = pointer::ThemeManager::init( - pointer::ThemeSpec::System, - env.require_global(), - env.require_global(), - ); - for seat in env.get_all_seats() { - sctk::seat::with_seat_data(&seat, |seat_data| { - if seat_data.has_pointer && !seat_data.defunct { - let pointer = seat.get_pointer(); - let themed_pointer = cursor_theme.theme_pointer(pointer.detach()); - let seat = seat.clone(); - let mut pos = (0.0, 0.0); - let mut cur_surface = None; - pointer.quick_assign(move |_, event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - match event { - wl_pointer::Event::Enter { - serial, - surface, - surface_x: y, - surface_y: x, - } => { - let _ = themed_pointer.set_cursor("default", Some(serial)); - cur_surface = Some(surface); - pos = (x, y); - } - wl_pointer::Event::Leave { - serial: _, - surface: _, - } => { - cur_surface = None; - } - wl_pointer::Event::Motion { - time: _, - surface_x: x, - surface_y: y, - } => { - pos = (x, y); - } - wl_pointer::Event::Button { - serial: _, - time: _, - button, - state, - } if state == wl_pointer::ButtonState::Pressed => { - if let Some(cur_surface) = &cur_surface { - bar_state.handle_click( - cur_surface, - &seat, - pos.0, - pos.1, - button.into(), - ); - } - } - wl_pointer::Event::Axis { - time: _, - axis, - value, - } if axis == wl_pointer::Axis::VerticalScroll => { - if let Some(cur_surface) = &cur_surface { - bar_state.handle_click( - cur_surface, - &seat, - pos.0, - pos.1, - if value > 0.0 { - PointerBtn::WheelDown - } else { - PointerBtn::WheelUp - }, - ); - } - } - _ => (), + loop { + tokio::select! { + readable = async_fd.readable() => { + readable?.clear_ready(); + event_queue.prepare_read()?.read()?; + event_queue.dispatch_pending(&mut state)?; + event_queue.flush()?; + } + readable = state.wait_for_status_cmd() => { + readable?.clear_ready(); + if let Err(e) = state.notify_available() { + if let Some(mut status_cmd) = state.shared_state.as_mut().unwrap().status_cmd.take() { + let _ = status_cmd.child.kill(); } - }); + state.set_error(e.to_string()); + state.draw_all(); + } + event_queue.flush()?; } - }); - } - - let mut event_loop = calloop::EventLoop::<BarState>::try_new().unwrap(); - - WaylandSource::new(queue) - .quick_insert(event_loop.handle()) - .unwrap(); - - if let Some(cmd) = &bar_state.status_cmd { - cmd.quick_insert(event_loop.handle()); - } - - loop { - if let Err(e) = event_loop.dispatch(None, &mut bar_state) { - bar_state.set_error(e.to_string()); } } } diff --git a/src/river_protocols.rs b/src/river_protocols.rs index c81a7f7..0a2cf91 100644 --- a/src/river_protocols.rs +++ b/src/river_protocols.rs @@ -1,36 +1,4 @@ -pub use generated::client1::*; -pub use generated::client2::*; +#![allow(clippy::enum_variant_names)] -mod generated { - // The generated code tends to trigger a lot of warnings - // so we isolate it into a very permissive module - #![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] - #![allow(non_upper_case_globals, non_snake_case, unused_imports)] - #![allow(clippy::all)] - - pub mod client1 { - // These imports are used by the generated code - use smithay_client_toolkit::reexports::client as wayland_client; - use wayland_client::protocol::*; - use wayland_client::sys; - use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap}; - use wayland_commons::map::{Object, ObjectMetadata}; - use wayland_commons::smallvec; - use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; - use wayland_commons::{Interface, MessageGroup}; - include!(concat!(env!("OUT_DIR"), "/river-status-unstable-v1.rs")); - } - - pub mod client2 { - // These imports are used by the generated code - use smithay_client_toolkit::reexports::client as wayland_client; - use wayland_client::protocol::*; - use wayland_client::sys; - use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap}; - use wayland_commons::map::{Object, ObjectMetadata}; - use wayland_commons::smallvec; - use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; - use wayland_commons::{Interface, MessageGroup}; - include!(concat!(env!("OUT_DIR"), "/river-control-unstable-v1.rs")); - } -} +pub mod control; +pub mod status; diff --git a/src/river_protocols/control.rs b/src/river_protocols/control.rs new file mode 100644 index 0000000..0e7d7e4 --- /dev/null +++ b/src/river_protocols/control.rs @@ -0,0 +1,89 @@ +pub mod protocol { + 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"); +} + +mod dispatch; + +use std::sync::Arc; + +use smithay_client_toolkit::globals::GlobalData; +use wayland_client::{protocol::wl_seat, Connection, Dispatch, QueueHandle}; + +use protocol::{zriver_command_callback_v1, zriver_control_v1}; + +use smithay_client_toolkit::error::GlobalError; +use smithay_client_toolkit::registry::GlobalProxy; + +#[derive(Debug, Clone)] +pub struct RiverControlState { + control: Arc<GlobalProxy<zriver_control_v1::ZriverControlV1>>, +} + +impl RiverControlState { + pub fn new() -> Self { + Self { + control: Arc::new(GlobalProxy::NotReady), + } + } + + pub fn is_available(&self) -> bool { + self.control.get().is_ok() + } + + pub fn control(&self) -> Result<&zriver_control_v1::ZriverControlV1, GlobalError> { + self.control.get() + } + + pub fn run_command<D, Args, Arg>( + &self, + qh: &QueueHandle<D>, + seat: &wl_seat::WlSeat, + args: Args, + ) -> Result<(), GlobalError> + where + D: Dispatch<zriver_control_v1::ZriverControlV1, GlobalData> + + Dispatch<zriver_command_callback_v1::ZriverCommandCallbackV1, RiverCommandCallbackData> + + 'static, + Args: IntoIterator<Item = Arg>, + Arg: Into<String>, + { + let control = self.control()?; + for arg in args { + control.add_argument(arg.into()); + } + control.run_command(seat, qh, RiverCommandCallbackData {})?; + Ok(()) + } +} + +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: ::smithay_client_toolkit::globals::GlobalData, + $crate::river_protocols::control::protocol::zriver_command_callback_v1::ZriverCommandCallbackV1: $crate::river_protocols::control::RiverCommandCallbackData, + ] => $crate::river_protocols::control::RiverControlState); + }; +} diff --git a/src/river_protocols/control/dispatch.rs b/src/river_protocols/control/dispatch.rs new file mode 100644 index 0000000..14759e7 --- /dev/null +++ b/src/river_protocols/control/dispatch.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use super::protocol::{zriver_command_callback_v1, zriver_control_v1}; +use wayland_client::{Connection, Dispatch, QueueHandle}; + +use smithay_client_toolkit::{ + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + registry::{ProvidesRegistryState, RegistryHandler}, +}; + +use super::{RiverCommandCallbackData, RiverControlHandler, RiverControlState}; + +impl<D> RegistryHandler<D> for RiverControlState +where + D: Dispatch<zriver_control_v1::ZriverControlV1, GlobalData> + + RiverControlHandler + + ProvidesRegistryState + + 'static, +{ + fn ready(data: &mut D, _conn: &Connection, qh: &QueueHandle<D>) { + data.river_control_state().control = + Arc::new(data.registry().bind_one(qh, 1..=1, GlobalData).into()); + } +} + +impl ProvidesBoundGlobal<zriver_control_v1::ZriverControlV1, 1> for RiverControlState { + fn bound_global(&self) -> Result<zriver_control_v1::ZriverControlV1, GlobalError> { + self.control.get().cloned() + } +} + +impl<D> Dispatch<zriver_control_v1::ZriverControlV1, GlobalData, D> for RiverControlState +where + D: Dispatch<zriver_control_v1::ZriverControlV1, GlobalData> + RiverControlHandler + 'static, +{ + fn event( + _: &mut D, + _: &zriver_control_v1::ZriverControlV1, + _: zriver_control_v1::Event, + _: &GlobalData, + _: &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), + } + } +} diff --git a/src/river_protocols/status.rs b/src/river_protocols/status.rs new file mode 100644 index 0000000..50c85e0 --- /dev/null +++ b/src/river_protocols/status.rs @@ -0,0 +1,134 @@ +pub mod protocol { + 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"); +} + +mod dispatch; + +use std::sync::{Arc, Weak}; + +use smithay_client_toolkit::globals::GlobalData; +use wayland_client::{protocol::wl_output, Connection, Dispatch, QueueHandle}; + +use protocol::{zriver_output_status_v1, zriver_status_manager_v1}; + +use smithay_client_toolkit::error::GlobalError; +use smithay_client_toolkit::registry::GlobalProxy; + +#[derive(Debug)] +pub struct RiverStatusState { + status_manager: GlobalProxy<zriver_status_manager_v1::ZriverStatusManagerV1>, + output_statuses: Vec<Weak<OutputStatusInner>>, +} + +impl RiverStatusState { + pub fn new() -> Self { + Self { + status_manager: GlobalProxy::NotReady, + output_statuses: Vec::new(), + } + } + + pub fn status_manager( + &self, + ) -> Result<&zriver_status_manager_v1::ZriverStatusManagerV1, GlobalError> { + self.status_manager.get() + } + + pub fn get_output_status( + &self, + status: &zriver_output_status_v1::ZriverOutputStatusV1, + ) -> Option<RiverOutputStatus> { + self.output_statuses + .iter() + .filter_map(Weak::upgrade) + .find(|inner| &inner.status == status) + .map(RiverOutputStatus) + } + + #[must_use = "The output status is destroyed if dropped"] + pub fn new_output_status<D>( + &mut self, + qh: &QueueHandle<D>, + output: &wl_output::WlOutput, + ) -> Result<RiverOutputStatus, GlobalError> + where + D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalData> + + Dispatch<zriver_output_status_v1::ZriverOutputStatusV1, RiverStatusData> + + 'static, + { + let manager = self.status_manager()?; + let output_status = manager.get_river_output_status(output, qh, RiverStatusData {})?; + + let output_status = RiverOutputStatus(Arc::new(OutputStatusInner { + status: output_status, + })); + + self.output_statuses.push(Arc::downgrade(&output_status.0)); + + Ok(output_status) + } +} + +pub trait RiverStatusHandler: Sized { + fn river_status_state(&mut self) -> &mut RiverStatusState; + + fn focused_tags_updated( + &mut self, + conn: &Connection, + qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + focused: u32, + ); + + fn urgent_tags_updated( + &mut self, + conn: &Connection, + qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + urgent: u32, + ); +} + +#[derive(Debug, Clone)] +pub struct RiverOutputStatus(Arc<OutputStatusInner>); + +impl PartialEq for RiverOutputStatus { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +#[derive(Debug)] +pub struct RiverStatusData { + // This is empty right now, but may be populated in the future. +} + +#[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: ::smithay_client_toolkit::globals::GlobalData, + $crate::river_protocols::status::protocol::zriver_output_status_v1::ZriverOutputStatusV1: $crate::river_protocols::status::RiverStatusData, + ] => $crate::river_protocols::status::RiverStatusState); + }; +} + +#[derive(Debug)] +struct OutputStatusInner { + status: zriver_output_status_v1::ZriverOutputStatusV1, +} + +impl Drop for OutputStatusInner { + fn drop(&mut self) { + self.status.destroy(); + } +} diff --git a/src/river_protocols/status/dispatch.rs b/src/river_protocols/status/dispatch.rs new file mode 100644 index 0000000..d38037d --- /dev/null +++ b/src/river_protocols/status/dispatch.rs @@ -0,0 +1,86 @@ +use super::protocol::{zriver_output_status_v1, zriver_status_manager_v1}; +use wayland_client::{Connection, Dispatch, QueueHandle}; + +use smithay_client_toolkit::{ + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + registry::{ProvidesRegistryState, RegistryHandler}, +}; + +use super::{RiverStatusData, RiverStatusHandler, RiverStatusState}; + +impl<D> RegistryHandler<D> for RiverStatusState +where + D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalData> + + RiverStatusHandler + + ProvidesRegistryState + + 'static, +{ + fn ready(data: &mut D, _conn: &Connection, qh: &QueueHandle<D>) { + data.river_status_state().status_manager = + data.registry().bind_one(qh, 1..=2, GlobalData).into(); + } +} + +impl ProvidesBoundGlobal<zriver_status_manager_v1::ZriverStatusManagerV1, 1> for RiverStatusState { + fn bound_global(&self) -> Result<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalError> { + self.status_manager.get().cloned() + } +} + +impl ProvidesBoundGlobal<zriver_status_manager_v1::ZriverStatusManagerV1, 2> for RiverStatusState { + fn bound_global(&self) -> Result<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalError> { + self.status_manager.get().cloned() + } +} + +impl<D> Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalData, D> + for RiverStatusState +where + D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, GlobalData> + + RiverStatusHandler + + 'static, +{ + fn event( + _: &mut D, + _: &zriver_status_manager_v1::ZriverStatusManagerV1, + _: zriver_status_manager_v1::Event, + _: &GlobalData, + _: &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, + status: &zriver_output_status_v1::ZriverOutputStatusV1, + event: zriver_output_status_v1::Event, + _udata: &RiverStatusData, + conn: &Connection, + qh: &QueueHandle<D>, + ) { + use zriver_output_status_v1::Event; + + // Remove any statuses that have been dropped + data.river_status_state() + .output_statuses + .retain(|status| status.upgrade().is_some()); + + if let Some(status) = data.river_status_state().get_output_status(status) { + match event { + Event::FocusedTags { tags } => data.focused_tags_updated(conn, qh, &status, tags), + Event::UrgentTags { tags } => data.urgent_tags_updated(conn, qh, &status, tags), + Event::ViewTags { tags: _ } => (), + } + } + } +} diff --git a/src/shared_state.rs b/src/shared_state.rs new file mode 100644 index 0000000..c4ec9d6 --- /dev/null +++ b/src/shared_state.rs @@ -0,0 +1,18 @@ +use smithay_client_toolkit::{reexports::client::QueueHandle, shm::slot::SlotPool}; + +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 config: Config, + pub status_cmd: Option<StatusCmd>, + pub blocks: Vec<Block>, + pub blocks_cache: Vec<ComputedBlock>, +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..ea5ca4e --- /dev/null +++ b/src/state.rs @@ -0,0 +1,430 @@ +use std::{future::pending, io}; + +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}, + Capability, SeatHandler, SeatState, + }, + shell::layer::{Anchor, Layer, LayerHandler, LayerState, LayerSurface, LayerSurfaceConfigure}, + shm::{slot::SlotPool, ShmHandler, ShmState}, +}; +use tokio::io::unix::AsyncFdReadyGuard; + +use crate::{ + bar::Bar, + config::Config, + delegate_river_control, delegate_river_status, + i3bar_protocol::Block, + pointer_btn::PointerBtn, + river_protocols::{ + control::{RiverControlHandler, RiverControlState}, + status::{RiverOutputStatus, RiverStatusHandler, RiverStatusState}, + }, + shared_state::SharedState, + status_cmd::StatusCmd, + text::ComputedText, +}; + +pub struct State { + registry_state: RegistryState, + seat_state: SeatState, + output_state: OutputState, + compositor_state: CompositorState, + shm_state: ShmState, + layer_state: LayerState, + river_status_state: RiverStatusState, + river_control_state: RiverControlState, + + pointers: Vec<(wl_pointer::WlPointer, wl_seat::WlSeat)>, + bars: Vec<Bar>, + pub shared_state: Option<SharedState>, +} + +impl State { + pub fn new(conn: &Connection, event_queue: &mut EventQueue<Self>) -> Self { + let mut error = Ok(()); + + 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 mut this = Self { + registry_state: RegistryState::new(conn, &event_queue.handle()), + seat_state: SeatState::new(), + output_state: OutputState::new(), + compositor_state: CompositorState::new(), + shm_state: ShmState::new(), + layer_state: LayerState::new(), + river_status_state: RiverStatusState::new(), + river_control_state: RiverControlState::new(), + + pointers: Vec::new(), + bars: Vec::new(), + shared_state: None, + }; + + while !this.registry_state.ready() { + event_queue.blocking_dispatch(&mut this).unwrap(); + } + + this.shared_state = Some(SharedState { + qh: event_queue.handle(), + pool: SlotPool::new(1, &this.shm_state).expect("Failed to create pool"), + config, + status_cmd, + blocks: Vec::new(), + blocks_cache: Vec::new(), + }); + + if let Err(e) = error { + this.set_error(e.to_string()); + } + + this + } + + pub fn set_blocks(&mut self, blocks: Vec<Block>) { + self.shared_state.as_mut().unwrap().blocks = blocks; + self.draw_all(); + } + + pub fn set_error(&mut self, error: impl Into<String>) { + self.set_blocks(vec![Block { + full_text: error.into(), + ..Default::default() + }]); + } + + pub fn notify_available(&mut self) -> anyhow::Result<()> { + if let Some(cmd) = &mut self.shared_state.as_mut().unwrap().status_cmd { + if let Some(blocks) = cmd.notify_available()? { + self.set_blocks(blocks); + } + } + Ok(()) + } + + pub fn draw_all(&mut self) { + for bar in &mut self.bars { + bar.draw(self.shared_state.as_mut().unwrap()); + } + } + + pub async fn wait_for_status_cmd(&self) -> io::Result<AsyncFdReadyGuard<i32>> { + match &self.shared_state.as_ref().unwrap().status_cmd { + Some(cmd) => cmd.async_fd.readable().await, + None => { + pending::<()>().await; + unreachable!() + } + } + } +} + +impl CompositorHandler for State { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn scale_factor_changed( + &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(self.shared_state.as_mut().expect("no shared_state?")); + } + } + + fn frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + _surface: &wl_surface::WlSurface, + _time: u32, + ) { + } +} + +impl OutputHandler for State { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &Connection, + qh: &QueueHandle<Self>, + output: wl_output::WlOutput, + ) { + let height = self.shared_state.as_ref().unwrap().config.height; + let surface = self.compositor_state.create_surface(qh).unwrap(); + let layer = LayerSurface::builder() + .output(&output) + .size((0, height)) + .anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT) + .namespace("i3bar-river") + .map(qh, &mut self.layer_state, surface, Layer::Top) + .expect("layer surface creation"); + let river_output_status = self.river_status_state.new_output_status(qh, &output).ok(); + self.bars.push(Bar { + first_configure: true, + width: 0, + height, + scale: 1, + layer, + blocks_btns: Default::default(), + river_output_status, + river_control: self.river_control_state.clone(), + tags_btns: Default::default(), + tags_info: Default::default(), + tags_computed: Vec::new(), + }); + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + _output: wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + _output: wl_output::WlOutput, + ) { + } +} + +impl LayerHandler for State { + fn layer_state(&mut self) -> &mut LayerState { + &mut self.layer_state + } + + fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, layer: &LayerSurface) { + self.bars.retain(|b| &b.layer != layer) + } + + 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(self.shared_state.as_mut().unwrap(), configure.new_size.0); + } +} + +impl SeatHandler for State { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {} + + fn new_capability( + &mut self, + _conn: &Connection, + qh: &QueueHandle<Self>, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer { + let pointer = self + .seat_state + .get_pointer(qh, &seat) + .expect("Failed to create pointer"); + self.pointers.push((pointer, seat)); + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer { + self.pointers.retain(|p| p.1 == seat); + } + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {} +} + +impl PointerHandler for State { + fn pointer_frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + pointer: &wl_pointer::WlPointer, + events: &[PointerEvent], + ) { + let seat = &self.pointers.iter().find(|p| &p.0 == pointer).unwrap().1; + 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 { .. } => { + // TODO: set_cursor() + } + PointerEventKind::Press { button, .. } => { + bar.click( + self.shared_state.as_mut().unwrap(), + button.into(), + seat, + event.position.0, + event.position.1, + ) + .unwrap(); + } + PointerEventKind::Axis { vertical, .. } => { + bar.click( + self.shared_state.as_mut().unwrap(), + if vertical.discrete > 0 { + PointerBtn::WheelDown + } else { + PointerBtn::WheelUp + }, + seat, + event.position.0, + event.position.1, + ) + .unwrap(); + } + _ => (), + } + } + } + } +} + +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 { + &mut self.river_status_state + } + + fn focused_tags_updated( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + focused: u32, + ) { + let bar = self + .bars + .iter_mut() + .find(|b| b.river_output_status.as_ref() == Some(output_status)) + .unwrap(); + bar.tags_info.focused = focused; + bar.draw(self.shared_state.as_mut().unwrap()); + } + + fn urgent_tags_updated( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + urgent: u32, + ) { + let bar = self + .bars + .iter_mut() + .find(|b| b.river_output_status.as_ref() == Some(output_status)) + .unwrap(); + bar.tags_info.urgent = urgent; + bar.draw(self.shared_state.as_mut().unwrap()); + } +} + +impl RiverControlHandler for State { + fn river_control_state(&mut self) -> &mut RiverControlState { + &mut self.river_control_state + } + + 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}"); + } +} + +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); + +impl ProvidesRegistryState for State { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![ + CompositorState, + OutputState, + ShmState, + SeatState, + LayerState, + RiverStatusState, + RiverControlState, + ]; +} + +#[derive(Debug)] +pub struct ComputedBlock { + pub block: Block, + pub full: ComputedText, + pub short: Option<ComputedText>, + pub min_width: Option<f64>, +} diff --git a/src/status_cmd.rs b/src/status_cmd.rs index a229200..4d9c453 100644 --- a/src/status_cmd.rs +++ b/src/status_cmd.rs @@ -1,18 +1,22 @@ -use std::io::{BufRead, BufReader, Result, Write}; +use std::io::{BufRead, BufReader, Write}; use std::os::unix::prelude::AsRawFd; use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio}; -use smithay_client_toolkit::reexports::calloop::{self, LoopHandle}; +use anyhow::Result; + +use tokio::io::unix::AsyncFd; +use tokio::io::Interest; use crate::i3bar_protocol::{Block, Event, Protocol}; -use crate::BarState; +#[derive(Debug)] pub struct StatusCmd { - child: Child, + pub child: Child, output: BufReader<ChildStdout>, input: ChildStdin, protocol: Protocol, buf: Vec<u8>, + pub async_fd: AsyncFd<i32>, } impl StatusCmd { @@ -24,17 +28,22 @@ 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), input, protocol: Protocol::Unknown, buf: Vec::new(), + async_fd, }) } 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() { @@ -57,35 +66,35 @@ impl StatusCmd { pub fn send_click_event(&mut self, event: &Event) -> Result<()> { if self.protocol.supports_clicks() { - writeln!(self.input, "{}", serde_json::to_string(event).unwrap(),)?; + 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"); - } + // 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"); + // } } |
