diff options
| -rw-r--r-- | .github/workflows/rust.yml | 8 | ||||
| -rw-r--r-- | Cargo.lock | 430 | ||||
| -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 | 14 | ||||
| -rw-r--r-- | src/i3bar_protocol.rs | 3 | ||||
| -rw-r--r-- | src/main.rs | 200 | ||||
| -rw-r--r-- | src/river_protocols.rs | 38 | ||||
| -rw-r--r-- | src/river_protocols/control.rs | 112 | ||||
| -rw-r--r-- | src/river_protocols/status.rs | 197 | ||||
| -rw-r--r-- | src/shared_state.rs | 30 | ||||
| -rw-r--r-- | src/state.rs | 479 | ||||
| -rw-r--r-- | src/status_cmd.rs | 71 | ||||
| -rw-r--r-- | src/tags.rs | 20 | ||||
| -rw-r--r-- | src/text.rs | 117 |
18 files changed, 1684 insertions, 1151 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: @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "atty" @@ -42,22 +42,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cairo-rs" -version = "0.15.12" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +checksum = "08f9ee4a4ca9239c9a839453dce04b7ddee2f859ec4cd7acd1f5703b68db549c" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", + "once_cell", "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +checksum = "5119ea655ec777b523f0b57279e70f8a4542f61b0e98a48f892b4ef043fd4c5d" dependencies = [ "glib-sys", "libc", @@ -65,19 +66,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" @@ -136,9 +124,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" dependencies = [ "atty", "humantime", @@ -149,24 +137,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -174,18 +162,36 @@ dependencies = [ ] [[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", @@ -204,16 +210,51 @@ dependencies = [ ] [[package]] +name = "gio" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1debf8d0315d69be0153aa76249db3c858ef69b7778ad3cc669e6d370c485" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da1bba9d3f2ab13a6e9932c40f240dc99ebc9f0bdc35cfb130d1a3df36f374c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] name = "glib" -version = "0.15.12" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +checksum = "d5abffa711471e015eb93d65d6ea20e7e9f6f7951fc0a1042280439319b2de06" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", @@ -225,9 +266,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "e195c1311fa6b04d7b896ea39385f6bd60ef5d25bf74a7c11c8c3f94f6c1a572" dependencies = [ "anyhow", "heck", @@ -240,9 +281,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.15.10" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +checksum = "b33357bb421a77bd849f6a0bfcaf3b4b256a2577802971bb5dd522d530f27021" dependencies = [ "libc", "system-deps", @@ -250,9 +291,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.15.10" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +checksum = "63ca11a57400f3d4fda594e002844be47900c9fb8b29e2155c6e37a1f24e51b3" dependencies = [ "glib-sys", "libc", @@ -293,16 +334,28 @@ dependencies = [ "serde", "serde_json", "smithay-client-toolkit", + "tokio", "toml", - "wayland-commons", + "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", +] + +[[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 +365,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "libloading" @@ -343,9 +396,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -366,24 +419,24 @@ 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]] name = "nix" -version = "0.24.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" dependencies = [ + "autocfg", "bitflags", "cfg-if", "libc", @@ -402,17 +455,18 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "pango" -version = "0.15.10" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +checksum = "7208c60f224cf6e44c551df5ee2ef38f9da0fd29d7c5a0402000b8ab0520e798" dependencies = [ "bitflags", + "gio", "glib", "libc", "once_cell", @@ -421,9 +475,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.10" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +checksum = "922441c228366ed98d3534b87bc7c987c50564094c3abbc3513717786419252d" dependencies = [ "glib-sys", "gobject-sys", @@ -433,9 +487,9 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7876a45c1f1d1a75a2601dc6d9ef2cb5a8be0e3d76f909d82450759929035366" +checksum = "a726760a29e5e17d965d5d7573548e385ae9ce99cb7adcf70d75d4627f23fc7a" dependencies = [ "bitflags", "cairo-rs", @@ -447,9 +501,9 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cf746594916c81d5f739af9335c5f55a1f4606d80b3e1d821f18cf95a29494" +checksum = "b8281198ea4596bdc6e22b0d3565471c9456d7125e423a785f866640c18b57ec" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -478,10 +532,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 +567,36 @@ 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 = "quick-xml" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +dependencies = [ + "memchr", +] + +[[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 +631,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 +643,18 @@ checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "serde" -version = "1.0.139" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -599,9 +663,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -610,17 +674,11 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "slotmap" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ - "version_check", + "autocfg", ] [[package]] @@ -632,27 +690,39 @@ 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#e5d7c9084f8d80bef4c625c6ef52ec42dde4e211" 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", + "wayland-scanner", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", ] [[package]] name = "syn" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" dependencies = [ "proc-macro2", "quote", @@ -683,18 +753,45 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +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 +809,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" - -[[package]] -name = "vec_map" -version = "0.8.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "version-compare" @@ -741,75 +832,91 @@ 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.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +checksum = "f3f96c52ca34b33e6bb55497327428be54dad648973aa5d3d1e02982d2fcc298" dependencies = [ - "bitflags", + "cc", "downcast-rs", - "libc", - "nix 0.22.3", + "io-lifetimes", + "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.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +checksum = "0f6ab5911cc8a28e9d781933c1afe0f97ffa6885ad8941294784d025f06e5ad9" 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.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +checksum = "fa5ce1bbc13a3d806648974cba1d53481fea2ac29483eb147e4cce1250fc5263" dependencies = [ - "nix 0.22.3", + "nix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.4" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +checksum = "e32104ba51988754f4429dbb59c7b7fd2ccf1feaa0009bb59e49a5e2f91e985d" dependencies = [ "bitflags", + "wayland-backend", "wayland-client", - "wayland-commons", + "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", ] [[package]] name = "wayland-scanner" -version = "0.29.4" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +checksum = "1fb55ca28710dcc155078db4e2bd29aacf24bd311261c48750f38c6c5430bf1f" dependencies = [ "proc-macro2", + "quick-xml", "quote", - "xml-rs", + "syn", ] [[package]] name = "wayland-sys" -version = "0.29.4" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +checksum = "882bda56e8397384ad66ec0ebe763e0b3b53153a6327344f7d74b85981a271e1" dependencies = [ "dlib", - "lazy_static", + "log", "pkg-config", ] @@ -845,6 +952,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" @@ -852,9 +1002,3 @@ checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" dependencies = [ "nom", ] - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" @@ -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.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" + +[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..dfa63a3 --- /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 configured: 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: Option<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 && 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() + } + + 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 let Some(river_control) = &self.river_control { + let cmd = match button { + PointerBtn::Left => "set-focused-tags", + PointerBtn::Right => "toggle-focused-tags", + _ => return Ok(()), + }; + 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.configured { + 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 + .get_pool(height as usize * stride as usize) + .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 + cairo_ctx.save().unwrap(); + cairo_ctx.set_operator(cairo::Operator::Source); + ss.config.background.apply(&cairo_ctx); + cairo_ctx.paint().unwrap(); + cairo_ctx.restore().unwrap(); + + // Display tags + let mut offset_left = 0.0; + if self.has_tags_provider() { + if self.tags_computed.is_empty() { + // TODO make configurable + for text in ["1", "2", "3", "4", "5", "6", "7", "8", "9"] { + self.tags_computed + .push(compute_tag_label(text, &ss.config.font, &cairo_ctx)); + } + } + self.tags_btns.clear(); + for (i, label) in self.tags_computed.iter().enumerate() { + let state = self.tags_info.get_state(i); + let (bg, fg) = match state { + TagState::Urgent => (ss.config.tag_urgent_bg, ss.config.tag_urgent_fg), + TagState::Focused => (ss.config.tag_focused_bg, ss.config.tag_focused_fg), + TagState::Active => (ss.config.tag_bg, ss.config.tag_fg), + TagState::Inactive => { + if ss.config.hide_inactive_tags { + continue; + } + (ss.config.tag_inactive_bg, ss.config.tag_inactive_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, + }, + ); + self.tags_btns.push(offset_left, label.width, i); + 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::ComputedText::new( + &block.full_text, + text::Attributes { + font: &config.font, + padding_left: 0.0, + padding_right: 0.0, + min_width, + align: block.align.unwrap_or_default(), + markup, + }, + 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::ComputedText::new( + short_text, + text::Attributes { + font: &config.font, + padding_left: 0.0, + padding_right: 0.0, + min_width, + align: block.align.unwrap_or_default(), + markup, + }, + 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..2af9af0 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, @@ -20,6 +20,8 @@ pub struct Config { pub tag_focused_bg: Color, pub tag_urgent_fg: Color, pub tag_urgent_bg: Color, + pub tag_inactive_fg: Color, + pub tag_inactive_bg: Color, // font and size pub font: Font, pub height: u32, @@ -29,6 +31,9 @@ pub struct Config { pub blocks_overlap: f64, // command pub command: Option<String>, + // misc + pub hide_inactive_tags: bool, + pub invert_touchpad_scrolling: bool, } impl Default for Config { @@ -44,6 +49,8 @@ impl Default for Config { tag_focused_bg: Color::from_rgba_hex(0x689d68ff), tag_urgent_fg: Color::from_rgba_hex(0x282828ff), tag_urgent_bg: Color::from_rgba_hex(0xcc241dff), + tag_inactive_fg: Color::from_rgba_hex(0xd79921ff), + tag_inactive_bg: Color::from_rgba_hex(0x282828ff), font: Font::new("monospace 10"), height: 24, separator_width: 2.0, @@ -51,6 +58,8 @@ impl Default for Config { blocks_r: 0.0, blocks_overlap: 0.0, command: None, + hide_inactive_tags: true, + invert_touchpad_scrolling: true, } } } @@ -79,6 +88,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..8ae31aa 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,55 @@ 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 std::os::unix::io::AsRawFd; -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 smithay_client_toolkit::reexports::client::Connection; +use tokio::io::{unix::AsyncFd, Interest}; -fn main() { - env_logger::init(); +use state::State; +use wayland_client::globals::registry_queue_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!"); +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + env_logger::init(); - let mut bar_state = BarState::new(&env); + 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 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); - } - }; + let async_fd = AsyncFd::with_interest( + event_queue.prepare_read()?.connection_fd().as_raw_fd(), + Interest::READABLE, + )?; - // 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); - } - } + event_queue.roundtrip(&mut state)?; - // 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()) - }); - - // 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.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..7cce23d --- /dev/null +++ b/src/river_protocols/control.rs @@ -0,0 +1,112 @@ +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 new file mode 100644 index 0000000..1779719 --- /dev/null +++ b/src/river_protocols/status.rs @@ -0,0 +1,197 @@ +use std::sync::{Arc, Weak}; + +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, + output_statuses: Vec<Weak<OutputStatusInner>>, +} + +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..=1, ())?; + Ok(Self { + status_manager, + output_statuses: Vec::new(), + }) + } + + 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, + ) -> RiverOutputStatus + where + D: Dispatch<zriver_status_manager_v1::ZriverStatusManagerV1, ()> + + Dispatch<zriver_output_status_v1::ZriverOutputStatusV1, RiverStatusData> + + 'static, + { + let output_status = + self.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)); + + 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, + ); + + fn views_tags_updated( + &mut self, + conn: &Connection, + qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + tags: Vec<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: () + ] => $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)] +struct OutputStatusInner { + status: zriver_output_status_v1::ZriverOutputStatusV1, +} + +impl Drop for OutputStatusInner { + fn drop(&mut self) { + self.status.destroy(); + } +} + +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, + 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 } => { + let tags = tags + .chunks_exact(4) + .map(|bytes| u32::from_ne_bytes(bytes.try_into().unwrap())) + .collect(); + data.views_tags_updated(conn, qh, &status, tags); + } + } + } + } +} + +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 new file mode 100644 index 0000000..554d856 --- /dev/null +++ b/src/shared_state.rs @@ -0,0 +1,30 @@ +use smithay_client_toolkit::{ + reexports::client::QueueHandle, + shm::{slot::SlotPool, ShmState}, +}; + +use crate::{ + config::Config, + i3bar_protocol::Block, + state::{ComputedBlock, State}, + status_cmd::StatusCmd, +}; + +#[derive(Debug)] +pub struct SharedState { + pub qh: QueueHandle<State>, + pub shm_state: ShmState, + pub pool: Option<SlotPool>, + pub config: Config, + pub status_cmd: Option<StatusCmd>, + pub blocks: Vec<Block>, + pub blocks_cache: Vec<ComputedBlock>, +} + +impl SharedState { + pub fn get_pool(&mut self, len: usize) -> &mut SlotPool { + self.pool.get_or_insert_with(|| { + SlotPool::new(len, &self.shm_state).expect("Failed to create pool") + }) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..ed6c3d1 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,479 @@ +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, ThemeSpec, ThemedPointer}, + Capability, SeatHandler, SeatState, + }, + shell::layer::{ + Anchor, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, + }, + shm::{ShmHandler, ShmState}, +}; +use tokio::io::unix::AsyncFdReadyGuard; +use wayland_client::globals::GlobalList; + +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, + layer_state: LayerShell, + river_status_state: Option<RiverStatusState>, + river_control_state: Option<RiverControlState>, + + 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, +} + +impl State { + pub fn new(event_queue: &mut EventQueue<Self>, globals: &GlobalList) -> 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 mut this = Self { + registry_state: RegistryState::new(globals), + 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(), + + seats: Vec::new(), + bars: Vec::new(), + shared_state: SharedState { + qh: event_queue.handle(), + shm_state: ShmState::bind(globals, &qh).unwrap(), + pool: None, + 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.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.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(&mut self.shared_state); + } + } + + 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, + None => { + pending::<()>().await; + unreachable!() + } + } + } +} + +impl CompositorHandler for 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(&mut self.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.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, &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)); + self.bars.push(Bar { + configured: false, + 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 LayerShellHandler for 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(&mut self.shared_state, 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, 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).unwrap(), // TODO: make a PR to remove this unwrap + finger_scroll: 0.0, + }); + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer { + self.seats.retain(|p| p.seat == 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 + .seats + .iter_mut() + .find(|p| &p.pointer == 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.shared_state.shm_state.wl_shm(), + &seat.pointer_surface, + ) + .unwrap(); + } + PointerEventKind::Press { button, .. } => { + bar.click( + &mut self.shared_state, + button.into(), + &seat.seat, + event.position.0, + event.position.1, + ) + .unwrap(); + } + 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; + }; + + bar.click( + &mut self.shared_state, + btn, + &seat.seat, + event.position.0, + event.position.1, + ) + .unwrap(); + } + _ => (), + } + } + } + } +} + +impl ShmHandler for State { + fn shm_state(&mut self) -> &mut ShmState { + &mut self.shared_state.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, + _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(&mut self.shared_state); + } + + 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(&mut self.shared_state); + } + + fn views_tags_updated( + &mut self, + _conn: &Connection, + _qh: &QueueHandle<Self>, + output_status: &RiverOutputStatus, + tags: Vec<u32>, + ) { + let bar = self + .bars + .iter_mut() + .find(|b| b.river_output_status.as_ref() == Some(output_status)) + .unwrap(); + bar.tags_info.active = tags.into_iter().fold(0, |a, b| a | b); + bar.draw(&mut self.shared_state); + } +} + +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}"); + } +} + +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![OutputState,]; +} + +#[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"); + // } } diff --git a/src/tags.rs b/src/tags.rs index 8114a74..d22853d 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,19 +1,21 @@ -use crate::text::{Attributes, ComputedText, Text}; +use crate::text::{Attributes, ComputedText}; use cairo::Context; use pango::FontDescription; use pangocairo::{cairo, pango}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TagState { + Urgent, Focused, + Active, Inactive, - Urgent, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct TagsInfo { pub focused: u32, pub urgent: u32, + pub active: u32, } impl TagsInfo { @@ -22,15 +24,18 @@ impl TagsInfo { TagState::Urgent } else if self.focused >> tag & 1 == 1 { TagState::Focused + } else if self.active >> tag & 1 == 1 { + TagState::Active } else { TagState::Inactive } } } -pub fn compute_tag_label(label: String, font: FontDescription, context: &Context) -> ComputedText { - let text = Text { - attr: Attributes { +pub fn compute_tag_label(label: &str, font: &FontDescription, context: &Context) -> ComputedText { + ComputedText::new( + label, + Attributes { font, padding_left: 25.0, padding_right: 25.0, @@ -38,7 +43,6 @@ pub fn compute_tag_label(label: String, font: FontDescription, context: &Context align: Default::default(), markup: false, }, - text: label, - }; - text.compute(context) + context, + ) } diff --git a/src/text.rs b/src/text.rs index 60ed707..9b27a11 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,9 +1,8 @@ use crate::color::Color; -use pango::{EllipsizeMode, FontDescription}; +use pango::FontDescription; use pangocairo::{cairo, pango}; use serde::Deserialize; use std::f64::consts::{FRAC_PI_2, PI, TAU}; -use std::ops::Deref; #[derive(Clone, Debug, PartialEq)] pub struct RenderOptions { @@ -17,8 +16,8 @@ pub struct RenderOptions { } #[derive(Clone, Debug, PartialEq)] -pub struct Attributes { - pub font: FontDescription, +pub struct Attributes<'a> { + pub font: &'a FontDescription, pub padding_left: f64, pub padding_right: f64, pub min_width: Option<f64>, @@ -40,96 +39,55 @@ impl Default for Align { } } -fn create_pango_layout(cairo_context: &cairo::Context) -> pango::Layout { - pangocairo::create_layout(cairo_context).unwrap() -} - -fn show_pango_layout(cairo_context: &cairo::Context, layout: &pango::Layout) { - pangocairo::show_layout(cairo_context, layout); -} - #[derive(Clone, Debug, PartialEq)] -pub struct Text { - pub attr: Attributes, - pub text: String, +pub struct ComputedText { + pub width: f64, + layout: pango::Layout, + height: f64, + padding_left: f64, } -impl Text { - pub fn compute(mut self, context: &cairo::Context) -> ComputedText { - let (mut width, height) = { - let layout = create_pango_layout(context); - layout.set_font_description(Some(&self.attr.font)); - if self.attr.markup { - layout.set_markup(&self.text); - } else { - layout.set_text(&self.text); - } +impl ComputedText { + pub fn new(text: &str, mut attr: Attributes, context: &cairo::Context) -> Self { + let layout = pangocairo::create_layout(context); + layout.set_font_description(Some(attr.font)); + if attr.markup { + layout.set_markup(text); + } else { + layout.set_text(text); + } - let (text_width, text_height) = layout.pixel_size(); - let width = f64::from(text_width) + self.attr.padding_right + self.attr.padding_right; - let height = f64::from(text_height); - (width, height) - }; + let (text_width, text_height) = layout.pixel_size(); + let mut width = f64::from(text_width) + attr.padding_right + attr.padding_right; + let height = f64::from(text_height); - if let Some(min_width) = self.attr.min_width { + if let Some(min_width) = attr.min_width { if width < min_width { let d = min_width - width; width = min_width; - match self.attr.align { - Align::Right => self.attr.padding_left += d, - Align::Left => self.attr.padding_right += d, + match attr.align { + Align::Right => attr.padding_left += d, + Align::Left => attr.padding_right += d, Align::Center => { - self.attr.padding_left += d * 0.5; - self.attr.padding_right += d * 0.5; + attr.padding_left += d * 0.5; + attr.padding_right += d * 0.5; } } } } - ComputedText { - text: self, + Self { width, + layout, height, + padding_left: attr.padding_left, } } -} -#[derive(Clone, Debug, PartialEq)] -pub struct ComputedText { - pub text: Text, - pub width: f64, - pub height: f64, -} - -impl Deref for ComputedText { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.text.text - } -} - -impl ComputedText { pub fn render(&self, context: &cairo::Context, options: RenderOptions) { - let text = &self.text; - let layout = create_pango_layout(context); - layout.set_font_description(Some(&text.attr.font)); - if text.attr.markup { - layout.set_markup(&text.text); - } else { - layout.set_text(&text.text); - } - context.save().unwrap(); context.translate(options.x_offset - options.overlap, 0.0); - // Set the width/height on the Pango layout so that it word-wraps/ellipises. - let text_width = self.width - text.attr.padding_left - text.attr.padding_right; - let text_height = self.height; - layout.set_ellipsize(EllipsizeMode::End); - layout.set_width(text_width as i32 * pango::SCALE); - layout.set_height(text_height as i32 * pango::SCALE); - // Draw background if let Some(bg) = options.bg_color { bg.apply(context); @@ -147,10 +105,10 @@ impl ComputedText { options.fg_color.apply(context); context.translate( - text.attr.padding_left + options.overlap, + self.padding_left + options.overlap, (options.bar_height - self.height) * 0.5, ); - show_pango_layout(context, &layout); + pangocairo::show_layout(context, &self.layout); context.restore().unwrap(); } } @@ -177,16 +135,17 @@ fn rounded_rectangle( } pub fn width_of(text: &str, context: &cairo::Context, markup: bool, font: &FontDescription) -> f64 { - let text = Text { - text: text.into(), - attr: Attributes { - font: font.clone(), + ComputedText::new( + text, + Attributes { + font, padding_left: 0.0, padding_right: 0.0, min_width: None, align: Default::default(), markup, }, - }; - text.compute(context).width + context, + ) + .width } |
