├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── default_config.toml ├── screenshots ├── bar.png └── full.png ├── src ├── config.rs ├── main.rs ├── tests │ ├── issue_50.rs │ └── mod.rs └── window_manager.rs └── workstyle.service /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ main ] 4 | pull_request: 5 | branches: [ main ] 6 | 7 | name: Rust 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | 24 | test: 25 | name: Test Suite 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: stable 33 | override: true 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: test 37 | 38 | fmt: 39 | name: Rustfmt 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions-rs/toolchain@v1 44 | with: 45 | profile: minimal 46 | toolchain: stable 47 | override: true 48 | - run: rustup component add rustfmt 49 | - uses: actions-rs/cargo@v1 50 | with: 51 | command: fmt 52 | args: --all -- --check 53 | 54 | clippy: 55 | name: Clippy 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | profile: minimal 62 | toolchain: stable 63 | override: true 64 | - run: rustup component add clippy 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: clippy 68 | args: -- -D warnings 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.3.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is-terminal", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 49 | dependencies = [ 50 | "windows-sys 0.48.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "1.0.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.48.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.70" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" 68 | 69 | [[package]] 70 | name = "async-trait" 71 | version = "0.1.68" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn 2.0.15", 78 | ] 79 | 80 | [[package]] 81 | name = "atty" 82 | version = "0.2.14" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 85 | dependencies = [ 86 | "hermit-abi 0.1.19", 87 | "libc", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "autocfg" 93 | version = "1.1.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 96 | 97 | [[package]] 98 | name = "bitflags" 99 | version = "1.3.2" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 102 | 103 | [[package]] 104 | name = "bytes" 105 | version = "1.4.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 108 | 109 | [[package]] 110 | name = "cc" 111 | version = "1.0.79" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "clap" 123 | version = "4.2.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" 126 | dependencies = [ 127 | "clap_builder", 128 | "clap_derive", 129 | "once_cell", 130 | ] 131 | 132 | [[package]] 133 | name = "clap_builder" 134 | version = "4.2.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" 137 | dependencies = [ 138 | "anstream", 139 | "anstyle", 140 | "bitflags", 141 | "clap_lex", 142 | "strsim", 143 | ] 144 | 145 | [[package]] 146 | name = "clap_derive" 147 | version = "4.2.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" 150 | dependencies = [ 151 | "heck", 152 | "proc-macro2", 153 | "quote", 154 | "syn 2.0.15", 155 | ] 156 | 157 | [[package]] 158 | name = "clap_lex" 159 | version = "0.4.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" 162 | 163 | [[package]] 164 | name = "colorchoice" 165 | version = "1.0.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 168 | 169 | [[package]] 170 | name = "convert_case" 171 | version = "0.4.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 174 | 175 | [[package]] 176 | name = "derive_more" 177 | version = "0.99.17" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 180 | dependencies = [ 181 | "convert_case", 182 | "proc-macro2", 183 | "quote", 184 | "rustc_version", 185 | "syn 1.0.109", 186 | ] 187 | 188 | [[package]] 189 | name = "dirs" 190 | version = "3.0.2" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" 193 | dependencies = [ 194 | "dirs-sys", 195 | ] 196 | 197 | [[package]] 198 | name = "dirs-sys" 199 | version = "0.3.7" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 202 | dependencies = [ 203 | "libc", 204 | "redox_users", 205 | "winapi", 206 | ] 207 | 208 | [[package]] 209 | name = "doc-comment" 210 | version = "0.3.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 213 | 214 | [[package]] 215 | name = "either" 216 | version = "1.8.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 219 | 220 | [[package]] 221 | name = "env_logger" 222 | version = "0.9.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 225 | dependencies = [ 226 | "atty", 227 | "humantime", 228 | "log", 229 | "regex", 230 | "termcolor", 231 | ] 232 | 233 | [[package]] 234 | name = "errno" 235 | version = "0.3.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 238 | dependencies = [ 239 | "errno-dragonfly", 240 | "libc", 241 | "windows-sys 0.48.0", 242 | ] 243 | 244 | [[package]] 245 | name = "errno-dragonfly" 246 | version = "0.1.2" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 249 | dependencies = [ 250 | "cc", 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "futures" 256 | version = "0.3.28" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 259 | dependencies = [ 260 | "futures-channel", 261 | "futures-core", 262 | "futures-executor", 263 | "futures-io", 264 | "futures-sink", 265 | "futures-task", 266 | "futures-util", 267 | ] 268 | 269 | [[package]] 270 | name = "futures-channel" 271 | version = "0.3.28" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 274 | dependencies = [ 275 | "futures-core", 276 | "futures-sink", 277 | ] 278 | 279 | [[package]] 280 | name = "futures-core" 281 | version = "0.3.28" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 284 | 285 | [[package]] 286 | name = "futures-executor" 287 | version = "0.3.28" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 290 | dependencies = [ 291 | "futures-core", 292 | "futures-task", 293 | "futures-util", 294 | ] 295 | 296 | [[package]] 297 | name = "futures-io" 298 | version = "0.3.28" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 301 | 302 | [[package]] 303 | name = "futures-macro" 304 | version = "0.3.28" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 307 | dependencies = [ 308 | "proc-macro2", 309 | "quote", 310 | "syn 2.0.15", 311 | ] 312 | 313 | [[package]] 314 | name = "futures-sink" 315 | version = "0.3.28" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 318 | 319 | [[package]] 320 | name = "futures-task" 321 | version = "0.3.28" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 324 | 325 | [[package]] 326 | name = "futures-util" 327 | version = "0.3.28" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 330 | dependencies = [ 331 | "futures-channel", 332 | "futures-core", 333 | "futures-io", 334 | "futures-macro", 335 | "futures-sink", 336 | "futures-task", 337 | "memchr", 338 | "pin-project-lite", 339 | "pin-utils", 340 | "slab", 341 | ] 342 | 343 | [[package]] 344 | name = "getrandom" 345 | version = "0.2.9" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 348 | dependencies = [ 349 | "cfg-if", 350 | "libc", 351 | "wasi", 352 | ] 353 | 354 | [[package]] 355 | name = "hashbrown" 356 | version = "0.12.3" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 359 | 360 | [[package]] 361 | name = "heck" 362 | version = "0.4.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 365 | 366 | [[package]] 367 | name = "hermit-abi" 368 | version = "0.1.19" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 371 | dependencies = [ 372 | "libc", 373 | ] 374 | 375 | [[package]] 376 | name = "hermit-abi" 377 | version = "0.2.6" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 380 | dependencies = [ 381 | "libc", 382 | ] 383 | 384 | [[package]] 385 | name = "hermit-abi" 386 | version = "0.3.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 389 | 390 | [[package]] 391 | name = "hex" 392 | version = "0.4.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 395 | 396 | [[package]] 397 | name = "humantime" 398 | version = "2.1.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 401 | 402 | [[package]] 403 | name = "hyprland" 404 | version = "0.3.12" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "dfa94ae7899f3f1bdcad21dcd50366a60f323375a25610889246f276d7f9fb06" 407 | dependencies = [ 408 | "async-trait", 409 | "derive_more", 410 | "doc-comment", 411 | "futures", 412 | "hex", 413 | "hyprland-macros", 414 | "lazy_static", 415 | "num-traits", 416 | "paste", 417 | "regex", 418 | "serde", 419 | "serde_json", 420 | "serde_repr", 421 | "strum", 422 | "tokio", 423 | ] 424 | 425 | [[package]] 426 | name = "hyprland-macros" 427 | version = "0.3.4" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "c941d3d52e979612af8cb94e8de49000c7fada2014a7791d173ab41339f4e4eb" 430 | dependencies = [ 431 | "quote", 432 | "syn 2.0.15", 433 | ] 434 | 435 | [[package]] 436 | name = "indexmap" 437 | version = "1.9.3" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 440 | dependencies = [ 441 | "autocfg", 442 | "hashbrown", 443 | ] 444 | 445 | [[package]] 446 | name = "io-lifetimes" 447 | version = "1.0.10" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 450 | dependencies = [ 451 | "hermit-abi 0.3.1", 452 | "libc", 453 | "windows-sys 0.48.0", 454 | ] 455 | 456 | [[package]] 457 | name = "is-terminal" 458 | version = "0.4.7" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 461 | dependencies = [ 462 | "hermit-abi 0.3.1", 463 | "io-lifetimes", 464 | "rustix", 465 | "windows-sys 0.48.0", 466 | ] 467 | 468 | [[package]] 469 | name = "itertools" 470 | version = "0.10.5" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 473 | dependencies = [ 474 | "either", 475 | ] 476 | 477 | [[package]] 478 | name = "itoa" 479 | version = "1.0.6" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 482 | 483 | [[package]] 484 | name = "lazy_static" 485 | version = "1.4.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 488 | 489 | [[package]] 490 | name = "libc" 491 | version = "0.2.141" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" 494 | 495 | [[package]] 496 | name = "linux-raw-sys" 497 | version = "0.3.1" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" 500 | 501 | [[package]] 502 | name = "lock_api" 503 | version = "0.4.9" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 506 | dependencies = [ 507 | "autocfg", 508 | "scopeguard", 509 | ] 510 | 511 | [[package]] 512 | name = "lockfile" 513 | version = "0.3.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "0adecf427f5b6ac140a0d1c5bc9dd0eb81b6f462486b78402c0a689a317b55dc" 516 | dependencies = [ 517 | "log", 518 | ] 519 | 520 | [[package]] 521 | name = "log" 522 | version = "0.4.17" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 525 | dependencies = [ 526 | "cfg-if", 527 | ] 528 | 529 | [[package]] 530 | name = "memchr" 531 | version = "2.6.3" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 534 | 535 | [[package]] 536 | name = "mio" 537 | version = "0.8.6" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 540 | dependencies = [ 541 | "libc", 542 | "log", 543 | "wasi", 544 | "windows-sys 0.45.0", 545 | ] 546 | 547 | [[package]] 548 | name = "num-traits" 549 | version = "0.2.15" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 552 | dependencies = [ 553 | "autocfg", 554 | ] 555 | 556 | [[package]] 557 | name = "num_cpus" 558 | version = "1.15.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 561 | dependencies = [ 562 | "hermit-abi 0.2.6", 563 | "libc", 564 | ] 565 | 566 | [[package]] 567 | name = "once_cell" 568 | version = "1.17.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 571 | 572 | [[package]] 573 | name = "parking_lot" 574 | version = "0.12.1" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 577 | dependencies = [ 578 | "lock_api", 579 | "parking_lot_core", 580 | ] 581 | 582 | [[package]] 583 | name = "parking_lot_core" 584 | version = "0.9.7" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 587 | dependencies = [ 588 | "cfg-if", 589 | "libc", 590 | "redox_syscall", 591 | "smallvec", 592 | "windows-sys 0.45.0", 593 | ] 594 | 595 | [[package]] 596 | name = "paste" 597 | version = "1.0.12" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 600 | 601 | [[package]] 602 | name = "pin-project-lite" 603 | version = "0.2.9" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 606 | 607 | [[package]] 608 | name = "pin-utils" 609 | version = "0.1.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 612 | 613 | [[package]] 614 | name = "proc-macro2" 615 | version = "1.0.56" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 618 | dependencies = [ 619 | "unicode-ident", 620 | ] 621 | 622 | [[package]] 623 | name = "quote" 624 | version = "1.0.26" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 627 | dependencies = [ 628 | "proc-macro2", 629 | ] 630 | 631 | [[package]] 632 | name = "redox_syscall" 633 | version = "0.2.16" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 636 | dependencies = [ 637 | "bitflags", 638 | ] 639 | 640 | [[package]] 641 | name = "redox_users" 642 | version = "0.4.3" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 645 | dependencies = [ 646 | "getrandom", 647 | "redox_syscall", 648 | "thiserror", 649 | ] 650 | 651 | [[package]] 652 | name = "regex" 653 | version = "1.9.5" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" 656 | dependencies = [ 657 | "aho-corasick", 658 | "memchr", 659 | "regex-automata", 660 | "regex-syntax", 661 | ] 662 | 663 | [[package]] 664 | name = "regex-automata" 665 | version = "0.3.8" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" 668 | dependencies = [ 669 | "aho-corasick", 670 | "memchr", 671 | "regex-syntax", 672 | ] 673 | 674 | [[package]] 675 | name = "regex-syntax" 676 | version = "0.7.5" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 679 | 680 | [[package]] 681 | name = "rustc_version" 682 | version = "0.4.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 685 | dependencies = [ 686 | "semver", 687 | ] 688 | 689 | [[package]] 690 | name = "rustix" 691 | version = "0.37.11" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" 694 | dependencies = [ 695 | "bitflags", 696 | "errno", 697 | "io-lifetimes", 698 | "libc", 699 | "linux-raw-sys", 700 | "windows-sys 0.48.0", 701 | ] 702 | 703 | [[package]] 704 | name = "rustversion" 705 | version = "1.0.12" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" 708 | 709 | [[package]] 710 | name = "ryu" 711 | version = "1.0.13" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 714 | 715 | [[package]] 716 | name = "scopeguard" 717 | version = "1.1.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 720 | 721 | [[package]] 722 | name = "semver" 723 | version = "1.0.17" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" 726 | 727 | [[package]] 728 | name = "serde" 729 | version = "1.0.160" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" 732 | dependencies = [ 733 | "serde_derive", 734 | ] 735 | 736 | [[package]] 737 | name = "serde_derive" 738 | version = "1.0.160" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" 741 | dependencies = [ 742 | "proc-macro2", 743 | "quote", 744 | "syn 2.0.15", 745 | ] 746 | 747 | [[package]] 748 | name = "serde_json" 749 | version = "1.0.96" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 752 | dependencies = [ 753 | "itoa", 754 | "ryu", 755 | "serde", 756 | ] 757 | 758 | [[package]] 759 | name = "serde_repr" 760 | version = "0.1.12" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" 763 | dependencies = [ 764 | "proc-macro2", 765 | "quote", 766 | "syn 2.0.15", 767 | ] 768 | 769 | [[package]] 770 | name = "signal-hook" 771 | version = "0.3.15" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" 774 | dependencies = [ 775 | "libc", 776 | "signal-hook-registry", 777 | ] 778 | 779 | [[package]] 780 | name = "signal-hook-registry" 781 | version = "1.4.1" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 784 | dependencies = [ 785 | "libc", 786 | ] 787 | 788 | [[package]] 789 | name = "slab" 790 | version = "0.4.8" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 793 | dependencies = [ 794 | "autocfg", 795 | ] 796 | 797 | [[package]] 798 | name = "smallvec" 799 | version = "1.10.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 802 | 803 | [[package]] 804 | name = "socket2" 805 | version = "0.4.9" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 808 | dependencies = [ 809 | "libc", 810 | "winapi", 811 | ] 812 | 813 | [[package]] 814 | name = "strsim" 815 | version = "0.10.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 818 | 819 | [[package]] 820 | name = "strum" 821 | version = "0.25.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 824 | dependencies = [ 825 | "strum_macros", 826 | ] 827 | 828 | [[package]] 829 | name = "strum_macros" 830 | version = "0.25.2" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" 833 | dependencies = [ 834 | "heck", 835 | "proc-macro2", 836 | "quote", 837 | "rustversion", 838 | "syn 2.0.15", 839 | ] 840 | 841 | [[package]] 842 | name = "swayipc" 843 | version = "3.0.1" 844 | source = "git+https://github.com/pierrechevalier83/swayipc-rs.git?branch=fix_crash_when_using_without_i3_or_sway#e8eb2d8efba285b577c5e585af203baf9096b85f" 845 | dependencies = [ 846 | "serde", 847 | "serde_json", 848 | "swayipc-types", 849 | ] 850 | 851 | [[package]] 852 | name = "swayipc-types" 853 | version = "1.3.0" 854 | source = "git+https://github.com/pierrechevalier83/swayipc-rs.git?branch=fix_crash_when_using_without_i3_or_sway#e8eb2d8efba285b577c5e585af203baf9096b85f" 855 | dependencies = [ 856 | "serde", 857 | "serde_json", 858 | "thiserror", 859 | ] 860 | 861 | [[package]] 862 | name = "syn" 863 | version = "1.0.109" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 866 | dependencies = [ 867 | "proc-macro2", 868 | "quote", 869 | "unicode-ident", 870 | ] 871 | 872 | [[package]] 873 | name = "syn" 874 | version = "2.0.15" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 877 | dependencies = [ 878 | "proc-macro2", 879 | "quote", 880 | "unicode-ident", 881 | ] 882 | 883 | [[package]] 884 | name = "termcolor" 885 | version = "1.2.0" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 888 | dependencies = [ 889 | "winapi-util", 890 | ] 891 | 892 | [[package]] 893 | name = "thiserror" 894 | version = "1.0.40" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 897 | dependencies = [ 898 | "thiserror-impl", 899 | ] 900 | 901 | [[package]] 902 | name = "thiserror-impl" 903 | version = "1.0.40" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 906 | dependencies = [ 907 | "proc-macro2", 908 | "quote", 909 | "syn 2.0.15", 910 | ] 911 | 912 | [[package]] 913 | name = "tokio" 914 | version = "1.27.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" 917 | dependencies = [ 918 | "autocfg", 919 | "bytes", 920 | "libc", 921 | "mio", 922 | "num_cpus", 923 | "parking_lot", 924 | "pin-project-lite", 925 | "signal-hook-registry", 926 | "socket2", 927 | "tokio-macros", 928 | "windows-sys 0.45.0", 929 | ] 930 | 931 | [[package]] 932 | name = "tokio-macros" 933 | version = "2.0.0" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" 936 | dependencies = [ 937 | "proc-macro2", 938 | "quote", 939 | "syn 2.0.15", 940 | ] 941 | 942 | [[package]] 943 | name = "toml" 944 | version = "0.5.11" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 947 | dependencies = [ 948 | "indexmap", 949 | "serde", 950 | ] 951 | 952 | [[package]] 953 | name = "unicode-ident" 954 | version = "1.0.8" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 957 | 958 | [[package]] 959 | name = "utf8parse" 960 | version = "0.2.1" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 963 | 964 | [[package]] 965 | name = "wasi" 966 | version = "0.11.0+wasi-snapshot-preview1" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 969 | 970 | [[package]] 971 | name = "winapi" 972 | version = "0.3.9" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 975 | dependencies = [ 976 | "winapi-i686-pc-windows-gnu", 977 | "winapi-x86_64-pc-windows-gnu", 978 | ] 979 | 980 | [[package]] 981 | name = "winapi-i686-pc-windows-gnu" 982 | version = "0.4.0" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 985 | 986 | [[package]] 987 | name = "winapi-util" 988 | version = "0.1.5" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 991 | dependencies = [ 992 | "winapi", 993 | ] 994 | 995 | [[package]] 996 | name = "winapi-x86_64-pc-windows-gnu" 997 | version = "0.4.0" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1000 | 1001 | [[package]] 1002 | name = "windows-sys" 1003 | version = "0.45.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1006 | dependencies = [ 1007 | "windows-targets 0.42.2", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "windows-sys" 1012 | version = "0.48.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1015 | dependencies = [ 1016 | "windows-targets 0.48.0", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "windows-targets" 1021 | version = "0.42.2" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1024 | dependencies = [ 1025 | "windows_aarch64_gnullvm 0.42.2", 1026 | "windows_aarch64_msvc 0.42.2", 1027 | "windows_i686_gnu 0.42.2", 1028 | "windows_i686_msvc 0.42.2", 1029 | "windows_x86_64_gnu 0.42.2", 1030 | "windows_x86_64_gnullvm 0.42.2", 1031 | "windows_x86_64_msvc 0.42.2", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "windows-targets" 1036 | version = "0.48.0" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1039 | dependencies = [ 1040 | "windows_aarch64_gnullvm 0.48.0", 1041 | "windows_aarch64_msvc 0.48.0", 1042 | "windows_i686_gnu 0.48.0", 1043 | "windows_i686_msvc 0.48.0", 1044 | "windows_x86_64_gnu 0.48.0", 1045 | "windows_x86_64_gnullvm 0.48.0", 1046 | "windows_x86_64_msvc 0.48.0", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "windows_aarch64_gnullvm" 1051 | version = "0.42.2" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1054 | 1055 | [[package]] 1056 | name = "windows_aarch64_gnullvm" 1057 | version = "0.48.0" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1060 | 1061 | [[package]] 1062 | name = "windows_aarch64_msvc" 1063 | version = "0.42.2" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1066 | 1067 | [[package]] 1068 | name = "windows_aarch64_msvc" 1069 | version = "0.48.0" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1072 | 1073 | [[package]] 1074 | name = "windows_i686_gnu" 1075 | version = "0.42.2" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1078 | 1079 | [[package]] 1080 | name = "windows_i686_gnu" 1081 | version = "0.48.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1084 | 1085 | [[package]] 1086 | name = "windows_i686_msvc" 1087 | version = "0.42.2" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1090 | 1091 | [[package]] 1092 | name = "windows_i686_msvc" 1093 | version = "0.48.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1096 | 1097 | [[package]] 1098 | name = "windows_x86_64_gnu" 1099 | version = "0.42.2" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1102 | 1103 | [[package]] 1104 | name = "windows_x86_64_gnu" 1105 | version = "0.48.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1108 | 1109 | [[package]] 1110 | name = "windows_x86_64_gnullvm" 1111 | version = "0.42.2" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1114 | 1115 | [[package]] 1116 | name = "windows_x86_64_gnullvm" 1117 | version = "0.48.0" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1120 | 1121 | [[package]] 1122 | name = "windows_x86_64_msvc" 1123 | version = "0.42.2" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1126 | 1127 | [[package]] 1128 | name = "windows_x86_64_msvc" 1129 | version = "0.48.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1132 | 1133 | [[package]] 1134 | name = "workstyle" 1135 | version = "0.9.0" 1136 | dependencies = [ 1137 | "anyhow", 1138 | "clap", 1139 | "dirs", 1140 | "env_logger", 1141 | "hyprland", 1142 | "indexmap", 1143 | "itertools", 1144 | "lockfile", 1145 | "log", 1146 | "once_cell", 1147 | "serde", 1148 | "serde_derive", 1149 | "signal-hook", 1150 | "swayipc", 1151 | "toml", 1152 | ] 1153 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "workstyle" 3 | version = "0.9.0" 4 | authors = ["Pierre Chevalier "] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "\nWorkspaces with style!\n\nThis program will dynamically rename your workspaces to indicate which programs are running in each workspace. It uses the i3 ipc protocol, which makes it compatible with sway and i3.\n\nBy default, each program is mapped to a unicode character for concision.\n\nThe short description of each program is configurable. In the absence of a config file, one will be generated automatically.\nSee ${XDG_CONFIG_HOME}/workstyle/config.yml for details." 8 | homepage = "https://github.com/pierrechevalier83/workstyle" 9 | repository = "https://github.com/pierrechevalier83/workstyle" 10 | exclude = ["screenshots"] 11 | 12 | [dependencies] 13 | dirs = "3.0.2" 14 | env_logger = "0.9" 15 | log = "0.4.14" 16 | serde = "1.0" 17 | serde_derive = "1.0" 18 | lockfile = "0.3.0" 19 | anyhow = "1.0" 20 | indexmap = "1.8" 21 | swayipc = { git = "https://github.com/pierrechevalier83/swayipc-rs.git", branch = "fix_crash_when_using_without_i3_or_sway" } 22 | 23 | 24 | once_cell = "1.9" 25 | toml = { "version" = "0.5.8", "features" = ["preserve_order"] } 26 | signal-hook = { version = "0.3.13", default-features = false, features = ["iterator"] } 27 | clap = { version = "4.0", features = ["derive", "std"] } 28 | hyprland = { version = "0.3.12" } 29 | itertools = "0.10.5" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Workstyle 2 | === 3 | 4 | Sway/i3/Hyprland workspaces with style: 5 | 6 | This application will dynamically rename your workspaces to indicate which programs are running in each one. 7 | 8 | A picture is better than a thousand words! 9 | 10 | * The workspace bar could look like this (uses waybar) 11 | ![alt tag](https://github.com/pierrechevalier83/workstyle/blob/master/screenshots/bar.png) 12 | 13 | * In context: 14 | ![alt tag](https://github.com/pierrechevalier83/workstyle/blob/master/screenshots/full.png) 15 | 16 | Note: if you are using waybar and want the workspaces to be displayed with their number, like in the screenshot, please set `"format": "{icon}",`. See [the waybar wiki](https://github.com/Alexays/Waybar/wiki/Module:-Workspaces) for more information on configuring waybar. 17 | 18 | Installation 19 | === 20 | 21 | Install the latest published version from crates.io with 22 | ``` 23 | cargo install workstyle 24 | ``` 25 | 26 | Or for Arch Linux users, install the freshest git version with your favourite AUR helper, e.g. 27 | ``` 28 | yay -S workstyle-git 29 | ``` 30 | 31 | Usage 32 | === 33 | 34 | Simply run the executable: 35 | ``` 36 | workstyle 37 | ``` 38 | 39 | ``` 40 | workspace --help 41 | ``` 42 | will give you some more context. 43 | 44 | Sway configuration 45 | === 46 | 47 | Add this line to your sway config: 48 | ``` 49 | exec_always --no-startup-id workstyle &> /tmp/workstyle.log 50 | ``` 51 | 52 | You may also want to control the log level with the environment variable: RUST_LOG to error, info or debug. 53 | 54 | Note that since your workspaces will be renamed all the time, you should configure your keybindings to use numbered workspaces instead of assuming that the name is the number: 55 | Prefer 56 | ``` 57 | bindsym $mod+1 workspace number 1 58 | ``` 59 | over 60 | ``` 61 | bindsym $mod+1 workspace 1 62 | ``` 63 | 64 | Hyprland configuration 65 | === 66 | 67 | Add this line to your Hyprland config: 68 | ``` 69 | exec-once = workstyle &> /tmp/workstyle.log 70 | ``` 71 | 72 | SystemD integration 73 | ==== 74 | 75 | Alternatively you can use the workstyle.service file to configure systemd to automatically start workstyle after you login 76 | 77 | Copy `workstyle.service` to `$HOME/.config/systemd/user/` 78 | 79 | and run 80 | 81 | ``` 82 | systemctl --user enable workstyle.service 83 | systemctl --user start workstyle.service 84 | ``` 85 | 86 | Configuration 87 | === 88 | 89 | The main configuration consists of deciding which icons to use for which applications. 90 | 91 | The config file is located at `${XDG_CONFIG_HOME}/workstyle/config.toml` or `/etc/xdg/workstyle/config.toml` (the former takes precedence over the latter). It will be generated if missing. Read the generated file. The syntax is in TOML and should be pretty self-explanatory. 92 | 93 | When an app isn't recogised in the config, `workstyle` will log the application name as an error. 94 | Simply add that string (case insensitive) to your config file, with an icon of your choice. 95 | 96 | If no matching icon can be found in the config, a blank space will be used. 97 | To override this, set the default icon in the config as per below: 98 | ```toml 99 | [other] 100 | fallback_icon = "your icon" 101 | ``` 102 | 103 | If you prefer not to have multiple copies of the same icon when there are multiple matching windows, set this config option: 104 | ```toml 105 | [other] 106 | deduplicate_icons = true 107 | ``` 108 | 109 | Note that the crate [`find_unicode`](https://github.com/pierrechevalier83/find_unicode/) can help find a unicode character directly from the command line. It now supports all of nerdfonts unicode space. 110 | 111 | Minimal waybar configuration so the workspace names are showed 112 | === 113 | 114 | * For i3/sway 115 | ``` 116 | "modules-left": ["sway/workspaces"], 117 | "sway/workspaces": { 118 | "format": "{icon}", 119 | }, 120 | ``` 121 | 122 | * For hyprland 123 | ``` 124 | "modules-left": ["wlr/workspaces"], 125 | "wlr/workspaces": { 126 | "format": "{icon}", 127 | }, 128 | ``` 129 | -------------------------------------------------------------------------------- /default_config.toml: -------------------------------------------------------------------------------- 1 | # Config for workstyle 2 | # 3 | # Format: 4 | # "pattern" = "icon" 5 | # 6 | # The pattern will be used to match against the application name, class_id or WM_CLASS. 7 | # The icon will be used to represent that application. 8 | # 9 | # Note if multiple patterns are present in the same application name, 10 | # precedence is given in order of apparition in this file. 11 | 12 | "alacritty" = "" 13 | "github" = "" 14 | "rust" = "" 15 | "google" = "" 16 | "private browsing" = "" 17 | "firefox" = "" 18 | "chrome" = "" 19 | "file manager" = "" 20 | "libreoffice calc" = "" 21 | "libreoffice writer" = "" 22 | "libreoffice" = "" 23 | "nvim" = "" 24 | "gthumb" = "" 25 | "menu" = "" 26 | "calculator" = "" 27 | "transmission" = "" 28 | "videostream" = "" 29 | "mpv" = "" 30 | "music" = "" 31 | "disk usage" = "" 32 | ".pdf" = "" 33 | 34 | [other] 35 | fallback_icon = "🤨" 36 | deduplicate_icons = false 37 | separator = ": " 38 | -------------------------------------------------------------------------------- /screenshots/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierrechevalier83/workstyle/34fbbeed5a80ae97c361f53ec95444977c391a84/screenshots/bar.png -------------------------------------------------------------------------------- /screenshots/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierrechevalier83/workstyle/34fbbeed5a80ae97c361f53ec95444977c391a84/screenshots/full.png -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use indexmap::map::IndexMap; 3 | use serde::de::{self, Deserialize, Deserializer, Error}; 4 | use serde_derive::Deserialize; 5 | use std::fs::{create_dir, File}; 6 | use std::io::{BufReader, Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | const DEFAULT_FALLBACK_ICON: &str = "-"; 10 | const DEFAULT_SEPARATOR: &str = ": "; 11 | const DEFAULT_CONFIG: &str = include_str!("../default_config.toml"); 12 | 13 | #[derive(Debug, Default, Clone)] 14 | pub struct Config { 15 | pub mappings: IndexMap, 16 | pub other: Other, 17 | } 18 | 19 | #[derive(Debug, Deserialize, Default, Clone)] 20 | #[serde(default, deny_unknown_fields)] 21 | pub struct Other { 22 | pub fallback_icon: Option, 23 | pub separator: Option, 24 | pub deduplicate_icons: bool, 25 | } 26 | 27 | impl Config { 28 | pub fn new() -> Result { 29 | let path = Self::path()?; 30 | if path.exists() { 31 | let mut buf = String::new(); 32 | File::open(path) 33 | .and_then(|f| BufReader::new(f).read_to_string(&mut buf)) 34 | .context("Failed to read configuration file")?; 35 | Ok(toml::from_str(&buf)?) 36 | } else { 37 | File::create(path) 38 | .and_then(|mut f| f.write_all(DEFAULT_CONFIG.as_bytes())) 39 | .context("Failed to create default configuration file")?; 40 | Ok(toml::from_str(DEFAULT_CONFIG)?) 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | pub(crate) fn from_str(s: &str) -> Result { 46 | toml::from_str(s).context("Failed to parse config as toml") 47 | } 48 | 49 | pub fn fallback_icon(&self) -> &str { 50 | self.other 51 | .fallback_icon 52 | .as_deref() 53 | .unwrap_or(DEFAULT_FALLBACK_ICON) 54 | } 55 | 56 | pub fn separator(&self) -> &str { 57 | let sep = self.other.separator.as_deref(); 58 | if let Some(sep) = sep { 59 | let fallback_icon = self.fallback_icon(); 60 | if let Some(icon) = self.mappings.values().find(|icon| icon.contains(sep)) { 61 | error!("Can't use separator: \"{sep}\" as it is contained in icon: \"{icon}\"."); 62 | DEFAULT_SEPARATOR 63 | } else if fallback_icon.contains(sep) { 64 | error!("Can't use separator: \"{sep}\" as it is contained in fallback icon: \"{fallback_icon}\""); 65 | DEFAULT_SEPARATOR 66 | } else { 67 | sep 68 | } 69 | } else { 70 | DEFAULT_SEPARATOR 71 | } 72 | } 73 | 74 | pub fn path() -> Result { 75 | let mut user_path = dirs::config_dir().context("Could not find the configuration path")?; 76 | let mut system_path = PathBuf::from("/etc/xdg"); 77 | 78 | for path in [&mut user_path, &mut system_path] { 79 | path.push(env!("CARGO_PKG_NAME")); 80 | path.push("config.toml"); 81 | } 82 | let path = if system_path.exists() && !user_path.exists() { 83 | system_path 84 | } else { 85 | user_path 86 | }; 87 | let dir = path 88 | .parent() 89 | .context("Expected path to contain a parent directory")?; 90 | if !dir.exists() { 91 | create_dir(dir).context("Failed to create configuration directory")?; 92 | } 93 | Ok(path) 94 | } 95 | } 96 | 97 | impl<'de> Deserialize<'de> for Config { 98 | fn deserialize(deserializer: D) -> Result 99 | where 100 | D: Deserializer<'de>, 101 | { 102 | struct Visitor; 103 | 104 | impl<'de> de::Visitor<'de> for Visitor { 105 | type Value = Config; 106 | 107 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 108 | formatter.write_str("workstyle configuration map") 109 | } 110 | 111 | fn visit_map(self, mut map: A) -> Result 112 | where 113 | A: de::MapAccess<'de>, 114 | { 115 | let mut config = Config::default(); 116 | while let Some((key, value)) = map.next_entry::()? { 117 | if key == "other" { 118 | config.other = Other::deserialize(value).map_err(A::Error::custom)?; 119 | } else { 120 | config 121 | .mappings 122 | .insert(key, String::deserialize(value).map_err(A::Error::custom)?); 123 | } 124 | } 125 | Ok(config) 126 | } 127 | } 128 | 129 | deserializer.deserialize_any(Visitor) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod config; 5 | #[cfg(test)] 6 | mod tests; 7 | mod window_manager; 8 | 9 | use std::collections::HashSet; 10 | use std::path::PathBuf; 11 | use std::process::exit; 12 | use std::sync::Mutex; 13 | use std::thread::{sleep, spawn}; 14 | use std::time::Duration; 15 | 16 | use anyhow::{Context, Result}; 17 | use clap::{Parser, ValueEnum}; 18 | use config::Config; 19 | use lockfile::Lockfile; 20 | use once_cell::sync::Lazy; 21 | use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM}; 22 | use signal_hook::iterator::Signals; 23 | use window_manager::{Window, WindowManager, WM}; 24 | 25 | /// Workspaces with style! 26 | /// 27 | /// This program will dynamically rename your workspaces to indicate which 28 | /// programs are running in each workspace. It uses the i3 ipc protocol, which 29 | /// makes it compatible with sway and i3. 30 | /// 31 | /// By default, each program is mapped to a unicode character for concision. 32 | /// 33 | /// The short description of each program is configurable. In the absence of a 34 | /// config file, one will be generated automatically. 35 | /// See ${XDG_CONFIG_HOME}/workstyle/config.yml for details. 36 | /// 37 | /// If you prefer not to have multiple copies of the same icon when there are 38 | /// multiple matching windows, set this config option: 39 | /// 40 | /// [other] 41 | /// deduplicate_icons = true 42 | #[derive(Parser, Debug)] 43 | #[clap(version, about, long_about)] 44 | struct Args { 45 | #[arg(short, long)] 46 | enforce_window_manager: Option, 47 | } 48 | 49 | #[derive(ValueEnum, Debug, Clone, Copy)] 50 | pub enum EnforceWindowManager { 51 | SwayOrI3, 52 | Hyprland, 53 | } 54 | 55 | static LOCK: Lazy>> = 56 | Lazy::new(|| Mutex::new(Lockfile::create(lockfile_path()).ok())); 57 | 58 | fn pretty_window(config: &Config, window: &Window) -> String { 59 | for (name, icon) in &config.mappings { 60 | if window.matches(name) { 61 | return icon.clone(); 62 | } 63 | } 64 | error!("Couldn't identify window: {window:?}"); 65 | info!("Make sure to add an icon for this file in your config file!"); 66 | config.fallback_icon().into() 67 | } 68 | 69 | fn pretty_windows(config: &Config, windows: &[Window]) -> String { 70 | let mut s = String::new(); 71 | if config.other.deduplicate_icons { 72 | let mut set = HashSet::new(); 73 | for window in windows { 74 | let icon = pretty_window(config, window); 75 | if set.get(&icon).is_none() { 76 | s.push_str(&icon); 77 | s.push(' '); 78 | set.insert(icon); 79 | } 80 | } 81 | } else { 82 | for window in windows { 83 | s.push_str(&pretty_window(config, window)); 84 | s.push(' '); 85 | } 86 | } 87 | s 88 | } 89 | 90 | fn lockfile_path() -> PathBuf { 91 | let mut lockfile_path = match dirs::runtime_dir() { 92 | Some(path) => path, 93 | None => PathBuf::from("/tmp"), 94 | }; 95 | lockfile_path.push("workstyle.lock"); 96 | lockfile_path 97 | } 98 | 99 | fn aquire_lock() { 100 | // Try to aquire the lock 101 | if LOCK.lock().unwrap().is_none() { 102 | error!("Failed to aquire the lock"); 103 | exit(1); 104 | } 105 | 106 | // Drop the lock on exit 107 | let mut signals = Signals::new([SIGTERM, SIGQUIT, SIGINT, SIGHUP]) 108 | .expect("Failed to create signals iterator"); 109 | spawn(move || { 110 | let _ = signals.forever().next(); 111 | drop(LOCK.lock().unwrap().take()); 112 | exit(0); 113 | }); 114 | 115 | // Drop the lock on panic 116 | std::panic::set_hook(Box::new(|info| { 117 | error!("{info}"); 118 | if let Ok(mut lock) = LOCK.lock() { 119 | drop(lock.take()); 120 | } 121 | })); 122 | } 123 | 124 | fn run() -> Result<()> { 125 | let args = Args::parse(); 126 | let mut wm = WindowManager::connect(args.enforce_window_manager)?; 127 | info!("Successfully connected to WM"); 128 | 129 | loop { 130 | // TODO: watch for changes using inotify and read the config only when needed 131 | let config = Config::new()?; 132 | let sep: &str = config.separator(); 133 | 134 | let workspaces = wm.get_windows_in_each_workspace()?; 135 | for (name, windows) in workspaces { 136 | let new_name = pretty_windows(&config, &windows); 137 | let num = name 138 | .split(sep) 139 | .next() 140 | .context("Unexpected workspace name")?; 141 | if new_name.is_empty() { 142 | wm.rename_workspace(&name, num)?; 143 | } else { 144 | wm.rename_workspace(&name, &format!("{num}{sep}{new_name}"))?; 145 | } 146 | } 147 | 148 | wm.wait_for_event()?; 149 | } 150 | } 151 | 152 | fn main() { 153 | env_logger::init(); 154 | let _ = Args::parse(); 155 | aquire_lock(); 156 | loop { 157 | if let Err(e) = run() { 158 | error!("{e:#}"); 159 | info!("Attempting to reconnect to the WM in 1 second"); 160 | sleep(Duration::from_secs(1)); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/tests/issue_50.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::pretty_window; 3 | use crate::window_manager::Window; 4 | 5 | const CONFIG_ISSUE_50: &str = "# Config for workstyle 6 | # 7 | # Format: 8 | # \"pattern\" = \"icon\" 9 | # 10 | # The pattern will be used to match against the application name, class_id or WM_CLASS. 11 | # The icon will be used to represent that application. 12 | # 13 | # Note if multiple patterns are present in the same application name, 14 | # precedence is given in order of apparition in this file. 15 | 16 | ## partials 17 | '/GitHub/' = '' 18 | '/GitLab/' = '' 19 | '/NVIM ?\\w*/' = '' 20 | '/npm/' = '' 21 | '/node/' = '' 22 | '/yarn/' = '' 23 | '/Stack Overflow/' = '' 24 | 25 | ## browsers 26 | 'google-chrome' = '' 27 | 'Google-chrome' = '' 28 | 'Google-chrome-unstable' = '' 29 | 'google-chrome-unstable' = '' 30 | 'Google-chrome-beta' = '' 31 | 'google-chrome-beta' = '' 32 | 'chromium' = '' 33 | 'firefox' = '' 34 | 'firefoxdeveloperedition' = '' 35 | 36 | ## default applications 37 | 'foot' = '' 38 | '/foot/' = '' 39 | 'floating_shell' = '' 40 | 'pcmanfm' = '' 41 | 'nemo' = '' 42 | 'pamac-manager' = '' 43 | '/Bluetooth/' = '' 44 | 'file-roller' = '' 45 | 'swappy' = '' 46 | 'org.kde.okular' = '' 47 | 'evince' = '' 48 | 49 | ## email 50 | 'Thunderbird' = '' 51 | 'thunderbird' = '' 52 | 'evolution' = '' 53 | 'kmail' = '' 54 | 55 | ## ide 56 | 'code' = '﬏' 57 | 'Code' = '﬏' 58 | '/- Visual Studio Code/' = '﬏' 59 | '/IntelliJ/' = '' 60 | 'code-url-handler' = '﬏' 61 | 'sublime_text' = '' 62 | 63 | # messenger 64 | 'whatsapp-for-linux' = '' 65 | 'Slack' = '' 66 | '/Telegram/' = '' 67 | '/Microsoft Teams/' = '' 68 | 'Signal' = '' 69 | 70 | ## auth 71 | 'polkit-gnome-authentication-agent-1' = '' 72 | 'Keybase' = '' 73 | 74 | ## additional applications 75 | 'balena-etcher' = '' 76 | 'Steam' = '' 77 | 'vlc' = '嗢' 78 | 'org.qbittorrent.qBittorrent' = '' 79 | 'transmission-gtk' = '' 80 | 'Insomnia' = '' 81 | 'Bitwarden' = '' 82 | 'Spotify' = '' 83 | 'YouTube Music' = 'ﱘ' 84 | 'alacritty' = '' 85 | 'kitty' = '' 86 | 'font-manager' = '' 87 | 'lutris' = '' 88 | '/Wine/' = '' 89 | 'Arctype' = '' 90 | 'Around' = '' 91 | 92 | [other] 93 | fallback_icon = '' 94 | deduplicate_icons = true"; 95 | 96 | #[test] 97 | fn test_pretty_window() { 98 | let w = Window { 99 | name: Some("Icons Icon | Font Awesome - Chromium".to_string()), 100 | app_id: None, 101 | window_properties_class: Some("chromium".to_string()), 102 | }; 103 | let c = Config::from_str(CONFIG_ISSUE_50).unwrap(); 104 | assert_eq!("", pretty_window(&c, &w)); 105 | } 106 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod issue_50; 3 | -------------------------------------------------------------------------------- /src/window_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::EnforceWindowManager; 2 | use anyhow::{anyhow, bail, Context, Result}; 3 | use hyprland::data::{Clients, Version, Workspaces}; 4 | use hyprland::dispatch::{Dispatch, DispatchType}; 5 | use hyprland::event_listener::EventListener; 6 | use hyprland::shared::HyprData; 7 | use itertools::Itertools; 8 | use std::collections::BTreeMap; 9 | use std::sync::{mpsc, mpsc::Receiver}; 10 | use std::thread; 11 | use swayipc::{Connection, EventStream, EventType, Node, NodeType}; 12 | 13 | trait NodeExt { 14 | fn is_workspace(&self) -> bool; 15 | fn is_window(&self) -> bool; 16 | fn name(&self) -> Option; 17 | fn app_id(&self) -> Option; 18 | fn window_properties_class(&self) -> Option; 19 | fn windows_in_node(&self) -> Vec; 20 | fn workspaces_in_node(&self) -> Result>>; 21 | } 22 | 23 | impl NodeExt for Node { 24 | fn is_workspace(&self) -> bool { 25 | // `__i3_scratch` is a special workspace that connot be renamed, so we just skip it 26 | self.name.as_deref() != Some("__i3_scratch") && self.node_type == NodeType::Workspace 27 | } 28 | fn is_window(&self) -> bool { 29 | matches!(self.node_type, NodeType::Con | NodeType::FloatingCon) 30 | } 31 | fn name(&self) -> Option { 32 | self.name.clone() 33 | } 34 | fn app_id(&self) -> Option { 35 | self.app_id.clone() 36 | } 37 | fn window_properties_class(&self) -> Option { 38 | self.window_properties 39 | .as_ref() 40 | .and_then(|prop| prop.class.clone()) 41 | } 42 | /// Recursively find all windows names in this node 43 | fn windows_in_node(&self) -> Vec { 44 | let mut res = Vec::new(); 45 | for node in self.nodes.iter().chain(self.floating_nodes.iter()) { 46 | res.extend(node.windows_in_node()); 47 | if node.is_window() { 48 | if let Some(window) = Window::from_node(node) { 49 | res.push(window); 50 | } 51 | } 52 | } 53 | res 54 | } 55 | /// Recursively find all workspaces in this node and the list of open windows for each of these 56 | /// workspaces 57 | fn workspaces_in_node(&self) -> Result>> { 58 | let mut res = BTreeMap::new(); 59 | for node in &self.nodes { 60 | if node.is_workspace() { 61 | res.insert( 62 | node.name().context("Expected some node name")?, 63 | node.windows_in_node(), 64 | ); 65 | } else { 66 | let workspaces = node.workspaces_in_node()?; 67 | for (k, v) in workspaces { 68 | res.insert(k, v); 69 | } 70 | } 71 | } 72 | Ok(res) 73 | } 74 | } 75 | 76 | #[derive(Debug)] 77 | pub struct Window { 78 | pub(crate) name: Option, 79 | pub(crate) app_id: Option, 80 | pub(crate) window_properties_class: Option, 81 | } 82 | 83 | impl Window { 84 | fn from_node(node: &Node) -> Option { 85 | if node.is_window() { 86 | let name = node.name(); 87 | let app_id = node.app_id(); 88 | let window_properties_class = node.window_properties_class(); 89 | if name.is_some() || app_id.is_some() || window_properties_class.is_some() { 90 | Some(Self { 91 | name, 92 | app_id, 93 | window_properties_class, 94 | }) 95 | } else { 96 | None 97 | } 98 | } else { 99 | None 100 | } 101 | } 102 | fn exists(&self) -> bool { 103 | self.name.is_some() || self.app_id.is_some() || self.window_properties_class.is_some() 104 | } 105 | pub fn matches(&self, pattern: &str) -> bool { 106 | self.name 107 | .as_ref() 108 | .map(|s| s.to_lowercase().contains(pattern)) 109 | .unwrap_or(false) 110 | || self 111 | .app_id 112 | .as_ref() 113 | .map(|s| s.to_lowercase().contains(pattern)) 114 | .unwrap_or(false) 115 | || self 116 | .window_properties_class 117 | .as_ref() 118 | .map(|s| s.to_lowercase().contains(pattern)) 119 | .unwrap_or(false) 120 | } 121 | } 122 | 123 | pub trait WM { 124 | fn connect(enforce: Option) -> Result>; 125 | fn get_windows_in_each_workspace(&mut self) -> Result>>; 126 | fn rename_workspace(&mut self, old: &str, new: &str) -> Result<()>; 127 | fn wait_for_event(&mut self) -> Result<()>; 128 | } 129 | 130 | pub enum WindowManager { 131 | SwayOrI3(Box), 132 | Hyprland(Box), 133 | } 134 | 135 | impl WM for WindowManager { 136 | fn connect(enforce: Option) -> Result> { 137 | let connect_to_sway_or_i3 = 138 | || SwayOrI3::connect(enforce).map(|wm| Box::new(Self::SwayOrI3(wm))); 139 | let connect_to_hyprland = 140 | || Hyprland::connect(enforce).map(|wm| Box::new(Self::Hyprland(wm))); 141 | match enforce { 142 | Some(EnforceWindowManager::SwayOrI3) => connect_to_sway_or_i3(), 143 | Some(EnforceWindowManager::Hyprland) => connect_to_hyprland(), 144 | None => { 145 | connect_to_sway_or_i3().or_else(|_| connect_to_hyprland()).map_err(|_| anyhow!("Couldn't connect to the window manager. Only Sway, I3 and Hyprland are officially supported.")) 146 | } 147 | 148 | } 149 | } 150 | fn get_windows_in_each_workspace(&mut self) -> Result>> { 151 | match self { 152 | Self::SwayOrI3(wm) => wm.get_windows_in_each_workspace(), 153 | Self::Hyprland(wm) => wm.get_windows_in_each_workspace(), 154 | } 155 | } 156 | fn rename_workspace(&mut self, old: &str, new: &str) -> Result<()> { 157 | match self { 158 | Self::SwayOrI3(wm) => wm.rename_workspace(old, new), 159 | Self::Hyprland(wm) => wm.rename_workspace(old, new), 160 | } 161 | } 162 | fn wait_for_event(&mut self) -> Result<()> { 163 | match self { 164 | Self::SwayOrI3(wm) => wm.wait_for_event(), 165 | Self::Hyprland(wm) => wm.wait_for_event(), 166 | } 167 | } 168 | } 169 | 170 | pub struct Hyprland { 171 | rx: Receiver<()>, 172 | } 173 | 174 | impl WM for Hyprland { 175 | fn connect(enforce: Option) -> Result> { 176 | match enforce { 177 | None | Some(EnforceWindowManager::Hyprland) => { 178 | Version::get()?; 179 | let (tx, rx) = mpsc::channel(); 180 | thread::spawn(move || { 181 | let mut listener = EventListener::new(); 182 | let tx_clone = tx.clone(); 183 | listener.add_window_open_handler(move |_| { 184 | tx_clone.send(()).unwrap(); 185 | }); 186 | let tx_clone = tx.clone(); 187 | listener.add_window_close_handler(move |_| { 188 | tx_clone.send(()).unwrap(); 189 | }); 190 | let tx_clone = tx.clone(); 191 | listener.add_window_moved_handler(move |_| { 192 | tx_clone.send(()).unwrap(); 193 | }); 194 | let tx_clone = tx.clone(); 195 | listener.add_layer_open_handler(move |_| { 196 | tx_clone.send(()).unwrap(); 197 | }); 198 | let tx_clone = tx.clone(); 199 | listener.add_layer_closed_handler(move |_| { 200 | tx_clone.send(()).unwrap(); 201 | }); 202 | listener.add_workspace_change_handler(move |_| { 203 | tx.send(()).unwrap(); 204 | }); 205 | listener.start_listener().map_err(|e| anyhow!(e)).unwrap(); 206 | }); 207 | Ok(Box::new(Self { rx })) 208 | } 209 | _ => { 210 | bail!("Not connecting to Hyprland as we've been explicitly asked not to") 211 | } 212 | } 213 | } 214 | 215 | fn get_windows_in_each_workspace(&mut self) -> Result>> { 216 | let empty_workspaces = Workspaces::get() 217 | .context("Failed to get workspaces")? 218 | .filter_map(|workspace| { 219 | if workspace.windows == 0 { 220 | Some((format!("{}", workspace.id), Vec::new())) 221 | } else { 222 | None 223 | } 224 | }); 225 | Ok(Clients::get() 226 | .context("Failed to get clients")? 227 | .map(|client| { 228 | ( 229 | client.workspace.id, 230 | ( 231 | // Keep the position so the order of the icons matches the order of the 232 | // windows on the screen, from left to right then top to bottom 233 | ( 234 | client.at.1, /*y position in pixel*/ 235 | client.at.0, /* x position in px */ 236 | ), 237 | Window { 238 | name: match client.title.as_str() { 239 | "" => None, 240 | s => Some(s.to_string()), 241 | }, 242 | app_id: None, 243 | window_properties_class: match client.class.as_str() { 244 | "" => None, 245 | s => Some(s.to_string()), 246 | }, 247 | }, 248 | ), 249 | ) 250 | }) 251 | .into_group_map() 252 | .into_iter() 253 | .map(|(k, mut v)| { 254 | // Sort by position 255 | v.sort_by(|(l, _), (r, _)| l.cmp(r)); 256 | ( 257 | format!("{k}"), 258 | v.into_iter() 259 | // We don't need the position anymore. Dismiss it 260 | .map(|(_pos, w)| w) 261 | .filter(|w| w.exists()) 262 | .collect(), 263 | ) 264 | }) 265 | .chain(empty_workspaces) 266 | .collect()) 267 | } 268 | 269 | fn rename_workspace(&mut self, old: &str, new: &str) -> Result<()> { 270 | Dispatch::call(DispatchType::RenameWorkspace( 271 | old.parse().context("Failed to parse workspace id")?, 272 | Some(new), 273 | )) 274 | .context(format!("Failed to rename workspace from {old} to {new}")) 275 | } 276 | 277 | fn wait_for_event(&mut self) -> Result<()> { 278 | self.rx.recv().context("Failed to wait for event") 279 | } 280 | } 281 | 282 | pub struct SwayOrI3 { 283 | connection: Connection, 284 | events: EventStream, 285 | } 286 | 287 | impl WM for SwayOrI3 { 288 | fn connect(enforce: Option) -> Result> { 289 | match enforce { 290 | None | Some(EnforceWindowManager::SwayOrI3) => Ok(Box::new(Self { 291 | connection: Connection::new().context("Couldn't connect to WM")?, 292 | events: Connection::new() 293 | .context("Couldn't connect to WM")? 294 | .subscribe([EventType::Window]) 295 | .context("Couldn't subscribe to events of type Window")?, 296 | })), 297 | _ => bail!("Not connecting to Sway or i3 as we've explicitly been asked not to"), 298 | } 299 | } 300 | 301 | fn get_windows_in_each_workspace(&mut self) -> Result>> { 302 | self.connection 303 | .get_tree() 304 | .context("get_tree() failed")? 305 | .workspaces_in_node() 306 | } 307 | 308 | fn rename_workspace(&mut self, old: &str, new: &str) -> Result<()> { 309 | if old == new { 310 | return Ok(()); 311 | } 312 | for result in self 313 | .connection 314 | .run_command(&format!("rename workspace \"{old}\" to \"{new}\"",)) 315 | .context("Failed to rename the workspace")? 316 | { 317 | result.context("Failed to rename the workspace")?; 318 | } 319 | Ok(()) 320 | } 321 | 322 | fn wait_for_event(&mut self) -> Result<()> { 323 | match self.events.next() { 324 | Some(Err(e)) => Err(anyhow!(e).context("Failed to receive next event")), 325 | None => bail!("Event stream ended"), 326 | _ => Ok(()), 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /workstyle.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=workstyle autostart 3 | BindsTo=sway-session.target 4 | 5 | [Service] 6 | Environment="RUST_LOG=debug" 7 | 8 | ExecStart=/usr/bin/workstyle 9 | Restart=always 10 | RestartSec=3 11 | 12 | ## Optional resource limits 13 | # CPUQuota=1% 14 | # MemoryMax=32M 15 | 16 | [Install] 17 | WantedBy=sway-session.target 18 | --------------------------------------------------------------------------------