summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Verevkin <34583604+MaxVerevkin@users.noreply.github.com>2022-11-27 21:25:15 +0200
committerGitHub <noreply@github.com>2022-11-27 21:25:15 +0200
commitd28693c9d293ea7c348c0e9e1a4e89ccff6dcae4 (patch)
tree0794e56251e223388463a84d09616ef47a918bb4
parent9ddde2d2ee9833412e39afbd1ed907cdea22f08f (diff)
parentd45a4ef408c0f5a2071194d4c3285f0b7cb27dd0 (diff)
downloadi3bar-mango-d28693c9d293ea7c348c0e9e1a4e89ccff6dcae4.tar.gz
Merge pull request #1 from MaxVerevkin/next
Rewrite using latest walayd libs & implement HiDPI
-rw-r--r--.github/workflows/rust.yml8
-rw-r--r--Cargo.lock430
-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.rs14
-rw-r--r--src/i3bar_protocol.rs3
-rw-r--r--src/main.rs200
-rw-r--r--src/river_protocols.rs38
-rw-r--r--src/river_protocols/control.rs112
-rw-r--r--src/river_protocols/status.rs197
-rw-r--r--src/shared_state.rs30
-rw-r--r--src/state.rs479
-rw-r--r--src/status_cmd.rs71
-rw-r--r--src/tags.rs20
-rw-r--r--src/text.rs117
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:
diff --git a/Cargo.lock b/Cargo.lock
index 95ab4c4..15e4189 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 5a2097f..40a46bc 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.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
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..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
}