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