diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a74f551 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +# https://bevyengine.org/learn/quick-start/getting-started/setup/#enable-fast-compiles-optional +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6d2ce18..6964588 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -60,11 +60,9 @@ jobs: strategy: matrix: toolchain: - - stable - - beta + - nightly steps: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - run: rustup component add rustfmt - run: cargo fmt --check --verbose - diff --git a/.harper-dictionary.txt b/.harper-dictionary.txt new file mode 100644 index 0000000..7db3249 --- /dev/null +++ b/.harper-dictionary.txt @@ -0,0 +1,2 @@ +focusable +TODO diff --git a/Cargo.lock b/Cargo.lock index 2d0daf8..dc9f9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,19 +14,19 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" version = "0.16.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" [[package]] name = "accesskit_atspi_common" version = "0.9.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "accesskit_consumer", @@ -39,7 +39,7 @@ dependencies = [ [[package]] name = "accesskit_consumer" version = "0.24.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "immutable-chunkmap", @@ -48,7 +48,7 @@ dependencies = [ [[package]] name = "accesskit_macos" version = "0.17.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "accesskit_consumer", @@ -61,7 +61,7 @@ dependencies = [ [[package]] name = "accesskit_unix" version = "0.12.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -76,7 +76,7 @@ dependencies = [ [[package]] name = "accesskit_windows" version = "0.22.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "accesskit_consumer", @@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "accesskit_winit" version = "0.22.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13-rc#c46afc041b1968a5af0186fa6aba3ea9cf24c8c3" dependencies = [ "accesskit", "accesskit_macos", @@ -100,29 +100,28 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", - "serde", "version_check", "zerocopy", ] @@ -138,9 +137,12 @@ dependencies = [ [[package]] name = "aligned-vec" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] [[package]] name = "android-activity" @@ -149,7 +151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.8.0", + "bitflags 2.9.4", "cc", "cesu8", "jni", @@ -169,12 +171,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -184,11 +180,61 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -201,9 +247,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arg_enum_proc_macro" @@ -213,7 +259,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -255,9 +301,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -265,31 +311,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-executor" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.0", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - [[package]] name = "async-io" version = "1.13.0" @@ -312,21 +333,20 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock 3.4.0", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.0", + "futures-lite 2.6.1", "parking", - "polling 3.7.4", - "rustix 0.38.44", + "polling 3.11.0", + "rustix 1.1.2", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -340,11 +360,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] @@ -374,47 +394,25 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ - "async-io 2.4.0", - "async-lock 3.4.0", + "async-io 2.6.0", + "async-lock 3.4.1", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.44", + "rustix 1.1.2", "signal-hook-registry", "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "windows-sys 0.61.2", ] [[package]] @@ -425,13 +423,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -490,15 +488,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "av1-grain" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", "arrayvec", @@ -510,18 +508,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ "arrayvec", ] [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -529,42 +527,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "bar-rs" -version = "0.1.0" -dependencies = [ - "bar-rs_derive", - "chrono", - "configparser", - "csscolorparser", - "ctrlc", - "directories", - "downcast-rs", - "handlebars", - "hyprland", - "iced", - "libc", - "niri-ipc", - "notify", - "reqwest", - "serde", - "serde_json", - "system-tray", - "tokio", - "udev", - "wayfire-rs", -] - -[[package]] -name = "bar-rs_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "windows-link", ] [[package]] @@ -590,9 +553,9 @@ checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" @@ -602,9 +565,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] [[package]] name = "bitstream-io" @@ -638,28 +604,28 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.6.0", + "futures-lite 2.6.1", "piper", ] [[package]] name = "built" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "by_address" @@ -669,22 +635,22 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -701,9 +667,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -711,32 +677,58 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "log", - "polling 3.7.4", + "polling 3.11.0", "rustix 0.38.44", "slab", "thiserror 1.0.69", ] +[[package]] +name = "calloop" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" +dependencies = [ + "bitflags 2.9.4", + "polling 3.11.0", + "rustix 1.1.2", + "slab", + "tracing", +] + [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", ] +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.3", + "rustix 1.1.2", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" -version = "1.2.12" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -760,9 +752,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -778,23 +770,63 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", ] +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "clipboard-win" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", ] @@ -815,7 +847,7 @@ version = "0.2.2" source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" dependencies = [ "dnd", - "mime 0.1.0", + "mime", "smithay-clipboard", ] @@ -839,7 +871,7 @@ dependencies = [ "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types 0.5.0", + "foreign-types", "libc", "objc", ] @@ -874,6 +906,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "com" version = "0.6.0" @@ -924,12 +972,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "configparser" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" - [[package]] name = "core-foundation" version = "0.9.4" @@ -955,7 +997,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -970,14 +1012,24 @@ dependencies = [ "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "cosmic-client-toolkit" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=d218c76#d218c76b58c7a3b20dd5e7943f93fc306a1b81b8" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=d0e95be#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" dependencies = [ + "bitflags 2.9.4", "cosmic-protocols", "libc", - "smithay-client-toolkit", + "smithay-client-toolkit 0.20.0", "wayland-client", "wayland-protocols", ] @@ -985,9 +1037,9 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=d218c76#d218c76b58c7a3b20dd5e7943f93fc306a1b81b8" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=d0e95be#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -998,20 +1050,21 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.12.1" -source = "git+https://github.com/pop-os/cosmic-text.git#9125dd48b771e9aa7833d106a9850e935f71eaa6" +version = "0.14.2" +source = "git+https://github.com/pop-os/cosmic-text.git#6514323fbe764998415068e3ae40cf476753d425" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "fontdb", + "harfrust", + "linebender_resource_handle", "log", "rangemap", "rustc-hash 1.1.0", - "rustybuzz", "self_cell", + "skrifa", "smol_str", "swash", "sys-locale", - "ttf-parser 0.21.1", "unicode-bidi", "unicode-linebreak", "unicode-script", @@ -1027,11 +1080,60 @@ dependencies = [ "libc", ] +[[package]] +name = "crabbar" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap", + "crabbar-core", + "ctrlc", + "fern", + "log", + "nix 0.30.1", + "ron", + "toml-example", +] + +[[package]] +name = "crabbar-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "crabbar-derive", + "csscolorparser", + "daemonize", + "darkmode", + "downcast-rs 2.0.2", + "iced", + "log", + "merge", + "notify", + "optfield", + "ron", + "serde", + "smithay-client-toolkit 0.20.0", + "tokio", + "toml 0.9.7", + "toml-example", +] + +[[package]] +name = "crabbar-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1063,9 +1165,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -1079,11 +1181,12 @@ dependencies = [ [[package]] name = "csscolorparser" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288" +checksum = "5fda6aace1fbef3aa217b27f4c8d7d071ef2a70a5ca51050b1f17d40299d3f16" dependencies = [ "phf", + "serde", ] [[package]] @@ -1094,19 +1197,20 @@ checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" [[package]] name = "ctrlc" -version = "3.4.5" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +checksum = "881c5d0a13b2f1498e2306e82cbada78390e152d4b1378fb28a84f4dcd0dc4f3" dependencies = [ - "nix 0.29.0", - "windows-sys 0.59.0", + "dispatch", + "nix 0.30.1", + "windows-sys 0.61.2", ] [[package]] name = "cursor-icon" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "d3d12" @@ -1114,44 +1218,38 @@ version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "libloading", "winapi", ] [[package]] -name = "darling" -version = "0.20.10" +name = "daemonize" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" dependencies = [ - "darling_core", - "darling_macro", + "libc", ] [[package]] -name = "darling_core" -version = "0.20.10" +name = "darkmode" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "075a9464059e42ad7f7c487897c1ceb65327daf056c2acf58b27ca62a51db2ab" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.98", + "dbus", ] [[package]] -name = "darling_macro" -version = "0.20.10" +name = "dbus" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" dependencies = [ - "darling_core", - "quote", - "syn 2.0.98", + "libc", + "libdbus-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1166,131 +1264,47 @@ dependencies = [ ] [[package]] -name = "derive_builder" -version = "0.20.2" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "derive_builder_macro", + "block-buffer", + "crypto-common", ] [[package]] -name = "derive_builder_core" -version = "0.20.2" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.98", -] +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "derive_builder_macro" -version = "0.20.2" +name = "dlib" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "derive_builder_core", - "syn 2.0.98", + "libloading", ] [[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +name = "dnd" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" dependencies = [ - "derive_more-impl", + "bitflags 2.9.4", + "mime", + "raw-window-handle", + "smithay-client-toolkit 0.19.2", + "smithay-clipboard", ] [[package]] -name = "derive_more-impl" -version = "1.0.0" +name = "document-features" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "dnd" -version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "bitflags 2.8.0", - "mime 0.1.0", - "raw-window-handle", - "smithay-client-toolkit", - "smithay-clipboard", -] - -[[package]] -name = "document-features" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -1301,10 +1315,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.1" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#8dfaba290f9a00d3e13be71f1e6f438889cf5546" [[package]] name = "drm" @@ -1312,7 +1332,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "bytemuck", "drm-ffi", "drm-fourcc", @@ -1347,61 +1367,72 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "encoding_rs" -version = "0.8.35" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "cfg-if", + "enumflags2_derive", + "serde", ] [[package]] -name = "enumflags2" -version = "0.7.11" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "enumflags2_derive", - "serde", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] -name = "enumflags2_derive" -version = "0.7.11" +name = "equator" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "etagere" @@ -1441,9 +1472,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1452,11 +1483,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -1496,6 +1527,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1506,78 +1557,67 @@ dependencies = [ ] [[package]] -name = "filetime" -version = "0.2.25" +name = "fern" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", + "colored", + "log", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "font-types" -version = "0.8.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c3a23a5a151afb1f74ea797f8c300dee41eff9ee3cb1bf94ed316d860c46b3" +checksum = "511e2c18a516c666d27867d2f9821f76e7d591f762e9fc41dd6cc5c90fe54b0b" dependencies = [ "bytemuck", ] [[package]] name = "fontconfig-parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ "roxmltree", ] [[package]] name = "fontdb" -version = "0.16.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.5", + "memmap2 0.9.8", "slotmap", "tinyvec", - "ttf-parser 0.20.0", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", + "ttf-parser", ] [[package]] @@ -1587,7 +1627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -1598,30 +1638,15 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1697,14 +1722,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", "futures-core", - "futures-io", - "parking", "pin-project-lite", ] @@ -1716,7 +1738,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -1761,42 +1783,42 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.1.2", + "windows-targets 0.52.6", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1804,9 +1826,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gl_generator" @@ -1852,7 +1874,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "gpu-alloc-types", ] @@ -1862,7 +1884,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", ] [[package]] @@ -1880,13 +1902,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.15.5", ] [[package]] @@ -1895,7 +1917,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", ] [[package]] @@ -1908,67 +1930,51 @@ dependencies = [ "svg_fmt", ] -[[package]] -name = "h2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] -name = "handlebars" -version = "6.3.0" +name = "harfrust" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9" +checksum = "1f3fd23d35c2d8bcf34a1f0e9ea8c0ad263f0c8a9a47108eee23aac76e71645a" dependencies = [ - "derive_builder", - "log", - "num-order", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror 2.0.11", + "bitflags 2.9.4", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hassle-rs" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "com", "libc", "libloading", @@ -1991,9 +1997,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2008,225 +2014,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "http" -version = "1.2.0" +name = "iana-time-zone" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ - "bytes", - "fnv", - "itoa", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "bytes", - "http", + "cc", ] [[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +name = "iced" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", + "dnd", + "iced_accessibility", + "iced_core", + "iced_futures", + "iced_renderer", + "iced_widget", + "iced_winit", + "image", + "mime", + "thiserror 1.0.69", + "window_clipboard", ] [[package]] -name = "httparse" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +name = "iced_accessibility" +version = "0.1.0" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", + "accesskit", + "accesskit_winit", ] [[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +name = "iced_core" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2 0.5.8", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "hyprland" -version = "0.4.0-beta.2" -source = "git+https://github.com/hyprland-community/hyprland-rs?branch=master#552c77353f89fcb101935b151c47aa925682077e" -dependencies = [ - "ahash", - "async-stream", - "derive_more", - "either", - "futures-lite 2.6.0", - "hyprland-macros", - "num-traits", - "once_cell", - "paste", - "phf", - "serde", - "serde_json", - "serde_repr", - "tokio", -] - -[[package]] -name = "hyprland-macros" -version = "0.4.0-beta.2" -source = "git+https://github.com/hyprland-community/hyprland-rs?branch=master#552c77353f89fcb101935b151c47aa925682077e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "iced" -version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" -dependencies = [ - "dnd", - "iced_accessibility", - "iced_core", - "iced_futures", - "iced_renderer", - "iced_widget", - "iced_winit", - "image", - "mime 0.1.0", - "thiserror 1.0.69", - "window_clipboard", -] - -[[package]] -name = "iced_accessibility" -version = "0.1.0" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" -dependencies = [ - "accesskit", - "accesskit_winit", -] - -[[package]] -name = "iced_core" -version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" -dependencies = [ - "bitflags 2.8.0", - "bytes", - "cosmic-client-toolkit", - "dnd", - "glam", - "log", - "mime 0.1.0", - "num-traits", - "once_cell", - "palette", - "raw-window-handle", - "rustc-hash 2.1.1", - "smol_str", - "thiserror 1.0.69", - "web-time", - "window_clipboard", + "bitflags 2.9.4", + "bytes", + "cosmic-client-toolkit", + "dnd", + "glam", + "log", + "mime", + "num-traits", + "once_cell", + "palette", + "raw-window-handle", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 1.0.69", + "web-time", + "window_clipboard", ] [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "futures", "iced_core", @@ -2252,9 +2116,9 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "bytemuck", "cosmic-text", "half", @@ -2273,7 +2137,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2285,7 +2149,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "bytes", "cosmic-client-toolkit", @@ -2300,7 +2164,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "bytemuck", "cosmic-text", @@ -2315,10 +2179,10 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "as-raw-xcb-connection", - "bitflags 2.8.0", + "bitflags 2.9.4", "bytemuck", "cosmic-client-toolkit", "futures", @@ -2344,12 +2208,13 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "cosmic-client-toolkit", "dnd", "iced_renderer", "iced_runtime", + "log", "num-traits", "once_cell", "rustc-hash 2.1.1", @@ -2361,7 +2226,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0-dev" -source = "git+https://github.com/Faervan/iced_pop-os.git?branch=master#08a562150a87661b9b68263c56bfa23a31b89682" +source = "git+https://github.com/pop-os/iced.git?branch=master#521a04d7e7589fdd61b314c92155277bf350d944" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -2382,161 +2247,16 @@ dependencies = [ "winapi", "window_clipboard", "winit", - "xkbcommon", + "xkbcommon 0.7.0", "xkbcommon-dl", "xkeysym", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "image" -version = "0.25.5" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", @@ -2544,8 +2264,9 @@ dependencies = [ "exr", "gif", "image-webp", + "moxcms", "num-traits", - "png", + "png 0.18.0", "qoi", "ravif", "rayon", @@ -2557,9 +2278,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", "quick-error", @@ -2567,36 +2288,36 @@ dependencies = [ [[package]] name = "imgref" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "immutable-chunkmap" -version = "2.0.6" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" dependencies = [ "arrayvec", ] [[package]] name = "indexmap" -version = "2.7.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", ] [[package]] name = "inotify" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", "inotify-sys", "libc", ] @@ -2627,7 +2348,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -2642,10 +2363,21 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.11.0" +name = "io-uring" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -2656,12 +2388,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - [[package]] name = "jni" version = "0.21.1" @@ -2686,24 +2412,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.3", "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2737,9 +2458,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -2765,23 +2486,38 @@ dependencies = [ "smallvec", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lebe" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libdbus-sys" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" +dependencies = [ + "pkg-config", +] [[package]] name = "libfuzzer-sys" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", @@ -2789,40 +2525,36 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.18", ] [[package]] -name = "libudev-sys" -version = "0.1.4" +name = "linebender_resource_handle" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" [[package]] name = "linux-raw-sys" @@ -2843,32 +2575,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" [[package]] -name = "litemap" -version = "0.7.4" +name = "linux-raw-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.25" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "loop9" @@ -2906,9 +2637,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" @@ -2921,9 +2652,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -2946,16 +2677,38 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merge" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e520ba58faea3487f75df198b1d079644ec226ea3b0507d002c6fa4b8cf93a" +dependencies = [ + "merge_derive", + "num-traits", +] + +[[package]] +name = "merge_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8f8ce6efff81cbc83caf4af0905c46e58cb46892f63ad3835e81b47eaf7968" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "metal" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -2969,12 +2722,6 @@ dependencies = [ "smithay-clipboard", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2983,9 +2730,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -2993,21 +2740,31 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "moxcms" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc7d85f3d741164e8972ad355e26ac6e51b20fcae5f911c7da8f2d8bbbb3f33" +dependencies = [ + "num-traits", + "pxfm", ] [[package]] name = "mutate_once" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] name = "naga" @@ -3017,7 +2774,7 @@ checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.8.0", + "bitflags 2.9.4", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", @@ -3030,30 +2787,13 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "native-tls" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndk" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -3092,16 +2832,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "niri-ipc" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa541cad3b426dd1ab72765ba71d1c20a1b8a17d80c692b099a759074f74242" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "nix" version = "0.26.4" @@ -3116,11 +2846,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -3144,12 +2874,11 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "notify" -version = "7.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.8.0", - "filetime", + "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", @@ -3158,17 +2887,14 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "notify-types" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" -dependencies = [ - "instant", -] +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "num-bigint" @@ -3188,7 +2914,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -3200,21 +2926,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-modular" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" - -[[package]] -name = "num-order" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" -dependencies = [ - "num-modular", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -3237,33 +2948,34 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.5.2", "libc", ] [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -3308,7 +3020,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "libc", "objc2", @@ -3324,7 +3036,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-core-location", @@ -3348,7 +3060,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-foundation", @@ -3390,7 +3102,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "dispatch", "libc", @@ -3415,7 +3127,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-foundation", @@ -3427,7 +3139,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-foundation", @@ -3450,7 +3162,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-cloud-kit", @@ -3482,7 +3194,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "objc2", "objc2-core-location", @@ -3500,69 +3212,36 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "openssl" -version = "0.10.70" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" -dependencies = [ - "bitflags 2.8.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "optfield" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "syn 2.0.106", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "orbclient" version = "0.3.48" @@ -3584,11 +3263,11 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ - "ttf-parser 0.25.1", + "ttf-parser", ] [[package]] @@ -3612,7 +3291,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -3634,12 +3313,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", + "parking_lot_core 0.9.12", ] [[package]] @@ -3658,15 +3337,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -3677,54 +3356,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" -dependencies = [ - "memchr", - "thiserror 2.0.11", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "pest_meta" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" -dependencies = [ - "once_cell", - "pest", - "sha2", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -3756,7 +3390,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -3770,22 +3404,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -3813,9 +3447,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" @@ -3830,6 +3464,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -3848,24 +3495,23 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi 0.5.2", "pin-project-lite", - "rustix 0.38.44", - "tracing", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -3888,39 +3534,70 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.22.23", + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.106", +] + +[[package]] +name = "pxfm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +dependencies = [ + "num-traits", ] [[package]] @@ -3940,22 +3617,28 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -3983,7 +3666,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -3994,9 +3677,9 @@ checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "rangemap" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" [[package]] name = "rav1e" @@ -4035,9 +3718,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -4056,9 +3739,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -4066,9 +3749,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4076,11 +3759,12 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.25.3" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" dependencies = [ "bytemuck", + "core_maths", "font-types", ] @@ -4095,38 +3779,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", + "bitflags 2.9.4", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -4136,9 +3800,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -4147,9 +3811,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "renderdoc-sys" @@ -4157,69 +3821,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime 0.3.17", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" [[package]] -name = "ring" -version = "0.17.8" +name = "ron" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", + "base64", + "bitflags 2.9.4", + "serde", + "serde_derive", + "unicode-ident", ] [[package]] @@ -4230,9 +3848,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -4241,105 +3859,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.8.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustls-webpki" -version = "0.102.8" +name = "rustix" +version = "0.37.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", ] [[package]] -name = "rustversion" -version = "1.0.19" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] [[package]] -name = "rustybuzz" -version = "0.14.1" +name = "rustix" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "libm", - "smallvec", - "ttf-parser 0.21.1", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] -name = "ryu" -version = "1.0.19" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -4350,15 +3919,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -4379,102 +3939,74 @@ checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.5", - "smithay-client-toolkit", + "memmap2 0.9.8", + "smithay-client-toolkit 0.19.2", "tiny-skia", ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "self_cell" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] -name = "security-framework-sys" -version = "2.14.0" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "core-foundation-sys", - "libc", + "serde_core", + "serde_derive", ] [[package]] -name = "self_cell" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" - -[[package]] -name = "serde" -version = "1.0.217" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_json" -version = "1.0.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "syn 2.0.106", ] [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "serde_spanned" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "serde_core", ] [[package]] @@ -4488,17 +4020,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -4507,9 +4028,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -4537,9 +4058,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skrifa" -version = "0.26.5" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92bf3f3af711d696eff796a4f28136927d40eb8108002b6f7919dc0cee27a5d" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" dependencies = [ "bytemuck", "read-fonts", @@ -4547,12 +4068,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -4565,9 +4083,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -4575,15 +4093,13 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "calloop", - "calloop-wayland-source", + "bitflags 2.9.4", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", - "memmap2 0.9.5", - "pkg-config", + "memmap2 0.9.8", "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", @@ -4593,7 +4109,36 @@ dependencies = [ "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", - "xkbcommon", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "calloop 0.14.3", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.8", + "pkg-config", + "rustix 1.1.2", + "thiserror 2.0.17", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkbcommon 0.8.0", "xkeysym", ] @@ -4604,7 +4149,7 @@ source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-dnd-5#5a3007de dependencies = [ "libc", "raw-window-handle", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "wayland-backend", ] @@ -4629,18 +4174,18 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "softbuffer" version = "0.4.1" -source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#6e75b1ad7e98397d37cb187886d05969bc480995" +source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#a3f77e251e7422803f693df6e3fc313c010c4dcb" dependencies = [ "as-raw-xcb-connection", "bytemuck", @@ -4649,13 +4194,13 @@ dependencies = [ "core-graphics", "drm", "fastrand 2.3.0", - "foreign-types 0.5.0", + "foreign-types", "js-sys", "log", - "memmap2 0.9.5", + "memmap2 0.9.8", "objc", "raw-window-handle", - "redox_syscall 0.4.1", + "redox_syscall 0.5.18", "rustix 0.38.44", "tiny-xlib", "wasm-bindgen", @@ -4667,27 +4212,15 @@ dependencies = [ "x11rb", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -4706,23 +4239,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "svg_fmt" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "swash" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e25b48fd1c222c9fdb61148e2203b750f9840c07922fd61b87c6015560b8f6" +checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" dependencies = [ "skrifa", "yazi", @@ -4742,35 +4269,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "sys-locale" version = "0.3.2" @@ -4780,27 +4287,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -4810,23 +4296,10 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml", + "toml 0.8.23", "version-compare", ] -[[package]] -name = "system-tray" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9671bfe2e71b503192be38bca219f05bb4f66e242b105420bf82116b27b9079" -dependencies = [ - "serde", - "thiserror 2.0.11", - "tokio", - "tracing", - "zbus", -] - [[package]] name = "target-lexicon" version = "0.12.16" @@ -4835,16 +4308,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.16.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if", "fastrand 2.3.0", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix 0.38.44", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] @@ -4856,6 +4328,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix 1.1.2", + "windows-sys 0.60.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4867,11 +4349,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.17", ] [[package]] @@ -4882,29 +4364,32 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "tiff" -version = "0.9.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" dependencies = [ + "fax", "flate2", - "jpeg-decoder", + "half", + "quick-error", "weezl", + "zune-jpeg", ] [[package]] @@ -4918,7 +4403,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", - "png", + "png 0.17.16", "tiny-skia-path", ] @@ -4946,21 +4431,11 @@ dependencies = [ "tracing", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -4973,21 +4448,22 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", - "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.8", + "slab", + "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4998,72 +4474,80 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-stream" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ - "native-tls", + "futures-core", + "pin-project-lite", "tokio", ] [[package]] -name = "tokio-rustls" -version = "0.26.1" +name = "toml" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ - "rustls", - "tokio", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] -name = "tokio-stream" -version = "0.1.17" +name = "toml" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", + "indexmap", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow 0.7.13", ] [[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +name = "toml-example" +version = "0.16.0" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", + "toml-example-derive", ] [[package]] -name = "toml" -version = "0.8.20" +name = "toml-example-derive" +version = "0.16.0" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.23", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -5073,49 +4557,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.6.11", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.23" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.1", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.7.13", ] [[package]] -name = "tower" -version = "0.5.2" +name = "toml_edit" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow 0.7.13", ] [[package]] -name = "tower-layer" -version = "0.3.3" +name = "toml_parser" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow 0.7.13", +] [[package]] -name = "tower-service" -version = "0.3.3" +name = "toml_writer" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" [[package]] name = "tracing" @@ -5123,6 +4607,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5130,72 +4615,38 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttf-parser" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" - -[[package]] -name = "ttf-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" - [[package]] name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] [[package]] -name = "udev" -version = "0.9.3" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" -dependencies = [ - "io-lifetimes", - "libc", - "libudev-sys", - "mio", - "pkg-config", -] +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uds_windows" @@ -5214,23 +4665,11 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" - -[[package]] -name = "unicode-ccc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" - [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-linebreak" @@ -5238,12 +4677,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - [[package]] name = "unicode-script" version = "0.5.7" @@ -5269,51 +4702,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "v_frame" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.0" @@ -5343,60 +4747,61 @@ dependencies = [ ] [[package]] -name = "want" -version = "0.3.1" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] [[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -5407,9 +4812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5417,22 +4822,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -5452,26 +4857,15 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wayfire-rs" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136a4a4b593464c96c21c4993592d20d69537cc89b82f0d1d320d991447c20cd" -dependencies = [ - "serde", - "serde_json", - "tokio", -] - [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", - "downcast-rs", - "rustix 0.38.44", + "downcast-rs 1.2.1", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -5479,12 +4873,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.8.0", - "rustix 0.38.44", + "bitflags 2.9.4", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -5495,42 +4889,68 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.8" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.1.2", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.6" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-scanner", "wayland-server", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5539,11 +4959,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5553,9 +4973,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml", @@ -5564,22 +4984,22 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.7" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fabd7ed68cff8e7657b8a8a1fbe90cb4a3f0c30d90da4bf179a7a23008a4cb" +checksum = "fcbd4f3aba6c9fba70445ad2a484c0ef0356c1a9459b1e8e435bedc1971a6222" dependencies = [ - "bitflags 2.8.0", - "downcast-rs", - "rustix 0.38.44", + "bitflags 2.9.4", + "downcast-rs 1.2.1", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -5589,9 +5009,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -5609,9 +5029,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" @@ -5625,7 +5045,7 @@ dependencies = [ "js-sys", "log", "naga", - "parking_lot 0.12.3", + "parking_lot 0.12.5", "profiling", "raw-window-handle", "smallvec", @@ -5646,14 +5066,14 @@ checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.8.0", + "bitflags 2.9.4", "cfg_aliases 0.1.1", "document-features", "indexmap", "log", "naga", "once_cell", - "parking_lot 0.12.3", + "parking_lot 0.12.5", "profiling", "raw-window-handle", "rustc-hash 1.1.0", @@ -5673,7 +5093,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.8.0", + "bitflags 2.9.4", "block", "cfg_aliases 0.1.1", "core-graphics-types", @@ -5694,7 +5114,7 @@ dependencies = [ "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", - "parking_lot 0.12.3", + "parking_lot 0.12.5", "profiling", "range-alloc", "raw-window-handle", @@ -5714,16 +5134,16 @@ version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "js-sys", "web-sys", ] [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -5743,11 +5163,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5766,7 +5186,7 @@ dependencies = [ "clipboard_wayland", "clipboard_x11", "dnd", - "mime 0.1.0", + "mime", "raw-window-handle", "thiserror 1.0.69", ] @@ -5788,8 +5208,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ "windows-core 0.54.0", - "windows-implement", - "windows-interface", + "windows-implement 0.53.0", + "windows-interface 0.53.0", "windows-targets 0.52.6", ] @@ -5812,6 +5232,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + [[package]] name = "windows-implement" version = "0.53.0" @@ -5820,7 +5253,18 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -5831,20 +5275,26 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "windows-result 0.2.0", - "windows-strings", - "windows-targets 0.52.6", + "proc-macro2", + "quote", + "syn 2.0.106", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-result" version = "0.1.2" @@ -5856,21 +5306,20 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -5909,6 +5358,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5948,13 +5415,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5973,6 +5457,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5991,6 +5481,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6009,12 +5505,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6033,6 +5541,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6051,6 +5565,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6069,6 +5589,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6087,18 +5613,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winit" version = "0.30.5" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#8dfaba290f9a00d3e13be71f1e6f438889cf5546" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.8.0", + "bitflags 2.9.4", "block2", "bytemuck", - "calloop", + "calloop 0.13.0", "cfg_aliases 0.2.1", "concurrent-queue", "core-foundation", @@ -6107,7 +5639,7 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2 0.9.5", + "memmap2 0.9.8", "ndk", "objc2", "objc2-app-kit", @@ -6117,10 +5649,10 @@ dependencies = [ "percent-encoding", "pin-project", "raw-window-handle", - "redox_syscall 0.4.1", + "redox_syscall 0.5.18", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "smol_str", "tracing", "unicode-segmentation", @@ -6149,33 +5681,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.1" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.33.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "x11-dl" @@ -6190,30 +5707,31 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading", "once_cell", - "rustix 0.38.44", + "rustix 1.1.2", "x11rb-protocol", + "xcursor", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xdg-home" @@ -6236,13 +5754,24 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "libc", + "memmap2 0.9.8", + "xkeysym", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.4", "dlib", "log", "once_cell", @@ -6260,9 +5789,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "yazi" @@ -6270,30 +5799,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - [[package]] name = "zbus" version = "3.15.2" @@ -6301,15 +5806,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" dependencies = [ "async-broadcast", - "async-executor", - "async-fs", - "async-io 1.13.0", - "async-lock 2.8.0", "async-process", "async-recursion", - "async-task", "async-trait", - "blocking", "byteorder", "derivative", "enumflags2", @@ -6363,78 +5862,28 @@ dependencies = [ [[package]] name = "zeno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0de2315dc13d00e5df3cd6b8d2124a6eaec6a2d4b6a1c5f37b7efad17fcc17" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.106", ] [[package]] @@ -6454,9 +5903,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.14" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index f088a9f..750b004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,60 @@ -[package] -name = "bar-rs" +[workspace] +resolver = "3" +members = ["core", "crabbar", "derive"] + +[workspace.package] version = "0.1.0" -edition = "2021" +edition = "2024" + +[workspace.dependencies] +crabbar.path = "crabbar" +core = { path = "core", package = "crabbar-core" } +derive = { path = "derive", package = "crabbar-derive" } -[dependencies] -chrono = "0.4.39" -configparser = "3.1.0" -ctrlc = "3.4.5" -directories = "5.0.1" -hyprland = { git = "https://github.com/hyprland-community/hyprland-rs", branch = "master" } -#iced = { git = "https://github.com/pop-os/iced.git", branch = "master", features = [ -# "tokio", -# "wayland", -# "winit" -#] } -iced = { git = "https://github.com/Faervan/iced_pop-os.git", branch = "master", features = [ - "tokio", - "wayland", - "winit", - "image" +iced = { git = "https://github.com/pop-os/iced.git", branch = "master", features = [ + "tokio", + "wayland", + "winit", + "image", +] } +tokio = { version = "1.46.1", features = [ + "macros", + "sync", + "net", + "io-util", + "fs", ] } -notify = "7.0.0" -system-tray = "0.5.0" -tokio = { version = "1.42.0", features = ["io-util", "macros", "process", "sync"] } -udev = { version = "0.9.1", features = ["mio"] } -bar-rs_derive = { path = "crates/bar-rs_derive"} -downcast-rs = "1.2.1" -csscolorparser = "0.7.0" -wayfire-rs = "0.2.2" -serde_json = "1.0.135" -niri-ipc = "=0.1.10" -handlebars = "6.3.0" -serde = { version = "1.0.217", features = ["derive"] } -reqwest = "0.12.12" -libc = "0.2.169" +log = "0.4.27" +anyhow = "1.0.98" +serde = { version = "1.0.219", features = ["derive"] } +smithay-client-toolkit = "0.20" +fern = { version = "0.7.1", features = ["colored"] } +toml = "0.9.2" +toml_edit = "0.23.2" +# toml-example = "0.14.0" +toml-example.path = "../toml-example/lib" +clap = { version = "4.5.41", features = [ + "deprecated", + "derive", + "env", + "wrap_help", + "string", +] } +daemonize = "0.5.0" +ctrlc = "3.4.7" +ron = "0.10.1" +chrono = "0.4.41" +nix = "0.30.1" +optfield = "0.4.0" +merge = "0.2.0" +csscolorparser = { version = "0.7.2", features = ["serde"] } +darkmode = "0.1.0" +downcast-rs = "2.0.1" +notify = "8.2.0" + +proc-macro2 = "1.0.92" +quote = "1.0.38" +syn = "2.0.94" -[profile.dev.package."*"] -opt-level = 3 +[workspace.lints.clippy] +unused_trait_names = "deny" diff --git a/README.md b/README.md index dacf255..206e77a 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,22 @@ -# bar-rs +# crabbar -A simple status bar, written using [iced-rs](https://github.com/iced-rs/iced/) (specifically the [pop-os fork](https://github.com/pop-os/iced/) of iced, which supports the [wlr layer shell protocol](https://wayland.app/protocols/wlr-layer-shell-unstable-v1)) - -![image](https://github.com/user-attachments/assets/c62d8399-0f80-4c3b-8cb8-a325db13fc32) - -![image](https://github.com/user-attachments/assets/d71b0fc2-a9fb-43e9-b358-9b1f2cb3d487) - -![image](https://github.com/user-attachments/assets/d0073653-01ed-4084-9c33-0d161cd98ec7) - - - -Currently bar-rs supports only a small amount of configuration. It works on Wayland compositors implementing the [wlr layer shell protocol](https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support), but right now only features [Hyprland](https://github.com/hyprwm/Hyprland/), [Niri](https://github.com/YaLTeR/niri/) and [Wayfire](https://github.com/WayfireWM/wayfire/) modules for active workspace and window display. - -For a list of all currently supported modules, see [the Wiki](https://github.com/Faervan/bar-rs/wiki#modules) +## Rewrite +`bar-rs` is currently undergoing a full rewrite (happening in this branch) and is renamed to `crabbar`. +See [#24](https://github.com/Faervan/bar-rs/issues/24) for the motivation behind this and [#25](https://github.com/Faervan/bar-rs/pull/25) for the to-do list. ## Features -- [x] Dynamic module activation/ordering -- [x] Hot config reloading -- [x] very basic style customization -- [x] basic vertical bar support -- [x] a base set of useful modules -- [x] Module interactivity (popups, buttons) -- [x] hyprland workspace + window modules -- [x] wayfire workspace + window modules -- [x] niri workspace + window modules +- [ ] Dynamic module activation/ordering +- [ ] Hot config reloading +- [ ] very basic style customization +- [ ] basic vertical bar support +- [ ] a base set of useful modules +- [ ] Module interactivity (popups, buttons) +- [ ] hyprland workspace + window modules +- [ ] wayfire workspace + window modules +- [ ] niri workspace + window modules - [ ] sway workspace + window modules - [ ] custom modules - [ ] additional modules (wifi, pacman updates...) @@ -36,93 +26,14 @@ For a list of all currently supported modules, see [the Wiki](https://github.com - [ ] X11 support - ... -## Installation -I aim for a release on the `AUR` after the [first milestone](https://github.com/Faervan/bar-rs/milestone/1) is reached. For now, you have to build bar-rs yourself. - -
-

Building

- -To use bar-rs you have to build the project yourself (very straight forward on an up-to-date system like Arch, harder on "stable" ones like Debian due to outdated system libraries) - -```sh -# Clone the project -git clone https://github.com/faervan/bar-rs.git -cd bar-rs - -# Build the project - This might take a while -cargo build --release - -# Install the bar-rs helper script to easily launch and kill bar-rs -bash install.sh - -# Optional: Clean unneeded build files afterwards: -find target/release/* ! -name bar-rs ! -name . -type d,f -exec rm -r {} + -``` -
- -
-

Updating

- -Enter the project directory again. - -```sh -# Update the project -git pull - -# Build the project - This will be considerably faster if you didn't clean the build files after installing -cargo build --release - -# Optional: Clean unneeded build files afterwards: -find target/release/* ! -name bar-rs ! -name . -type d,f -exec rm -r {} + -``` -
- -
-

Extra dependencies

- -bar-rs depends on the following cli utilities: -- free -- grep -- awk -- printf -- pactl -- wpctl -- playerctl -
- -
-

Usage

- -Launch bar-rs using the `bar-rs` script (after installing it using the `install.sh` script): -```sh -bar-rs open -``` - -Alternatively, you may launch bar-rs directly: - -```sh -./target/release/bar-rs -# or using cargo: -cargo run --release -``` -
- -## Configuration -Example configurations can be found in [default_config](https://github.com/Faervan/bar-rs/tree/main/default_config).
-See [the Wiki](https://github.com/Faervan/bar-rs/wiki) for more. - -## Logs -If bar-rs is launched via the `bar-rs` script, it's logs are saved to `/tmp/bar-rs.log` and should only contain anything if there is an error. -If an error occurs and all dependencies are installed on your system, please feel free to open an [issue](https://github.com/faervan/bar-rs/issues) - ## Recommendations + feature requests -If you have an idea on what could improve bar-rs, or you would like to see a specific feature implemented, please open an [issue](https://github.com/faervan/bar-rs/issues). +If you have an idea on what could improve `crabbar`, or you would like to see a specific feature implemented, please open an [issue](https://github.com/faervan/crabbar/issues). ## Contributing -If you want to contribute, create an [issue](https://github.com/faervan/bar-rs/issues) about the feature you'd like to implement or comment on an existing one. You may also contact me on [matrix](https://matrix.to/#/@faervan:matrix.org) or [discord](https://discord.com/users/738658712620630076). +If you want to contribute, create an [issue](https://github.com/faervan/crabbar/issues) about the feature you'd like to implement or comment on an existing one. You may also contact me on [matrix](https://matrix.to/#/@faervan:matrix.org) or [discord](https://discord.com/users/738658712620630076). Contributing by creating new modules should be pretty easy and straight forward if you know a bit about rust. You just have to implement the `Module` and `Builder` traits for your new module and register it in `src/modules/mod.rs`.
Take a look at [docs.iced.rs](https://docs.iced.rs/iced/) for info about what to place in the `view()` method of the `Module` trait. ## Extra credits -Next to all the great crates this projects depends on (see `Cargo.toml`) and the cli utils listed in [Extra dependencies](#extra-dependencies), bar-rs also uses [NerdFont](https://www.nerdfonts.com/) (see `assets/3270`) +Next to all the great crates this projects depends on (see `Cargo.toml`), `crabbar` also uses [NerdFont](https://www.nerdfonts.com/) (see `assets/3270`) diff --git a/bar-rs b/bar-rs deleted file mode 100644 index 8d987a5..0000000 --- a/bar-rs +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/sh - -project_path="" -pid_file="/tmp/bar-rs.pid" -log_file="/tmp/bar-rs.log" - -open() { - target="release" - for arg in "$@"; do - if [ "$arg" == "--debug" ]; then - target="debug" - break - fi - done - - if [ -f "$pid_file" ] && pid=$(<"$pid_file") && kill -0 "$pid" 2>/dev/null; then - echo -e "bar-rs is already running with PID $pid,\nrun \`bar-rs kill\` to close it" - exit 1 - fi - - cmd="$project_path/target/$target/bar-rs" - - if [ ! -f "$cmd" ]; then - echo -e "$cmd does not exist, make sure to build bar-rs using:\n\t\`cargo build --release\`\nor reinstall this script if you've moved the project directory:\n\t\`bash install.sh\`" - exit 1 - fi - - RUST_BACKTRACE=full nohup $cmd > $log_file 2>&1 & - echo $! > $pid_file -} - -close() { - if [ -f "$pid_file" ]; then - pid=$(<"$pid_file") - kill -2 $pid - rm $pid_file - else - echo "PID file ($pid_file) does not exist. Exiting." - exit 1 - fi -} - -uninstall() { - path=$(realpath "$0") - if [ "$(dirname $path)" == "/usr/local/bin" ]; then - if [ "$UID" -ne 0 -a "$EUID" -ne 0 ]; then - sudo rm $path - else - rm $path - fi - else - echo This script is not installed to /usr/local/bin, you should remove it manually if desired - exit 1 - fi -} - -# MAIN -case "$1" in - open) - open $@ - ;; - kill) - close - ;; - reopen) - close - open $@ - ;; - uninstall) - uninstall - ;; - *) - echo "bar-rs: bar-rs [open | kill | reopen | uninstall] [--debug]" - ;; -esac diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..e286341 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "crabbar-core" +version.workspace = true +edition.workspace = true + +[dependencies] +derive.workspace = true + +clap.workspace = true +serde.workspace = true +anyhow.workspace = true +ron.workspace = true +log.workspace = true +tokio.workspace = true +iced.workspace = true +smithay-client-toolkit.workspace = true +toml.workspace = true +toml-example.workspace = true +optfield.workspace = true +merge.workspace = true +csscolorparser.workspace = true +darkmode.workspace = true +downcast-rs.workspace = true +daemonize.workspace = true +notify.workspace = true + +[lints] +workspace = true diff --git a/core/src/config/load.rs b/core/src/config/load.rs new file mode 100644 index 0000000..7fb6c55 --- /dev/null +++ b/core/src/config/load.rs @@ -0,0 +1,174 @@ +use std::{ + any::TypeId, + collections::{HashMap, HashSet}, + fs, + path::PathBuf, + sync::Arc, +}; + +use log::{debug, error, info}; +use serde::Deserialize; +use toml::Table; + +use crate::{ + config::{MainConfig, style::ContainerStyleOverride}, + module::{CustomModules, Module}, + state::State, +}; + +impl State { + pub fn load_config(&mut self) { + info!("Loading configuration."); + debug!("ConfigRoot: {:#?}", self.config_root); + + if let Err(e) = self.load_main_config() { + error!( + "Failed to load global configuration from {:?}: {e}", + self.config_root.config() + ); + } + + self.themes = HashMap::new(); + if let Err(e) = self.load_themes() { + error!( + "Failed to load themes from {:?}: {e}", + self.config_root.theme_dir() + ); + } + + self.styles = HashMap::new(); + if let Err(e) = self.load_styles() { + error!( + "Failed to load styles from {:?}: {e}", + self.config_root.style_dir() + ); + } + + if let Err(e) = self.load_module_config() { + error!( + "Failed to load the module config from {:?}: {e}", + self.config_root.module_dir() + ); + } + } + + /// Load both the [GlobalConfig](crate::config::GlobalConfig) + /// and the [configuration presets](crate::config::ConfigOptions) + fn load_main_config(&mut self) -> anyhow::Result<()> { + let main_cfg: MainConfig = parse_from_file(self.config_root.config())?; + self.config = Arc::new(main_cfg.global); + self.config_presets = main_cfg.bar; + debug!("Loaded global configuration"); + + Ok(()) + } + + fn load_themes(&mut self) -> anyhow::Result<()> { + self.parse_from_dir(self.config_root.theme_dir(), |state, theme_name, theme| { + state.themes.insert(theme_name, theme); + })?; + debug!("Loaded themes"); + + Ok(()) + } + + fn load_styles(&mut self) -> anyhow::Result<()> { + self.parse_from_dir(self.config_root.style_dir(), |state, style_name, style| { + state.styles.insert(style_name, style); + })?; + debug!("Loaded styles"); + + Ok(()) + } + + fn load_module_config(&mut self) -> anyhow::Result<()> { + self.parse_from_dir( + self.config_root.module_dir(), + |state, variant, config: Table| { + let is_custom = config + .get("type") + .and_then(|value| value.as_str()) + .is_some_and(|s| s.to_lowercase() == "custom"); + let module = match is_custom { + true => Some(( + TypeId::of::(), + state.registry.get_module_mut::() as &mut dyn Module, + )), + false => state + .registry + .module_by_name_mut(&variant) + .map(|(id, m)| (*id, m.as_mut())), + }; + if let Some((type_id, module)) = module { + let style = config.get("appearance").and_then(|value| { + value + .clone() + .try_into::() + .inspect_err(|e| error!("Syntax error in `{variant}.toml`: {e}")) + .ok() + }); + + let (mut added, mut removed) = (vec![], vec![]); + let mut variants: HashSet = + HashSet::from_iter(module.variant_names().into_iter().map(str::to_string)); + module.read_config(&variant, config, &state.engine); + + // Determine added/removed module variants due to the new config (e.g. a custom + // module might have been created or deleted) + let mut new = vec![]; + for variant in module.variant_names() { + if !variants.remove(variant) { + new.push(variant.to_string()); + } + } + added.push((type_id, new)); + removed.extend(variants.into_iter()); + + for (id, added) in added.into_iter() { + state.registry.add_module_names(id, added.into_iter()); + } + state.registry.remove_module_names(removed.into_iter()); + + if let Some(style) = style { + state.registry.set_style_override(&type_id, &variant, style); + } + } + }, + )?; + debug!("Loaded module configurations"); + + Ok(()) + } + + /// `entry_handler` takes the filename (`.toml` stripped) and the parsed content [T] to put this + /// value into the [State] + fn parse_from_dir Deserialize<'a>>( + &mut self, + dir_path: PathBuf, + entry_handler: fn(&mut State, String, T), + ) -> anyhow::Result<()> { + for entry in fs::read_dir(dir_path)?.flat_map(|t| { + t.inspect_err(|e| error!("Invalid directory entry: {e}")) + .ok() + }) { + let name = entry.file_name().into_string().map_err(|os_string| { + anyhow::anyhow!("{os_string:?} cannot be converted to a valid Rust String.") + })?; + + let Some(cfg_name) = name.strip_suffix(".toml") else { + debug!("Skipped `{name}` because it does not have a `.toml` extension"); + continue; + }; + + let cfg = parse_from_file(entry.path())?; + entry_handler(self, cfg_name.to_string(), cfg); + } + + Ok(()) + } +} + +fn parse_from_file Deserialize<'a>>(path: PathBuf) -> anyhow::Result { + let content = fs::read_to_string(path)?; + Ok(toml::from_str(&content)?) +} diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs new file mode 100644 index 0000000..f16d313 --- /dev/null +++ b/core/src/config/mod.rs @@ -0,0 +1,156 @@ +use std::collections::HashMap; + +use clap::Args; +use merge::Merge; +use module::{ModuleLayout, ModuleLayoutOverride}; +use optfield::optfield; +use serde::{Deserialize, Serialize}; +use toml_example::TomlExample; +use window::{WindowConfig, WindowConfigOverride}; + +use crate::helpers::merge::overwrite_if_some; + +pub mod load; +pub mod module; +pub mod prepare; +pub mod source; +pub mod style; +pub mod theme; +pub mod window; + +#[derive(Deserialize, Default, Debug, TomlExample, PartialEq)] +#[serde(default)] +pub struct MainConfig { + #[serde(flatten)] + #[toml_example(nesting)] + pub global: GlobalConfig, + + #[serde(flatten)] + #[toml_example(nesting)] + pub bar: HashMap, +} + +#[optfield( + pub GlobalConfigOverride, + attrs = (derive(Args, Debug, Clone, Serialize, Deserialize)), + field_doc, + field_attrs, + merge_fn +)] +#[derive(Args, Debug, Clone, Serialize, Deserialize, TomlExample, PartialEq)] +#[serde(default)] +pub struct GlobalConfig { + #[arg(long)] + /// Whether to watch the configuration directory for file changes and automatically update the + /// config. Even if this is true, windows can be individually configured not to be affected. + pub hot_reloading: bool, + #[arg(long)] + /// How often the windows should be updated with new content (in seconds) + pub reload_interval: f32, +} + +impl Default for GlobalConfig { + fn default() -> Self { + Self { + hot_reloading: true, + reload_interval: 3., + } + } +} + +// Note: all fields from ConfigOptions need to be present for ConfigOptionOverride as well! +#[derive(Args, Debug, Clone, Serialize, Deserialize, TomlExample, PartialEq)] +#[serde(default)] +pub struct ConfigOptions { + #[arg(long)] + /// Name of the theme to use + pub theme: String, + + #[arg(long)] + /// Name of the style to use + pub style: String, + + #[arg(long)] + /// Whether to apply changes when the configuration files where changed. This will not + /// overwrite settings set through the IPC. + /// + /// This will not take affect if the global `hot_reloading` setting is disabled. + pub hot_reloading: bool, + + #[command(flatten)] + #[toml_example(nesting)] + /// The modules that should be enabled + pub modules: ModuleLayout, + + #[command(flatten)] + #[serde(flatten)] + #[toml_example(nesting)] + pub window: WindowConfig, +} + +impl Default for ConfigOptions { + fn default() -> Self { + Self { + theme: match darkmode::detect().unwrap_or(darkmode::Mode::Dark) { + darkmode::Mode::Light => "light", + darkmode::Mode::Dark | darkmode::Mode::Default => "dark", + } + .to_string(), + style: String::from("crabbar"), + hot_reloading: true, + modules: ModuleLayout::default(), + window: WindowConfig::default(), + } + } +} + +#[derive(Args, Merge, Debug, Default, Clone, Serialize, Deserialize)] +pub struct ConfigOptionOverride { + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// Name of the theme to use + pub theme: Option, + + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// Name of the style to use + pub style: Option, + + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// Whether to update apply changes when the configuration files where changed. This will not + /// overwrite settings set through the IPC. + pub hot_reloading: Option, + + #[command(flatten)] + /// The modules that should be enabled + pub modules: ModuleLayoutOverride, + + #[command(flatten)] + pub window: WindowConfigOverride, +} + +impl ConfigOptions { + pub fn merge_opt( + &mut self, + ConfigOptionOverride { + theme, + style, + hot_reloading, + modules, + window, + }: ConfigOptionOverride, + ) { + if let Some(theme) = theme { + self.theme = theme; + } + if let Some(style) = style { + self.style = style; + } + if let Some(hot_reloading) = hot_reloading { + self.hot_reloading = hot_reloading; + } + self.modules.merge_opt(modules); + self.window.merge_opt(window); + } +} diff --git a/core/src/config/module.rs b/core/src/config/module.rs new file mode 100644 index 0000000..78cad41 --- /dev/null +++ b/core/src/config/module.rs @@ -0,0 +1,58 @@ +use clap::Args; +use merge::Merge; +use optfield::optfield; +use serde::{Deserialize, Serialize}; +use toml_example::TomlExample; + +use crate::helpers::merge::overwrite_if_some; + +#[optfield( + pub ModuleLayoutOverride, + attrs = (derive(Args, Merge, Debug, Default, Clone, Serialize, Deserialize)), + field_doc, + field_attrs, + merge_fn = pub, +)] +#[derive(Args, Merge, Debug, Clone, Serialize, Deserialize, TomlExample, PartialEq)] +#[serde(default)] +pub struct ModuleLayout { + #[arg(short = 'L', long = "module_left")] + #[merge(strategy = overwrite_if_some)] + /// Modules that should be displayed on the left of the bar + pub left: Vec, + + #[arg(short = 'C', long = "module_center")] + #[merge(strategy = overwrite_if_some)] + /// Modules that should be displayed on the center of the bar + pub center: Vec, + + #[arg(short = 'R', long = "module_right")] + #[merge(strategy = overwrite_if_some)] + /// Modules that should be displayed on the right of the bar + pub right: Vec, +} + +impl Default for ModuleLayout { + fn default() -> Self { + ModuleLayout { + left: vec!["workspaces".to_string(), "window".to_string()], + center: vec!["date".to_string(), "time".to_string()], + right: vec![ + "mpris".to_string(), + "volume".to_string(), + "cpu".to_string(), + "memory".to_string(), + "disk_space".to_string(), + ], + } + } +} + +impl ModuleLayout { + pub fn all(&self) -> impl Iterator { + self.left + .iter() + .chain(self.center.iter()) + .chain(self.right.iter()) + } +} diff --git a/core/src/config/prepare.rs b/core/src/config/prepare.rs new file mode 100644 index 0000000..db96f31 --- /dev/null +++ b/core/src/config/prepare.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use crate::{ + config::{style::ContainerStyle, theme::Theme, ConfigOptions}, + window::WindowRuntimeOptions, +}; + +/// Load the configured presets, then merge them with the [WindowRuntimeOptions] +pub fn merge_config( + opts: &WindowRuntimeOptions, + config_presets: &HashMap, + theme_presets: &HashMap, + style_presets: &HashMap, +) -> (ConfigOptions, Theme, ContainerStyle) { + let mut config = match config_presets.get(&opts.name) { + Some(config) => config.clone(), + None => { + log::error!("No such configuration: {}", opts.name); + ConfigOptions::default() + } + }; + config.merge_opt(opts.config.clone()); + + // TODO! Handle iced builtin themes + let mut theme = match theme_presets.get(&config.theme) { + Some(theme) => theme.clone(), + None => { + log::error!("No such theme: {}", config.theme); + Theme::default() + } + }; + theme.merge_opt(opts.theme.clone()); + + let mut style = match style_presets.get(&config.style) { + Some(style) => style.clone(), + None => { + log::error!("No such style: {}", config.style); + ContainerStyle::default() + } + }; + style.merge_opt(opts.style.clone()); + + (config, theme, style) +} diff --git a/src/event_action.rs b/core/src/config/source.rs similarity index 100% rename from src/event_action.rs rename to core/src/config/source.rs diff --git a/core/src/config/style.rs b/core/src/config/style.rs new file mode 100644 index 0000000..df69076 --- /dev/null +++ b/core/src/config/style.rs @@ -0,0 +1,285 @@ +use std::collections::HashMap; + +use clap::Args; +use iced::{Color, Padding}; +use merge::Merge; +use optfield::optfield; +use serde::{Deserialize, Serialize}; +use toml_example::TomlExample; + +use crate::{config::theme::Theme, helpers::merge::overwrite_if_some}; + +#[optfield( + pub StyleOverride, + attrs = add(derive(Default)), + field_doc, + field_attrs, + merge_fn = pub +)] +#[derive(Debug, Args, Merge, Clone, Serialize, Deserialize, PartialEq, TomlExample)] +#[serde(default)] +pub struct Style { + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// The size of text (and text icons) + pub font_size: f32, + + #[serde(with = "serde_with_color")] + #[arg(long, value_parser = clap_parse::color)] + #[toml_example(default = "#fff")] + #[merge(strategy = overwrite_if_some)] + /// The font color + pub color: ColorDescriptor, + + #[serde(with = "serde_with_color")] + #[arg(long, value_parser = clap_parse::color)] + #[toml_example(default = "$background")] + #[merge(strategy = overwrite_if_some)] + /// The background color + pub background_color: Option, + + #[serde(with = "serde_with_padding")] + #[arg(long, value_parser = clap_parse::color)] + #[toml_example(default = [0])] + #[merge(strategy = overwrite_if_some)] + /// The space around this item separating it from neighboring items + pub margin: Padding, +} + +impl Default for Style { + fn default() -> Self { + Self { + font_size: 16., + color: ColorDescriptor::ThemeColor("text".to_string()), + background_color: None, + margin: Padding::ZERO, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ColorDescriptor { + Color(Color), + ThemeColor(String), +} + +impl ColorDescriptor { + pub fn as_color(&self, theme: &Theme) -> Color { + match self { + ColorDescriptor::Color(color) => *color, + ColorDescriptor::ThemeColor(name) => theme.get_named_color(name), + } + } +} + +// Note: all fields from ContainerStyle need to be present for ContainerStyleOverride as well! +#[derive(Debug, Clone, Serialize, Deserialize, TomlExample, Default, PartialEq)] +pub struct ContainerStyle { + #[toml_example(nesting)] + /// The style of this item and default style for all contained items + pub style: Style, + + #[serde(with = "serde_with_padding")] + #[toml_example(default = [4, 10])] + /// The space around the contained items + pub padding: Padding, + + #[toml_example(nesting)] + /// Style classes available for all contained items + pub class: HashMap, +} + +#[derive(Debug, Default, Args, Merge, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct ContainerStyleOverride { + #[command(flatten)] + pub style: StyleOverride, + + #[serde(with = "serde_with_padding")] + #[arg(long, value_parser = clap_parse::padding)] + #[merge(strategy = overwrite_if_some)] + /// The space around the contained items + pub padding: Option, + + #[arg(skip)] + #[merge(skip)] + pub class: HashMap, +} + +impl ContainerStyle { + pub fn merge_opt( + &mut self, + ContainerStyleOverride { + style, + padding, + class, + }: ContainerStyleOverride, + ) { + self.style.merge_opt(style); + if let Some(padding) = padding { + self.padding = padding; + } + for (class, style_override) in class { + match self.class.get_mut(&class) { + Some(style) => style.merge_opt(style_override), + None => { + let mut style = Style::default(); + style.merge_opt(style_override); + self.class.insert(class, style); + } + } + } + } + + pub fn class(&self, class: &str) -> &Style { + self.class.get(class).unwrap_or(&self.style) + } +} + +mod clap_parse { + use iced::{Color, Padding}; + + use crate::config::style::ColorDescriptor; + + pub fn padding(value: &str) -> anyhow::Result { + let vec: Vec = value + .split(',') + .map(|v| v.trim().parse()) + .collect::>()?; + Ok(match vec.len() { + 1 => Padding::from(vec[0]), + 2 => Padding::from([vec[0], vec[1]]), + 4 => Padding::from([vec[0], vec[1], vec[2], vec[3]]), + _ => { + return Err(anyhow::anyhow!( + "Invalid number of values: expected 1, 2 or 4", + )); + } + }) + } + + pub fn color(value: &str) -> Result { + Ok(match value.strip_prefix('$') { + Some(name) => ColorDescriptor::ThemeColor(name.to_string()), + None => ColorDescriptor::Color(Color::from(csscolorparser::parse(value)?.to_array())), + }) + } +} + +mod serde_with_padding { + use iced::Padding; + use serde::{Deserializer, Serializer, de::Error as _, ser::SerializeSeq as _}; + + use crate::helpers::accept_option::{AcceptOption, ImplAcceptOption}; + + impl ImplAcceptOption for Padding {} + + pub fn serialize(value: &A, serializer: S) -> Result + where + S: Serializer, + A: AcceptOption, + { + let value = value.as_opt(); + let Some(value) = value else { + return serializer.serialize_none(); + }; + let vec = match value { + _ if value.top == value.bottom + && value.top == value.left + && value.top == value.right => + { + vec![value.top] + } + _ if value.top == value.bottom && value.left == value.right => { + vec![value.left, value.top] + } + _ => vec![value.top, value.right, value.bottom, value.left], + }; + if A::IS_OPTION { + serializer.serialize_some(&vec) + } else { + let mut seq = serializer.serialize_seq(Some(vec.len()))?; + for item in &vec { + seq.serialize_element(item)?; + } + seq.end() + } + } + pub fn deserialize<'de, D, A>(deserializer: D) -> Result + where + D: Deserializer<'de>, + A: AcceptOption, + { + let vec: Option> = A::deserialize_v(deserializer)?; + let padding = match vec { + Some(vec) => Some(match vec.len() { + 1 => Padding::from(vec[0]), + 2 => Padding::from([vec[0], vec[1]]), + 4 => Padding::from([vec[0], vec[1], vec[2], vec[3]]), + _ => { + return Err(D::Error::custom( + "Invalid number of values: expected 1, 2 or 4", + )); + } + }), + None => None, + }; + Ok(A::from_opt(padding)) + } +} + +mod serde_with_color { + use iced::Color; + use serde::{Deserializer, Serialize as _, Serializer, de::Error as _}; + + use crate::helpers::accept_option::{AcceptOption, ImplAcceptOption}; + + use super::ColorDescriptor; + + impl ImplAcceptOption for ColorDescriptor {} + + pub fn serialize(value: &A, serializer: S) -> Result + where + S: Serializer, + A: AcceptOption, + { + let value = value.as_opt(); + let Some(value) = value else { + return serializer.serialize_none(); + }; + let string = match value { + ColorDescriptor::Color(color) => { + let css_color = csscolorparser::Color::from(color.into_rgba8()); + match css_color.name() { + Some(name) => name.to_string(), + None => css_color.to_css_hex(), + } + } + ColorDescriptor::ThemeColor(name) => format!("${name}"), + }; + if A::IS_OPTION { + Some(string).serialize(serializer) + } else { + string.serialize(serializer) + } + } + pub fn deserialize<'de, D, A>(deserializer: D) -> Result + where + D: Deserializer<'de>, + A: AcceptOption, + { + let string: Option = A::deserialize_v(deserializer)?; + let color_descriptor = match string { + Some(string) => Some(match string.strip_prefix('$') { + Some(name) => ColorDescriptor::ThemeColor(name.to_string()), + None => ColorDescriptor::Color(Color::from( + csscolorparser::parse(&string) + .map_err(D::Error::custom)? + .to_array(), + )), + }), + None => None, + }; + Ok(A::from_opt(color_descriptor)) + } +} diff --git a/core/src/config/theme.rs b/core/src/config/theme.rs new file mode 100644 index 0000000..dcba6fc --- /dev/null +++ b/core/src/config/theme.rs @@ -0,0 +1,269 @@ +use std::collections::HashMap; + +use clap::Args; +use iced::{daemon::DefaultStyle, Color}; +use merge::Merge; +use optfield::optfield; +use serde::{Deserialize, Serialize}; +use toml_example::TomlExample; + +use crate::helpers::{ + merge::{overwrite_if_some, overwrite_or_append}, + serde_with::SerdeIntermediate, +}; + +#[optfield( + pub ThemeOverride, + attrs = add(derive(Default)), + field_doc, + field_attrs, + merge_fn = pub +)] +#[derive(Args, Merge, Debug, Clone, Serialize, Deserialize, TomlExample, PartialEq)] +pub struct Theme { + #[serde(with = "serde_with")] + #[toml_example(default = "rgba(0, 0, 0, 0.5)")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// The background of the bar + pub background: Color, + + #[serde(with = "serde_with")] + #[toml_example(default = "#0000")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// The background of the modules + pub mod_background: Color, + + #[serde(with = "serde_with")] + #[toml_example(default = "white")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// Normal text color + pub text: Color, + + #[serde(with = "serde_with")] + #[toml_example(default = "rgb(0, 0, 255)")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// Special/foreground text color + pub primary: Color, + + #[serde(with = "serde_with")] + #[toml_example(default = "#0f0")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// Color of success + pub success: Color, + + #[serde(with = "serde_with")] + #[toml_example(default = "red")] + #[arg(long, value_parser = clap_parse::color)] + #[merge(strategy = overwrite_if_some)] + /// Color of failure + pub danger: Color, + + #[serde(default, with = "SerdeIntermediate")] + #[toml_example(skip)] + #[arg( + long, + value_parser = clap_parse::custom_colors, + help = "Additional custom color variables\n \ + Example: `--custom \"my_color1=blue my_color2=#fed\"`" + )] + #[merge(strategy = overwrite_or_append)] + /// Additional custom color variables + pub custom: HashMap, +} + +impl Default for Theme { + fn default() -> Self { + Self { + background: iced::color!(0x000000, 0.5), + mod_background: iced::color!(0x000000, 0.), + text: iced::color!(0xffffff), + primary: iced::color!(0x0000ff), + success: iced::color!(0x00ff00), + danger: iced::color!(0xff0000), + custom: HashMap::default(), + } + } +} + +impl DefaultStyle for Theme { + fn default_style(&self) -> iced::daemon::Appearance { + iced::daemon::Appearance { + background_color: self.background, + text_color: self.text, + icon_color: self.primary, + } + } +} + +impl Theme { + pub fn get_named_color(&self, name: &str) -> Color { + match name { + "background" => self.background, + "mod_background" => self.mod_background, + "text" => self.text, + "primary" => self.primary, + "success" => self.success, + "danger" => self.danger, + custom => self.custom.get(custom).copied().unwrap_or_else(|| { + log::error!("{custom}: The theme does not contain any such color."); + Color::default() + }), + } + } +} + +mod clap_parse { + use std::collections::HashMap; + + use clap::Parser; + use iced::Color; + + pub fn color(value: &str) -> Result { + let color = csscolorparser::parse(value)?; + Ok(Color::from(color.to_array())) + } + + pub fn custom_colors(value: &str) -> anyhow::Result> { + #[derive(Parser)] + #[command(no_binary_name = true)] + struct Custom { + colors: Vec, + } + let custom = Custom::try_parse_from(value.split_whitespace())?; + custom + .colors + .into_iter() + .map(|pair| { + pair.split_once('=') + .ok_or(anyhow::anyhow!( + "Invalid key value pair, expected `VARIABLE=COLOR`" + )) + .and_then(|(k, v)| { + csscolorparser::parse(v) + .map(|v| (k.to_string(), Color::from(v.to_array()))) + .map_err(|e| anyhow::anyhow!("{v}: invalid color value: {e}")) + }) + }) + .collect::>() + } +} + +mod serde_with { + use iced::Color; + use serde::{Deserializer, Serialize as _, Serializer}; + + use crate::helpers::accept_option::{AcceptOption, ImplAcceptOption}; + + impl ImplAcceptOption for Color {} + + pub fn serialize(value: &A, serializer: S) -> Result + where + S: Serializer, + A: AcceptOption, + { + let value = value.as_opt(); + let Some(value) = value else { + return serializer.serialize_none(); + }; + let color = csscolorparser::Color::from(value.into_rgba8()); + if A::IS_OPTION { + Some(color).serialize(serializer) + } else { + color.serialize(serializer) + } + } + pub fn deserialize<'de, D, A>(deserializer: D) -> Result + where + D: Deserializer<'de>, + A: AcceptOption, + { + let color: Option = A::deserialize_v(deserializer)?; + Ok(A::from_opt(color.map(|c| Color::from(c.to_array())))) + } +} + +mod intermediate { + use std::collections::HashMap; + + use iced::Color; + use serde::{Deserialize, Serialize}; + + use crate::helpers::serde_with::ImplSerdeIntermediate; + + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + struct ColorIntermediate(#[serde(with = "super::serde_with")] Color); + + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + pub struct MapIntermediate(HashMap); + + impl From<&HashMap> for MapIntermediate { + fn from(value: &HashMap) -> Self { + Self( + value + .iter() + .map(|(k, v)| (k.clone(), ColorIntermediate(*v))) + .collect(), + ) + } + } + + impl From for HashMap { + fn from(value: MapIntermediate) -> Self { + value.0.into_iter().map(|(k, v)| (k, v.0)).collect() + } + } + + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + pub struct OptionIntermediate(Option); + + impl From<&Option>> for OptionIntermediate { + fn from(value: &Option>) -> Self { + OptionIntermediate(value.as_ref().map(Into::into)) + } + } + + impl From for Option> { + fn from(value: OptionIntermediate) -> Self { + value.0.map(Into::into) + } + } + + impl ImplSerdeIntermediate for HashMap {} + impl ImplSerdeIntermediate for Option> {} +} + +mod catalog { + use iced::widget::{container, text}; + + use crate::config::theme::Theme; + + type StyleFn<'a, Style> = Box Style + 'a>; + + impl container::Catalog for Theme { + type Class<'a> = StyleFn<'a, container::Style>; + fn default<'a>() -> Self::Class<'a> { + Box::new(|_| container::Style::default()) + } + fn style(&self, class: &Self::Class<'_>) -> container::Style { + class(self) + } + } + + impl text::Catalog for Theme { + type Class<'a> = StyleFn<'a, text::Style>; + fn default<'a>() -> Self::Class<'a> { + Box::new(|_| text::Style::default()) + } + fn style(&self, class: &Self::Class<'_>) -> text::Style { + class(self) + } + } +} diff --git a/core/src/config/window.rs b/core/src/config/window.rs new file mode 100644 index 0000000..c01fb9e --- /dev/null +++ b/core/src/config/window.rs @@ -0,0 +1,246 @@ +use clap::Args; +use merge::Merge; +use optfield::optfield; +use serde::{Deserialize, Serialize}; +use smithay_client_toolkit::shell::wlr_layer::{Anchor, KeyboardInteractivity}; +use toml_example::TomlExample; + +use crate::helpers::merge::overwrite_if_some; + +#[optfield( + pub WindowConfigOverride, + attrs = add(derive(Default)), + field_doc, + field_attrs, + merge_fn = pub +)] +#[derive(Args, Merge, Debug, Clone, Serialize, Deserialize, TomlExample)] +#[serde(default)] +pub struct WindowConfig { + #[arg(long, value_parser = clap_parser::parse_anchor)] + #[serde(with = "serde_with::anchor")] + #[toml_example(default = "Bottom")] + #[merge(strategy = overwrite_if_some)] + /// The anchor to use when positioning the window. May be `top`, `bottom`, `left` or `right` + pub anchor: Anchor, + + #[arg(long, value_parser = clap_parser::parse_monitor)] + #[serde(with = "serde_with::monitor")] + #[toml_example(enum)] + #[merge(strategy = overwrite_if_some)] + /// The monitor to open on + pub monitor: MonitorSelection, + + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// The height of the window + pub height: u32, + + #[arg(long)] + #[merge(strategy = overwrite_if_some)] + /// The width of the window + pub width: u32, + + #[arg(long, value_parser = clap_parser::parse_keyboard)] + #[serde(with = "serde_with::keyboard")] + #[toml_example(enum)] + #[merge(strategy = overwrite_if_some)] + /// Determines if the window should be focusable and receive keyboard inputs. May be `none`, + /// `on_demand` or `exclusive`. + pub keyboard_focus: KeyboardInteractivity, +} + +impl PartialEq for WindowConfig { + fn eq(&self, other: &Self) -> bool { + let monitor_equal = match self.monitor { + MonitorSelection::All => matches!(other.monitor, MonitorSelection::All), + MonitorSelection::Active => matches!(other.monitor, MonitorSelection::Active), + MonitorSelection::Name(ref name) => { + matches!(&other.monitor, MonitorSelection::Name(n) if n == name) + } + }; + self.anchor == other.anchor + && self.height == other.height + && self.width == other.width + && self.keyboard_focus == other.keyboard_focus + && monitor_equal + } +} + +impl Default for WindowConfig { + fn default() -> Self { + Self { + anchor: Anchor::BOTTOM, + monitor: MonitorSelection::Active, + height: 30, + width: 1000, + keyboard_focus: KeyboardInteractivity::None, + } + } +} + +#[derive(Debug, Clone)] +pub enum MonitorSelection { + All, + Active, + Name(String), +} + +mod clap_parser { + use smithay_client_toolkit::shell::wlr_layer::{Anchor, KeyboardInteractivity}; + + use super::MonitorSelection; + + pub fn parse_anchor(value: &str) -> Result { + Anchor::from_name(&value.to_uppercase()) + .ok_or("allowed anchors are: `top`, `bottom`, `left` and `right`") + } + + pub fn parse_keyboard(value: &str) -> Result { + Ok(match value { + "none" => KeyboardInteractivity::None, + "on_demand" => KeyboardInteractivity::OnDemand, + "exclusive" => KeyboardInteractivity::Exclusive, + _ => return Err( + "allowed keyboard_interactivity values are: `none`, `on_demand` and `exclusive`", + ), + }) + } + + pub fn parse_monitor(value: &str) -> Result { + Ok(match value { + "all" => MonitorSelection::All, + "active" => MonitorSelection::Active, + name => MonitorSelection::Name(String::from(name)), + }) + } +} + +mod serde_with { + use smithay_client_toolkit::shell::wlr_layer::{Anchor, KeyboardInteractivity}; + + use crate::helpers::accept_option::ImplAcceptOption; + + macro_rules! gen_serde_with { + ($mod_name:ident, $type:ty, [ $( ($variant_name:expr, $variant_value:path) ),* $(,)? ]) => { + pub mod $mod_name { + #[allow(unused_imports)] + use smithay_client_toolkit::shell::wlr_layer::Anchor; + #[allow(unused_imports)] + use smithay_client_toolkit::shell::wlr_layer::KeyboardInteractivity; + + use serde::{de::Error as _, ser::Error as _}; + + pub fn serialize(value: &A, serializer: S) -> Result + where + S: serde::ser::Serializer, + A: crate::helpers::accept_option::AcceptOption<$type>, + { + let value = value.as_opt(); + let Some(value) = value else { + return serializer.serialize_none(); + }; + let string = match *value { + $( + $variant_value => $variant_name, + )* + _ => { + return Err(S::Error::custom(format!( + "No string representation for {value:?} defined" + ))) + } + }; + if A::IS_OPTION { + serializer.serialize_some(string) + } else { + serializer.serialize_str(string) + } + } + pub fn deserialize<'de, D, A>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + A: crate::helpers::accept_option::AcceptOption<$type>, + { + let value: Option = A::deserialize_v(deserializer)?; + Ok(A::from_opt(match value { + Some(value) => Some(match value.to_lowercase().as_str() { + $( + $variant_name => $variant_value, + )* + _ => { + return Err(D::Error::custom(format!("Invalid value name: {value}"))) + } + }), + None => None, + })) + } + } + }; + } + + impl ImplAcceptOption for Anchor {} + + gen_serde_with!( + anchor, + Anchor, + [ + ("top", Anchor::TOP), + ("bottom", Anchor::BOTTOM), + ("left", Anchor::LEFT), + ("right", Anchor::RIGHT) + ] + ); + + impl ImplAcceptOption for KeyboardInteractivity {} + + gen_serde_with!( + keyboard, + KeyboardInteractivity, + [ + ("none", KeyboardInteractivity::None), + ("on_demand", KeyboardInteractivity::OnDemand), + ("exclusive", KeyboardInteractivity::Exclusive) + ] + ); + + pub mod monitor { + use crate::{config::window::MonitorSelection, helpers::accept_option::ImplAcceptOption}; + + impl ImplAcceptOption for MonitorSelection {} + + pub fn serialize(value: &A, serializer: S) -> Result + where + S: serde::ser::Serializer, + A: crate::helpers::accept_option::AcceptOption, + { + let value = value.as_opt(); + let Some(value) = value else { + return serializer.serialize_none(); + }; + let string = match value { + MonitorSelection::All => "all", + MonitorSelection::Active => "active", + MonitorSelection::Name(name) => name.as_str(), + }; + if A::IS_OPTION { + serializer.serialize_some(string) + } else { + serializer.serialize_str(string) + } + } + pub fn deserialize<'de, D, A>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + A: crate::helpers::accept_option::AcceptOption, + { + let value: Option = A::deserialize_v(deserializer)?; + Ok(A::from_opt(value.map( + |value| match value.to_lowercase().as_str() { + "all" => MonitorSelection::All, + "active" => MonitorSelection::Active, + name => MonitorSelection::Name(String::from(name)), + }, + ))) + } + } +} diff --git a/core/src/daemon.rs b/core/src/daemon.rs new file mode 100644 index 0000000..53a117c --- /dev/null +++ b/core/src/daemon.rs @@ -0,0 +1,119 @@ +use std::{ + fs, + io::Write as _, + path::{Path, PathBuf}, +}; + +use daemonize::Daemonize; +use iced::futures::{channel::mpsc::Sender, SinkExt as _}; +use log::{error, info}; +use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _}, + net::{UnixListener, UnixStream}, + sync::oneshot, +}; + +use crate::{ipc::IpcRequest, message::Message, state::State}; + +pub fn run( + windows: Vec, + daemonize: bool, + socket_path: PathBuf, + pid_path: PathBuf, + config_path: PathBuf, +) -> anyhow::Result<()> { + if daemonize { + daemonize_process()?; + } + + let mut pid_file = fs::File::create(&pid_path)?; + pid_file.write_all(std::process::id().to_string().as_bytes())?; + drop(pid_file); + + iced::daemon(State::title, State::update, State::view) + .subscription(State::subscribe) + .theme(State::theme) + .run_with(move || State::new(socket_path, pid_path, config_path, windows))?; + + Ok(()) +} + +pub async fn bind_to_ipc(sender: &mut Sender) -> anyhow::Result { + let (sx, rx) = oneshot::channel(); + sender + .send(Message::read_state(move |state| { + sx.send(state.socket_path.clone()).unwrap(); + })) + .await?; + let socket_path = rx.await?; + Ok(UnixListener::bind(&socket_path)?) +} + +pub async fn publish_ipc_commands( + sender: Sender, + listener: UnixListener, +) -> anyhow::Result<()> { + loop { + match listener.accept().await { + Ok((stream, _addr)) => { + let sender = sender.clone(); + tokio::spawn(async move { + if let Err(e) = handle_client(stream, sender).await { + error!("Communication with client failed: {e}") + } + }); + } + Err(e) => error!("Failed to accept an incoming IPC connection: {e}"), + } + } +} + +async fn handle_client(mut stream: UnixStream, mut sender: Sender) -> anyhow::Result<()> { + let mut msg_len = [0; 4]; + stream.read_exact(&mut msg_len).await?; + let msg_len = u32::from_ne_bytes(msg_len) as usize; + + let mut msg = vec![0; msg_len]; + stream.read_exact(&mut msg).await?; + let msg = String::from_utf8_lossy(&msg); + + match ron::from_str::(&msg) { + Ok(cmd) => { + let (sx, rx) = oneshot::channel(); + sender + .send(Message::IpcCommand { + request: cmd, + responder: sx, + }) + .await?; + let response = rx.await?; + stream + .write_all(ron::to_string(&response)?.as_bytes()) + .await?; + } + Err(e) => error!("Received an invalid IPC command: {msg}\n{e}"), + } + Ok(()) +} + +pub fn exit_cleanup(socket_path: &Path, pid_path: &Path) { + if let Err(e) = fs::remove_file(socket_path) { + error!("Could not remove socket file at {socket_path:?}: {e}"); + } + if let Err(e) = fs::remove_file(pid_path) { + error!("Could not remove PID file at {pid_path:?}: {e}"); + } +} + +fn daemonize_process() -> anyhow::Result<()> { + let daemon = Daemonize::new(); + + info!( + "Daemonizeing this process. \ + Run `crabbar close` to terminate the daemon." + ); + + daemon.start()?; + + Ok(()) +} diff --git a/core/src/directories.rs b/core/src/directories.rs new file mode 100644 index 0000000..5839ba4 --- /dev/null +++ b/core/src/directories.rs @@ -0,0 +1,78 @@ +use std::path::PathBuf; + +pub fn runtime_dir() -> std::ffi::OsString { + let mut fallback_dir = from_env_or("/tmp", "XDG_RUNTIME_DIR"); + fallback_dir.push("/crabbar"); + from_env_or(fallback_dir, "CRABBAR_RUN_DIR") +} + +pub fn log_file() -> std::ffi::OsString { + let home = std::env::var("HOME").unwrap(); + let fallback_dir = from_env_or(format!("{home}/.local/state"), "XDG_STATE_HOME"); + from_env_or( + format!( + "{}/crabbar.log", + fallback_dir.into_string().expect("Invalid UTF8") + ), + "CRABBAR_LOG_FILE", + ) +} + +pub fn config_dir() -> std::ffi::OsString { + let home = std::env::var("HOME").unwrap(); + let mut fallback_dir = from_env_or(format!("{home}/.config"), "XDG_CONFIG_HOME"); + fallback_dir.push("/crabbar"); + + from_env_or(fallback_dir, "CRABBAR_CONFIG_DIR") +} + +#[derive(Debug)] +pub struct ConfigRoot(PathBuf); + +impl Default for ConfigRoot { + fn default() -> Self { + Self(config_dir().into()) + } +} + +impl ConfigRoot { + pub fn new>(path: P) -> Self { + Self(path.into()) + } + + /// `TODO`! This cannot be predefined, it has to depend on the packaging. + /// https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s11.html + /// https://wiki.archlinux.org/title/Arch_package_guidelines#Directories + pub fn default_config_dir() -> Self { + ConfigRoot(PathBuf::from("/usr/share/crabbar")) + } + + pub fn root(&self) -> PathBuf { + self.0.clone() + } + + pub fn config(&self) -> PathBuf { + self.0.join("config.toml") + } + pub fn theme_dir(&self) -> PathBuf { + self.0.join("themes") + } + pub fn style_dir(&self) -> PathBuf { + self.0.join("styles") + } + pub fn module_dir(&self) -> PathBuf { + self.0.join("modules") + } + pub fn source_dir(&self) -> PathBuf { + self.0.join("sources") + } +} + +fn from_env_or, T: Into>( + default: T, + key: S, +) -> std::ffi::OsString { + std::env::var(key) + .map(Into::into) + .unwrap_or_else(|_| default.into()) +} diff --git a/core/src/helpers/accept_option.rs b/core/src/helpers/accept_option.rs new file mode 100644 index 0000000..926fab4 --- /dev/null +++ b/core/src/helpers/accept_option.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use iced::Color; +use serde::{de::DeserializeOwned, Deserialize, Deserializer}; + +/// For the purpose of overwriting configuration presets at runtime, configuration structs are +/// automatically duplicated with all fields wrapped in [Option]. [AcceptOption] makes it easy to +/// generate implementations that accept both the standard field type [T] and the wrapped +/// [Option] +pub trait AcceptOption { + /// If true, [Self] is [Option]. If false, [Self] is [T]. + const IS_OPTION: bool; + fn as_opt(&self) -> Option<&T>; + fn as_opt_mut(&mut self) -> Option<&mut T>; + fn into_opt(self) -> Option; + fn from_opt(opt: Option) -> Self; + fn deserialize_v<'de, D, V>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + V: DeserializeOwned; +} +impl AcceptOption for T +where + T: ImplAcceptOption, +{ + const IS_OPTION: bool = false; + fn as_opt(&self) -> Option<&T> { + Some(self) + } + fn as_opt_mut(&mut self) -> Option<&mut T> { + Some(self) + } + fn into_opt(self) -> Option { + Some(self) + } + fn from_opt(opt: Option) -> Self { + opt.unwrap() + } + fn deserialize_v<'de, D, V>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + V: DeserializeOwned, + { + Ok(Some(V::deserialize(deserializer)?)) + } +} +impl AcceptOption for Option +where + T: AcceptOption, +{ + const IS_OPTION: bool = true; + fn as_opt(&self) -> Option<&T> { + self.as_ref() + } + fn as_opt_mut(&mut self) -> Option<&mut T> { + self.as_mut() + } + fn into_opt(self) -> Option { + self + } + fn from_opt(opt: Option) -> Self { + opt + } + fn deserialize_v<'de, D, V>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + V: DeserializeOwned, + { + as Deserialize>::deserialize(deserializer) + } +} + +/// A helper trait that allows to automatically implement [AcceptOption] for [T] by implementing +/// [ImplAcceptOption] for [T] +pub trait ImplAcceptOption {} + +impl ImplAcceptOption for String {} +impl ImplAcceptOption for Vec {} +impl ImplAcceptOption for u32 {} +impl ImplAcceptOption for f32 {} +impl ImplAcceptOption for bool {} +impl ImplAcceptOption for HashMap {} diff --git a/core/src/helpers/merge.rs b/core/src/helpers/merge.rs new file mode 100644 index 0000000..5502a6e --- /dev/null +++ b/core/src/helpers/merge.rs @@ -0,0 +1,26 @@ +use crate::helpers::accept_option::AcceptOption; + +pub fn overwrite_if_some(left: &mut T, right: T) +where + T: AcceptOption, +{ + let opt_right = right.into_opt(); + if opt_right.is_some() { + *left = AcceptOption::from_opt(opt_right); + } +} + +pub fn overwrite_or_append(left: &mut T, right: T) +where + T: AcceptOption, + V: Extend + IntoIterator, +{ + let opt_left = left.as_opt_mut(); + let opt_right = right.into_opt(); + if let Some(right) = opt_right { + match opt_left { + Some(left) => left.extend(right), + None => *left = AcceptOption::from_opt(Some(right)), + } + } +} diff --git a/core/src/helpers/mod.rs b/core/src/helpers/mod.rs new file mode 100644 index 0000000..0d5c408 --- /dev/null +++ b/core/src/helpers/mod.rs @@ -0,0 +1,4 @@ +pub mod accept_option; +pub mod merge; +pub mod serde_with; +pub mod task_constructor; diff --git a/core/src/helpers/serde_with.rs b/core/src/helpers/serde_with.rs new file mode 100644 index 0000000..95e1711 --- /dev/null +++ b/core/src/helpers/serde_with.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub trait SerdeIntermediate +where + Self: Sized, +{ + fn serialize<'a, S>(&'a self, serilizer: S) -> Result + where + S: Serializer, + I: From<&'a Self> + Serialize; + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + I: Into + Deserialize<'de>; +} + +impl SerdeIntermediate for T +where + T: ImplSerdeIntermediate, +{ + fn serialize<'a, S>(&'a self, serializer: S) -> Result + where + S: Serializer, + I: From<&'a Self> + Serialize, + { + I::from(self).serialize(serializer) + } + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + I: Into + Deserialize<'de>, + { + Ok(I::deserialize(deserializer)?.into()) + } +} + +pub trait ImplSerdeIntermediate {} diff --git a/core/src/helpers/task_constructor.rs b/core/src/helpers/task_constructor.rs new file mode 100644 index 0000000..c71c46a --- /dev/null +++ b/core/src/helpers/task_constructor.rs @@ -0,0 +1,45 @@ +use std::marker::PhantomData; + +use iced::Task; + +use crate::message::Message; + +#[allow(clippy::type_complexity)] +/// Used to create a [Task] using a type [T] that is not in the scope. When the type [T] comes into +/// scope, the [TaskConstructor] can be build into a [Task]. +pub struct TaskConstructor { + constructors: Vec Task>>, + _phantom: PhantomData<(T, M)>, +} + +impl Default for TaskConstructor { + fn default() -> Self { + Self::new() + } +} + +impl TaskConstructor +where + M: 'static, +{ + pub fn new() -> Self { + TaskConstructor { + constructors: vec![], + _phantom: PhantomData, + } + } + + pub fn chain(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut T) -> Task + 'static, + { + self.constructors.push(Box::new(f)); + self + } + + pub fn build(self, t: &mut T) -> Task { + self.constructors + .into_iter() + .fold(Task::none(), |task, constructor| task.chain(constructor(t))) + } +} diff --git a/core/src/ipc.rs b/core/src/ipc.rs new file mode 100644 index 0000000..1169024 --- /dev/null +++ b/core/src/ipc.rs @@ -0,0 +1,113 @@ +use crate::{ + config::{style::ContainerStyle, theme::Theme, ConfigOptions}, + window::{Window, WindowCommand, WindowRuntimeOptions}, +}; +use std::{ + collections::HashMap, + fs, + io::{Read as _, Write as _}, + os::unix::net::UnixStream, + path::Path, +}; + +use clap::Subcommand; +use serde::{Deserialize, Serialize}; + +#[derive(Subcommand, Debug, Deserialize, Serialize)] +pub enum IpcRequest { + #[command(name = "list")] + /// List all open windows + ListWindows, + /// List all available configuration presets + Configs, + /// List all available modules + Modules, + /// List all available themes + Themes, + /// List all available styles + Styles, + /// Perform a window action + Window { + #[arg(long, global = true)] + /// Optional ID of the window. Will fallback to the most recently opened if not specified. + id: Option, + #[command(subcommand)] + cmd: Box, + }, + #[command(display_order = 1)] + /// Close `crabbar` (with all windows) + Close, +} + +#[derive(Subcommand, Debug, Deserialize, Serialize)] +pub enum WindowRequest { + /// Open a new window + Open(Box), + /// Close a window + Close { + #[arg(short = 'A', long)] + /// Close all open windows + all: bool, + }, + /// Reopen a window to apply settings like bar height/width + Reopen { + #[arg(short = 'A', long)] + /// Reopen all open windows + all: bool, + }, + #[command(flatten)] + Command(WindowCommand), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum IpcResponse { + WindowList(HashMap), + ConfigList(HashMap), + ModuleList(Vec), + ThemeList(HashMap), + StyleList(HashMap), + Window { + id: Vec, + event: WindowResponse, + }, + Closing, + Error(String), +} + +impl IpcResponse { + pub fn error(msg: S) -> Self { + Self::Error(msg.to_string()) + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WindowResponse { + Opened, + Closed, + Reopened, + Config(ConfigOptions), + Theme(Theme), + Style(ContainerStyle), + ConfigApplied, + ThemeApplied, + StyleApplied, +} + +pub fn request(request: IpcRequest, socket_path: &Path) -> anyhow::Result { + if !fs::exists(socket_path)? { + return Err(anyhow::anyhow!( + "The crabbar daemon is not running or \ + the wrong runtime directory is used." + )); + } + let mut stream = UnixStream::connect(socket_path)?; + + let write_buf = ron::to_string(&request)?; + let buf_len = write_buf.len() as u32; + stream.write_all(&buf_len.to_ne_bytes())?; + stream.write_all(write_buf.as_bytes())?; + + let mut response = String::new(); + stream.read_to_string(&mut response)?; + Ok(ron::from_str(&response)?) +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..1006068 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,87 @@ +pub use derive; + +use crate::{config::theme::Theme, message::Message}; + +pub mod config; +pub mod daemon; +pub mod directories; +mod helpers; +pub mod ipc; +mod message; +pub mod module; +pub mod registry; +mod state; +pub mod subscription; +pub mod template_engine; +pub mod window; + +pub type Element<'a> = iced::Element<'a, Message, Theme, iced::Renderer>; + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use iced::{Color, Padding}; + use toml_example::TomlExample; + + use crate::config::{ + ConfigOptions, GlobalConfig, MainConfig, + style::{ColorDescriptor, ContainerStyle, Style}, + theme::Theme, + }; + + #[test] + fn verify_default_main_config() { + let example = MainConfig::toml_example(); + assert_eq!( + toml::from_str::(&example).unwrap(), + MainConfig { + bar: [(String::from("example"), ConfigOptions::default())].into(), + ..Default::default() + } + ); + } + + #[test] + fn verify_default_global_config() { + let example = GlobalConfig::toml_example(); + assert_eq!( + toml::from_str::(&example).unwrap(), + GlobalConfig::default() + ); + } + + #[test] + fn verify_default_config() { + let example = ConfigOptions::toml_example(); + assert_eq!( + toml::from_str::(&example).unwrap(), + ConfigOptions::default() + ); + } + + #[test] + fn verify_default_style_config() { + let example = ContainerStyle::toml_example(); + let default_style = Style { + font_size: 16., + color: ColorDescriptor::Color(Color::WHITE), + background_color: None, + margin: Padding::ZERO, + }; + assert_eq!( + toml::from_str::(&example).unwrap(), + ContainerStyle { + style: default_style.clone(), + padding: Padding::from([4, 10]), + class: HashMap::from([(String::from("example"), default_style)]) + } + ); + } + + #[test] + fn verify_default_theme_config() { + let example = Theme::toml_example(); + assert_eq!(toml::from_str::(&example).unwrap(), Theme::default()); + } +} diff --git a/core/src/message.rs b/core/src/message.rs new file mode 100644 index 0000000..b565a06 --- /dev/null +++ b/core/src/message.rs @@ -0,0 +1,106 @@ +use std::{fmt::Debug, sync::Arc}; + +use crate::{ + config::GlobalConfig, + ipc::{IpcRequest, IpcResponse}, +}; +use iced::{ + event::wayland::OutputEvent, + futures::{channel::mpsc, Sink}, +}; +use smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput; +use tokio::sync::oneshot; + +use crate::{state::State, subscription::Subscription}; + +pub struct UpdateFn(pub Arc>); + +impl Debug for UpdateFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "UpdateFn(Arc)") + } +} + +#[allow(clippy::type_complexity)] +/// Capturing closure that is executed with read access to [T] +pub struct ReadFn(Arc>); +impl Debug for ReadFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ReadFn<{}>", std::any::type_name::()) + } +} +impl ReadFn { + pub fn execute(self, t: &T) { + Arc::into_inner(self.0).unwrap()(t) + } +} + +#[derive(Debug)] +pub enum Message { + ReadState(ReadFn), + Update(Vec), + ReloadConfig, + OutputEvent { + event: Box, + wl_output: WlOutput, + }, + OutputsReady, + IpcCommand { + request: IpcRequest, + responder: oneshot::Sender, + }, +} + +impl Message { + /// Can be used to capture a [oneshot::Sender] and return a value based on the current [State]. + /// See [MessageSenderExt] + pub fn read_state(f: F) -> Self + where + F: FnOnce(&State) + Send + Sync + 'static, + { + Self::ReadState(ReadFn(Arc::new(Box::new(f)))) + } +} + +pub trait MessageSenderExt +where + Self: Sink + Unpin, + >::Error: Debug, +{ + async fn send(&mut self, msg: M) { + iced::futures::SinkExt::send(self, msg) + .await + .unwrap_or_else(|e| log::error!("Internal error: Message could not be send: {e:#?}")); + } + /// Execute the given closure with read access to the [State], then return its output + async fn read_with(&mut self, f: F) -> OUT + where + F: FnOnce(&State) -> OUT + Send + Sync + 'static, + OUT: Debug + Send + Sync + 'static; + async fn read_config(&mut self) -> Arc; + async fn read_subscriptions(&mut self) -> Vec; +} + +impl MessageSenderExt for mpsc::Sender { + async fn read_with(&mut self, f: F) -> OUT + where + F: FnOnce(&State) -> OUT + Send + Sync + 'static, + OUT: Debug + Send + Sync + 'static, + { + let (sx, rx) = oneshot::channel(); + self.send(Message::read_state(move |state| sx.send(f(state)).unwrap())) + .await; + rx.await.unwrap() + } + async fn read_config(&mut self) -> Arc { + self.read_with(|state| state.config.clone()).await + } + async fn read_subscriptions(&mut self) -> Vec { + // TODO! + self.read_with(|_state| { + let subs = vec![]; + subs + }) + .await + } +} diff --git a/core/src/module.rs b/core/src/module.rs new file mode 100644 index 0000000..4ea57ea --- /dev/null +++ b/core/src/module.rs @@ -0,0 +1,160 @@ +use std::{collections::HashMap, fmt::Debug}; + +use downcast_rs::{Downcast, impl_downcast}; +use smithay_client_toolkit::shell::wlr_layer::Anchor; +use toml::Table; + +pub use custom::CustomModules; + +use crate::{ + Element, + config::{ + style::{ContainerStyle, ContainerStyleOverride}, + theme::Theme, + }, + registry::Registry, + template_engine::TemplateEngine, +}; + +pub type Context = HashMap>; + +pub trait Module: Downcast + Debug + Send + Sync { + fn variant_names(&self) -> Vec<&str>; + + fn active(&self) -> bool { + true + } + + fn view( + &self, + variant: &str, + anchor: &Anchor, + context: &Context, + theme: &Theme, + style: &ContainerStyle, + ) -> Element<'_>; + + #[allow(unused_variables)] + fn default_style(&self, variant: &str) -> ContainerStyleOverride { + ContainerStyleOverride::default() + } + + #[allow(unused_variables)] + fn sources(&self, variant: &str) -> Vec<&String> { + vec![] + } + + #[allow(unused_variables)] + fn read_config(&mut self, variant: &str, config: Table, engine: &TemplateEngine) {} +} +impl_downcast!(Module); + +pub fn register_modules(registry: &mut Registry) { + registry.register_module::(); + registry.register_module::(); +} + +mod dummy { + use derive::Builder; + use iced::widget::text; + + use crate::{ + Element, + config::{style::ContainerStyle, theme::Theme}, + module::Module, + }; + + #[derive(Builder, Debug)] + pub struct DummyModule; + + impl Module for DummyModule { + fn variant_names(&self) -> Vec<&str> { + vec!["dummy"] + } + fn view( + &self, + _variant: &str, + _anchor: &smithay_client_toolkit::shell::wlr_layer::Anchor, + _context: &super::Context, + _theme: &Theme, + _style: &ContainerStyle, + ) -> Element<'_> { + text!("This is a dummy module!").into() + } + } +} + +mod custom { + use std::{collections::HashMap, fmt::Debug}; + + use derive::Builder; + use toml::Table; + + use crate::{ + Element, + config::{style::ContainerStyle, theme::Theme}, + message::Message, + template_engine::Token, + }; + + use super::Module; + + #[derive(Builder, Default, Debug)] + pub struct CustomModules { + modules: HashMap, + } + + struct CustomModule { + _sources: Vec, + _config: Table, + token: Box + Send + Sync>, + } + + impl Debug for CustomModule { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "CustomModule") + } + } + + impl Module for CustomModules { + fn variant_names(&self) -> Vec<&str> { + self.modules.keys().map(String::as_str).collect() + } + fn view( + &self, + variant: &str, + anchor: &smithay_client_toolkit::shell::wlr_layer::Anchor, + context: &super::Context, + theme: &Theme, + style: &ContainerStyle, + ) -> Element<'_> { + let Some(custom) = self.modules.get(variant) else { + log::error!("Invalid variant name of custom module: {variant}"); + return "Invalid variant name".into(); + }; + custom.token.render(context, anchor, style, theme) + } + fn read_config( + &mut self, + variant: &str, + config: Table, + engine: &crate::template_engine::TemplateEngine, + ) { + let format = match config.get("format") { + Some(toml::Value::String(fmt)) => fmt, + _ => { + log::warn!("No format was specified for the custom module!"); + "No format specified" + } + }; + self.modules.insert( + String::from(variant), + CustomModule { + _sources: vec![], + token: engine.render_token(format), + _config: config, + }, + ); + } + } +} diff --git a/core/src/registry.rs b/core/src/registry.rs new file mode 100644 index 0000000..fbc2334 --- /dev/null +++ b/core/src/registry.rs @@ -0,0 +1,152 @@ +use std::{ + any::{Any, TypeId}, + collections::{HashMap, HashSet}, +}; + +use merge::Merge as _; + +use crate::{config::style::ContainerStyleOverride, module::Module}; + +pub trait Builder: Any { + type Output; + fn build() -> Self::Output; +} + +#[allow(clippy::type_complexity)] +#[derive(Default, Debug)] +pub struct Registry { + modules: HashMap>, + module_variants: HashMap, + variant_styles: HashMap, + resolvers: HashMap Option<(TypeId, String)>>, +} + +impl Registry { + pub fn register_module(&mut self) + where + T::Output: Module, + { + let output = T::build(); + let type_id = TypeId::of::(); + for name in output.variant_names() { + self.module_variants.insert(name.to_string(), type_id); + self.variant_styles + .insert(name.to_string(), output.default_style(name)); + } + self.modules.insert(type_id, Box::new(output)); + } + + pub fn try_get_module(&self) -> Option<&T> { + let id = &TypeId::of::(); + self.modules.get(id).and_then(|t| t.downcast_ref::()) + } + + pub fn try_get_module_mut(&mut self) -> Option<&mut T> { + let id = &TypeId::of::(); + self.modules.get_mut(id).and_then(|t| t.downcast_mut::()) + } + + pub fn get_module(&self) -> &T { + self.try_get_module().unwrap() + } + + pub fn get_module_mut(&mut self) -> &mut T { + self.try_get_module_mut().unwrap() + } + + pub fn module_by_name_mut(&mut self, name: &String) -> Option<(&TypeId, &mut Box)> { + self.module_variants + .get(name) + .and_then(|id| self.modules.get_mut(id).map(|m| (id, m))) + } + + pub fn iter_mut(&mut self) -> impl Iterator> { + self.modules.values_mut() + } + + pub fn iter_enabled<'a, 'b, I>( + &'a self, + enabled: I, + ) -> impl Iterator, &'a ContainerStyleOverride)> + where + I: Iterator, + { + enabled.filter_map(|variant| { + self.module_variants + .get(variant) + .copied() + .or_else(|| { + self.resolvers + .get(variant) + .and_then(|f| f().and_then(|(id, v)| (v == *variant).then_some(id))) + }) + .and_then(|id| self.modules.get(&id)) + .map(|module| (variant, module, &self.variant_styles[variant])) + }) + } + + pub fn iter_enabled_mut<'a, I>( + &'a mut self, + enabled: I, + ) -> impl Iterator> + where + I: Iterator, + { + let resolver_types = self + .resolvers + .values() + .filter_map(|r| r().map(|(id, _)| id)) + .collect::>(); + let type_ids = self + .module_variants + .iter() + .map(|(k, v)| (k.as_str(), v)) + .collect::>(); + let enabled: HashSet<&str> = enabled.collect(); + self.modules.values_mut().filter(move |m| { + let names = m.variant_names(); + names.into_iter().any(|name| { + enabled.contains(name) + // TODO: explain why this is needed (if it is needed) + || type_ids + .get(name) + .is_some_and(|ty| resolver_types.contains(*ty)) + }) + }) + } + + pub fn add_resolver(&mut self, name: S, f: fn() -> Option<(TypeId, String)>) { + self.resolvers.insert(name.to_string(), f); + } + + pub fn module_names(&self) -> impl Iterator { + self.module_variants.keys() + } + + pub fn add_module_names(&mut self, type_id: TypeId, names: impl Iterator) { + let module = &self.modules[&type_id]; + for variant in names { + let style = module.default_style(&variant); + self.module_variants.insert(variant.clone(), type_id); + self.variant_styles.insert(variant, style); + } + } + + pub fn remove_module_names(&mut self, names: impl Iterator) { + for name in names { + self.module_variants.remove(&name); + self.variant_styles.remove(&name); + } + } + + pub fn set_style_override( + &mut self, + type_id: &TypeId, + variant: &str, + mut style: ContainerStyleOverride, + ) { + let default = self.modules[type_id].default_style(variant); + style.merge(default); + *self.variant_styles.get_mut(variant).unwrap() = style; + } +} diff --git a/core/src/state.rs b/core/src/state.rs new file mode 100644 index 0000000..8f0ec85 --- /dev/null +++ b/core/src/state.rs @@ -0,0 +1,353 @@ +use crate::{ + Element, + config::{ConfigOptions, GlobalConfig, prepare, style::ContainerStyle, theme::Theme}, + daemon, + directories::ConfigRoot, + ipc::{IpcRequest, IpcResponse, WindowRequest, WindowResponse}, + message::Message, + module::register_modules, + registry::Registry, + template_engine::TemplateEngine, + window::{Window, WindowRuntimeOptions}, +}; +use std::{ + collections::{HashMap, VecDeque}, + path::PathBuf, + sync::Arc, + time::Duration, +}; + +use iced::{ + Task, event::wayland, platform_specific::shell::commands::layer_surface::destroy_layer_surface, + window::Id, +}; +use log::{error, info}; +use smithay_client_toolkit::{ + output::OutputInfo, reexports::client::protocol::wl_output::WlOutput, +}; +use tokio::time::sleep; + +pub type Outputs = Vec<(WlOutput, Option)>; + +#[derive(Debug, Default)] +pub struct State { + pub socket_path: PathBuf, + pid_path: PathBuf, + outputs: Outputs, + /// If false, we have to wait for new Outputs before opening a window + outputs_ready: bool, + pub windows: HashMap, + window_ids: HashMap, + opening_queue: VecDeque, + pub subscriptions: Vec>, + /// Every opened window gets a unique ID equal to the count of windows opened beforehand + id_count: usize, + pub config_root: ConfigRoot, + pub config: Arc, + /// Window configuration presets + pub config_presets: HashMap, + pub themes: HashMap, + pub styles: HashMap, + pub registry: Registry, + pub engine: TemplateEngine, +} + +impl State { + pub fn new( + socket_path: PathBuf, + pid_path: PathBuf, + config_path: PathBuf, + windows: Vec, + ) -> (Self, Task) { + let mut registry = Registry::default(); + register_modules(&mut registry); + + let mut state = State { + socket_path, + pid_path, + config_root: ConfigRoot::new(config_path), + registry, + ..Default::default() + }; + + state.load_config(); + + let task = windows + .into_iter() + .fold(Task::none(), |task, config_preset| { + task.chain( + state + .open_window(WindowRuntimeOptions::new(config_preset)) + .0, + ) + }); + + (state, task) + } + + pub fn title(&self, _id: Id) -> String { + "crabbar".to_string() + } + + pub fn update(&mut self, msg: Message) -> Task { + use Message::*; + match msg { + ReadState(f) => f.execute(self), + Update(updates) => { + for updatefn in updates { + Arc::into_inner(updatefn.0).unwrap()() + } + } + ReloadConfig => { + log::info!("Reloading configuration"); + self.load_config(); + for window in self + .windows + .values_mut() + .filter(|w| w.wants_hot_reloading()) + { + let (config, theme, style) = prepare::merge_config( + window.runtime_options(), + &self.config_presets, + &self.themes, + &self.styles, + ); + window.reload_config(config, theme, style); + } + } + OutputEvent { event, wl_output } => match *event { + wayland::OutputEvent::Created(info_maybe) => { + let first_output = self.outputs.is_empty(); + log::debug!("got new output: {info_maybe:#?}"); + self.outputs.push((wl_output, info_maybe)); + if !self.outputs_ready && first_output { + return Task::future(async { + sleep(Duration::from_millis(500)).await; + Message::OutputsReady + }); + } + } + wayland::OutputEvent::InfoUpdate(info) => { + if let Some((_, info_maybe)) = + self.outputs.iter_mut().find(|(wlo, _)| wlo == &wl_output) + { + *info_maybe = Some(info); + } + } + wayland::OutputEvent::Removed => { + let pos = self.outputs.iter().position(|(wlo, _)| wlo == &wl_output); + if let Some(pos) = pos { + self.outputs.remove(pos); + } + } + }, + OutputsReady => { + log::debug!("Outputs are ready"); + self.outputs_ready = true; + return self.flush_opening_queue(); + } + IpcCommand { request, responder } => { + info!("Received ipc request: {request:?}"); + + let mut task = Task::none(); + let response = match request { + IpcRequest::ListWindows => IpcResponse::WindowList( + self.windows + .values() + .map(|w| (w.naive_id(), w.clone())) + .collect(), + ), + IpcRequest::Configs => IpcResponse::ConfigList(self.config_presets.clone()), + IpcRequest::Modules => { + IpcResponse::ModuleList(self.registry.module_names().cloned().collect()) + } + IpcRequest::Themes => IpcResponse::ThemeList(self.themes.clone()), + IpcRequest::Styles => IpcResponse::StyleList(self.styles.clone()), + IpcRequest::Close => { + info!("closing the daemon"); + daemon::exit_cleanup(&self.socket_path, &self.pid_path); + task = iced::exit(); + IpcResponse::Closing + } + IpcRequest::Window { cmd, id } => { + let cmd: WindowRequest = *cmd; + match cmd { + WindowRequest::Open(opts) => { + info!("Opening new window"); + let naive_id; + (task, naive_id) = self.open_window(*opts); + IpcResponse::Window { + id: vec![naive_id], + event: WindowResponse::Opened, + } + } + _ => { + if self.windows.is_empty() { + IpcResponse::error("Command failed because no windows are open") + } else if id.is_some_and(|id| !self.window_ids.contains_key(&id)) { + IpcResponse::error("No window with the specified ID is open") + } else { + let (naive_id, window_id) = id + .map_or_else( + || self.window_ids.iter().last(), + |id| self.window_ids.get_key_value(&id), + ) + .map(|(k, v)| (*k, *v)) + .expect("Previously checked"); + let response; + (response, task) = + self.handle_window_request(cmd, window_id, naive_id); + response + } + } + } + } + }; + if responder.send(response).is_err() { + error!("IPC response channel closed"); + } + return task; + } + } + Task::none() + } + + fn handle_window_request( + &mut self, + cmd: WindowRequest, + window_id: Id, + naive_id: usize, + ) -> (IpcResponse, Task) { + let mut task = Task::none(); + ( + match cmd { + WindowRequest::Close { all } => { + if all { + let (naive_ids, window_ids): (Vec, Vec) = + self.window_ids.drain().collect(); + self.windows.clear(); + info!( + "Closing all open windows ({} windows are open)", + naive_ids.len() + ); + for window_id in window_ids { + task = task.chain(destroy_layer_surface(window_id)); + } + IpcResponse::Window { + id: naive_ids, + event: WindowResponse::Closed, + } + } else { + self.window_ids.remove(&naive_id); + self.windows.remove(&window_id); + info!("Closing window with id {naive_id}"); + task = destroy_layer_surface(window_id); + IpcResponse::Window { + id: vec![naive_id], + event: WindowResponse::Closed, + } + } + } + WindowRequest::Reopen { all } => { + if all { + let (naive_ids, window_ids): (Vec, Vec) = + self.window_ids.iter().collect(); + info!( + "Reopening all open windows ({} windows are open)", + naive_ids.len() + ); + for window_id in window_ids { + task = task + .chain(destroy_layer_surface(window_id)) + .chain(self.windows[&window_id].open(&self.outputs)); + } + IpcResponse::Window { + id: naive_ids, + event: WindowResponse::Reopened, + } + } else { + info!("Reopening window with id {naive_id}"); + task = destroy_layer_surface(window_id) + .chain(self.windows[&window_id].open(&self.outputs)); + IpcResponse::Window { + id: vec![naive_id], + event: WindowResponse::Reopened, + } + } + } + WindowRequest::Command(cmd) => { + if let Some(window) = self.windows.get_mut(&window_id) { + let (response, cmd_task) = window.handle_ipc(cmd); + task = task.chain(cmd_task.build(self)); + IpcResponse::Window { + id: vec![naive_id], + event: response, + } + } else { + unreachable!("window_id has been verified beforehand"); + } + } + WindowRequest::Open(_) => unreachable!(), + }, + task, + ) + } + + pub fn view(&self, id: Id) -> Element<'_> { + match self.windows.get(&id) { + Some(window) => window.view(&self.registry), + None => "Invalid window ID".into(), + } + } + + pub fn theme(&self, id: Id) -> Theme { + match self.windows.get(&id) { + Some(window) => window.theme(), + None => { + error!("Internal error: requested theme for invalid window ID"); + Theme::default() + } + } + } + + fn open_window(&mut self, opts: WindowRuntimeOptions) -> (Task, usize) { + let naive_id = self.id_count; + let (config, theme, style) = + prepare::merge_config(&opts, &self.config_presets, &self.themes, &self.styles); + + let window = Window::new(naive_id, opts, config, theme, style); + + let mut task = Task::none(); + log::debug!("Should the window be opened? {}", self.outputs_ready); + + if self.outputs_ready { + task = window.open(&self.outputs); + } else { + self.opening_queue.push_back(window.window_id()); + } + self.window_ids.insert(naive_id, window.window_id()); + self.windows.insert(window.window_id(), window); + self.id_count += 1; + (task, naive_id) + } + + fn flush_opening_queue(&mut self) -> Task { + let mut task = Task::none(); + while let Some(id) = self.opening_queue.pop_front() { + task = task.chain(self.windows[&id].open(&self.outputs)); + } + task + } + + pub fn reopen_window(&self, window_id: &Id) -> Task { + self.windows[window_id].reopen(&self.outputs) + } + + pub fn hot_reloading(&self) -> bool { + self.config.hot_reloading + && self + .windows + .values() + .any(|window| window.wants_hot_reloading()) + } +} diff --git a/core/src/subscription/mod.rs b/core/src/subscription/mod.rs new file mode 100644 index 0000000..316ef5f --- /dev/null +++ b/core/src/subscription/mod.rs @@ -0,0 +1,168 @@ +use std::{fmt::Debug, sync::Arc, time::Duration}; + +use iced::{ + event::{listen_with, wayland, PlatformSpecific}, + futures::future::BoxFuture, + stream, +}; +use log::{error, warn}; +use tokio::{sync::mpsc, time::sleep}; + +use crate::{ + daemon, + message::{Message, MessageSenderExt as _, UpdateFn}, + state::State, +}; + +mod reload; + +impl State { + pub fn subscribe(&self) -> iced::Subscription { + let module_subs = iced::Subscription::run(|| { + stream::channel(1, |mut sender| async move { + let subs = sender.read_subscriptions().await; + let (sx, mut rx) = mpsc::channel(1); + + let config = sender.read_config().await; + + for sub in subs { + sub.run(&sx); + } + + drop(sx); + + let mut updates = vec![]; + loop { + tokio::select! { + all_subs_dropped = async { + loop { + match rx.recv().await { + Some(SubscriptionUpdate::Buffered(update)) => { + updates.push(update); + } + Some(SubscriptionUpdate::Immediate(update)) => { + updates.push(update); + return false; + } + None => { + warn!("All Subscription senders dropped, canceling Subscription"); + return true; + } + } + } + } => if all_subs_dropped {return;}, + _ = sleep(Duration::from_secs_f32(config.reload_interval)) => {} + } + sender + .send(Message::Update(std::mem::take(&mut updates))) + .await; + } + }) + }); + + let reload_watcher = match self.hot_reloading() { + true => reload::subscription(), + false => iced::Subscription::none(), + }; + + iced::Subscription::batch([ + reload_watcher, + // Window events + listen_with(|event, _, _| { + if let iced::Event::PlatformSpecific(PlatformSpecific::Wayland( + wayland::Event::Output(event, wl_output), + )) = event + { + Some(Message::OutputEvent { + event: Box::new(event), + wl_output, + }) + } else { + None + } + }), + // IPC commands + iced::Subscription::run(|| { + stream::channel(1, |mut sender| async move { + let listener = match daemon::bind_to_ipc(&mut sender).await { + Ok(l) => l, + Err(e) => { + error!("Failed to bind to IPC socket: {e}"); + return; + } + }; + if let Err(e) = daemon::publish_ipc_commands(sender, listener).await { + error!("IPC publisher failed: {e}"); + } + }) + }), + module_subs, + ]) + } +} + +pub enum SubscriptionUpdate { + Buffered(UpdateFn), + Immediate(UpdateFn), +} + +impl SubscriptionUpdate { + pub fn buffered(f: F) -> Self + where + F: Fn() + Send + Sync + 'static, + { + Self::Buffered(UpdateFn(Arc::new(Box::new(f)))) + } + + pub fn immediate(f: F) -> Self + where + F: Fn() + Send + Sync + 'static, + { + Self::Immediate(UpdateFn(Arc::new(Box::new(f)))) + } +} + +#[allow(clippy::type_complexity)] +pub struct Subscription( + Option< + Box< + dyn FnOnce(mpsc::Sender) -> BoxFuture<'static, ()> + + Send + + Sync + + 'static, + >, + >, +); + +impl Debug for Subscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Subscription(Option) + -> BoxFuture<'static, ()> + 'static>>)" + ) + } +} + +impl Subscription { + pub fn new(f: F) -> Self + where + F: FnOnce(mpsc::Sender) -> BoxFuture<'static, ()> + + Send + + Sync + + 'static, + { + Self(Some(Box::new(f))) + } + + pub fn none() -> Self { + Self(None) + } + + fn run(self, sx: &mpsc::Sender) { + if let Some(f) = self.0 { + let sx = sx.clone(); + tokio::spawn(f(sx)); + } + } +} diff --git a/core/src/subscription/reload.rs b/core/src/subscription/reload.rs new file mode 100644 index 0000000..6976c1c --- /dev/null +++ b/core/src/subscription/reload.rs @@ -0,0 +1,40 @@ +use std::time::Duration; + +use iced::{futures::executor, stream, Subscription}; +use notify::{ + event::ModifyKind, Config, Error, Event, EventKind, RecommendedWatcher, RecursiveMode, + Watcher as _, +}; +use tokio::time::sleep; + +use crate::message::{Message, MessageSenderExt as _}; + +pub fn subscription() -> Subscription { + Subscription::run(|| { + stream::channel(1, |mut sender| async move { + let config_path = sender.read_with(|state| state.config_root.root()).await; + + let mut watcher = RecommendedWatcher::new( + move |result: Result| { + let event = result.unwrap(); + + if matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) { + executor::block_on(async { + sender.send(Message::ReloadConfig).await; + }); + } + }, + Config::default(), + ) + .unwrap(); + + watcher + .watch(&config_path, RecursiveMode::Recursive) + .unwrap(); + + loop { + sleep(Duration::from_secs(1)).await; + } + }) + }) +} diff --git a/core/src/template_engine.rs b/core/src/template_engine.rs new file mode 100644 index 0000000..c3dbfe5 --- /dev/null +++ b/core/src/template_engine.rs @@ -0,0 +1,94 @@ +use std::{collections::HashMap, fmt::Debug}; + +use smithay_client_toolkit::shell::wlr_layer::Anchor; + +use crate::{ + Element, + config::{style::ContainerStyle, theme::Theme}, + message::Message, + module::Context, +}; + +pub trait Token: Send + Sync { + fn render<'a>( + &'a self, + context: &Context, + anchor: &Anchor, + style: &ContainerStyle, + theme: &Theme, + ) -> Element<'a>; +} + +type ToTokenRenderer = fn(&TemplateEngine, &str) -> Box>; + +pub struct TemplateEngine { + token_registry: HashMap<&'static str, ToTokenRenderer>, +} + +impl Debug for TemplateEngine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TemplateEngine") + } +} + +impl Default for TemplateEngine { + fn default() -> Self { + Self::new() + } +} + +impl TemplateEngine { + pub fn new() -> Self { + Self { + token_registry: HashMap::from([("text", Self::text as ToTokenRenderer)]), + } + } + + pub fn render_token(&self, content: &str) -> Box> { + if let Some((wrapper, cnt)) = self.parse_wrapper(content) { + self.token_registry[wrapper](self, cnt) + } else { + Box::new(TextToken(content.to_string())) + } + } + + fn parse_wrapper<'a>(&self, content: &'a str) -> Option<(&'a str, &'a str)> { + let i1 = content.find('('); + let i2 = content.rfind(')'); + i1.and_then(|i1| i2.map(|i2| (i1, i2))) + .map(|(i1, i2)| { + let x = content.split_at(i1); + (x.0, x.1.split_at(i2 - i1).0) + }) + .and_then(|(z1, z2)| { + let mut chars = z2.chars(); + (chars.next().is_some() && self.token_registry.contains_key(&z1)) + .then_some((z1, chars.as_str())) + }) + } + + fn text(&self, content: &str) -> Box> { + Box::new(TextToken(String::from(content))) + } +} + +struct TextToken(String); + +impl Token for TextToken { + fn render<'a>( + &'a self, + _context: &Context, + _anchor: &Anchor, + style: &ContainerStyle, + theme: &Theme, + ) -> Element<'a> { + let style = style.class("text"); + iced::widget::container( + iced::widget::text(&self.0) + .size(style.font_size) + .color(style.color.as_color(theme)), + ) + .padding(style.margin) + .into() + } +} diff --git a/core/src/window.rs b/core/src/window.rs new file mode 100644 index 0000000..1a12b8c --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,280 @@ +use std::collections::HashMap; + +use clap::{Args, Subcommand}; +use iced::{ + Background, + Length::Fill, + Task, + platform_specific::shell::commands::layer_surface::{destroy_layer_surface, get_layer_surface}, + runtime::platform_specific::wayland::layer_surface::{IcedOutput, SctkLayerSurfaceSettings}, + widget::{Row, container}, + window::Id, +}; +use log::info; +use merge::Merge as _; +use serde::{Deserialize, Serialize}; +use smithay_client_toolkit::shell::wlr_layer::Layer; + +use crate::{ + Element, + config::{ + ConfigOptionOverride, ConfigOptions, prepare, + style::{ContainerStyle, ContainerStyleOverride}, + theme::{Theme, ThemeOverride}, + window::MonitorSelection, + }, + helpers::task_constructor::TaskConstructor, + ipc::WindowResponse, + message::Message, + registry::Registry, + state::{Outputs, State}, +}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Window { + naive_id: usize, + #[serde(skip, default = "id_default")] + window_id: Id, + runtime_options: WindowRuntimeOptions, + config: ConfigOptions, + theme: Theme, + style: ContainerStyle, + /// TODO! Remove this + dummy: String, +} + +#[derive(Args, Debug, Clone, Deserialize, Serialize)] +/// Configurations options that apply only to this specific window instance. They are applied by +/// CLI arguments at window creation and can be changed at runtime through the IPC. +pub struct WindowRuntimeOptions { + #[arg(default_value = "example")] + /// Name of the configuration to use + pub name: String, + + #[command(flatten)] + pub config: ConfigOptionOverride, + + #[command(flatten)] + pub theme: ThemeOverride, + + #[command(flatten)] + pub style: ContainerStyleOverride, +} + +impl WindowRuntimeOptions { + pub fn new(name: String) -> Self { + Self { + name, + config: ConfigOptionOverride::default(), + theme: ThemeOverride::default(), + style: ContainerStyleOverride::default(), + } + } +} + +fn id_default() -> Id { + Id::NONE +} + +#[derive(Subcommand, Debug, Deserialize, Serialize)] +pub enum WindowCommand { + /// Print the current configuration + GetConfig, + /// Print the current theme variables + GetTheme, + /// Print the current styles + GetStyle, + /// Override configuration settings + SetConfig { + #[arg(short, long)] + /// Reopen the window to ensure all settings are applied + reopen: bool, + #[command(flatten)] + cfg: ConfigOptionOverride, + }, + /// Override theme variables + SetTheme(ThemeOverride), + /// Override style settings + SetStyle(ContainerStyleOverride), +} + +impl Window { + pub fn new( + id: usize, + runtime_options: WindowRuntimeOptions, + config: ConfigOptions, + theme: Theme, + style: ContainerStyle, + ) -> Self { + Self { + naive_id: id, + window_id: Id::unique(), + runtime_options, + config, + theme, + style, + dummy: String::from("dummy"), + } + } + + pub fn view<'a>(&'a self, registry: &'a Registry) -> Element<'a> { + let content = registry + .iter_enabled(self.config.modules.all().chain([&self.dummy])) + .fold(Row::new(), |row, (variant_name, module, style)| { + let mut mod_style = self.style.clone(); + mod_style.merge_opt(style.clone()); + + let module_view = module.view( + variant_name, + &self.config.window.anchor, + &HashMap::new(), + &self.theme, + &mod_style, + ); + row.push( + container(module_view) + .padding(mod_style.padding) + .style(move |theme| iced::widget::container::Style { + background: mod_style + .style + .background_color + .clone() + .map(|b| Background::Color(b.as_color(theme))), + ..Default::default() + }), + ) + }); + + iced::widget::container(content) + .padding(self.style.padding) + .style(|theme| iced::widget::container::Style { + icon_color: Some(theme.primary), + text_color: Some(theme.text), + background: Some(iced::Background::Color(theme.background)), + ..Default::default() + }) + .width(Fill) + .height(Fill) + .into() + } + + pub fn handle_ipc(&mut self, cmd: WindowCommand) -> (WindowResponse, TaskConstructor) { + use WindowCommand::*; + let mut task = TaskConstructor::new(); + let response = match cmd { + GetConfig => WindowResponse::Config(self.config.clone()), + GetTheme => WindowResponse::Theme(self.theme.clone()), + GetStyle => WindowResponse::Style(self.style.clone()), + SetConfig { reopen, cfg } => { + if reopen { + let window_id = self.window_id; + task.chain(move |state: &mut State| state.reopen_window(&window_id)); + } + if cfg.theme.is_some() || cfg.style.is_some() { + let window_id = self.window_id; + task.chain(move |state: &mut State| { + let window = state.windows.get_mut(&window_id).unwrap(); + let (config, theme, style) = prepare::merge_config( + &window.runtime_options, + &state.config_presets, + &state.themes, + &state.styles, + ); + window.reload_config(config, theme, style); + Task::none() + }); + } else { + self.config.merge_opt(cfg.clone()); + } + self.runtime_options.config.merge(cfg); + WindowResponse::ConfigApplied + } + SetTheme(theme) => { + self.theme.merge_opt(theme.clone()); + self.runtime_options.theme.merge(theme); + WindowResponse::ThemeApplied + } + SetStyle(style) => { + self.style.merge_opt(style.clone()); + self.runtime_options.style.merge(style); + WindowResponse::StyleApplied + } + }; + (response, task) + } + + pub fn open(&self, outputs: &Outputs) -> Task { + info!("opening window with id {}", self.naive_id); + let (output, info) = match &self.config.window.monitor { + MonitorSelection::All => (IcedOutput::All, None), + MonitorSelection::Active => (IcedOutput::Active, None), + MonitorSelection::Name(name) => outputs + .iter() + .find(|(_, info)| { + info.as_ref() + .is_some_and(|info| info.name.as_ref() == Some(name)) + }) + .map(|(o, info)| (IcedOutput::Output(o.clone()), info.as_ref())) + .unwrap_or_else(|| { + log::error!("No output with name {name} could be found!"); + (IcedOutput::Active, None) + }), + }; + + let (_x, _y) = info + .as_ref() + .and_then(|i| i.logical_size.map(|(x, y)| (x as u32, y as u32))) + .unwrap_or((1920, 1080)); + + get_layer_surface(SctkLayerSurfaceSettings { + layer: Layer::Top, + keyboard_interactivity: self.config.window.keyboard_focus, + // exclusive_zone: self.config.exclusive_zone(), + // size: self.config.dimension(x, y), + size: Some(( + Some(self.config.window.width), + Some(self.config.window.height), + )), + anchor: self.config.window.anchor, + namespace: format!("crabbar{}", self.naive_id), + exclusive_zone: 30, + output, + // margin: (&self.config.style.margin).into(), + id: self.window_id, + ..Default::default() + }) + } + + pub fn reopen(&self, outputs: &Outputs) -> Task { + info!("Reopening window with id {}", self.naive_id); + destroy_layer_surface(self.window_id).chain(self.open(outputs)) + } + + pub fn window_id(&self) -> Id { + self.window_id + } + + pub fn naive_id(&self) -> usize { + self.naive_id + } + + pub fn theme(&self) -> Theme { + let theme = self.theme.clone(); + log::info!("applied theme: {theme:#?}"); + theme + } + + pub fn wants_hot_reloading(&self) -> bool { + self.config.hot_reloading + } + + pub fn reload_config(&mut self, config: ConfigOptions, theme: Theme, style: ContainerStyle) { + self.config = config; + self.theme = theme; + self.style = style; + } + + pub fn runtime_options(&self) -> &WindowRuntimeOptions { + &self.runtime_options + } +} diff --git a/crabbar/Cargo.toml b/crabbar/Cargo.toml new file mode 100644 index 0000000..ee931e8 --- /dev/null +++ b/crabbar/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crabbar" +version.workspace = true +edition.workspace = true + +[dependencies] +core.workspace = true + +log.workspace = true +anyhow.workspace = true +fern.workspace = true +clap.workspace = true +ctrlc.workspace = true +ron.workspace = true +chrono.workspace = true +nix.workspace = true +toml-example.workspace = true + +[lints] +workspace = true diff --git a/crabbar/src/cli.rs b/crabbar/src/cli.rs new file mode 100644 index 0000000..fae0b0e --- /dev/null +++ b/crabbar/src/cli.rs @@ -0,0 +1,183 @@ +use core::{ + config::{style::ContainerStyle, theme::Theme, MainConfig}, + daemon, directories, + ipc::{self, IpcRequest, IpcResponse, WindowResponse}, +}; +use std::{fs, path::PathBuf}; + +use clap::{Parser, Subcommand}; +use log::{error, info}; +use nix::unistd::Pid; +use toml_example::TomlExample as _; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct CliArgs { + #[arg(long, default_value = directories::runtime_dir())] + /// Runtime directory to be used for IPC socket communication + run_dir: PathBuf, + #[arg(short, long, default_value = directories::config_dir())] + /// Path of the main configuration directory + config_dir: PathBuf, + #[arg(long, default_value = directories::log_file())] + /// Path of the logfile. + pub log_file: PathBuf, + #[arg(long, global = true)] + /// Set the logging level to debug + pub debug: bool, + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + #[command(display_order = 0)] + /// Open the `crabbar` daemon + Open { + #[arg(short = 'D', long)] + /// Keep `crabbar` attached to this terminal + dont_daemonize: bool, + /// Open windows using the given configuration presets + windows: Vec, + }, + /// Print the default global and window configuration + DefaultConfig, + /// Print the default style + DefaultStyle, + /// Print the default theme + DefaultTheme, + #[command(flatten)] + Ipc(IpcRequest), +} + +pub fn handle_cli_commands(args: CliArgs) -> anyhow::Result<()> { + let socket_path = args.run_dir.join("crabbar.sock"); + + match args.command { + Command::Open { + dont_daemonize, + windows, + } => { + std::fs::create_dir_all(&args.run_dir)?; + let pid_path = args.run_dir.join("crabbar.pid"); + + if fs::exists(&socket_path)? { + if fs::read_to_string(&pid_path) + .ok() + .and_then(|s| s.parse::().ok()) + .is_some_and(|pid| nix::sys::signal::kill(Pid::from_raw(pid), None).is_ok()) + { + return Err(anyhow::anyhow!("`crabbar` is running already!")); + } + info!( + "The previous crabbar instance did not exit gracefully, removing the socket file." + ); + if let Err(e) = fs::remove_file(&socket_path) { + error!("Could not remove socket file at {socket_path:?}: {e}"); + } + } + + let socket_path2 = socket_path.clone(); + let pid_path2 = pid_path.clone(); + ctrlc::set_handler(move || { + daemon::exit_cleanup(&socket_path2, &pid_path2); + std::process::exit(0); + })?; + + daemon::run( + windows, + !dont_daemonize, + socket_path, + pid_path, + args.config_dir, + )?; + } + Command::DefaultConfig => println!("{}", MainConfig::toml_example()), + Command::DefaultStyle => println!("{}", ContainerStyle::toml_example()), + Command::DefaultTheme => println!("{}", Theme::toml_example()), + Command::Ipc(cmd) => { + let response = ipc::request(cmd, &socket_path)?; + match response { + IpcResponse::WindowList(windows) => match windows.is_empty() { + true => info!("No windows are open!"), + false => { + info!("{} windows are open:", windows.len(),); + let mut windows: Vec<_> = windows.into_iter().collect(); + windows.sort_by_key(|&(id, _)| id); + for (id, window) in windows { + println!("{id}:\t{window:#?}") + } + } + }, + IpcResponse::ConfigList(presets) => match presets.is_empty() { + true => info!("No configuration presets are available!"), + false => { + info!("{} configuration presets are available:", presets.len(),); + let mut presets: Vec<_> = presets.into_iter().collect(); + presets.sort_by_key(|(id, _)| id.clone()); + for (name, preset) in presets { + println!("{name}:\t{preset:#?}") + } + } + }, + IpcResponse::ModuleList(mut modules) => match modules.is_empty() { + true => info!("No modules are available!"), + false => { + info!("{} modules available:", modules.len(),); + modules.sort(); + println!("\t{}", modules.join(", ")); + } + }, + IpcResponse::ThemeList(themes) => match themes.is_empty() { + true => info!("No themes are available!"), + false => { + info!("{} themes are available:", themes.len(),); + let mut themes: Vec<_> = themes.into_iter().collect(); + themes.sort_by_key(|(id, _)| id.clone()); + for (name, theme) in themes { + println!("{name}:\t{theme:#?}") + } + } + }, + IpcResponse::StyleList(styles) => match styles.is_empty() { + true => info!("No styles are available!"), + false => { + info!("{} styles are available:", styles.len(),); + let mut styles: Vec<_> = styles.into_iter().collect(); + styles.sort_by_key(|(id, _)| id.clone()); + for (name, style) in styles { + println!("{name}:\t{style:#?}") + } + } + }, + IpcResponse::Closing => info!("Closing the crabbar daemon."), + IpcResponse::Error(msg) => error!("{msg}"), + IpcResponse::Window { id, event } => match event { + WindowResponse::Opened => info!("Opened new window with id {id:?}"), + WindowResponse::Closed => info!("Closed window with id {id:?}"), + WindowResponse::Reopened => info!("Reopened window with id {id:?}"), + WindowResponse::Config(cfg) => { + info!("Configuration of window with id {id:?}:\n{cfg:#?}") + } + WindowResponse::Theme(theme) => { + info!("Theme of window with id {id:?}:\n{theme:#?}") + } + WindowResponse::Style(style) => { + info!("Style of window with id {id:?}:\n{style:#?}") + } + WindowResponse::ConfigApplied => info!("The configuration has been updated"), + WindowResponse::ThemeApplied => info!("The theme has been updated"), + WindowResponse::StyleApplied => info!("The style has been updated"), + }, + } + } + } + + Ok(()) +} + +impl CliArgs { + pub fn command_is_open(&self) -> bool { + matches!(self.command, Command::Open { .. }) + } +} diff --git a/crabbar/src/logger.rs b/crabbar/src/logger.rs new file mode 100644 index 0000000..11e47e5 --- /dev/null +++ b/crabbar/src/logger.rs @@ -0,0 +1,40 @@ +use std::path::Path; + +use fern::colors::ColoredLevelConfig; + +pub fn init(log_path: &Path, debug: bool, command_is_open: bool) -> anyhow::Result<()> { + let colors = ColoredLevelConfig::new() + .trace(fern::colors::Color::BrightBlue) + .debug(fern::colors::Color::BrightMagenta) + .info(fern::colors::Color::Blue) + .warn(fern::colors::Color::Magenta) + .error(fern::colors::Color::Red); + + let level = match debug { + true => log::LevelFilter::Debug, + false => log::LevelFilter::Info, + }; + + let dispatch = fern::Dispatch::new() + .format(move |out, msg, record| { + out.finish(format_args!( + "[{} {} {}] {}", + chrono::Local::now().format("%d/%m/%Y %H:%M:%S:%.3f"), + colors.color(record.level()), + record.target(), + msg + )) + }) + .level(log::LevelFilter::Warn) + .level_for("crabbar", level) + .level_for("crabbar_core", level) + .chain(std::io::stdout()); + + if command_is_open { + dispatch.chain(fern::log_file(log_path)?).apply()? + } else { + dispatch.apply()? + } + + Ok(()) +} diff --git a/crabbar/src/main.rs b/crabbar/src/main.rs new file mode 100644 index 0000000..173d127 --- /dev/null +++ b/crabbar/src/main.rs @@ -0,0 +1,14 @@ +use clap::Parser as _; +use cli::{handle_cli_commands, CliArgs}; + +mod cli; +mod logger; + +fn main() -> anyhow::Result<()> { + let args = CliArgs::parse(); + log::info!("{args:#?}"); + + logger::init(&args.log_file, args.debug, args.command_is_open())?; + + handle_cli_commands(args) +} diff --git a/crates/bar-rs_derive/Cargo.lock b/crates/bar-rs_derive/Cargo.lock deleted file mode 100644 index 1d78bb2..0000000 --- a/crates/bar-rs_derive/Cargo.lock +++ /dev/null @@ -1,47 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bar-rs_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "2.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/crates/bar-rs_derive/Cargo.toml b/crates/bar-rs_derive/Cargo.toml deleted file mode 100644 index 111e921..0000000 --- a/crates/bar-rs_derive/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "bar-rs_derive" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.92" -quote = "1.0.38" -syn = "2.0.94" diff --git a/default_config/horizontal.ini b/default_config/horizontal.ini deleted file mode 100644 index e13460e..0000000 --- a/default_config/horizontal.ini +++ /dev/null @@ -1,58 +0,0 @@ -[general] -# monitor = DP-1 -anchor = top -hot_reloading = true -# hard_reloading = true - -[modules] -left = workspaces, window -center = date, time -right = media, volume, cpu, memory - -[style] -spacing = 10 20 20 -padding = 0 10 - -[module_style] -font_size = 17 -icon_size = 20 -text_color = white -icon_color = white - -[module:time] -icon_size = 24 - -[module:battery] -spacing = 5 - -[module:media] -spacing = 20 - -[module:hyprland.workspaces] -active_color = black -active_background = rgba(255, 255, 255, 0.6) -active_padding = -2 10 -1 5 -spacing = 15 - -[module:wayfire.workspaces] -(0, 0) = 󰈹 -(1, 0) =  -(2, 0) = 󰓓 -(0, 1) =  -(1, 1) =  -fallback_icon =  -icon_size = 18 - -[module:wayfire.window] -max_length = 50 - -[module:niri.workspaces] -spacing = 15 -padding = 0 12 0 6 -icon_margin = -2 0 0 0 -icon_size = 25 -active_size = 25 - -[module:niri.window] -max_length = 50 -# show_app_id = true diff --git a/default_config/vertical.ini b/default_config/vertical.ini deleted file mode 100644 index 29a1e0f..0000000 --- a/default_config/vertical.ini +++ /dev/null @@ -1,32 +0,0 @@ -[general] -# monitor = DP-1 -anchor = left -hot_reloading = true -# hard_reloading = true - -[style] -padding = 20 5 -width = 65 - -[modules] -left = time, date -center = workspaces -right = volume, battery, cpu, memory - -[module:time] -icon_size = 24 - -[module:date] -format = %a %d. %b - -[module:battery] -format = {{capacity}}% - -[module:hyprland.workspaces] -active_color = black -active_background = rgba(255, 255, 255, 0.5) - -[module:niri.workspaces] -icon_size = 24 -active_size = 24 -icon_margin = 0 0 0 20 diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..c87b1ec --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "crabbar-derive" +version.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +syn.workspace = true +quote.workspace = true + +[lints] +workspace = true diff --git a/crates/bar-rs_derive/src/lib.rs b/derive/src/lib.rs similarity index 93% rename from crates/bar-rs_derive/src/lib.rs rename to derive/src/lib.rs index 459ca5a..fafbdbe 100644 --- a/crates/bar-rs_derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; +use syn::{Data, DeriveInput, Fields, parse_macro_input}; #[proc_macro_derive(Builder)] pub fn derive_builder(input: TokenStream) -> TokenStream { diff --git a/install.sh b/install.sh deleted file mode 100644 index 4539c12..0000000 --- a/install.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -directory=$(dirname $(realpath "$0")) - -sed -i "s|project_path=\"\"|project_path=\"$directory\"|" $directory/bar-rs - -cp_cmd="cp $directory/bar-rs /usr/local/bin" -chmod_cmd="chmod +x /usr/local/bin/bar-rs" - -if [ "$UID" -ne 0 -a "$EUID" -ne 0 ]; then - sudo $cp_cmd - sudo $chmod_cmd -else - $cp_cmd - $chmod_cmd -fi - -sed -i "s|project_path=\"$directory\"|project_path=\"\"|" $directory/bar-rs - -echo -e "Uninstall bar-rs by running \`bar-rs uninstall\`\n" -echo You need to build the project before you can open the bar: -echo -e "\`cargo build --release\` to build for release (recommended)" -echo -e "\`cargo build\` to build for debug (not recommended)" - -echo Done diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..dc85c99 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +error_on_line_overflow = true diff --git a/src/button.rs b/src/button.rs deleted file mode 100644 index c741fc5..0000000 --- a/src/button.rs +++ /dev/null @@ -1,487 +0,0 @@ -/// Literally 100% copypasta from https://github.com/iced-rs/iced/blob/master/widget/src/button.rs -use iced::core::widget::tree; -use iced::core::{keyboard, overlay, renderer, touch}; -use iced::{ - core::{ - event, layout, mouse, - widget::{Operation, Tree}, - Clipboard, Layout, Shell, Widget, - }, - id::Id, - widget::button::{Catalog, Status, Style, StyleFn}, - Element, Event, Length, Padding, Rectangle, Size, -}; -use iced::{Background, Color, Vector}; - -type EventHandlerFn<'a, Message> = Box< - dyn Fn( - iced::Event, - iced::core::Layout, - iced::mouse::Cursor, - &mut dyn iced::core::Clipboard, - &Rectangle, - ) -> Message - + 'a, ->; - -enum ButtonEventHandler<'a, Message> -where - Message: Clone, -{ - Message(Message), - F(EventHandlerFn<'a, Message>), - FMaybe(EventHandlerFn<'a, Option>), -} - -impl ButtonEventHandler<'_, Message> -where - Message: Clone, -{ - fn get( - &self, - event: iced::Event, - layout: iced::core::Layout, - cursor: iced::mouse::Cursor, - clipboard: &mut dyn iced::core::Clipboard, - viewport: &Rectangle, - ) -> Option { - match self { - ButtonEventHandler::Message(msg) => Some(msg.clone()), - ButtonEventHandler::F(f) => Some(f(event, layout, cursor, clipboard, viewport)), - ButtonEventHandler::FMaybe(f) => f(event, layout, cursor, clipboard, viewport), - } - } -} - -pub struct Button<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> -where - Renderer: iced::core::Renderer, - Theme: Catalog, - Message: Clone, -{ - content: Element<'a, Message, Theme, Renderer>, - on_event: Option>, - id: Id, - width: Length, - height: Length, - padding: Padding, - clip: bool, - class: Theme::Class<'a>, -} - -impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> -where - Renderer: iced::core::Renderer, - Theme: Catalog, - Message: Clone, -{ - /// Creates a new [`Button`] with the given content. - pub fn new(content: impl Into>) -> Self { - let content = content.into(); - let size = content.as_widget().size_hint(); - - Button { - content, - id: Id::unique(), - on_event: None, - width: size.width.fluid(), - height: size.height.fluid(), - padding: Padding::ZERO, - clip: false, - class: Theme::default(), - } - } - - /// Defines the on_event action of the [`Button`] - pub fn on_event(mut self, msg: Message) -> Self { - self.on_event = Some(ButtonEventHandler::Message(msg)); - self - } - - /// Defines the on_event action of the [`Button`], if Some - pub fn on_event_maybe(mut self, msg: Option) -> Self { - if let Some(msg) = msg { - self.on_event = Some(ButtonEventHandler::Message(msg)); - } - self - } - - /// Determines the on_event action of the [`Button`] using a closure - pub fn on_event_with(mut self, f: F) -> Self - where - F: Fn( - iced::Event, - iced::core::Layout, - iced::mouse::Cursor, - &mut dyn iced::core::Clipboard, - &Rectangle, - ) -> Message - + 'a, - { - self.on_event = Some(ButtonEventHandler::F(Box::new(f))); - self - } - - /// Determines the on_event action of the [`Button`] with a closure, if Some - pub fn on_event_maybe_with(self, f: Option) -> Self - where - F: Fn( - iced::Event, - iced::core::Layout, - iced::mouse::Cursor, - &mut dyn iced::core::Clipboard, - &Rectangle, - ) -> Message - + 'a, - { - if let Some(f) = f { - self.on_event_with(f) - } else { - self - } - } - - /// Determines the on_event action of the [`Button`] using a closure which might return a Message - pub fn on_event_try(mut self, f: F) -> Self - where - F: Fn( - iced::Event, - iced::core::Layout, - iced::mouse::Cursor, - &mut dyn iced::core::Clipboard, - &Rectangle, - ) -> Option - + 'a, - { - self.on_event = Some(ButtonEventHandler::FMaybe(Box::new(f))); - self - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets whether the contents of the [`Button`] should be clipped on - /// overflow. - pub fn clip(mut self, clip: bool) -> Self { - self.clip = clip; - self - } - - /// Sets the style of the [`Button`]. - #[must_use] - pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self - where - Theme::Class<'a>: From>, - { - self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); - self - } - - /// Sets the [`Id`] of the [`Button`]. - pub fn id(mut self, id: Id) -> Self { - self.id = id; - self - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -struct State { - is_hovered: bool, - is_pressed: bool, - is_focused: bool, -} - -impl<'a, Message, Theme, Renderer> Widget - for Button<'a, Message, Theme, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + iced::core::Renderer, - Theme: Catalog, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State::default()) - } - - fn children(&self) -> Vec { - vec![Tree::new(&self.content)] - } - - fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.content)); - } - - fn size(&self) -> Size { - Size { - width: self.width, - height: self.height, - } - } - - fn layout( - &self, - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout::padded(limits, self.width, self.height, self.padding, |limits| { - self.content - .as_widget() - .layout(&mut tree.children[0], renderer, limits) - }) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation, - ) { - operation.container(None, layout.bounds(), &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &Rectangle, - ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( - &mut tree.children[0], - event.clone(), - layout.children().next().unwrap(), - cursor, - renderer, - clipboard, - shell, - viewport, - ) { - return event::Status::Captured; - } - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) - | Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_event.is_some() { - let bounds = layout.bounds(); - - if cursor.is_over(bounds) { - let state = tree.state.downcast_mut::(); - - state.is_pressed = true; - - return event::Status::Captured; - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) - | Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_event.as_ref() { - let state = tree.state.downcast_mut::(); - - if state.is_pressed { - state.is_pressed = false; - - let bounds = layout.bounds(); - - if cursor.is_over(bounds) { - if let Some(msg) = - on_press.get(event, layout, cursor, clipboard, viewport) - { - shell.publish(msg); - } - } - - return event::Status::Captured; - } - } - } - Event::Keyboard(keyboard::Event::KeyPressed { ref key, .. }) => { - if let Some(on_press) = self.on_event.as_ref() { - let state = tree.state.downcast_mut::(); - if state.is_focused - && matches!(key, keyboard::Key::Named(keyboard::key::Named::Enter)) - { - state.is_pressed = true; - if let Some(msg) = on_press.get(event, layout, cursor, clipboard, viewport) - { - shell.publish(msg); - } - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) - | Event::Mouse(mouse::Event::CursorLeft) => { - let state = tree.state.downcast_mut::(); - state.is_hovered = false; - state.is_pressed = false; - } - _ => {} - } - - event::Status::Ignored - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Theme, - renderer_style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if self.on_event.is_none() { - Status::Disabled - } else if is_mouse_over { - let state = tree.state.downcast_ref::(); - - if state.is_pressed { - Status::Pressed - } else { - Status::Hovered - } - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); - - if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 { - renderer.fill_quad( - renderer::Quad { - bounds, - border: style.border, - shadow: style.shadow, - }, - style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } - - let viewport = if self.clip { - bounds.intersection(viewport).unwrap_or(*viewport) - } else { - *viewport - }; - - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &renderer::Style { - text_color: style.text_color, - icon_color: style.icon_color.unwrap_or(renderer_style.icon_color), - scale_factor: renderer_style.scale_factor, - }, - content_layout, - cursor, - &viewport, - ); - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let is_mouse_over = cursor.is_over(layout.bounds()); - - if is_mouse_over && self.on_event.is_some() { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - translation: Vector, - ) -> Option> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - translation, - ) - } - - fn id(&self) -> Option { - Some(self.id.clone()) - } - - fn set_id(&mut self, id: Id) { - self.id = id; - } -} - -impl<'a, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> -where - Message: Clone + 'a, - Theme: Catalog + 'a, - Renderer: iced::core::Renderer + 'a, -{ - fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { - Self::new(button) - } -} - -pub fn button<'a, Message, Theme, Renderer>( - content: impl Into>, -) -> Button<'a, Message, Theme, Renderer> -where - Theme: Catalog + 'a, - Renderer: iced::core::Renderer, - Message: Clone, -{ - Button::new(content) -} diff --git a/src/config/anchor.rs b/src/config/anchor.rs deleted file mode 100644 index 3e13b45..0000000 --- a/src/config/anchor.rs +++ /dev/null @@ -1,42 +0,0 @@ -use iced::platform_specific::shell::commands::layer_surface::Anchor; - -#[derive(Debug, Default, Clone, Copy)] -pub enum BarAnchor { - Left, - Right, - #[default] - Top, - Bottom, -} - -impl BarAnchor { - pub fn vertical(&self) -> bool { - match self { - BarAnchor::Top | BarAnchor::Bottom => false, - BarAnchor::Left | BarAnchor::Right => true, - } - } -} - -impl From for String { - fn from(anchor: BarAnchor) -> String { - match anchor { - BarAnchor::Top => "top", - BarAnchor::Bottom => "bottom", - BarAnchor::Left => "left", - BarAnchor::Right => "right", - } - .to_string() - } -} - -impl From<&BarAnchor> for Anchor { - fn from(anchor: &BarAnchor) -> Self { - match anchor { - BarAnchor::Top => Anchor::TOP, - BarAnchor::Bottom => Anchor::BOTTOM, - BarAnchor::Left => Anchor::LEFT, - BarAnchor::Right => Anchor::RIGHT, - } - } -} diff --git a/src/config/enabled_modules.rs b/src/config/enabled_modules.rs deleted file mode 100644 index 1da2987..0000000 --- a/src/config/enabled_modules.rs +++ /dev/null @@ -1,50 +0,0 @@ -use configparser::ini::Ini; - -#[derive(Debug)] -pub struct EnabledModules { - pub left: Vec, - pub center: Vec, - pub right: Vec, -} - -impl Default for EnabledModules { - fn default() -> Self { - let vec = |list: &[&str]| list.iter().map(|i| i.to_string()).collect(); - - Self { - left: vec(&["hyprland.workspaces", "hyprland.window"]), - center: vec(&["date", "time"]), - right: vec(&["media", "volume", "cpu", "memory"]), - } - } -} - -impl From<&Ini> for EnabledModules { - fn from(ini: &Ini) -> Self { - let get = |field: &str| { - ini.get("modules", field) - .map(|value| value.split(',').map(|v| v.trim().to_string()).collect()) - }; - - let default = Self::default(); - - Self { - left: get("left").unwrap_or(default.left), - center: get("center").unwrap_or(default.center), - right: get("right").unwrap_or(default.right), - } - } -} - -impl EnabledModules { - pub fn get_all(&self) -> impl Iterator { - self.left - .iter() - .chain(self.center.iter()) - .chain(self.right.iter()) - } - - pub fn contains(&self, x: &String) -> bool { - self.left.contains(x) || self.center.contains(x) || self.right.contains(x) - } -} diff --git a/src/config/insets.rs b/src/config/insets.rs deleted file mode 100644 index 9c9a425..0000000 --- a/src/config/insets.rs +++ /dev/null @@ -1,47 +0,0 @@ -use iced::{runtime::platform_specific::wayland::layer_surface::IcedMargin, Padding, Radius}; - -pub struct Insets { - a: f32, - b: f32, - c: f32, - d: f32, -} - -impl Insets { - pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self { - Self { a, b, c, d } - } -} - -impl From for IcedMargin { - fn from(insets: Insets) -> Self { - Self { - top: insets.a as i32, - right: insets.b as i32, - bottom: insets.c as i32, - left: insets.d as i32, - } - } -} - -impl From for Padding { - fn from(insets: Insets) -> Self { - Self { - top: insets.a, - right: insets.b, - bottom: insets.c, - left: insets.d, - } - } -} - -impl From for Radius { - fn from(insets: Insets) -> Self { - Self { - top_left: insets.a, - top_right: insets.b, - bottom_right: insets.c, - bottom_left: insets.d, - } - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index 751d958..0000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::{ - any::TypeId, - collections::{HashMap, HashSet}, - fs::{create_dir_all, File}, - io::Write, - path::PathBuf, - sync::Arc, -}; - -use anchor::BarAnchor; -use configparser::ini::{Ini, IniDefault}; -use directories::ProjectDirs; -pub use enabled_modules::EnabledModules; -use handlebars::Handlebars; -use iced::{ - futures::{channel::mpsc::Sender, SinkExt}, - platform_specific::shell::commands::layer_surface::KeyboardInteractivity, -}; -use module_config::ModuleConfig; -use popup_config::PopupConfig; -use tokio::sync::mpsc; - -use crate::{registry::Registry, Message}; -pub use thrice::Thrice; - -pub mod anchor; -mod enabled_modules; -mod insets; -pub mod module_config; -pub mod parse; -pub mod popup_config; -mod thrice; - -#[derive(Debug)] -pub struct Config { - pub hard_reload: bool, - pub enabled_modules: EnabledModules, - pub enabled_listeners: HashSet, - pub module_config: ModuleConfig, - pub popup_config: PopupConfig, - pub anchor: BarAnchor, - pub monitor: Option, - pub kb_focus: KeyboardInteractivity, -} - -impl Config { - fn default(registry: &Registry) -> Self { - let enabled_modules = EnabledModules::default(); - Self { - hard_reload: false, - enabled_listeners: registry - .enabled_listeners(&enabled_modules, &None) - .chain( - registry - .all_listeners() - .flat_map(|(l_id, l)| { - l.config().into_iter().map(move |option| (l_id, option)) - }) - .filter_map(|(l_id, option)| option.default.then_some(*l_id)), - ) - .collect(), - enabled_modules, - module_config: ModuleConfig::default(), - popup_config: PopupConfig::default(), - anchor: BarAnchor::default(), - monitor: None, - kb_focus: KeyboardInteractivity::None, - } - } - - pub fn exclusive_zone(&self) -> i32 { - (match self.anchor { - BarAnchor::Left | BarAnchor::Right => self.module_config.global.width.unwrap_or(30), - BarAnchor::Top | BarAnchor::Bottom => self.module_config.global.height.unwrap_or(30), - }) as i32 - } -} - -pub fn get_config_dir() -> PathBuf { - let config_dir = ProjectDirs::from("fun.killarchive", "faervan", "bar-rs") - .map(|dirs| dirs.config_local_dir().to_path_buf()) - .unwrap_or_else(|| { - eprintln!("Failed to get config directory"); - PathBuf::from("") - }); - let _ = create_dir_all(&config_dir); - let config_file = config_dir.join("bar-rs.ini"); - - if let Ok(mut file) = File::create_new(&config_file) { - file.write_all(include_bytes!("../../default_config/horizontal.ini")) - .unwrap_or_else(|e| { - eprintln!( - "Failed to write default config to {}: {e}", - config_file.to_string_lossy() - ) - }); - } - - config_file -} - -pub fn read_config(path: &PathBuf, registry: &mut Registry, templates: &mut Handlebars) -> Config { - let mut ini = Ini::new(); - let mut defaults = IniDefault::default(); - defaults.delimiters = vec!['=']; - ini.load_defaults(defaults); - let Ok(_) = ini.load(path) else { - eprintln!("Failed to read config from {}", path.to_string_lossy()); - return Config::default(registry); - }; - let config: Config = (&ini, &*registry).into(); - let empty_map = HashMap::new(); - registry - .get_modules_mut(config.enabled_modules.get_all(), &config) - .map(|m| { - let name = m.name(); - let cfg_map = ini - .get_map_ref() - .get(&format!("module:{}", name)) - .unwrap_or(&empty_map); - let popup_cfg_map = ini - .get_map_ref() - .get(&format!("module_popup:{}", name)) - .unwrap_or(&empty_map); - (m, cfg_map, popup_cfg_map) - }) - .for_each(|(m, cfg_map, popup_cfg_map)| m.read_config(cfg_map, popup_cfg_map, templates)); - config -} - -pub async fn get_config(sender: &mut Sender) -> (Arc, Arc) { - let (sx, mut rx) = mpsc::channel(1); - sender - .send(Message::GetConfig(sx)) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to request config failed with err: {err}"); - }); - rx.recv().await.unwrap() -} - -pub struct ConfigEntry { - pub section: String, - pub name: String, - pub default: bool, -} - -impl ConfigEntry { - pub fn new(section: S, name: S, default: bool) -> Self { - Self { - section: section.to_string(), - name: name.to_string(), - default, - } - } -} diff --git a/src/config/module_config.rs b/src/config/module_config.rs deleted file mode 100644 index 9081812..0000000 --- a/src/config/module_config.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::collections::HashMap; - -use configparser::ini::Ini; -use iced::{ - runtime::platform_specific::wayland::layer_surface::IcedMargin, Background, Border, Color, - Padding, -}; - -use crate::modules::OnClickAction; - -use super::{parse::StringExt, Thrice}; - -#[derive(Debug, Default)] -pub struct ModuleConfig { - pub global: GlobalModuleConfig, - pub local: LocalModuleConfig, -} - -#[derive(Debug)] -pub struct GlobalModuleConfig { - pub spacing: Thrice, - pub width: Option, - pub height: Option, - pub margin: IcedMargin, - pub padding: Padding, - pub background_color: Color, -} - -impl Default for GlobalModuleConfig { - fn default() -> Self { - Self { - spacing: 20_f32.into(), - width: None, - height: None, - margin: IcedMargin::default(), - padding: Padding::default(), - background_color: Color::from_rgba(0., 0., 0., 0.5), - } - } -} - -#[derive(Debug)] -pub struct LocalModuleConfig { - pub text_color: Color, - pub icon_color: Color, - pub font_size: f32, - pub icon_size: f32, - pub text_margin: Padding, - pub icon_margin: Padding, - pub spacing: f32, - pub margin: Padding, - pub padding: Padding, - pub background: Option, - pub border: Border, - pub action: OnClickAction, -} - -impl Default for LocalModuleConfig { - fn default() -> Self { - Self { - text_color: Color::WHITE, - icon_color: Color::WHITE, - font_size: 16., - icon_size: 20., - text_margin: Padding::default(), - icon_margin: Padding::default(), - spacing: 10., - margin: Padding::default(), - padding: Padding::default(), - background: None, - border: Border::default(), - action: OnClickAction::default(), - } - } -} - -#[derive(Default, Debug)] -pub struct ModuleConfigOverride { - pub text_color: Option, - pub icon_color: Option, - pub font_size: Option, - pub icon_size: Option, - pub text_margin: Option, - pub icon_margin: Option, - pub spacing: Option, - pub margin: Option, - pub padding: Option, - pub background: Option>, - pub border: Option, - pub action: Option, -} - -impl From<&HashMap>> for ModuleConfigOverride { - fn from(map: &HashMap>) -> Self { - Self { - text_color: map.get("text_color").and_then(|s| s.into_color()), - icon_color: map.get("icon_color").and_then(|s| s.into_color()), - font_size: map.get("font_size").and_then(|s| s.into_float()), - icon_size: map.get("icon_size").and_then(|s| s.into_float()), - text_margin: map - .get("text_margin") - .and_then(|s| s.into_insets().map(|i| i.into())), - icon_margin: map - .get("icon_margin") - .and_then(|s| s.into_insets().map(|i| i.into())), - spacing: map.get("spacing").and_then(|s| s.into_float()), - margin: map - .get("margin") - .and_then(|s| s.into_insets().map(|i| i.into())), - padding: map - .get("padding") - .and_then(|s| s.into_insets().map(|i| i.into())), - background: map.get("background").map(|s| s.into_background()), - border: { - let color = map.get("border_color").and_then(|s| s.into_color()); - let width = map.get("border_width").and_then(|s| s.into_float()); - let radius = map - .get("border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Some(Border { - color: color.unwrap_or_default(), - width: width.unwrap_or_default(), - radius: radius.unwrap_or_default(), - }) - } else { - None - } - }, - action: { - let left = map - .get("on_click") - .and_then(|s| s.as_ref().map(|s| s.into())); - let center = map - .get("on_middle_click") - .and_then(|s| s.as_ref().map(|s| s.into())); - let right = map - .get("on_right_click") - .and_then(|s| s.as_ref().map(|s| s.into())); - if left.is_some() || center.is_some() || right.is_some() { - Some(OnClickAction { - left, - center, - right, - }) - } else { - None - } - }, - } - } -} - -impl From<&Ini> for ModuleConfig { - fn from(ini: &Ini) -> Self { - let global = Self::default().global; - let local = Self::default().local; - let section = "style"; - let module_section = "module_style"; - ModuleConfig { - global: GlobalModuleConfig { - background_color: ini - .get(section, "background") - .into_color() - .unwrap_or(global.background_color), - spacing: ini - .get(section, "spacing") - .into_thrice_float() - .unwrap_or(global.spacing), - height: ini.get(section, "height").and_then(|v| v.parse().ok()), - width: ini.get(section, "width").and_then(|v| v.parse().ok()), - margin: ini - .get(section, "margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(global.margin), - padding: ini - .get(section, "padding") - .into_insets() - .map(|i| i.into()) - .unwrap_or(global.padding), - }, - local: LocalModuleConfig { - text_color: ini - .get(module_section, "text_color") - .into_color() - .unwrap_or(local.text_color), - icon_color: ini - .get(module_section, "icon_color") - .into_color() - .unwrap_or(local.icon_color), - font_size: ini - .get(module_section, "font_size") - .into_float() - .unwrap_or(local.font_size), - icon_size: ini - .get(module_section, "icon_size") - .into_float() - .unwrap_or(local.icon_size), - text_margin: ini - .get(module_section, "text_margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(local.text_margin), - icon_margin: ini - .get(module_section, "icon_margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(local.icon_margin), - spacing: ini - .get(module_section, "spacing") - .into_float() - .unwrap_or(local.spacing), - margin: ini - .get(module_section, "margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(local.margin), - padding: ini - .get(module_section, "padding") - .into_insets() - .map(|i| i.into()) - .unwrap_or(local.padding), - background: ini.get(module_section, "background").into_background(), - border: { - let color = ini - .get(module_section, "border_color") - .into_color() - .unwrap_or(local.border.color); - let width = ini - .get(module_section, "border_width") - .into_float() - .unwrap_or(local.border.width); - let radius = ini - .get(module_section, "border_radius") - .into_insets() - .map(|i| i.into()) - .unwrap_or(local.border.radius); - Border { - color, - width, - radius, - } - }, - action: { - let left = ini.get(module_section, "on_click").map(|s| (&s).into()); - let center = ini - .get(module_section, "on_middle_click") - .map(|s| (&s).into()); - let right = ini - .get(module_section, "on_right_click") - .map(|s| (&s).into()); - OnClickAction { - left, - center, - right, - } - }, - }, - } - } -} diff --git a/src/config/parse.rs b/src/config/parse.rs deleted file mode 100644 index a4b0756..0000000 --- a/src/config/parse.rs +++ /dev/null @@ -1,150 +0,0 @@ -use configparser::ini::Ini; -use iced::{ - platform_specific::shell::commands::layer_surface::KeyboardInteractivity, Background, Color, -}; - -use crate::{registry::Registry, OptionExt}; - -use super::{anchor::BarAnchor, insets::Insets, Config, Thrice}; - -impl From<(&Ini, &Registry)> for Config { - fn from((ini, registry): (&Ini, &Registry)) -> Self { - let enabled_modules = ini.into(); - let default = Self::default(registry); - Self { - hard_reload: ini - .get("general", "hard_reloading") - .into_bool() - .unwrap_or(default.hard_reload), - enabled_listeners: registry - .all_listeners() - .fold(vec![], |mut acc, (id, l)| { - l.config().into_iter().for_each(|option| { - if ini - .get(&option.section, &option.name) - .into_bool() - .unwrap_or(option.default) - { - acc.push(*id); - } - }); - acc - }) - .into_iter() - .chain(registry.enabled_listeners(&enabled_modules, &None)) - .collect(), - enabled_modules, - module_config: ini.into(), - popup_config: ini.into(), - anchor: ini - .get("general", "anchor") - .into_anchor() - .unwrap_or(default.anchor), - monitor: ini.get("general", "monitor"), - kb_focus: ini - .get("general", "kb_focus") - .into_kb_focus() - .unwrap_or(default.kb_focus), - } - } -} - -pub trait StringExt { - fn into_bool(self) -> Option; - fn into_color(self) -> Option; - fn into_float(self) -> Option; - fn into_thrice_float(self) -> Option>; - fn into_anchor(self) -> Option; - fn into_insets(self) -> Option; - fn into_background(self) -> Option; - fn into_kb_focus(self) -> Option; -} - -impl StringExt for &Option { - fn into_bool(self) -> Option { - self.as_ref().and_then(|v| match v.to_lowercase().as_str() { - "0" | "f" | "n" | "no" | "false" | "disabled" | "disable" | "off" => Some(false), - "1" | "t" | "y" | "yes" | "true" | "enabled" | "enable" | "on" => Some(true), - _ => None, - }) - } - fn into_color(self) -> Option { - self.as_ref().and_then(|color| { - csscolorparser::parse(color) - .map(|v| v.into_ext()) - .ok() - .map_none(|| println!("Failed to parse color!")) - }) - } - fn into_float(self) -> Option { - self.as_ref().and_then(|v| v.parse().ok()) - } - fn into_thrice_float(self) -> Option> { - self.as_ref().and_then(|value| { - if let [left, center, right] = value.split_whitespace().collect::>()[..] { - left.parse() - .and_then(|l| center.parse().map(|c| (l, c))) - .and_then(|(l, c)| right.parse().map(|r| (l, c, r))) - .ok() - .map(|all| all.into()) - } else { - value.parse::().ok().map(|all| all.into()) - } - .map_none(|| eprintln!("Failed to parse value as float")) - }) - } - fn into_anchor(self) -> Option { - self.as_ref().and_then(|value| match value.as_str() { - "top" => Some(BarAnchor::Top), - "bottom" => Some(BarAnchor::Bottom), - "left" => Some(BarAnchor::Left), - "right" => Some(BarAnchor::Right), - _ => None, - }) - } - fn into_insets(self) -> Option { - self.as_ref().and_then(|value| { - let values = value - .split_whitespace() - .filter_map(|i| i.parse::().ok()) - .collect::>(); - match values[..] { - [all] => Some(Insets::new(all, all, all, all)), - [vertical, horizontal] => { - Some(Insets::new(vertical, horizontal, vertical, horizontal)) - } - [top, right, bottom, left] => Some(Insets::new(top, right, bottom, left)), - _ => { - eprintln!("Failed to parse value as insets"); - None - } - } - }) - } - fn into_background(self) -> Option { - self.into_color().map(Background::Color) - } - fn into_kb_focus(self) -> Option { - self.as_ref().and_then(|v| match v.as_str() { - "none" => Some(KeyboardInteractivity::None), - "on_demand" => Some(KeyboardInteractivity::OnDemand), - "exclusive" => Some(KeyboardInteractivity::Exclusive), - _ => None, - }) - } -} - -pub trait IntoExt { - fn into_ext(self) -> T; -} - -impl IntoExt for csscolorparser::Color { - fn into_ext(self) -> Color { - Color { - r: self.r, - g: self.g, - b: self.b, - a: self.a, - } - } -} diff --git a/src/config/popup_config.rs b/src/config/popup_config.rs deleted file mode 100644 index c639555..0000000 --- a/src/config/popup_config.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::collections::HashMap; - -use configparser::ini::Ini; -use iced::{Background, Border, Color, Padding}; - -use super::parse::StringExt; - -#[derive(Debug)] -pub struct PopupConfig { - pub width: i32, - pub height: i32, - /// Whether the content of the popup should fill the size of the popup window - pub fill_content_to_size: bool, - pub padding: Padding, - pub text_color: Color, - pub icon_color: Color, - pub font_size: f32, - pub icon_size: f32, - pub text_margin: Padding, - pub icon_margin: Padding, - pub spacing: f32, - pub background: Background, - pub border: Border, -} - -impl Default for PopupConfig { - fn default() -> Self { - Self { - width: 300, - height: 300, - fill_content_to_size: false, - padding: [10, 20].into(), - text_color: Color::WHITE, - icon_color: Color::WHITE, - font_size: 14., - icon_size: 24., - text_margin: Padding::default(), - icon_margin: Padding::default(), - spacing: 0., - background: Background::Color(Color { - r: 0., - g: 0., - b: 0., - a: 0.8, - }), - border: Border::default().rounded(8), - } - } -} - -#[derive(Debug, Default)] -pub struct PopupConfigOverride { - pub width: Option, - pub height: Option, - pub fill_content_to_size: Option, - pub padding: Option, - pub text_color: Option, - pub icon_color: Option, - pub font_size: Option, - pub icon_size: Option, - pub text_margin: Option, - pub icon_margin: Option, - pub spacing: Option, - pub background: Option, - pub border: Option, -} - -impl From<&Ini> for PopupConfig { - fn from(ini: &Ini) -> Self { - let default = Self::default(); - let section = "popup_style"; - Self { - width: ini - .get(section, "width") - .and_then(|s| s.parse().ok()) - .unwrap_or(default.width), - height: ini - .get(section, "height") - .and_then(|s| s.parse().ok()) - .unwrap_or(default.height), - fill_content_to_size: ini - .get(section, "fill_content_to_size") - .into_bool() - .unwrap_or(default.fill_content_to_size), - padding: ini - .get(section, "padding") - .into_insets() - .map(|i| i.into()) - .unwrap_or(default.padding), - text_color: ini - .get(section, "text_color") - .into_color() - .unwrap_or(default.text_color), - icon_color: ini - .get(section, "icon_color") - .into_color() - .unwrap_or(default.icon_color), - font_size: ini - .get(section, "font_size") - .into_float() - .unwrap_or(default.font_size), - icon_size: ini - .get(section, "icon_size") - .into_float() - .unwrap_or(default.icon_size), - text_margin: ini - .get(section, "text_margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(default.text_margin), - icon_margin: ini - .get(section, "icon_margin") - .into_insets() - .map(|i| i.into()) - .unwrap_or(default.icon_margin), - spacing: ini - .get(section, "spacing") - .into_float() - .unwrap_or(default.spacing), - background: ini - .get(section, "background") - .into_background() - .unwrap_or(default.background), - border: { - let color = ini - .get(section, "border_color") - .into_color() - .unwrap_or(default.border.color); - let width = ini - .get(section, "border_width") - .into_float() - .unwrap_or(default.border.width); - let radius = ini - .get(section, "border_radius") - .into_insets() - .map(|i| i.into()) - .unwrap_or(default.border.radius); - Border { - color, - width, - radius, - } - }, - } - } -} - -impl PopupConfigOverride { - pub fn update(&mut self, config: &HashMap>) { - if let Some(width) = config - .get("width") - .and_then(|s| s.as_ref().and_then(|v| v.parse().ok())) - { - self.width = Some(width); - } - if let Some(height) = config - .get("height") - .and_then(|s| s.as_ref().and_then(|v| v.parse().ok())) - { - self.height = Some(height); - } - self.fill_content_to_size = config - .get("fill_content_to_size") - .and_then(|s| s.into_bool()); - self.padding = config - .get("padding") - .and_then(|s| s.into_insets().map(|i| i.into())); - self.text_color = config.get("text_color").and_then(|s| s.into_color()); - self.icon_color = config.get("icon_color").and_then(|s| s.into_color()); - self.font_size = config.get("font_size").and_then(|s| s.into_float()); - self.icon_size = config.get("icon_size").and_then(|s| s.into_float()); - self.text_margin = config - .get("text_margin") - .and_then(|s| s.into_insets().map(|i| i.into())); - self.icon_margin = config - .get("icon_margin") - .and_then(|s| s.into_insets().map(|i| i.into())); - self.spacing = config.get("spacing").and_then(|s| s.into_float()); - self.background = config.get("background").and_then(|s| s.into_background()); - self.border = { - let color = config.get("border_color").and_then(|s| s.into_color()); - let width = config.get("border_width").and_then(|s| s.into_float()); - let radius = config - .get("border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Some(Border { - color: color.unwrap_or_default(), - width: width.unwrap_or_default(), - radius: radius.unwrap_or_default(), - }) - } else { - None - } - }; - } -} diff --git a/src/config/thrice.rs b/src/config/thrice.rs deleted file mode 100644 index 9e25ec7..0000000 --- a/src/config/thrice.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[derive(Debug)] -pub struct Thrice { - pub left: T, - pub center: T, - pub right: T, -} - -impl From for Thrice { - fn from(value: f32) -> Self { - Self { - left: value, - center: value, - right: value, - } - } -} - -impl From<(f32, f32, f32)> for Thrice { - fn from((left, center, right): (f32, f32, f32)) -> Self { - Self { - left, - center, - right, - } - } -} diff --git a/src/fill.rs b/src/fill.rs deleted file mode 100644 index 1c6fcf6..0000000 --- a/src/fill.rs +++ /dev/null @@ -1,113 +0,0 @@ -use iced::{ - widget::{text::Rich, Container, Text}, - Alignment::Center, - Length::Fill, -}; - -use crate::config::anchor::BarAnchor; - -pub trait FillExt { - fn fill(self, anchor: &BarAnchor) -> Self; - fn fillx(self, vertical: bool) -> Self; - fn fill_maybe(self, fill: bool) -> Self; -} - -impl FillExt for Text<'_> { - fn fill(self, anchor: &BarAnchor) -> Self { - self.fillx(anchor.vertical()) - } - fn fillx(self, vertical: bool) -> Self { - match vertical { - true => self.width(Fill), - false => self.height(Fill), - } - .center() - } - fn fill_maybe(self, fill: bool) -> Self { - match fill { - true => self.height(Fill).width(Fill), - false => self, - } - } -} - -impl FillExt for Rich<'_, Link> -where - Link: Clone, -{ - fn fill(self, anchor: &BarAnchor) -> Self { - self.fillx(anchor.vertical()) - } - fn fillx(self, vertical: bool) -> Self { - match vertical { - true => self.center(), - false => self.height(Fill).align_y(Center), - } - } - fn fill_maybe(self, fill: bool) -> Self { - match fill { - true => self.height(Fill).width(Fill), - false => self, - } - } -} - -impl FillExt for Container<'_, Message> { - fn fill(self, anchor: &BarAnchor) -> Self { - self.fillx(anchor.vertical()) - } - fn fillx(self, vertical: bool) -> Self { - match vertical { - true => self.width(Fill), - false => self.height(Fill), - } - } - fn fill_maybe(self, fill: bool) -> Self { - match fill { - true => self.height(Fill).width(Fill), - false => self, - } - } -} - -impl FillExt for iced::widget::button::Button<'_, Message> -where - Message: Clone, -{ - fn fill(self, anchor: &BarAnchor) -> Self { - self.fillx(anchor.vertical()) - } - fn fillx(self, vertical: bool) -> Self { - match vertical { - true => self.width(Fill), - false => self.height(Fill), - } - } - fn fill_maybe(self, fill: bool) -> Self { - match fill { - true => self.height(Fill).width(Fill), - false => self, - } - } -} - -impl FillExt for crate::button::Button<'_, Message> -where - Message: Clone, -{ - fn fill(self, anchor: &BarAnchor) -> Self { - self.fillx(anchor.vertical()) - } - fn fillx(self, vertical: bool) -> Self { - match vertical { - true => self.width(Fill), - false => self.height(Fill), - } - } - fn fill_maybe(self, fill: bool) -> Self { - match fill { - true => self.height(Fill).width(Fill), - false => self, - } - } -} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs deleted file mode 100644 index 68823f5..0000000 --- a/src/helpers/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub trait UnEscapeString { - /// Unescape special characters like '\n' and '\t' - fn unescape(self) -> Option; -} - -impl UnEscapeString for Option<&Option> { - fn unescape(self) -> Option { - self.and_then(|s| { - s.as_ref() - .map(|s| s.replace(r"\n", "\n").replace(r"\t", "\t")) - }) - } -} diff --git a/src/list.rs b/src/list.rs deleted file mode 100644 index a72641a..0000000 --- a/src/list.rs +++ /dev/null @@ -1,100 +0,0 @@ -use iced::{ - widget::{column, row, Column, Container, Row}, - Alignment, Element, Padding, Pixels, -}; - -use crate::config::anchor::BarAnchor; - -pub trait DynamicAlign { - fn align(self, anchor: &BarAnchor, alignment: Alignment) -> Self; -} - -impl DynamicAlign for Container<'_, Message> { - fn align(self, anchor: &BarAnchor, alignment: Alignment) -> Self { - match anchor.vertical() { - true => self.align_y(alignment), - false => self.align_x(alignment), - } - } -} - -pub enum List<'a, Message, Theme, Renderer> { - Row(Row<'a, Message, Theme, Renderer>), - Column(Column<'a, Message, Theme, Renderer>), -} - -impl<'a, Message, Theme, Renderer> List<'a, Message, Theme, Renderer> -where - Renderer: iced::core::Renderer, -{ - pub fn new(anchor: &BarAnchor) -> List<'a, Message, Theme, Renderer> { - match anchor.vertical() { - true => List::Column(Column::new()), - false => List::Row(Row::new()), - } - } - - pub fn with_children( - anchor: &BarAnchor, - children: impl IntoIterator>, - ) -> Self { - match anchor.vertical() { - true => List::Column(Column::with_children(children)), - false => List::Row(Row::with_children(children)), - } - } - - pub fn spacing(self, amount: impl Into) -> List<'a, Message, Theme, Renderer> { - match self { - List::Row(row) => List::Row(row.spacing(amount)), - List::Column(col) => List::Column(col.spacing(amount)), - } - } - - pub fn padding

(self, padding: P) -> List<'a, Message, Theme, Renderer> - where - P: Into, - { - match self { - List::Row(row) => List::Row(row.padding(padding)), - List::Column(col) => List::Column(col.padding(padding)), - } - } -} - -pub fn list<'a, Message, Theme, Renderer>( - anchor: &BarAnchor, - children: impl IntoIterator>, -) -> List<'a, Message, Theme, Renderer> -where - Renderer: iced::core::Renderer, -{ - match anchor.vertical() { - true => List::Column(column(children)), - false => List::Row(row(children)), - } -} - -impl<'a, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> -where - Message: 'a, - Theme: 'a, - Renderer: iced::core::Renderer + 'a, -{ - fn from(list: List<'a, Message, Theme, Renderer>) -> Self { - match list { - List::Row(row) => Self::new(row), - List::Column(col) => Self::new(col), - } - } -} - -macro_rules! list { - ($anchor:expr) => ( - $crate::list::List::new($anchor) - ); - ($anchor:expr, $($x:expr),+ $(,)?) => ( - $crate::list::List::with_children($anchor, [$(iced::core::Element::from($x)),+]) - ); -} diff --git a/src/listeners/hyprland.rs b/src/listeners/hyprland.rs deleted file mode 100644 index 8f07cb7..0000000 --- a/src/listeners/hyprland.rs +++ /dev/null @@ -1,76 +0,0 @@ -use bar_rs_derive::Builder; -use hyprland::{data::Client, event_listener::AsyncEventListener, shared::HyprDataActiveOptional}; -use iced::{futures::SinkExt, stream, Subscription}; - -use crate::{ - config::ConfigEntry, - modules::hyprland::{ - window::update_window, - workspaces::{get_workspaces, HyprWorkspaceMod}, - }, - Message, -}; - -use super::Listener; - -#[derive(Debug, Builder)] -pub struct HyprListener; - -impl Listener for HyprListener { - fn config(&self) -> Vec { - vec![] - } - fn subscription(&self) -> Subscription { - Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let workspaces = get_workspaces(None).await; - sender - .send(Message::update(move |reg| { - let ws = reg.get_module_mut::(); - ws.active = workspaces.0; - ws.open = workspaces.1; - })) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to send workspaces failed with err: {err}"); - }); - if let Ok(window) = Client::get_active_async().await { - update_window(&mut sender, window.map(|w| w.title)).await; - } - - let mut listener = AsyncEventListener::new(); - - let senderx = sender.clone(); - listener.add_active_window_changed_handler(move |data| { - let mut sender = senderx.clone(); - Box::pin(async move { - update_window(&mut sender, data.map(|window| window.title)).await; - }) - }); - - let senderx = sender.clone(); - listener.add_workspace_changed_handler(move |data| { - let mut sender = senderx.clone(); - Box::pin(async move { - let workspaces = get_workspaces(Some(data.id)).await; - sender - .send(Message::update(move |reg| { - let ws = reg.get_module_mut::(); - ws.active = workspaces.0; - ws.open = workspaces.1; - })) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to send workspaces failed with err: {err}"); - }); - }) - }); - - listener - .start_listener_async() - .await - .expect("Failed to listen for hyprland events"); - }) - }) - } -} diff --git a/src/listeners/mod.rs b/src/listeners/mod.rs deleted file mode 100644 index ad1db8a..0000000 --- a/src/listeners/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{any::Any, fmt::Debug}; - -use downcast_rs::{impl_downcast, Downcast}; -use hyprland::HyprListener; -use iced::Subscription; -use niri::NiriListener; -use reload::ReloadListener; -use wayfire::WayfireListener; - -use crate::{config::ConfigEntry, registry::Registry, Message}; - -pub mod hyprland; -pub mod niri; -mod reload; -pub mod wayfire; - -pub trait Listener: Any + Debug + Send + Sync + Downcast { - fn config(&self) -> Vec { - vec![] - } - fn subscription(&self) -> Subscription; -} -impl_downcast!(Listener); - -pub fn register_listeners(registry: &mut Registry) { - registry.register_listener::(); - registry.register_listener::(); - registry.register_listener::(); - registry.register_listener::(); -} diff --git a/src/listeners/niri.rs b/src/listeners/niri.rs deleted file mode 100644 index d7e3216..0000000 --- a/src/listeners/niri.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::{collections::HashMap, env, sync::Arc}; - -use bar_rs_derive::Builder; -use iced::{futures::SinkExt, stream, Subscription}; -use niri_ipc::{socket::SOCKET_PATH_ENV, Event, Request}; -use tokio::{ - io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, - net::UnixStream, - sync::mpsc, -}; - -use crate::{ - config::ConfigEntry, - modules::niri::{NiriWindowMod, NiriWorkspaceMod}, - registry::Registry, - Message, UpdateFn, -}; - -use super::Listener; - -#[derive(Debug, Builder)] -pub struct NiriListener; - -impl Listener for NiriListener { - fn config(&self) -> Vec { - vec![] - } - fn subscription(&self) -> Subscription { - Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let (sx, mut rx) = mpsc::channel(1); - sender - .send(Message::GetReceiver(sx, |reg| { - reg.get_module::().sender.subscribe() - })) - .await - .unwrap(); - let mut receiver = rx.recv().await.unwrap(); - drop(rx); - let socket_path = env::var(SOCKET_PATH_ENV).expect("No niri socket was found!"); - let mut socket = UnixStream::connect(&socket_path).await.unwrap(); - let mut buf = serde_json::to_string(&Request::EventStream).unwrap(); - socket.write_all(buf.as_bytes()).await.unwrap(); - socket.shutdown().await.unwrap(); - let mut reader = BufReader::new(socket); - reader - .read_line(&mut buf) - .await - .map_err(|e| { - eprintln!("Failed to build an event stream with niri: {e}"); - }) - .ok(); - buf.clear(); - loop { - tokio::select! { - Ok(_) = reader.read_line(&mut buf) => { - let reply = serde_json::from_str::(&buf); - type F = Box; - let msg: Option = match reply { - Ok(event) => match event { - Event::WorkspacesChanged { workspaces } => Some(Box::new(move |reg| { - let active_ws = workspaces - .iter() - .find_map(|ws| ws.is_focused.then_some(ws.id)); - let mut workspaces: HashMap> = - workspaces.into_iter().fold(HashMap::new(), |mut acc, ws| { - match acc - .get_mut(ws.output.as_ref().unwrap_or(&String::new())) - { - Some(workspaces) => workspaces.push(ws), - None => { - acc.insert( - ws.output.clone().unwrap_or_default(), - vec![ws], - ); - } - } - acc - }); - for (_, workspaces) in workspaces.iter_mut() { - workspaces.sort_by(|a, b| a.idx.cmp(&b.idx)); - } - let ws_mod = reg.get_module_mut::(); - ws_mod.focused = active_ws.unwrap(); - ws_mod.workspaces = workspaces - })), - Event::WorkspaceActivated { id, focused } => match focused { - true => Some(Box::new(move |reg| { - reg.get_module_mut::().focused = id - })), - false => None, - }, - Event::WindowsChanged { windows } => Some(Box::new(move |reg| { - let window_mod = reg.get_module_mut::(); - window_mod.focused = - windows.iter().find(|w| w.is_focused).map(|w| w.id); - window_mod.windows = windows - .into_iter() - .map(|w| (w.id, w)) - .collect() - })), - Event::WindowFocusChanged { id } => Some(Box::new(move |reg| { - reg.get_module_mut::().focused = id - })), - Event::WindowOpenedOrChanged { window } => Some(Box::new(move |reg| { - let window_mod = reg.get_module_mut::(); - if window.is_focused { - window_mod.focused = Some(window.id); - } - window_mod - .windows - .insert(window.id, window); - })), - Event::WindowClosed { id } => Some(Box::new(move |reg| { - reg.get_module_mut::().windows.remove(&id); - })), - _ => None, - }, - Err(err) => { - eprintln!("Failed to decode Niri IPC msg as Event: {err}"); - None - } - }; - if let Some(msg) = msg { - sender - .send(Message::Update(Arc::new(UpdateFn(msg)))) - .await - .unwrap(); - } - buf.clear(); - } - Ok(action) = receiver.recv() => { - if let Some(id) = action.downcast_ref::() { - let mut socket = UnixStream::connect(&socket_path).await.unwrap(); - let buf = serde_json::to_string(&Request::Action(niri_ipc::Action::FocusWorkspace { reference: niri_ipc::WorkspaceReferenceArg::Id(*id) })).unwrap(); - socket.write_all(buf.as_bytes()).await.unwrap(); - socket.shutdown().await.unwrap(); - } - } - } - } - }) - }) - } -} diff --git a/src/listeners/reload.rs b/src/listeners/reload.rs deleted file mode 100644 index a541915..0000000 --- a/src/listeners/reload.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{env, path::PathBuf, time::Duration}; - -use bar_rs_derive::Builder; -use iced::{ - futures::{executor, SinkExt}, - stream, Subscription, -}; -use notify::{ - event::{ModifyKind, RemoveKind}, - Config, Error, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, -}; -use tokio::time::sleep; - -use crate::{ - config::{get_config, ConfigEntry}, - Message, -}; - -use super::Listener; - -#[derive(Debug, Builder)] -pub struct ReloadListener; - -impl Listener for ReloadListener { - fn config(&self) -> Vec { - vec![ConfigEntry::new("general", "hot_reloading", true)] - } - - fn subscription(&self) -> Subscription { - Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let config_path = get_config(&mut sender).await.0; - let config_pathx = config_path.clone(); - - let mut watcher = RecommendedWatcher::new( - move |result: Result| { - let event = result.unwrap(); - - if event.paths.contains(&config_pathx) && (matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) - || matches!(event.kind, EventKind::Remove(RemoveKind::File))) - { - executor::block_on(async { - sender.send(Message::ReloadConfig) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to request config reload failed with err: {err}"); - }); - }); - } - }, - Config::default() - ).unwrap(); - - watcher - .watch( - config_path.parent().unwrap_or(&default_config_path()), - RecursiveMode::Recursive, - ) - .unwrap(); - - loop { - sleep(Duration::from_secs(1)).await; - } - }) - }) - } -} - -fn default_config_path() -> PathBuf { - format!( - "{}/.config/bar-rs", - env::var("HOME").expect("Env $HOME is not set?") - ) - .into() -} diff --git a/src/listeners/wayfire.rs b/src/listeners/wayfire.rs deleted file mode 100644 index 4715d46..0000000 --- a/src/listeners/wayfire.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::{error::Error, time::Duration}; - -use bar_rs_derive::Builder; -use iced::{ - futures::{channel::mpsc::Sender, SinkExt}, - stream, Subscription, -}; -use serde_json::Value; -use tokio::time::sleep; -use wayfire_rs::ipc::WayfireSocket; - -use crate::{ - modules::wayfire::{WayfireWindowMod, WayfireWorkspaceMod}, - Message, -}; - -use super::Listener; - -#[derive(Debug, Builder)] -pub struct WayfireListener; - -async fn send_first_values( - socket: &mut WayfireSocket, - sender: &mut Sender, -) -> Result<(), Box> { - let title = socket.get_focused_view().await.ok().map(|v| v.title); - let workspace = socket.get_focused_output().await?.workspace; - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().title = title; - reg.get_module_mut::().active = (workspace.x, workspace.y); - })) - .await?; - Ok(()) -} - -impl Listener for WayfireListener { - fn subscription(&self) -> iced::Subscription { - Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let Ok(mut socket) = WayfireSocket::connect().await else { - eprintln!("Failed to connect to wayfire socket"); - return; - }; - - send_first_values(&mut socket, &mut sender) - .await - .unwrap_or_else(|e| { - eprintln!("Failed to send initial wayfire module data: {e}") - }); - - socket - .watch(Some(vec![ - "wset-workspace-changed".to_string(), - "view-focused".to_string(), - "view-title-changed".to_string(), - "view-unmapped".to_string(), - ])) - .await - .expect("Failed to watch wayfire socket (but we're connected already)!"); - - let mut active_window = None; - - while let Ok(Value::Object(msg)) = socket.read_message().await { - match msg.get("event") { - Some(Value::String(val)) if val == "wset-workspace-changed" => { - let Some(Value::Object(obj)) = msg.get("new-workspace") else { - continue; - }; - - // serde_json::Value::Object => (i64, i64) - if let Some((x, y)) = obj.get("x").and_then(|x| { - x.as_i64().and_then(|x| { - obj.get("y").and_then(|y| y.as_i64().map(|y| (x, y))) - }) - }) { - // With this wayfire will send an additional msg, see the None - // match arm... No idea why tho - sleep(Duration::from_millis(150)).await; - let title = socket.get_focused_view().await.ok().map(|v| v.title); - active_window = title.clone(); - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().active = (x, y); - reg.get_module_mut::().title = title - })) - .await - .unwrap(); - } - } - - Some(Value::String(val)) - if val == "view-focused" || val == "view-title-changed" => - { - let Some(Value::String(title)) = msg - .get("view") - .and_then(|v| v.as_object()) - .and_then(|o| o.get("title").map(|t| t.to_owned())) - else { - continue; - }; - match Some(&title) == active_window.as_ref() { - true => continue, - false => active_window = Some(title.clone()), - } - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().title = Some(title) - })) - .await - .unwrap(); - } - - // That sure seems useless, but we need the view-unmapped events that - // somehow end up in the None match arm - Some(Value::String(val)) if val == "view-unmapped" => {} - - None => { - if let Some("ok") = msg.get("result").and_then(|r| r.as_str()) { - let Some(title) = msg.get("info").map(|info| { - if info.is_null() { - return None; - } - info.as_object() - .and_then(|obj| obj.get("title")) - .and_then(|t| t.as_str()) - .map(|s| s.to_string()) - }) else { - continue; - }; - match title == active_window { - true => continue, - false => active_window = title.clone(), - } - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().title = title - })) - .await - .unwrap(); - }; - } - - _ => eprintln!("got unknown event from wayfire ipc: {msg:#?}"), - } - } - - eprintln!("Failed to read messages from the Wayfire socket!"); - }) - }) - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 79ea73d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,508 +0,0 @@ -use std::{ - any::{Any, TypeId}, - fmt::Debug, - path::PathBuf, - process::{exit, Command}, - sync::Arc, - time::Duration, -}; - -use config::{anchor::BarAnchor, get_config_dir, read_config, Config, EnabledModules, Thrice}; -use fill::FillExt; -use handlebars::Handlebars; -use iced::{ - daemon, - platform_specific::shell::commands::{ - layer_surface::{destroy_layer_surface, get_layer_surface, Layer}, - output::{get_output, get_output_info, OutputInfo}, - popup::{destroy_popup, get_popup}, - }, - runtime::platform_specific::wayland::{ - layer_surface::{IcedOutput, SctkLayerSurfaceSettings}, - popup::{SctkPopupSettings, SctkPositioner}, - }, - stream, - theme::Palette, - widget::{container, stack}, - window::Id, - Alignment, Color, Element, Font, Rectangle, Subscription, Task, Theme, -}; -use list::{list, DynamicAlign}; -use listeners::register_listeners; -use modules::{register_modules, Module}; -use registry::Registry; -use resolvers::register_resolvers; -use tokio::{ - sync::{broadcast, mpsc}, - time::sleep, -}; - -mod config; -#[macro_use] -mod list; -mod button; -mod event_action; -mod fill; -mod helpers; -mod listeners; -mod modules; -mod registry; -mod resolvers; -mod tooltip; - -const NERD_FONT: Font = Font::with_name("3270 Nerd Font"); - -fn main() -> iced::Result { - daemon("Bar", Bar::update, Bar::view) - .theme(Bar::theme) - .font(include_bytes!("../assets/3270/3270NerdFont-Regular.ttf")) - .subscription(|state| { - if state.open { - Subscription::batch({ - state - .registry - .get_modules(state.config.enabled_modules.get_all(), &state.config) - .filter(|m| state.config.enabled_modules.contains(&m.name())) - .filter_map(|m| m.subscription()) - .chain( - state - .registry - .get_listeners(&state.config.enabled_listeners) - .map(|l| l.subscription()), - ) - }) - } else { - Subscription::none() - } - }) - .run_with(Bar::new) -} - -pub struct UpdateFn(Box); -impl Debug for UpdateFn { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "UpdateFn(Box) can't be displayed" - ) - } -} -pub struct ActionFn(Box); -impl Debug for ActionFn { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "ActionFn(Box) can't be displayed" - ) - } -} - -#[derive(Debug, Clone)] -pub enum Message { - Popup { - type_id: TypeId, - dimension: Rectangle, - }, - Update(Arc), - Action(Arc), - GetConfig(mpsc::Sender<(Arc, Arc)>), - GetReceiver( - mpsc::Sender>>, - fn(&Registry) -> broadcast::Receiver>, - ), - Spawn(Arc), - ReloadConfig, - LoadRegistry, - GotOutput(Option), - GotOutputInfo(Option), -} - -impl Message { - fn update(f: F) -> Self - where - F: FnOnce(&mut Registry) + Send + Sync + 'static, - { - Message::Update(Arc::new(UpdateFn(Box::new(f)))) - } - fn action(f: F) -> Self - where - F: FnOnce(&Registry) + Send + Sync + 'static, - { - Message::Action(Arc::new(ActionFn(Box::new(f)))) - } - #[allow(dead_code)] - fn command(command: S, args: I) -> Self - where - I: IntoIterator, - S: AsRef, - { - let mut cmd = Command::new(command); - cmd.args(args); - Message::Spawn(Arc::new(cmd)) - } - fn command_sh(arg: S) -> Self - where - S: AsRef, - { - let mut cmd = Command::new("sh"); - cmd.arg("-c"); - cmd.arg(arg); - Message::Spawn(Arc::new(cmd)) - } - fn popup<'a, T>( - width: i32, - height: i32, - anchor: &BarAnchor, - ) -> impl Fn( - iced::Event, - iced::core::Layout, - iced::mouse::Cursor, - &mut dyn iced::core::Clipboard, - &Rectangle, - ) -> Message - + 'a - where - T: Module, - { - let anchor = *anchor; - move |_: iced::Event, - layout: iced::core::Layout, - _: iced::mouse::Cursor, - _: &mut dyn iced::core::Clipboard, - _: &Rectangle| { - let bounds = layout.bounds(); - let position = layout.position(); - let x = match anchor { - BarAnchor::Left => bounds.width as i32, - BarAnchor::Right => -width, - _ => position.x as i32, - }; - let y = match anchor { - BarAnchor::Top => bounds.height as i32, - BarAnchor::Bottom => -height, - _ => position.y as i32, - }; - Message::Popup { - type_id: TypeId::of::(), - dimension: Rectangle { - x, - y, - width, - height, - }, - } - } - } -} - -#[derive(Debug)] -struct Bar<'a> { - config_file: Arc, - config: Arc, - registry: Registry, - logical_size: Option<(u32, u32)>, - output: IcedOutput, - layer_id: Id, - open: bool, - popup: Option<(TypeId, Id)>, - templates: Handlebars<'a>, -} - -impl Bar<'_> { - fn new() -> (Self, Task) { - let mut registry = Registry::default(); - register_modules(&mut registry); - register_listeners(&mut registry); - register_resolvers(&mut registry); - - let mut templates = Handlebars::new(); - - let config_file = get_config_dir(); - let config = read_config(&config_file, &mut registry, &mut templates); - - ctrlc::set_handler(|| { - println!("Received exit signal...Exiting"); - exit(0); - }) - .unwrap(); - - let bar = Self { - config_file: config_file.into(), - config: config.into(), - registry, - logical_size: None, - output: IcedOutput::Active, - layer_id: Id::unique(), - open: true, - popup: None, - templates, - }; - let task = match &bar.config.monitor { - Some(_) => bar.try_get_output(), - None => bar.open(), - }; - - (bar, task) - } - - fn update(&mut self, msg: Message) -> Task { - match msg { - Message::Popup { type_id, dimension } => { - let settings = |id| SctkPopupSettings { - parent: self.layer_id, - id, - positioner: SctkPositioner { - size: Some((dimension.width as u32, dimension.height as u32)), - anchor_rect: Rectangle { - x: dimension.x, - y: dimension.y, - width: dimension.width, - height: dimension.height, - }, - ..Default::default() - }, - parent_size: None, - grab: true, - }; - return match self.popup { - None => { - let id = Id::unique(); - self.popup = Some((type_id, id)); - get_popup(settings(id)) - } - Some((old_ty_id, id)) => match old_ty_id == type_id { - true => { - self.popup = None; - destroy_popup(id) - } - false => { - self.popup = Some((type_id, id)); - destroy_popup(id).chain(get_popup(settings(id))) - } - }, - }; - } - Message::Update(task) => { - Arc::into_inner(task).unwrap().0(&mut self.registry); - } - Message::Action(task) => { - Arc::into_inner(task).unwrap().0(&self.registry); - } - Message::GetConfig(sx) => sx - .try_send((self.config_file.clone(), self.config.clone())) - .unwrap(), - Message::GetReceiver(sx, f) => sx.try_send(f(&self.registry)).unwrap(), - Message::Spawn(cmd) => { - Arc::into_inner(cmd) - .unwrap() - .spawn() - .inspect_err(|e| eprintln!("Failed to spawn command: {e}")) - .ok(); - } - Message::ReloadConfig => { - println!( - "Reloading config from {}", - self.config_file.to_string_lossy() - ); - self.config = - read_config(&self.config_file, &mut self.registry, &mut self.templates).into(); - if self.config.hard_reload { - self.open = false; - self.registry = Registry::default(); - return destroy_layer_surface(self.layer_id) - .chain(self.open()) - .chain(Task::done(Message::LoadRegistry)); - } - } - Message::LoadRegistry => { - register_modules(&mut self.registry); - register_listeners(&mut self.registry); - register_resolvers(&mut self.registry); - self.config = - read_config(&self.config_file, &mut self.registry, &mut self.templates).into(); - self.open = true; - } - Message::GotOutput(optn) => { - return match optn { - Some(output) => { - self.output = output; - self.try_get_output_info() - } - None => Task::stream(stream::channel(1, |_| async { - sleep(Duration::from_millis(500)).await; - })) - .chain(self.try_get_output()), - } - } - Message::GotOutputInfo(optn) => { - return match optn { - Some(info) => { - self.logical_size = info.logical_size.map(|(x, y)| (x as u32, y as u32)); - self.open() - } - None => Task::stream(stream::channel(1, |_| async { - sleep(Duration::from_millis(500)).await; - })) - .chain(self.try_get_output_info()), - } - } - } - Task::none() - } - - fn view(&self, window_id: Id) -> Element { - if window_id == self.layer_id { - self.bar_view() - } else if let Some(mod_id) = self - .popup - .and_then(|(m_id, p_id)| (p_id == window_id).then_some(m_id)) - { - self.registry.get_module_by_id(mod_id).popup_wrapper( - &self.config.popup_config, - &self.config.anchor, - &self.templates, - ) - } else { - "Internal error".into() - } - } - - fn bar_view(&self) -> Element { - let anchor = &self.config.anchor; - let make_list = |spacing: fn(&Thrice) -> f32, - field: fn(&EnabledModules) -> &Vec| { - container( - list( - anchor, - self.registry - .get_modules(field(&self.config.enabled_modules).iter(), &self.config) - .filter(|&m| m.active()) - .map(|m| { - m.wrapper( - &self.config.module_config.local, - m.view( - &self.config.module_config.local, - &self.config.popup_config, - anchor, - &self.templates, - ), - anchor, - ) - }), - ) - .spacing(spacing(&self.config.module_config.global.spacing)), - ) - .fillx(!anchor.vertical()) - }; - let left = make_list(|s| s.left, |m| &m.left); - let center = make_list(|s| s.center, |m| &m.center); - let right = make_list(|s| s.right, |m| &m.right); - container(stack!( - center.align(anchor, Alignment::Center), - list( - anchor, - [(left, Alignment::Start), (right, Alignment::End)] - .map(|(e, align)| e.align(anchor, align).into()) - ) - )) - .padding(self.config.module_config.global.padding) - .into() - } - - fn open(&self) -> Task { - let (x, y) = self.logical_size.unwrap_or((1920, 1080)); - let (width, height) = match self.config.anchor.vertical() { - true => ( - self.config.module_config.global.width.unwrap_or(30), - self.config.module_config.global.height.unwrap_or(y), - ), - false => ( - self.config.module_config.global.width.unwrap_or(x), - self.config.module_config.global.height.unwrap_or(30), - ), - }; - get_layer_surface(SctkLayerSurfaceSettings { - layer: Layer::Top, - keyboard_interactivity: self.config.kb_focus, - anchor: (&self.config.anchor).into(), - exclusive_zone: self.config.exclusive_zone(), - size: Some((Some(width), Some(height))), - namespace: "bar-rs".to_string(), - output: self.output.clone(), - margin: self.config.module_config.global.margin, - id: self.layer_id, - ..Default::default() - }) - } - - fn try_get_output(&self) -> Task { - let monitor = self.config.monitor.clone(); - get_output(move |output_state| { - output_state - .outputs() - .find(|o| { - output_state - .info(o) - .map(|info| info.name == monitor) - .unwrap_or(false) - }) - .clone() - }) - .map(|optn| Message::GotOutput(optn.map(IcedOutput::Output))) - } - - fn try_get_output_info(&self) -> Task { - let monitor = self.config.monitor.clone(); - get_output_info(move |output_state| { - output_state - .outputs() - .find(|o| { - output_state - .info(o) - .map(|info| info.name == monitor) - .unwrap_or(false) - }) - .and_then(|o| output_state.info(&o)) - .clone() - }) - .map(Message::GotOutputInfo) - } - - fn theme(&self, window_id: Id) -> Theme { - if let Some(mod_id) = self - .popup - .and_then(|(m_id, p_id)| (p_id == window_id).then_some(m_id)) - { - self.registry.get_module_by_id(mod_id).popup_theme() - } else { - Theme::custom( - "Bar theme".to_string(), - Palette { - background: self.config.module_config.global.background_color, - text: Color::WHITE, - primary: Color::WHITE, - success: Color::WHITE, - danger: Color::WHITE, - }, - ) - } - } -} - -trait OptionExt { - fn map_none(self, f: F) -> Self - where - F: FnOnce(); -} - -impl OptionExt for Option { - fn map_none(self, f: F) -> Self - where - F: FnOnce(), - { - if self.is_none() { - f(); - } - self - } -} diff --git a/src/modules/battery.rs b/src/modules/battery.rs deleted file mode 100644 index e86d671..0000000 --- a/src/modules/battery.rs +++ /dev/null @@ -1,540 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::Display; -use std::{collections::HashMap, time::Duration}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::button::Style; -use iced::widget::{column, container, scrollable}; -use iced::{futures::SinkExt, stream, widget::text, Element, Subscription}; -use tokio::{fs, io, runtime, select, sync::mpsc, task, time::sleep}; -use udev::Device; - -use crate::button::button; -use crate::config::popup_config::{PopupConfig, PopupConfigOverride}; -use crate::helpers::UnEscapeString; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Debug, Builder)] -pub struct BatteryMod { - avg: AverageStats, - batteries: Vec, - cfg_override: ModuleConfigOverride, - popup_cfg_override: PopupConfigOverride, - icons: BTreeMap, - icons_charging: BTreeMap, -} - -impl Default for BatteryMod { - fn default() -> Self { - BatteryMod { - avg: AverageStats::default(), - batteries: vec![], - cfg_override: Default::default(), - popup_cfg_override: PopupConfigOverride { - width: Some(250), - height: Some(250), - ..Default::default() - }, - icons: BTreeMap::from([ - (80, "󱊣".to_string()), - (60, "󱊢".to_string()), - (25, "󱊡".to_string()), - (0, "󰂎".to_string()), - ]), - icons_charging: BTreeMap::from([ - (80, "󱊦 ".to_string()), - (60, "󱊥 ".to_string()), - (25, "󱊤 ".to_string()), - (0, "󰢟 ".to_string()), - ]), - } - } -} - -impl BatteryMod { - fn icon(&self, capacity: Option, charging: Option) -> &String { - let capacity = capacity.unwrap_or(self.avg.capacity); - let is_charging = charging.unwrap_or(self.avg.charging); - let icons = match is_charging { - true => &self.icons_charging, - false => &self.icons, - }; - icons - .iter() - .filter(|(k, _)| capacity >= **k) - .last() - .unwrap() - .1 - } -} - -#[derive(Debug, Default)] -struct AverageStats { - capacity: u8, - charging: bool, - hours: u16, - minutes: u16, - // If you have a laptop with AC not plugged in, yet all batteries report power_now as 0, - // something's gotta be wrong. - _cursed: bool, -} - -#[derive(Debug, Default)] -enum BatteryState { - Charging, - Discharging, - #[default] - Idle, -} - -impl Display for BatteryState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Charging => "charging", - Self::Discharging => "discharging", - Self::Idle => "", - } - ) - } -} - -#[derive(Debug, Default)] -struct Battery { - name: String, - model_name: String, - energy_now: f32, - energy_full: f32, - health: u8, - state: BatteryState, - /// (hours, minutes) - remaining: Option<(u16, u16)>, -} - -impl Battery { - fn capacity(&self) -> u8 { - (self.energy_now / self.energy_full * 100.) as u8 - } - - fn is_charging(&self) -> bool { - matches!(self.state, BatteryState::Charging) - } - - fn icon<'a>(&'a self, module: &'a BatteryMod) -> &'a String { - module.icon(Some(self.capacity()), Some(self.is_charging())) - } -} - -#[derive(Default, Debug)] -struct BatteryStats { - name: String, - model_name: String, - energy_now: f32, - energy_full: f32, - energy_full_design: f32, - power_now: f32, - voltage_now: f32, - charging: bool, -} - -impl Module for BatteryMod { - fn name(&self) -> String { - "battery".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - handlebars: &Handlebars, - ) -> Element { - let mut ctx = BTreeMap::new(); - ctx.insert("capacity", self.avg.capacity as u16); - ctx.insert("hours", self.avg.hours); - ctx.insert("minutes", self.avg.minutes); - button( - list![ - anchor, - container( - text(self.icon(None, None)) - .fill(anchor) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .font(NERD_FONT) - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text(handlebars.render("battery", &ctx).unwrap_or_default()) - .fill(anchor) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)), - ) - .on_event_with(Message::popup::( - self.popup_cfg_override.width.unwrap_or(popup_config.width), - self.popup_cfg_override - .height - .unwrap_or(popup_config.height), - anchor, - )) - .style(|_, _| Style::default()) - .into() - } - - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - container(scrollable( - column(self.batteries.iter().map(|bat| { - let state = bat.state.to_string(); - let capacity = bat.capacity().to_string(); - let energy = (bat.energy_now.floor() as u32 / 1000000).to_string(); - let health = bat.health.to_string(); - - let remaining = bat - .remaining - .and_then(|(hours, minutes)| { - let time_ctx = BTreeMap::from([("hours", hours), ("minutes", minutes)]); - template - .render("battery_popup_time_remaining", &time_ctx) - .map_err(|e| eprintln!("Failed to render remaining battery time: {e}")) - .ok() - }) - .unwrap_or_default(); - - let mut ctx = BTreeMap::new(); - ctx.insert("name", &bat.name); - ctx.insert("state", &state); - ctx.insert("icon", bat.icon(self)); - ctx.insert("capacity", &capacity); - ctx.insert("energy", &energy); - ctx.insert("health", &health); - ctx.insert("time_remaining", &remaining); - ctx.insert("model", &bat.model_name); - - container( - text( - template - .render("battery_popup", &ctx) - .map_err(|e| eprintln!("Failed to render battery stats: {e}")) - .unwrap_or_default(), - ) - .size( - self.popup_cfg_override - .font_size - .unwrap_or(config.font_size), - ) - .color( - self.popup_cfg_override - .text_color - .unwrap_or(config.text_color), - ), - ) - .padding( - self.popup_cfg_override - .text_margin - .unwrap_or(config.text_margin), - ) - .into() - })) - .spacing(self.popup_cfg_override.spacing.unwrap_or(config.spacing)), - )) - .padding(self.popup_cfg_override.padding.unwrap_or(config.padding)) - .style(|_| container::Style { - background: Some( - self.popup_cfg_override - .background - .unwrap_or(config.background), - ), - border: self.popup_cfg_override.border.unwrap_or(config.border), - ..Default::default() - }) - .fill_maybe( - self.popup_cfg_override - .fill_content_to_size - .unwrap_or(config.fill_content_to_size), - ) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.popup_cfg_override.update(popup_config); - templates - .register_template_string( - "battery", - config - .get("format") - .unescape() - .unwrap_or("{{capacity}}% ({{hours}}h {{minutes}}min left)".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery format: {e}")); - templates - .register_template_string( - "battery_popup", - popup_config - .get("format").unescape() - .unwrap_or("{{name}}: {{state}}\n\t{{icon}} {{capacity}}% ({{energy}} Wh)\n\thealth: {{health}}%{{time_remaining}}\n\tmodel: {{model}}".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - templates - .register_template_string( - "battery_popup_time_remaining", - popup_config - .get("format_time") - .unescape() - .unwrap_or("\n\t{{hours}}h {{minutes}}min remaining".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup time format: {e}")); - } - - impl_on_click!(); - - fn subscription(&self) -> Option> { - Some(Subscription::run(|| { - let (sx, mut rx) = mpsc::channel(10); - std::thread::spawn(move || { - let local = task::LocalSet::new(); - let runtime = runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - runtime.block_on(local.run_until(async move { - task::spawn_local(async move { - let socket = udev::MonitorBuilder::new() - .and_then(|b| b.match_subsystem_devtype("power_supply", "power_supply")) - .and_then(|b| b.listen()) - .expect("Failed to build udev MonitorBuilder"); - - loop { - let Some(event) = socket.iter().next() else { - sleep(Duration::from_millis(10)).await; - continue; - }; - - if event.sysname() != "AC" { - continue; - } - sleep(Duration::from_secs(1)).await; - if sx.send(()).await.is_err() { - return; - } - } - }) - .await - .unwrap(); - })); - }); - - stream::channel(1, |mut sender| async move { - tokio::spawn(async move { - loop { - let (avg, batteries) = get_stats(None, false).await.unwrap(); - if sender - .send(Message::update(move |reg| { - let m = reg.get_module_mut::(); - m.avg = avg; - m.batteries = batteries - })) - .await - .is_err() - { - return; - } - select! { - _ = sleep(Duration::from_secs(30)) => {} - _ = rx.recv() => {} - } - } - }); - }) - })) - } -} - -impl From<(&Device, String)> for BatteryStats { - fn from((device, name): (&Device, String)) -> Self { - BatteryStats { - name, - model_name: get_property(device, "POWER_SUPPLY_MODEL_NAME").to_string(), - energy_now: get_property(device, "POWER_SUPPLY_ENERGY_NOW") - .parse() - .unwrap_or(0.), - energy_full: get_property(device, "POWER_SUPPLY_ENERGY_FULL") - .parse() - .unwrap_or(0.), - energy_full_design: get_property(device, "POWER_SUPPLY_ENERGY_FULL_DESIGN") - .parse() - .unwrap_or(0.), - power_now: get_property(device, "POWER_SUPPLY_POWER_NOW") - .parse() - .unwrap_or(0.), - voltage_now: get_property(device, "POWER_SUPPLY_VOLTAGE_NOW") - .parse() - .unwrap_or(0.), - charging: matches!(get_property(device, "POWER_SUPPLY_STATUS"), "Charging"), - } - } -} - -impl From<&Vec> for AverageStats { - fn from(batteries: &Vec) -> Self { - let energy_now = batteries.iter().fold(0., |mut acc, bat| { - acc += bat.energy_now; - acc - }); - let energy_full = batteries.iter().fold(0., |mut acc, bat| { - acc += bat.energy_full; - acc - }); - let (power_now, voltage_now) = - batteries - .iter() - .filter(|bat| bat.power_now != 0.) - .fold((0., 0.), |mut acc, bat| { - acc.0 += bat.power_now; - acc.1 += bat.voltage_now; - acc - }); - - let capacity = (100. / energy_full * energy_now).round() as u8; - let charging = batteries.iter().any(|bat| bat.charging); - let time_remaining = match charging { - true => { - (energy_full - energy_now) - / 1000000. - / ((power_now / 1000000.) * (voltage_now / 1000000.)) - * 12.55 - } - false => energy_now / power_now, - }; - AverageStats { - capacity, - charging, - hours: time_remaining.floor() as u16, - minutes: ((time_remaining - time_remaining.floor()) * 60.) as u16, - _cursed: power_now == 0., - } - } -} - -impl From for Battery { - fn from(stats: BatteryStats) -> Self { - let remaining = (stats.power_now != 0.).then(|| { - let t = match stats.charging { - true => { - (stats.energy_full - stats.energy_now) - / 1000000. - / ((stats.power_now / 1000000.) * (stats.voltage_now / 1000000.)) - * 12.55 - } - false => stats.energy_now / stats.power_now, - }; - (t.floor() as u16, ((t - t.floor()) * 60.) as u16) - }); - Battery { - name: stats.name, - model_name: stats.model_name, - energy_now: stats.energy_now, - energy_full: stats.energy_full, - health: (stats.energy_full / stats.energy_full_design * 100.) as u8, - state: match stats.charging { - true => BatteryState::Charging, - false => match stats.power_now == 0. { - true => BatteryState::Idle, - false => BatteryState::Discharging, - }, - }, - remaining, - } - } -} - -fn get_property<'a>(device: &'a Device, property: &'static str) -> &'a str { - device - .property_value(property) - .and_then(|v| v.to_str()) - .unwrap_or("") -} - -async fn get_stats( - selection: Option<&Vec>, - is_blacklist: bool, -) -> Result<(AverageStats, Vec), io::Error> { - let mut entries = fs::read_dir("/sys/class/power_supply").await?; - let mut batteries = vec![]; - while let Ok(Some(dev_name)) = entries.next_entry().await { - if let Some(selection) = selection { - if is_blacklist - == selection.contains(&dev_name.file_name().to_string_lossy().to_string()) - { - continue; - } - } - if let Ok(dev_type) = - fs::read_to_string(&format!("{}/type", dev_name.path().to_string_lossy())).await - { - if dev_type.trim() == "Battery" { - batteries.push(dev_name.path()); - } - } - } - let batteries = batteries.iter().fold(vec![], |mut acc, bat| { - let Ok(device) = Device::from_syspath(bat) else { - eprintln!( - "Battery {} could not be turned into a udev Device", - bat.to_string_lossy() - ); - return acc; - }; - - acc.push(BatteryStats::from(( - &device, - bat.file_name().unwrap().to_string_lossy().to_string(), - ))); - acc - }); - Ok(( - (&batteries).into(), - batteries.into_iter().map(|b| b.into()).collect(), - )) -} - -/* - How upower calculates remaining time (upower/src/up-daemon.c): - /* calculate a quick and dirty time remaining value - * NOTE: Keep in sync with per-battery estimation code! */ - if (energy_rate_total > 0) { - if (state_total == UP_DEVICE_STATE_DISCHARGING) - time_to_empty_total = SECONDS_PER_HOUR * (energy_total / energy_rate_total); - else if (state_total == UP_DEVICE_STATE_CHARGING) - time_to_full_total = SECONDS_PER_HOUR * ((energy_full_total - energy_total) / energy_rate_total); - } -*/ diff --git a/src/modules/cpu.rs b/src/modules/cpu.rs deleted file mode 100644 index 6f15c7b..0000000 --- a/src/modules/cpu.rs +++ /dev/null @@ -1,362 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - fs::File, - hash::Hash, - io::{self, BufRead, BufReader}, - num, - time::Duration, -}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::{button::Style, container, scrollable, Container, Text}; -use iced::{futures::SinkExt, stream, widget::text, Element, Subscription}; -use tokio::time::sleep; - -use crate::{ - button::button, - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - popup_config::{PopupConfig, PopupConfigOverride}, - }, - fill::FillExt, - helpers::UnEscapeString, - impl_on_click, impl_wrapper, Message, NERD_FONT, -}; - -use super::Module; - -#[derive(Debug, Builder)] -pub struct CpuMod { - avg_usage: CpuStats, - cores: BTreeMap>, - cfg_override: ModuleConfigOverride, - popup_cfg_override: PopupConfigOverride, - icon: Option, -} - -impl Default for CpuMod { - fn default() -> Self { - Self { - avg_usage: Default::default(), - cores: BTreeMap::new(), - cfg_override: Default::default(), - popup_cfg_override: PopupConfigOverride { - width: Some(150), - height: Some(350), - ..Default::default() - }, - icon: None, - } - } -} - -impl Module for CpuMod { - fn name(&self) -> String { - "cpu".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - button( - list![ - anchor, - container( - text!("{}", self.icon.as_ref().unwrap_or(&"󰻠".to_string())) - .fill(anchor) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text!["{}%", self.avg_usage.all] - .fill(anchor) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)), - ) - .on_event_with(Message::popup::( - self.popup_cfg_override.width.unwrap_or(popup_config.width), - self.popup_cfg_override - .height - .unwrap_or(popup_config.height), - anchor, - )) - .style(|_, _| Style::default()) - .into() - } - - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - let fmt_text = |text: Text<'a>| -> Container<'a, Message> { - container( - text.size( - self.popup_cfg_override - .font_size - .unwrap_or(config.font_size), - ) - .color( - self.popup_cfg_override - .text_color - .unwrap_or(config.text_color), - ), - ) - .padding( - self.popup_cfg_override - .text_margin - .unwrap_or(config.text_margin), - ) - }; - let ctx = BTreeMap::from([ - ("total", self.avg_usage.all.to_string()), - ("user", self.avg_usage.user.to_string()), - ("system", self.avg_usage.system.to_string()), - ("guest", self.avg_usage.guest.to_string()), - ( - "cores", - self.cores - .iter() - .map(|(ty, stats)| { - let core = BTreeMap::from([ - ("index", ty.get_core_index().to_string()), - ("total", stats.all.to_string()), - ("user", stats.user.to_string()), - ("system", stats.system.to_string()), - ("guest", stats.guest.to_string()), - ]); - template - .render("cpu_core", &core) - .map_err(|e| eprintln!("Failed to render cpu core stats: {e}")) - .unwrap_or_default() - }) - .collect::>() - .join("\n"), - ), - ]); - let format = template - .render("cpu", &ctx) - .map_err(|e| eprintln!("Failed to render cpu stats: {e}")) - .unwrap_or_default(); - container(scrollable(fmt_text(text(format)))) - .padding(self.popup_cfg_override.padding.unwrap_or(config.padding)) - .style(|_| container::Style { - background: Some( - self.popup_cfg_override - .background - .unwrap_or(config.background), - ), - border: self.popup_cfg_override.border.unwrap_or(config.border), - ..Default::default() - }) - .fill_maybe( - self.popup_cfg_override - .fill_content_to_size - .unwrap_or(config.fill_content_to_size), - ) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.popup_cfg_override.update(popup_config); - self.icon = config.get("icon").and_then(|v| v.clone()); - templates - .register_template_string( - "cpu", - popup_config - .get("format") - .unescape() - .unwrap_or("Total: {{total}}%\nUser: {{user}}%\nSystem: {{system}}%\nGuest: {{guest}}%\n{{cores}}".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - templates - .register_template_string( - "cpu_core", - popup_config - .get("format_core") - .unescape() - .unwrap_or("Core {{index}}: {{total}}%".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - } - - impl_on_click!(); - - fn subscription(&self) -> Option> { - Some(Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let interval: u64 = 500; - let gap: u64 = 2000; - loop { - let Ok(mut raw_stats1) = read_raw_stats() - .map_err(|e| eprintln!("Failed to read cpu stats from /proc/stat: {e:?}")) - else { - return; - }; - sleep(Duration::from_millis(interval)).await; - let Ok(mut raw_stats2) = read_raw_stats() else { - eprintln!("Failed to read cpu stats from /proc/stat"); - return; - }; - - let avg = ( - &raw_stats1.remove(&CpuType::All).unwrap(), - &raw_stats2.remove(&CpuType::All).unwrap(), - ) - .into(); - - let cores = raw_stats1 - .into_iter() - .filter_map(|(ty, stats1)| { - raw_stats2 - .get(&ty) - .map(|stats2| (ty, (&stats1, stats2).into())) - }) - .collect(); - - sender - .send(Message::update(move |reg| { - let m = reg.get_module_mut::(); - m.avg_usage = avg; - m.cores = cores - })) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to send cpu_usage failed with err: {err}"); - }); - - sleep(Duration::from_millis(gap)).await; - } - }) - })) - } -} - -#[derive(Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -enum CpuType { - #[default] - All, - Core(u8), -} - -impl CpuType { - fn get_core_index(&self) -> u8 { - match self { - CpuType::All => 255, - CpuType::Core(index) => *index, - } - } -} - -impl From<&str> for CpuType { - fn from(value: &str) -> Self { - value - .strip_prefix("cpu") - .and_then(|v| v.parse().ok().map(Self::Core)) - .unwrap_or(Self::All) - } -} - -#[derive(Default, Debug)] -struct CpuStats { - all: T, - user: T, - system: T, - guest: T, - total: T, -} - -impl TryFrom<&str> for CpuStats { - type Error = ReadError; - fn try_from(value: &str) -> Result { - let values: Result, num::ParseIntError> = - value.split_whitespace().map(|p| p.parse()).collect(); - // Documentation can be found at - // https://docs.kernel.org/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat - let [user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice] = - values?[..] - else { - return Err(ReadError::ValueListInvalid); - }; - let all = user + nice + system + irq + softirq; - Ok(CpuStats { - all, - user: user + nice, - system, - guest: guest + guest_nice, - total: all + idle + iowait + steal, - }) - } -} - -impl From<(&CpuStats, &CpuStats)> for CpuStats { - fn from((stats1, stats2): (&CpuStats, &CpuStats)) -> Self { - let delta_all = stats2.all - stats1.all; - let delta_user = stats2.user - stats1.user; - let delta_system = stats2.system - stats1.system; - let delta_guest = stats2.guest - stats1.guest; - let delta_total = stats2.total - stats1.total; - if delta_total == 0 { - return Self::default(); - } - Self { - all: ((delta_all as f32 / delta_total as f32) * 100.) as u8, - user: ((delta_user as f32 / delta_total as f32) * 100.) as u8, - system: ((delta_system as f32 / delta_total as f32) * 100.) as u8, - guest: ((delta_guest as f32 / delta_total as f32) * 100.) as u8, - total: 0, - } - } -} - -fn read_raw_stats() -> Result>, ReadError> { - let file = File::open("/proc/stat")?; - let reader = BufReader::new(file); - let lines = reader.lines().filter_map(|l| { - l.ok().and_then(|line| { - let (cpu, data) = line.split_once(' ')?; - Some((cpu.into(), data.try_into().ok()?)) - }) - }); - Ok(lines.collect()) -} - -#[allow(dead_code)] -#[derive(Debug)] -enum ReadError { - IoError(io::Error), - ParseError(num::ParseIntError), - ValueListInvalid, -} - -impl From for ReadError { - fn from(value: io::Error) -> Self { - Self::IoError(value) - } -} - -impl From for ReadError { - fn from(value: num::ParseIntError) -> Self { - Self::ParseError(value) - } -} diff --git a/src/modules/date.rs b/src/modules/date.rs deleted file mode 100644 index ce38b03..0000000 --- a/src/modules/date.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::collections::HashMap; - -use bar_rs_derive::Builder; -use chrono::Local; -use handlebars::Handlebars; -use iced::widget::{container, text}; -use iced::Element; - -use crate::config::popup_config::PopupConfig; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Debug, Builder)] -pub struct DateMod { - cfg_override: ModuleConfigOverride, - icon: String, - fmt: String, -} - -impl Default for DateMod { - fn default() -> Self { - Self { - cfg_override: Default::default(), - icon: "".to_string(), - fmt: "%a, %d. %b".to_string(), - } - } -} - -impl Module for DateMod { - fn name(&self) -> String { - "date".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - let time = Local::now(); - list![ - anchor, - container( - text!("{}", self.icon) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - .fill(anchor) - ) - .fill(anchor) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text!("{}", time.format(&self.fmt)) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - .fill(anchor) - ) - .fill(anchor) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.icon = config - .get("icon") - .and_then(|v| v.clone()) - .unwrap_or(default.icon); - self.fmt = config - .get("format") - .and_then(|v| v.clone()) - .unwrap_or(default.fmt); - } - - impl_on_click!(); -} diff --git a/src/modules/disk_usage.rs b/src/modules/disk_usage.rs deleted file mode 100644 index 003455a..0000000 --- a/src/modules/disk_usage.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - ffi::CString, - mem, -}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::{ - widget::{button::Style, container, scrollable, text, Container, Text}, - Element, -}; -use libc::{__errno_location, statvfs}; - -use crate::{ - button::button, - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - popup_config::{PopupConfig, PopupConfigOverride}, - }, - fill::FillExt, - helpers::UnEscapeString, - impl_on_click, impl_wrapper, Message, NERD_FONT, -}; - -use super::Module; - -#[derive(Debug, Builder, Default)] -pub struct DiskUsageMod { - icon: Option, - cfg_override: ModuleConfigOverride, - popup_cfg_override: PopupConfigOverride, - path: CString, -} - -#[derive(Debug, Default)] -/// All values are represented in megabytes, except the `_perc` fields -struct FileSystemStats { - total: u64, - free: u64, - used: u64, - /// Free space in percentage points - free_perc: u8, - /// Used space in percentage points - used_perc: u8, -} - -impl From for BTreeMap<&'static str, u64> { - fn from(value: FileSystemStats) -> Self { - BTreeMap::from([ - ("total", value.total), - ("total_gb", value.total / 1000), - ("used", value.used), - ("used_gb", value.used / 1000), - ("free", value.free), - ("free_gb", value.free / 1000), - ("used_perc", value.used_perc.into()), - ("free_perc", value.free_perc.into()), - ]) - } -} - -impl From for FileSystemStats { - fn from(value: statvfs) -> Self { - let free_perc = (value.f_bavail as f32 / value.f_blocks as f32 * 100.) as u8; - Self { - total: value.f_blocks * value.f_frsize / 1_000_000, - free: value.f_bavail * value.f_frsize / 1_000_000, - used: (value.f_blocks - value.f_bavail) * value.f_frsize / 1_000_000, - free_perc, - used_perc: 100 - free_perc, - } - } -} - -impl Module for DiskUsageMod { - fn name(&self) -> String { - "disk_usage".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - handlebars: &Handlebars, - ) -> Element { - let Ok(stats) = get_stats(&self.path) else { - return "Error".into(); - }; - let ctx: BTreeMap<&'static str, u64> = stats.into(); - let format = handlebars - .render("disk_usage", &ctx) - .map_err(|e| eprintln!("Failed to render disk_usage stats: {e}")) - .unwrap_or_default(); - button( - list![ - anchor, - container( - text!("{}", self.icon.as_ref().unwrap_or(&"󰦚".to_string())) - .fill(anchor) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text(format) - .fill(anchor) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)), - ) - .on_event_with(Message::popup::( - self.popup_cfg_override.width.unwrap_or(popup_config.width), - self.popup_cfg_override - .height - .unwrap_or(popup_config.height), - anchor, - )) - .style(|_, _| Style::default()) - .into() - } - - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - let fmt_text = |text: Text<'a>| -> Container<'a, Message> { - container( - text.size( - self.popup_cfg_override - .font_size - .unwrap_or(config.font_size), - ) - .color( - self.popup_cfg_override - .text_color - .unwrap_or(config.text_color), - ), - ) - .padding( - self.popup_cfg_override - .text_margin - .unwrap_or(config.text_margin), - ) - }; - let Ok(stats) = get_stats(&self.path) else { - return "Error".into(); - }; - let ctx: BTreeMap<&'static str, u64> = stats.into(); - let format = template - .render("disk_usage_popup", &ctx) - .map_err(|e| eprintln!("Failed to render disk_usage stats: {e}")) - .unwrap_or_default(); - container(scrollable(fmt_text(text(format)))) - .padding(self.popup_cfg_override.padding.unwrap_or(config.padding)) - .style(|_| container::Style { - background: Some( - self.popup_cfg_override - .background - .unwrap_or(config.background), - ), - border: self.popup_cfg_override.border.unwrap_or(config.border), - ..Default::default() - }) - .fill_maybe( - self.popup_cfg_override - .fill_content_to_size - .unwrap_or(config.fill_content_to_size), - ) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.popup_cfg_override.update(popup_config); - self.icon = config.get("icon").and_then(|v| v.clone()); - self.path = config - .get("path") - .and_then(|v| v.clone().and_then(|v| CString::new(v).ok())) - .unwrap_or_else(|| CString::new("/").unwrap()); - templates - .register_template_string( - "disk_usage", - config - .get("format") - .unescape() - .unwrap_or("{{used_perc}}%".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - templates - .register_template_string( - "disk_usage_popup", - popup_config - .get("format") - .unescape() - .unwrap_or("Total: {{total_gb}} GB\nUsed: {{used_gb}} GB ({{used_perc}}%)\nFree: {{free_gb}} GB ({{free_perc}}%)".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - } - - impl_on_click!(); -} - -/// Get file system statistics using the statvfs system call, see -/// https://man7.org/linux/man-pages/man3/statvfs.3.html -fn get_stats(path: &CString) -> Result { - let mut raw_stats: statvfs = unsafe { mem::zeroed() }; - if unsafe { libc::statvfs(path.as_ptr(), &mut raw_stats) } != 0 { - eprintln!( - "Got an error while executing the statvfs syscall: {}", - unsafe { *__errno_location() } - ); - return Err(()); - } - Ok(raw_stats.into()) -} diff --git a/src/modules/hyprland/mod.rs b/src/modules/hyprland/mod.rs deleted file mode 100644 index c3c3656..0000000 --- a/src/modules/hyprland/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod window; -pub mod workspaces; diff --git a/src/modules/hyprland/window.rs b/src/modules/hyprland/window.rs deleted file mode 100644 index c2d131f..0000000 --- a/src/modules/hyprland/window.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::{container, rich_text, span, text}; -use iced::{ - futures::{channel::mpsc::Sender, SinkExt}, - Element, -}; - -use crate::config::popup_config::PopupConfig; -use crate::tooltip::ElementExt; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - listeners::hyprland::HyprListener, - modules::{require_listener, Message, Module}, -}; -use crate::{impl_on_click, impl_wrapper}; - -#[derive(Debug, Builder)] -pub struct HyprWindowMod { - title: Option, - max_length: usize, - cfg_override: ModuleConfigOverride, -} - -impl Default for HyprWindowMod { - fn default() -> Self { - Self { - title: None, - max_length: 25, - cfg_override: Default::default(), - } - } -} - -impl HyprWindowMod { - pub fn get_title(&self) -> Option { - self.title - .as_ref() - .map(|title| match title.len() > self.max_length { - true => format!( - "{}...", - title.chars().take(self.max_length - 3).collect::() - ), - false => title.to_string(), - }) - } -} - -impl Module for HyprWindowMod { - fn name(&self) -> String { - "hyprland.window".to_string() - } - - fn active(&self) -> bool { - self.title.is_some() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - container( - rich_text([span(self.get_title().unwrap_or_default()) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color))]) - .fill(anchor), - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)) - .tooltip_maybe( - self.get_title() - .and_then(|t| (t.len() > self.max_length).then_some(text(t).size(12))), - ) - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![require_listener::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.max_length = config - .get("max_length") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(Self::default().max_length); - } - - impl_on_click!(); -} - -pub async fn update_window(sender: &mut Sender, title: Option) { - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().title = title - })) - .await - .unwrap_or_else(|err| { - eprintln!("Trying to send workspaces failed with err: {err}"); - }); -} diff --git a/src/modules/hyprland/workspaces.rs b/src/modules/hyprland/workspaces.rs deleted file mode 100644 index 0f57572..0000000 --- a/src/modules/hyprland/workspaces.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::{any::TypeId, collections::HashMap, time::Duration}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use hyprland::{ - data::{Workspace, Workspaces}, - shared::{HyprData, HyprDataActive, HyprDataVec}, -}; -use iced::{ - widget::{container, rich_text, span}, - Background, Border, Color, Element, Padding, -}; -use tokio::time::sleep; - -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - parse::StringExt, - popup_config::PopupConfig, - }, - fill::FillExt, - impl_on_click, impl_wrapper, - list::list, - listeners::hyprland::HyprListener, - modules::{require_listener, Module}, - Message, NERD_FONT, -}; - -#[derive(Debug, Builder)] -pub struct HyprWorkspaceMod { - pub active: usize, - // (Name, Fullscreen state) - pub open: Vec<(String, bool)>, - cfg_override: ModuleConfigOverride, - icon_padding: Padding, - icon_background: Option, - icon_border: Border, - active_padding: Option, - active_size: f32, - active_color: Color, - active_background: Option, - active_icon_border: Border, -} - -impl Default for HyprWorkspaceMod { - fn default() -> Self { - Self { - active: 0, - open: vec![], - cfg_override: ModuleConfigOverride::default(), - icon_padding: Padding::default(), - icon_background: None, - icon_border: Border::default(), - active_padding: None, - active_size: 20., - active_color: Color::WHITE, - active_background: None, - active_icon_border: Border::default().rounded(8), - } - } -} - -impl Module for HyprWorkspaceMod { - fn name(&self) -> String { - "hyprland.workspaces".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - list( - anchor, - self.open.iter().enumerate().map(|(id, (ws, _))| { - let mut span = span(ws) - .padding(self.icon_padding) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .background_maybe(self.icon_background) - .border(self.icon_border) - .font(NERD_FONT); - if id == self.active { - span = span - .padding(self.active_padding.unwrap_or(self.icon_padding)) - .size(self.active_size) - .color(self.active_color) - .background_maybe(self.active_background) - .border(self.active_icon_border); - } - container(rich_text![span].fill(anchor)) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)) - .into() - }), - ) - .padding(self.cfg_override.padding.unwrap_or(config.padding)) - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![require_listener::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.icon_padding = config - .get("icon_padding") - .and_then(|v| v.into_insets().map(|i| i.into())) - .unwrap_or(default.icon_padding); - self.icon_background = config - .get("icon_background") - .map(|v| v.into_background()) - .unwrap_or(default.icon_background); - self.icon_border = { - let color = config.get("icon_border_color").and_then(|s| s.into_color()); - let width = config.get("icon_border_width").and_then(|s| s.into_float()); - let radius = config - .get("icon_border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Border { - color: color.unwrap_or_default(), - width: width.unwrap_or(1.), - radius: radius.unwrap_or(8_f32.into()), - } - } else { - default.active_icon_border - } - }; - self.active_padding = config - .get("active_padding") - .map(|v| v.into_insets().map(|i| i.into())) - .unwrap_or(default.active_padding); - self.active_size = config - .get("active_size") - .and_then(|v| v.into_float()) - .unwrap_or(default.active_size); - self.active_color = config - .get("active_color") - .and_then(|v| v.into_color()) - .unwrap_or(default.active_color); - self.active_background = config - .get("active_background") - .map(|v| v.into_background()) - .unwrap_or(default.active_background); - self.active_icon_border = { - let color = config - .get("active_border_color") - .and_then(|s| s.into_color()); - let width = config - .get("active_border_width") - .and_then(|s| s.into_float()); - let radius = config - .get("active_border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Border { - color: color.unwrap_or_default(), - width: width.unwrap_or(1.), - radius: radius.unwrap_or(8_f32.into()), - } - } else { - default.active_icon_border - } - }; - } - - impl_on_click!(); -} - -pub async fn get_workspaces(active: Option) -> (usize, Vec<(String, bool)>) { - // Sleep a bit, to reduce the probability that a nonexisting ws is still reported active - sleep(Duration::from_millis(10)).await; - let Ok(workspaces) = Workspaces::get_async().await else { - eprintln!("[hyprland.workspaces] Failed to get Workspaces!"); - return (0, vec![]); - }; - let mut open = workspaces.to_vec(); - open.sort_by(|a, b| a.id.cmp(&b.id)); - ( - open.iter() - .position(|ws| { - ws.id - == active - .unwrap_or_else(|| Workspace::get_active().map(|ws| ws.id).unwrap_or(0)) - }) - .unwrap_or(0), - open.into_iter() - .map(|ws| (ws.name, ws.fullscreen)) - .collect(), - ) -} diff --git a/src/modules/media.rs b/src/modules/media.rs deleted file mode 100644 index ddce5a3..0000000 --- a/src/modules/media.rs +++ /dev/null @@ -1,536 +0,0 @@ -use std::collections::{BTreeMap, HashSet}; -use std::{collections::HashMap, process::Stdio}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::button::Style; -use iced::widget::{column, container, image, row, scrollable, Container, Text}; -use iced::Length::Fill; -use iced::{futures::SinkExt, stream, widget::text, Element, Subscription}; -use serde::Deserialize; -use tokio::{ - io::{AsyncBufReadExt, BufReader}, - process::Command, -}; - -use crate::button::button; -use crate::config::popup_config::{PopupConfig, PopupConfigOverride}; -use crate::helpers::UnEscapeString; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Debug, Builder)] -pub struct MediaMod { - track: Option, - img: Option>, - active_player: Option, - cfg_override: ModuleConfigOverride, - popup_cfg_override: PopupConfigOverride, - icon: String, - ctrl_icons: PlayerCtrlIcons, - max_length: usize, - max_title_length: usize, - players: HashSet, - cover_width: f32, -} - -#[derive(Debug)] -struct PlayerCtrlIcons { - previous: String, - play: String, - pause: String, - next: String, -} - -impl Default for MediaMod { - fn default() -> Self { - Self { - track: None, - img: None, - active_player: None, - cfg_override: Default::default(), - popup_cfg_override: PopupConfigOverride { - width: Some(300), - height: Some(450), - ..Default::default() - }, - icon: String::from(""), - ctrl_icons: PlayerCtrlIcons { - previous: String::from("󰒮"), - play: String::from(""), - pause: String::from(""), - next: String::from("󰒭"), - }, - max_length: 28, - max_title_length: 16, - players: HashSet::from(["spotify".to_string(), "kew".to_string()]), - cover_width: 260., - } - } -} - -impl MediaMod { - fn get_active_trimmed(&self) -> Option { - self.track.as_ref().map(|track| { - let mut title = track.title.clone(); - let mut artist = track.artist.clone(); - if self.is_overlength() { - if title.len() > self.max_title_length { - title = title.chars().take(self.max_title_length - 3).collect(); - title.push_str("..."); - } - if title.len() + artist.len() + 3 > self.max_length { - artist = artist - .chars() - .take(self.max_length - title.len() - 6) - .collect(); - artist.push_str("..."); - } - } - match track.artist.is_empty() { - true => title, - false => format!("{} - {}", title, artist), - } - }) - } - - fn is_overlength(&self) -> bool { - self.track - .as_ref() - .is_some_and(|t| t.title.len() + t.artist.len() + 3 > self.max_length) - } - - fn new_track(&mut self, track: TrackInfo) { - if self.players.contains(&track.player) { - self.active_player = Some(track.player.clone()); - self.track = Some(track) - } - } -} - -#[derive(Debug)] -struct TrackInfo { - title: String, - artist: String, - album: String, - art_url: String, - player: String, - art_is_local: bool, - length: f32, - paused: bool, -} - -impl<'de> Deserialize<'de> for TrackInfo { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let map: serde_json::Map = - Deserialize::deserialize(deserializer)?; - - let mut art_url = map - .get("art_url") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); - - let art_is_local = match art_url.strip_prefix("file://") { - Some(file) => { - art_url = file.to_string(); - true - } - None => false, - }; - - Ok(TrackInfo { - title: map - .get("title") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(), - artist: map - .get("artist") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(), - album: map - .get("album") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(), - player: map - .get("player") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(), - art_url, - art_is_local, - length: map - .get("length") - .and_then(|v| v.as_f64()) - .unwrap_or_default() as f32, - paused: map - .get("status") - .map(|v| !matches!(v.as_str(), Some("Playing"))) - .unwrap_or(true), - }) - } -} - -impl Module for MediaMod { - fn name(&self) -> String { - "media".to_string() - } - - fn active(&self) -> bool { - self.track.is_some() - } - - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - button( - list![ - anchor, - container( - text(&self.icon) - .fill(anchor) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text(self.get_active_trimmed().unwrap_or_default()) - .fill(anchor) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)) - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)), - ) - .on_event_maybe_with(self.track.as_ref().map(|_| { - Message::popup::( - self.popup_cfg_override.width.unwrap_or(popup_config.width), - self.popup_cfg_override - .height - .unwrap_or(popup_config.height), - anchor, - ) - })) - .style(|_, _| Style::default()) - .into() - } - - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - let fmt_text = |text: Text<'a>| -> Container<'a, Message> { - container( - text.size( - self.popup_cfg_override - .font_size - .unwrap_or(config.font_size), - ) - .color( - self.popup_cfg_override - .text_color - .unwrap_or(config.text_color), - ), - ) - .padding( - self.popup_cfg_override - .text_margin - .unwrap_or(config.text_margin), - ) - }; - container(match &self.track { - Some(track) => { - let minutes = (track.length / 60000000.).trunc(); - let icon = |icon| { - container( - text(icon) - .font(NERD_FONT) - .size( - self.popup_cfg_override - .icon_size - .unwrap_or(config.icon_size), - ) - .color( - self.popup_cfg_override - .icon_color - .unwrap_or(config.icon_color), - ), - ) - .padding( - self.popup_cfg_override - .icon_margin - .unwrap_or(config.icon_margin), - ) - }; - let cmd = |cmd| { - Message::command_sh(format!( - "playerctl {cmd}{}", - self.active_player - .as_ref() - .map(|p| format!(" -p {p}")) - .unwrap_or_default() - )) - }; - let status = track - .paused - .then_some(" (paused)".to_string()) - .unwrap_or_default(); - let length_ctx = BTreeMap::from([ - ("minutes", minutes as u32), - ( - "seconds", - ((track.length / 1000000.) - minutes * 60.).round() as u32, - ), - ]); - let length = template - .render("media_popup_length", &length_ctx) - .map_err(|e| eprintln!("Failed to render media popup length: {e}")) - .unwrap_or_default(); - let ctx = BTreeMap::from([ - ("title", &track.title), - ("artist", &track.artist), - ("album", &track.album), - ("status", &status), - ("length", &length), - ]); - as Into>>::into(scrollable( - column![ - match track.art_is_local { - true => >>::into( - image( - track - .art_url - .strip_prefix("file://") - .unwrap_or(&track.art_url) - ) - .width(self.cover_width) - ), - false => - if let Some(bytes) = self.img.clone() { - >>::into(image( - image::Handle::from_bytes(bytes), - )) - } else { - fmt_text(text("No cover available")).into() - }, - }, - container( - row![ - button(icon(&self.ctrl_icons.previous)) - .on_event(cmd("previous")) - .style(|_, _| Style::default()), - button(icon(match track.paused { - true => &self.ctrl_icons.play, - false => &self.ctrl_icons.pause, - })) - .on_event(cmd("play-pause")) - .style(|_, _| Style::default()), - button(icon(&self.ctrl_icons.next)) - .on_event(cmd("next")) - .style(|_, _| Style::default()), - ] - .spacing(20) - ) - .center_x(Fill), - fmt_text(text( - template - .render("media_popup", &ctx) - .map_err(|e| eprintln!("Failed to render media popup stats: {e}")) - .unwrap_or_default() - )), - ] - .spacing(self.popup_cfg_override.spacing.unwrap_or(config.spacing)), - )) - } - None => fmt_text(text("No media is playing right now")).into(), - }) - .padding(self.popup_cfg_override.padding.unwrap_or(config.padding)) - .style(|_| container::Style { - background: Some( - self.popup_cfg_override - .background - .unwrap_or(config.background), - ), - border: self.popup_cfg_override.border.unwrap_or(config.border), - ..Default::default() - }) - .fill_maybe( - self.popup_cfg_override - .fill_content_to_size - .unwrap_or(config.fill_content_to_size), - ) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.popup_cfg_override.update(popup_config); - self.icon = config - .get("icon") - .and_then(|v| v.clone()) - .unwrap_or(default.icon); - self.max_length = config - .get("max_length") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(default.max_length); - self.max_title_length = config - .get("max_title_length") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(default.max_title_length); - self.players = popup_config - .get("players") - .and_then(|v| { - v.as_ref() - .map(|v| v.split(',').map(|i| i.trim().to_string()).collect()) - }) - .unwrap_or(default.players); - self.cover_width = popup_config - .get("cover_width") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(default.cover_width); - self.ctrl_icons = { - let default = default.ctrl_icons; - PlayerCtrlIcons { - previous: popup_config - .get("icon_previous") - .cloned() - .flatten() - .unwrap_or(default.previous), - play: popup_config - .get("icon_play") - .cloned() - .flatten() - .unwrap_or(default.play), - pause: popup_config - .get("icon_pause") - .cloned() - .flatten() - .unwrap_or(default.pause), - next: popup_config - .get("icon_next") - .cloned() - .flatten() - .unwrap_or(default.next), - } - }; - templates - .register_template_string( - "media_popup", - popup_config.get("format").unescape().unwrap_or( - "{{title}}{{status}}\nin: {{album}}\nby: {{artist}}\n{{length}}".to_string(), - ), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup time format: {e}")); - templates - .register_template_string( - "media_popup_length", - popup_config - .get("format_length") - .unescape() - .unwrap_or("{{minutes}}min {{seconds}}sec".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup time format: {e}")); - } - - impl_on_click!(); - - fn subscription(&self) -> Option> { - Some(Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let mut child = Command::new("sh") - .arg("-c") - .arg( - "playerctl --follow metadata --format '{\"title\": \"{{title}}\", \"artist\": \"{{artist}}\", \"album\": \"{{album}}\", \"art_url\": \"{{mpris:artUrl}}\", \"length\": {{mpris:length}}, \"status\": \"{{status}}\", \"player\": \"{{playerName}}\"}'", - ) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to read output from playerctl"); - - let stdout = child - .stdout - .take() - .expect("child did not have a handle to stdout"); - - let mut reader = BufReader::new(stdout).lines(); - let mut last_track = String::new(); - - loop { - let line = reader.next_line().await.ok().flatten(); - if let Some(track) = line - .as_ref() - .and_then(|line| serde_json::from_str::(line.as_str()).ok()) - { - if let Some(url) = (!track.art_is_local).then_some(track.art_url.clone()) { - if url != last_track { - last_track = url.clone(); - let mut sender = sender.clone(); - tokio::task::spawn(async move { - let Ok(response) = reqwest::get(&url).await else { - eprintln!("Failed to get media cover: \"{url}\""); - return; - }; - let Ok(bytes) = response.bytes().await else { - eprintln!( - "Failed to get bytes from media cover: \"{url}\"" - ); - return; - }; - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().img = - Some(bytes.to_vec()) - })) - .await - .unwrap(); - }); - } - } - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().new_track(track) - })) - .await - .unwrap(); - } else if matches!(line.as_ref().map(|l| l.trim()), Some("")) { - sender - .send(Message::update(move |reg| { - reg.get_module_mut::().track = None - })) - .await - .unwrap(); - } - } - }) - })) - } -} diff --git a/src/modules/memory.rs b/src/modules/memory.rs deleted file mode 100644 index 07cc66e..0000000 --- a/src/modules/memory.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{collections::HashMap, process::Command}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::container; -use iced::{widget::text, Element}; - -use crate::config::popup_config::PopupConfig; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Debug, Default, Builder)] -pub struct MemoryMod { - cfg_override: ModuleConfigOverride, - icon: Option, -} - -impl Module for MemoryMod { - fn name(&self) -> String { - "memory".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - let usage = Command::new("sh") - .arg("-c") - .arg("free | grep Mem | awk '{printf \"%.0f\", $3/$2 * 100.0}'") - .output() - .map(|out| String::from_utf8_lossy(&out.stdout).to_string()) - .unwrap_or_else(|e| { - eprintln!("Failed to get memory usage. err: {e}"); - "0".to_string() - }) - .parse() - .unwrap_or_else(|e| { - eprintln!("Failed to parse memory usage (output from free), e: {e}"); - 999 - }); - - list![ - anchor, - container( - text!("{}", self.icon.as_ref().unwrap_or(&"󰍛".to_string())) - .fill(anchor) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text!["{}%", usage] - .fill(anchor) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.icon = config.get("icon").and_then(|v| v.clone()); - } - - impl_on_click!(); -} diff --git a/src/modules/mod.rs b/src/modules/mod.rs deleted file mode 100644 index 4fd35dc..0000000 --- a/src/modules/mod.rs +++ /dev/null @@ -1,271 +0,0 @@ -use std::{ - any::{Any, TypeId}, - collections::HashMap, - fmt::Debug, -}; - -use battery::BatteryMod; -use cpu::CpuMod; -use date::DateMod; -use disk_usage::DiskUsageMod; -use downcast_rs::{impl_downcast, Downcast}; -use handlebars::Handlebars; -use hyprland::{window::HyprWindowMod, workspaces::HyprWorkspaceMod}; -use iced::{ - theme::Palette, - widget::{container, Container}, - Alignment, Color, Event, Theme, -}; -use iced::{widget::container::Style, Element, Subscription}; -use media::MediaMod; -use memory::MemoryMod; -use niri::{NiriWindowMod, NiriWorkspaceMod}; -use time::TimeMod; -use volume::VolumeMod; -use wayfire::{WayfireWindowMod, WayfireWorkspaceMod}; - -use crate::{ - config::{anchor::BarAnchor, module_config::LocalModuleConfig, popup_config::PopupConfig}, - fill::FillExt, - listeners::Listener, - registry::Registry, - Message, -}; - -pub mod battery; -pub mod cpu; -pub mod date; -pub mod disk_usage; -pub mod hyprland; -pub mod media; -pub mod memory; -pub mod niri; -pub mod sys_tray; -pub mod time; -pub mod volume; -pub mod wayfire; - -pub trait Module: Any + Debug + Send + Sync + Downcast { - /// The name used to enable the Module in the config. - fn name(&self) -> String; - /// Whether the module is currently active and should be shown. - fn active(&self) -> bool { - true - } - /// What the module actually shows. - /// See [widgets-and-elements](https://docs.iced.rs/iced/#widgets-and-elements). - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - template: &Handlebars, - ) -> Element; - /// The wrapper around this module, which defines things like background color or border for - /// this module. - fn wrapper<'a>( - &'a self, - config: &'a LocalModuleConfig, - content: Element<'a, Message>, - anchor: &BarAnchor, - ) -> Element<'a, Message> { - container( - container(content) - .fill(anchor) - .padding(config.padding) - .style(|_| Style { - background: config.background, - border: config.border, - ..Default::default() - }), - ) - .fill(anchor) - .padding(config.margin) - .into() - } - /// The module may optionally have a subscription listening for external events. - /// See [passive-subscriptions](https://docs.iced.rs/iced/#passive-subscriptions). - fn subscription(&self) -> Option> { - None - } - /// Modules may require shared subscriptions. Add `require_listener::()` - /// for every [Listener] this module requires. - fn requires(&self) -> Vec { - vec![] - } - #[allow(unused_variables)] - /// Read configuration options from the config section of this module - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - } - #[allow(unused_variables)] - /// The action to perform on a on_click event - fn on_click<'a>( - &'a self, - event: iced::Event, - config: &'a LocalModuleConfig, - ) -> Option<&'a dyn Action> { - None - } - #[allow(unused_variables, dead_code)] - /// Handle an action (likely produced by a user interaction). - fn handle_action(&mut self, action: &dyn Action) {} - #[allow(unused_variables)] - /// The view of a popup - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - "Missing implementation".into() - } - /// The wrapper around a popup - fn popup_wrapper<'a>( - &'a self, - config: &'a PopupConfig, - anchor: &BarAnchor, - template: &Handlebars, - ) -> Element<'a, Message> { - let align = |elem: Container<'a, Message>| -> Container<'a, Message> { - match anchor { - BarAnchor::Top => elem.align_y(Alignment::Start), - BarAnchor::Bottom => elem.align_y(Alignment::End), - BarAnchor::Left => elem.align_x(Alignment::Start), - BarAnchor::Right => elem.align_x(Alignment::End), - } - }; - align(container(self.popup_view(config, template)).fill(anchor)).into() - } - /// The theme of a popup - fn popup_theme(&self) -> Theme { - Theme::custom( - "Default popup theme".to_string(), - Palette { - background: Color::TRANSPARENT, - text: Color::WHITE, - primary: Color::WHITE, - success: Color::WHITE, - danger: Color::WHITE, - }, - ) - } -} -impl_downcast!(Module); - -pub trait Action: Any + Debug + Send + Sync + Downcast { - fn as_message(&self) -> Message; -} -impl_downcast!(Action); - -impl From<&String> for Box { - fn from(value: &String) -> Box { - Box::new(CommandAction(value.clone())) - } -} - -#[derive(Debug)] -pub struct CommandAction(String); - -impl Action for CommandAction { - fn as_message(&self) -> Message { - Message::command_sh(&self.0) - } -} - -#[derive(Debug, Default)] -pub struct OnClickAction { - pub left: Option>, - pub center: Option>, - pub right: Option>, -} - -impl OnClickAction { - pub fn event(&self, event: Event) -> Option<&dyn Action> { - match event { - Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left)) => { - self.left.as_deref() - } - Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Middle)) => { - self.center.as_deref() - } - Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Right)) => { - self.right.as_deref() - } - _ => None, - } - } -} - -pub fn require_listener() -> TypeId -where - T: Listener, -{ - TypeId::of::() -} - -pub fn register_modules(registry: &mut Registry) { - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); - registry.register_module::(); -} - -#[macro_export] -macro_rules! impl_wrapper { - () => { - fn wrapper<'a>( - &'a self, - config: &'a LocalModuleConfig, - content: Element<'a, Message>, - anchor: &BarAnchor, - ) -> Element<'a, Message> { - iced::widget::container( - $crate::button::button(content) - .fill(anchor) - .padding(self.cfg_override.padding.unwrap_or(config.padding)) - .on_event_try(|evt, _, _, _, _| { - self.on_click(evt, config).map(|evt| evt.as_message()) - }) - .style(|_, _| iced::widget::button::Style { - background: self.cfg_override.background.unwrap_or(config.background), - border: self.cfg_override.border.unwrap_or(config.border), - ..Default::default() - }), - ) - .fill(anchor) - .padding(self.cfg_override.margin.unwrap_or(config.margin)) - .into() - } - }; -} - -#[macro_export] -macro_rules! impl_on_click { - () => { - fn on_click<'a>( - &'a self, - event: iced::Event, - config: &'a LocalModuleConfig, - ) -> Option<&'a dyn $crate::modules::Action> { - self.cfg_override - .action - .as_ref() - .unwrap_or(&config.action) - .event(event) - } - }; -} diff --git a/src/modules/niri/mod.rs b/src/modules/niri/mod.rs deleted file mode 100644 index fa7f072..0000000 --- a/src/modules/niri/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod window; -mod workspaces; - -pub use window::NiriWindowMod; -pub use workspaces::NiriWorkspaceMod; diff --git a/src/modules/niri/window.rs b/src/modules/niri/window.rs deleted file mode 100644 index 55fd743..0000000 --- a/src/modules/niri/window.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::collections::BTreeMap; -use std::{any::TypeId, collections::HashMap}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::button::Style; -use iced::widget::{container, scrollable, text}; -use iced::Element; -use niri_ipc::Window; - -use crate::button::button; -use crate::config::popup_config::{PopupConfig, PopupConfigOverride}; -use crate::helpers::UnEscapeString; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - parse::StringExt, - }, - fill::FillExt, - listeners::niri::NiriListener, - modules::{require_listener, Module}, - Message, -}; -use crate::{impl_on_click, impl_wrapper}; - -#[derive(Debug, Builder)] -pub struct NiriWindowMod { - // (title, app_id) - pub windows: HashMap, - pub focused: Option, - max_length: usize, - show_app_id: bool, - cfg_override: ModuleConfigOverride, - popup_cfg_override: PopupConfigOverride, -} - -impl Default for NiriWindowMod { - fn default() -> Self { - Self { - windows: HashMap::new(), - focused: None, - max_length: 25, - show_app_id: false, - cfg_override: Default::default(), - popup_cfg_override: PopupConfigOverride { - width: Some(400), - height: Some(250), - ..Default::default() - }, - } - } -} - -impl NiriWindowMod { - fn get_title(&self) -> Option<&String> { - self.focused.and_then(|id| { - self.windows.get(&id).and_then(|w| match self.show_app_id { - true => w.app_id.as_ref(), - false => w.title.as_ref(), - }) - }) - } - - fn trimmed_title(&self) -> String { - self.get_title() - .map(|title| match title.len() > self.max_length { - true => format!( - "{}...", - &title.chars().take(self.max_length - 3).collect::() - ), - false => title.to_string(), - }) - .unwrap_or_default() - } -} - -impl Module for NiriWindowMod { - fn name(&self) -> String { - "niri.window".to_string() - } - - fn active(&self) -> bool { - self.focused.is_some() - } - - fn view( - &self, - config: &LocalModuleConfig, - popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - button( - text(self.trimmed_title()) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - .fill(anchor), - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)) - .on_event_with(Message::popup::( - self.popup_cfg_override.width.unwrap_or(popup_config.width), - self.popup_cfg_override - .height - .unwrap_or(popup_config.height), - anchor, - )) - .style(|_, _| Style::default()) - .into() - } - - fn popup_view<'a>( - &'a self, - config: &'a PopupConfig, - template: &Handlebars, - ) -> Element<'a, Message> { - container(scrollable( - container( - if let Some(window) = self.focused.and_then(|id| self.windows.get(&id)) { - let unset = String::from("Unset"); - let window_id = window.id.to_string(); - let workspace_id = window.workspace_id.unwrap_or_default().to_string(); - let ctx = BTreeMap::from([ - ("title", window.title.as_ref().unwrap_or(&unset)), - ("app_id", window.app_id.as_ref().unwrap_or(&unset)), - ("window_id", &window_id), - ("workspace_id", &workspace_id), - ]); - text(template.render("niri.window", &ctx).unwrap_or_default()) - } else { - "No window focused".into() - } - .color( - self.popup_cfg_override - .text_color - .unwrap_or(config.text_color), - ) - .size( - self.popup_cfg_override - .font_size - .unwrap_or(config.font_size), - ), - ) - .padding( - self.popup_cfg_override - .text_margin - .unwrap_or(config.text_margin), - ), - )) - .padding(self.popup_cfg_override.padding.unwrap_or(config.padding)) - .style(|_| container::Style { - background: Some( - self.popup_cfg_override - .background - .unwrap_or(config.background), - ), - border: self.popup_cfg_override.border.unwrap_or(config.border), - ..Default::default() - }) - .fill_maybe( - self.popup_cfg_override - .fill_content_to_size - .unwrap_or(config.fill_content_to_size), - ) - .into() - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![require_listener::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - popup_config: &HashMap>, - templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.popup_cfg_override.update(popup_config); - self.max_length = config - .get("max_length") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(default.max_length); - self.show_app_id = config - .get("show_app_id") - .and_then(|v| v.into_bool()) - .unwrap_or(default.show_app_id); - templates - .register_template_string( - "niri.window", - popup_config - .get("format") - .unescape() - .unwrap_or("Title: {{title}}\nApplication ID: {{app_id}}\nWindow ID: {{window_id}}\nWorkspace ID: {{workspace_id}}".to_string()), - ) - .unwrap_or_else(|e| eprintln!("Failed to parse battery popup format: {e}")); - } - - impl_on_click!(); -} diff --git a/src/modules/niri/workspaces.rs b/src/modules/niri/workspaces.rs deleted file mode 100644 index 5ef27c6..0000000 --- a/src/modules/niri/workspaces.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::{ - any::{Any, TypeId}, - collections::HashMap, - sync::Arc, -}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::{ - widget::{button, container, text}, - Background, Border, Color, Element, Padding, -}; -use niri_ipc::Workspace; -use tokio::sync::broadcast; - -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - parse::StringExt, - popup_config::PopupConfig, - }, - fill::FillExt, - impl_on_click, impl_wrapper, list, - listeners::niri::NiriListener, - modules::{require_listener, Module}, - Message, NERD_FONT, -}; - -#[derive(Debug, Builder)] -pub struct NiriWorkspaceMod { - pub workspaces: HashMap>, - pub focused: u64, - pub sender: broadcast::Sender>, - cfg_override: ModuleConfigOverride, - icon_padding: Padding, - icon_background: Option, - icon_border: Border, - active_padding: Option, - active_size: f32, - active_color: Color, - active_background: Option, - active_icon_border: Border, - // Output, (idx, icon) - icons: HashMap>, - fallback_icon: String, - active_fallback_icon: String, - output_order: Vec, -} - -impl Default for NiriWorkspaceMod { - fn default() -> Self { - Self { - workspaces: HashMap::new(), - focused: 0, - sender: broadcast::channel(1).0, - cfg_override: Default::default(), - icon_padding: Padding::default(), - icon_background: None, - icon_border: Border::default(), - active_padding: None, - active_size: 20., - active_color: Color::WHITE, - active_background: None, - active_icon_border: Border::default().rounded(8), - icons: HashMap::new(), - fallback_icon: String::from(""), - active_fallback_icon: String::from(""), - output_order: vec![], - } - } -} - -impl NiriWorkspaceMod { - fn sort_by_outputs<'a, F, I>(&'a self, f: F) -> Vec> - where - F: Fn((&'a String, &'a Vec)) -> I, - I: Iterator>, - { - match self.output_order.is_empty() { - true => self - .workspaces - .iter() - .flat_map(f) - .collect::>>(), - false => self - .output_order - .iter() - .filter_map(|o| self.workspaces.get_key_value(o)) - .flat_map(f) - .collect::>>(), - } - } -} - -impl Module for NiriWorkspaceMod { - fn name(&self) -> String { - "niri.workspaces".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - list( - anchor, - self.sort_by_outputs(|(output, workspaces)| { - workspaces.iter().map(|ws| { - let mut text = text( - self.icons - .get(&output.to_lowercase()) - .and_then(|icons| icons.get(&ws.idx)) - .unwrap_or(match ws.id == self.focused { - true => &self.active_fallback_icon, - false => &self.fallback_icon, - }), - ) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT); - let mut btn_style = button::Style { - background: self.icon_background, - border: self.icon_border, - ..Default::default() - }; - let id = ws.id; - if id == self.focused { - text = text.size(self.active_size).color(self.active_color); - btn_style.background = self.active_background; - btn_style.border = self.active_icon_border; - } - container( - button(text) - .padding(match id == self.focused { - true => self.active_padding.unwrap_or(self.icon_padding), - false => self.icon_padding, - }) - .style(move |_, _| btn_style) - .on_press(Message::action(move |reg| { - reg.get_module::() - .sender - .send(Arc::new(id)) - .unwrap(); - })), - ) - .fill(anchor) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)) - .into() - }) - }), - ) - .padding(self.cfg_override.padding.unwrap_or(config.padding)) - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![require_listener::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.icon_padding = config - .get("icon_padding") - .and_then(|v| v.into_insets().map(|i| i.into())) - .unwrap_or(default.icon_padding); - self.icon_background = config - .get("icon_background") - .map(|v| v.into_background()) - .unwrap_or(default.icon_background); - self.icon_border = { - let color = config.get("icon_border_color").and_then(|s| s.into_color()); - let width = config.get("icon_border_width").and_then(|s| s.into_float()); - let radius = config - .get("icon_border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Border { - color: color.unwrap_or_default(), - width: width.unwrap_or(1.), - radius: radius.unwrap_or(8_f32.into()), - } - } else { - default.active_icon_border - } - }; - self.active_padding = config - .get("active_padding") - .map(|v| v.into_insets().map(|i| i.into())) - .unwrap_or(default.active_padding); - self.active_size = config - .get("active_size") - .and_then(|v| v.into_float()) - .unwrap_or(default.active_size); - self.active_color = config - .get("active_color") - .and_then(|v| v.into_color()) - .unwrap_or(default.active_color); - self.active_background = config - .get("active_background") - .map(|v| v.into_background()) - .unwrap_or(default.active_background); - self.active_icon_border = { - let color = config - .get("active_border_color") - .and_then(|s| s.into_color()); - let width = config - .get("active_border_width") - .and_then(|s| s.into_float()); - let radius = config - .get("active_border_radius") - .and_then(|s| s.into_insets().map(|i| i.into())); - if color.is_some() || width.is_some() || radius.is_some() { - Border { - color: color.unwrap_or_default(), - width: width.unwrap_or(1.), - radius: radius.unwrap_or(8_f32.into()), - } - } else { - default.active_icon_border - } - }; - self.fallback_icon = config - .get("fallback_icon") - .and_then(|v| v.clone()) - .unwrap_or(default.fallback_icon); - self.active_fallback_icon = config - .get("active_fallback_icon") - .and_then(|v| v.clone()) - .unwrap_or(default.active_fallback_icon); - self.output_order = config - .get("output_order") - .and_then(|v| v.clone()) - .map(|v| v.split(',').map(|v| v.trim().to_string()).collect()) - .unwrap_or(default.output_order); - config.iter().for_each(|(key, val)| { - let Some(val) = val.clone() else { - return; - }; - if let [output, idx] = key.split(':').map(|i| i.trim()).collect::>()[..] { - if let Ok(idx) = idx.parse() { - match self.icons.get_mut(output) { - Some(icons) => { - icons.insert(idx, val); - } - None => { - self.icons - .insert(output.to_string(), HashMap::from([(idx, val)])); - } - } - } - } - }); - } - - impl_on_click!(); -} diff --git a/src/modules/sys_tray.rs b/src/modules/sys_tray.rs deleted file mode 100644 index f209fbc..0000000 --- a/src/modules/sys_tray.rs +++ /dev/null @@ -1,22 +0,0 @@ -use iced::{futures::Stream, stream}; -//use system_tray::client::Client; - -use crate::Message; - -pub fn _system_tray() -> impl Stream { - stream::channel(1, |mut _sender| async move { - /*let client = Client::new().await.unwrap(); - let mut tray_rx = client.subscribe(); - - let initial_items = client.items(); - - println!("initial_items: {initial_items:#?}\n\n"); - - // do something with initial items... - drop(initial_items); - - while let Ok(ev) = tray_rx.recv().await { - println!("{ev:#?}"); // do something with event... - }*/ - }) -} diff --git a/src/modules/time.rs b/src/modules/time.rs deleted file mode 100644 index ad76b9f..0000000 --- a/src/modules/time.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::collections::HashMap; - -use bar_rs_derive::Builder; -use chrono::Local; -use handlebars::Handlebars; -use iced::widget::{container, text}; -use iced::Element; - -use crate::config::popup_config::PopupConfig; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Debug, Builder)] -pub struct TimeMod { - cfg_override: ModuleConfigOverride, - icon: String, - fmt: String, -} - -impl Default for TimeMod { - fn default() -> Self { - Self { - cfg_override: Default::default(), - icon: "".to_string(), - fmt: "%H:%M".to_string(), - } - } -} - -impl Module for TimeMod { - fn name(&self) -> String { - "time".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - let time = Local::now(); - list![ - anchor, - container( - text!("{}", self.icon) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - .fill(anchor) - ) - .fill(anchor) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text!("{}", time.format(&self.fmt)) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - .fill(anchor) - ) - .fill(anchor) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - let default = Self::default(); - self.cfg_override = config.into(); - self.icon = config - .get("icon") - .and_then(|v| v.clone()) - .unwrap_or(default.icon); - self.fmt = config - .get("format") - .and_then(|v| v.clone()) - .unwrap_or(default.fmt); - } - - impl_on_click!(); -} diff --git a/src/modules/volume.rs b/src/modules/volume.rs deleted file mode 100644 index efdd3dd..0000000 --- a/src/modules/volume.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::{collections::HashMap, process::Stdio}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::{button, container}; -use iced::{futures::SinkExt, stream, widget::text, Element, Subscription}; -use tokio::{ - io::{AsyncBufReadExt, BufReader}, - process::Command, -}; - -use crate::config::popup_config::PopupConfig; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -use super::Module; - -#[derive(Default, Debug, Builder)] -pub struct VolumeMod { - level: u16, - icon: &'static str, - cfg_override: ModuleConfigOverride, -} - -impl Module for VolumeMod { - fn name(&self) -> String { - "volume".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - list![ - anchor, - button( - text!("{}", self.icon) - .fill(anchor) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT) - ) - .style(|_, _| button::Style::default()) - .on_press(Message::command_sh( - "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" - )) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)), - container( - text!["{}%", self.level,] - .fill(anchor) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color)) - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)), - ] - .spacing(self.cfg_override.spacing.unwrap_or(config.spacing)) - .into() - } - - impl_wrapper!(); - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - } - - impl_on_click!(); - - fn subscription(&self) -> Option> { - Some(Subscription::run(|| { - stream::channel(1, |mut sender| async move { - let volume = || { - Message::update(move |reg| { - let vmod = reg.get_module_mut::(); - let volume = get_volume(); - vmod.level = volume.0; - vmod.icon = volume.1; - }) - }; - - sender.send(volume()).await.unwrap_or_else(|err| { - eprintln!("Trying to send volume failed with err: {err}"); - }); - - let mut child = Command::new("sh") - .arg("-c") - .arg("pactl subscribe") - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to spawn pactl to monitor volume changes"); - - let stdout = child - .stdout - .take() - .expect("child did not have a handle to stdout"); - - let mut reader = BufReader::new(stdout).lines(); - - while let Some(line) = reader.next_line().await.unwrap() { - if line.contains("'change' on sink") { - sender.send(volume()).await.unwrap_or_else(|err| { - eprintln!("Trying to send volume failed with err: {err}"); - }); - } - } - }) - })) - } -} - -fn get_volume() -> (u16, &'static str) { - let volume = String::from_utf8( - std::process::Command::new("sh") - .arg("-c") - .arg("wpctl get-volume @DEFAULT_AUDIO_SINK@") - .output() - .expect("Couldn't get volume from wpctl") - .stdout, - ) - .expect("Couldn't convert output from wpctl to String"); - let mut volume = volume - .as_str() - .strip_prefix("Volume: ") - .unwrap_or_else(|| { - eprintln!( - "Failed to get volume from wpctl, tried: `wpctl get-volume @DEFAULT_AUDIO_SINK@`" - ); - "0" - }) - .trim(); - let mut muted = false; - if let Some(x) = volume.strip_suffix(" [MUTED]") { - volume = x; - muted = true; - } - let volume = volume.parse::().unwrap(); - let volume = (volume * 100.) as u16; - ( - volume, - match muted { - true => "󰖁", - false => match volume { - n if n >= 50 => "󰕾", - n if n >= 25 => "󰖀", - _ => "󰕿", - }, - }, - ) -} diff --git a/src/modules/wayfire/mod.rs b/src/modules/wayfire/mod.rs deleted file mode 100644 index 9e86521..0000000 --- a/src/modules/wayfire/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod window; -mod workspaces; - -pub use window::WayfireWindowMod; -pub use workspaces::WayfireWorkspaceMod; diff --git a/src/modules/wayfire/window.rs b/src/modules/wayfire/window.rs deleted file mode 100644 index 386482a..0000000 --- a/src/modules/wayfire/window.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::{container, rich_text, span, text}; -use iced::Element; - -use crate::config::popup_config::PopupConfig; -use crate::tooltip::ElementExt; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - listeners::wayfire::WayfireListener, - modules::Module, - Message, -}; -use crate::{impl_on_click, impl_wrapper}; - -#[derive(Debug, Builder)] -pub struct WayfireWindowMod { - pub title: Option, - max_length: usize, - cfg_override: ModuleConfigOverride, -} - -impl Default for WayfireWindowMod { - fn default() -> Self { - Self { - title: None, - max_length: 25, - cfg_override: Default::default(), - } - } -} - -impl WayfireWindowMod { - pub fn get_title(&self) -> Option { - self.title - .as_ref() - .map(|title| match title.len() > self.max_length { - true => format!( - "{}...", - title.chars().take(self.max_length - 3).collect::() - ), - false => title.to_string(), - }) - } -} - -impl Module for WayfireWindowMod { - fn name(&self) -> String { - "wayfire.window".to_string() - } - - fn active(&self) -> bool { - self.title.is_some() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - container( - rich_text([span(self.get_title().unwrap_or_default()) - .size(self.cfg_override.font_size.unwrap_or(config.font_size)) - .color(self.cfg_override.text_color.unwrap_or(config.text_color))]) - .fill(anchor), - ) - .padding(self.cfg_override.text_margin.unwrap_or(config.text_margin)) - .tooltip_maybe( - self.get_title() - .and_then(|t| (t.len() > self.max_length).then_some(text(t).size(12))), - ) - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![TypeId::of::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.max_length = config - .get("max_length") - .and_then(|v| v.as_ref().and_then(|v| v.parse().ok())) - .unwrap_or(Self::default().max_length); - } - - impl_on_click!(); -} diff --git a/src/modules/wayfire/workspaces.rs b/src/modules/wayfire/workspaces.rs deleted file mode 100644 index 56bdc0f..0000000 --- a/src/modules/wayfire/workspaces.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -use bar_rs_derive::Builder; -use handlebars::Handlebars; -use iced::widget::{container, rich_text, span}; -use iced::Element; -use iced::Padding; - -use crate::config::parse::StringExt; -use crate::config::popup_config::PopupConfig; -use crate::{ - config::{ - anchor::BarAnchor, - module_config::{LocalModuleConfig, ModuleConfigOverride}, - }, - fill::FillExt, - listeners::wayfire::WayfireListener, - modules::Module, - Message, NERD_FONT, -}; -use crate::{impl_on_click, impl_wrapper}; - -/// I am unaware of a IPC method that gives a list of currently active workspaces (the ones with an -/// open window), and this is generally tricky here, since all workspaces of a wset grid are active -/// in a way. It would probably be posible to calculate the workspace of each active window -/// manually, but I'm too lazy to do that atm. - -#[derive(Debug, Default, Builder)] -pub struct WayfireWorkspaceMod { - pub active: (i64, i64), - icons: HashMap<(i64, i64), String>, - cfg_override: ModuleConfigOverride, - icon_padding: Padding, - fallback_icon: Option, -} - -impl Module for WayfireWorkspaceMod { - fn name(&self) -> String { - "wayfire.workspaces".to_string() - } - - fn view( - &self, - config: &LocalModuleConfig, - _popup_config: &PopupConfig, - anchor: &BarAnchor, - _handlebars: &Handlebars, - ) -> Element { - container( - rich_text([span( - self.icons - .get(&self.active) - .or(self.fallback_icon.as_ref()) - .cloned() - .unwrap_or(format!("{}/{}", self.active.0, self.active.1)), - ) - .padding(self.icon_padding) - .size(self.cfg_override.icon_size.unwrap_or(config.icon_size)) - .color(self.cfg_override.icon_color.unwrap_or(config.icon_color)) - .font(NERD_FONT)]) - .fill(anchor), - ) - .padding(self.cfg_override.icon_margin.unwrap_or(config.icon_margin)) - .into() - } - - impl_wrapper!(); - - fn requires(&self) -> Vec { - vec![TypeId::of::()] - } - - fn read_config( - &mut self, - config: &HashMap>, - _popup_config: &HashMap>, - _templates: &mut Handlebars, - ) { - self.cfg_override = config.into(); - self.icon_padding = config - .get("icon_padding") - .and_then(|v| v.into_insets().map(|i| i.into())) - .unwrap_or(Self::default().icon_padding); - self.fallback_icon = config.get("fallback_icon").and_then(|v| v.clone()); - config.iter().for_each(|(key, val)| { - if let Some(key) = key - .strip_prefix('(') - .and_then(|v| v.strip_suffix(')')) - .and_then(|v| { - let [x, y] = v.split(',').map(|item| item.trim()).collect::>()[..] - else { - return None; - }; - x.parse().and_then(|x| y.parse().map(|y| (x, y))).ok() - }) - { - self.icons.insert(key, val.clone().unwrap_or(String::new())); - } - }); - } - - impl_on_click!(); -} diff --git a/src/registry.rs b/src/registry.rs deleted file mode 100644 index 76f6707..0000000 --- a/src/registry.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::{ - any::{Any, TypeId}, - collections::{HashMap, HashSet}, - fmt::Debug, -}; - -use crate::{ - config::{Config, EnabledModules}, - listeners::Listener, - modules::Module, - OptionExt, -}; - -pub trait Builder: Any { - type Output; - fn build() -> Self::Output; -} - -#[allow(clippy::type_complexity)] -#[derive(Default, Debug)] -pub struct Registry { - modules: HashMap>, - listeners: HashMap>, - module_names: HashMap, - resolvers: HashMap) -> Option>, -} - -#[allow(dead_code)] -impl Registry { - pub fn register_module(&mut self) - where - T::Output: Module, - { - let output = T::build(); - let type_id = TypeId::of::(); - self.module_names.insert(output.name(), type_id); - self.modules.insert(type_id, Box::new(output)); - } - - pub fn register_listener(&mut self) - where - T::Output: Listener, - { - let output = T::build(); - self.listeners.insert(TypeId::of::(), Box::new(output)); - } - - pub fn try_get_module(&self) -> Option<&T> { - let id = &TypeId::of::(); - self.modules.get(id).and_then(|t| t.downcast_ref::()) - } - - pub fn try_get_listener(&self) -> Option<&T> { - let id = &TypeId::of::(); - self.listeners.get(id).and_then(|t| t.downcast_ref::()) - } - - pub fn try_get_module_mut(&mut self) -> Option<&mut T> { - let id = &TypeId::of::(); - self.modules.get_mut(id).and_then(|t| t.downcast_mut::()) - } - - pub fn try_get_listener_mut(&mut self) -> Option<&mut T> { - let id = &TypeId::of::(); - self.listeners - .get_mut(id) - .and_then(|t| t.downcast_mut::()) - } - - pub fn get_module_by_id(&self, id: TypeId) -> &dyn Module { - self.modules.get(&id).unwrap().as_ref() - } - - pub fn get_module(&self) -> &T { - self.try_get_module().unwrap() - } - - pub fn get_listener(&self) -> &T { - self.try_get_listener().unwrap() - } - - pub fn get_module_mut(&mut self) -> &mut T { - self.try_get_module_mut().unwrap() - } - - pub fn get_listener_mut(&mut self) -> &mut T { - self.try_get_listener_mut().unwrap() - } - - pub fn get_modules<'a, I>( - &'a self, - enabled: I, - config: &'a Config, - ) -> impl Iterator> - where - I: Iterator, - { - enabled.filter_map(|id| { - self.module_names - .get(id) - .copied() - .or_else(|| self.resolvers.get(id).and_then(|f| f(Some(config)))) - .and_then(|id| self.modules.get(&id)) - }) - } - - pub fn get_modules_mut<'a, I>( - &'a mut self, - enabled: I, - config: &Config, - ) -> impl Iterator> - where - I: Iterator, - { - let resolver_types = self - .resolvers - .values() - .filter_map(|r| r(Some(config))) - .collect::>(); - let type_ids = self - .module_names - .iter() - .collect::>(); - let enabled: HashSet<&String> = enabled.collect(); - self.modules.values_mut().filter(move |m| { - let name = m.name(); - enabled.contains(&name) - || type_ids - .get(&name) - .is_some_and(|ty| resolver_types.contains(*ty)) - }) - } - - pub fn get_listeners<'a>( - &'a self, - enabled: &'a HashSet, - ) -> impl Iterator> { - enabled - .iter() - .map(|id| self.listeners.get(id).expect("Listener was not registered")) - } - - pub fn enabled_listeners<'a>( - &'a self, - modules: &'a EnabledModules, - config: &'a Option<&Config>, - ) -> impl Iterator + 'a { - modules - .get_all() - .filter_map(|m| { - self.module_names - .get(m) - .copied() - .or_else(|| self.resolvers.get(m).and_then(|f| f(*config))) - .map_none(|| { - if !m.is_empty() { - eprintln!("No Module named {m} is registered") - } - }) - .and_then(|m_id| self.modules.get(&m_id).map(|m| m.requires())) - }) - .flat_map(|required| required.into_iter()) - } - - pub fn all_listeners(&self) -> impl Iterator)> { - self.listeners.iter() - } - - pub fn add_resolver(&mut self, name: S, f: fn(Option<&Config>) -> Option) { - self.resolvers.insert(name.to_string(), f); - } -} diff --git a/src/resolvers.rs b/src/resolvers.rs deleted file mode 100644 index e8e57f8..0000000 --- a/src/resolvers.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::{any::TypeId, env}; - -use crate::{ - config::Config, - modules::{ - hyprland::{window::HyprWindowMod, workspaces::HyprWorkspaceMod}, - niri::{NiriWindowMod, NiriWorkspaceMod}, - wayfire::{WayfireWindowMod, WayfireWorkspaceMod}, - }, - registry::Registry, -}; - -pub fn register_resolvers(registry: &mut Registry) { - registry.add_resolver("window", window); - registry.add_resolver("workspaces", workspaces); -} - -fn window(_config: Option<&Config>) -> Option { - env::var("XDG_CURRENT_DESKTOP") - .ok() - .and_then(|var| match var.as_str() { - "niri" => Some(TypeId::of::()), - "Hyprland" => Some(TypeId::of::()), - "Wayfire:wlroots" => Some(TypeId::of::()), - _ => None, - }) -} - -fn workspaces(_config: Option<&Config>) -> Option { - env::var("XDG_CURRENT_DESKTOP") - .ok() - .and_then(|var| match var.as_str() { - "niri" => Some(TypeId::of::()), - "Hyprland" => Some(TypeId::of::()), - "Wayfire:wlroots" => Some(TypeId::of::()), - _ => None, - }) -} diff --git a/src/tooltip.rs b/src/tooltip.rs deleted file mode 100644 index cec7ea0..0000000 --- a/src/tooltip.rs +++ /dev/null @@ -1,58 +0,0 @@ -use iced::widget::container; -use iced::widget::tooltip::Position; -use iced::{ - widget::{container::Style, Tooltip}, - Element, -}; -use iced::{Background, Border, Color, Theme}; - -pub trait ElementExt<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced::core::text::Renderer + 'a, -{ - fn tooltip( - self, - tooltip: impl Into>, - ) -> Tooltip<'a, Message, Theme, Renderer>; - fn tooltip_maybe( - self, - tooltip: Option>>, - ) -> Element<'a, Message, Theme, Renderer>; -} - -impl<'a, Message, Renderer, Elem> ElementExt<'a, Message, Renderer> for Elem -where - Message: 'a, - Renderer: iced::core::text::Renderer + 'a, - Elem: Into>, -{ - fn tooltip( - self, - tooltip: impl Into>, - ) -> Tooltip<'a, Message, Theme, Renderer> { - iced::widget::tooltip( - self, - container(tooltip).padding([2, 10]).style(|_| Style { - text_color: Some(Color::WHITE), - background: Some(Background::Color(Color::BLACK)), - border: Border { - color: Color::WHITE, - width: 1., - radius: 5_f32.into(), - }, - ..Default::default() - }), - Position::Bottom, - ) - } - fn tooltip_maybe( - self, - tooltip: Option>>, - ) -> Element<'a, Message, Theme, Renderer> { - match tooltip { - Some(t) => self.tooltip(t).into(), - None => self.into(), - } - } -} diff --git a/wiki/Home.md b/wiki/Home.md deleted file mode 100644 index 094a066..0000000 --- a/wiki/Home.md +++ /dev/null @@ -1,76 +0,0 @@ -# Welcome to the bar-rs wiki! - -While the configuration options aren't extensive at the moment, it's still good to know what tools you've got!
-There are some configuration examples at [default_config](https://github.com/Faervan/bar-rs/blob/main/default_config) - -*If you find that this wiki contains wrong information or is missing something critical, please open an [issue](https://github.com/Faervan/bar-rs/issues/new?template=Blank+issue).* - -## Config path -On Linux, the config path is `$XDG_DATA_HOME/bar-rs/bar-rs.ini` or `$HOME/.local/share/bar-rs/bar-rs.ini` - -**Example:**
-`/home/alice/.config/bar-rs/bar-rs.ini` - -If it isn't, you may check [here](https://docs.rs/directories/latest/directories/struct.ProjectDirs.html#method.config_local_dir) - -## Syntax -bar-rs uses an ini-like configuration (as provided by [configparser](https://docs.rs/configparser/latest/configparser/)), which should be pretty easy to understand and use. - -It looks like this: -```ini -[section] -key = value -``` - -## Data types -| Data type | Description | Examples | -| --------- | ----------- | -------- | -| bool | Either yes or no | `true` or `false`, `1` or `0`, `enabled` or `disabled`... | -| Color | A color as defined in the [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/) | `rgba(255, 0, 0, 0.5)`, `blue`, `rgb(255, 255, 255)` | -| String | Just a String | `DP-1` | -| float | A floating point number | `20`, `5.8` | -| u32 | A positive integer of range $2^{32}$ (0 to 4_294_967_295) | `0`, `50`, `1920` | -| i32 | A signed integer (positive or negative) of range $2^{32}$ (-2_147_483_648 to 2_147_483_647) | `-500`, `2147483647` | -| usize | A positive integer of range 0 - a lot (depends on your architecture, but probably enough) | `0`, `100000` | -| Value list | A list of values, separated by spaces. | `20 5 20` | -| Insets | A list of four values, representing all four directions (usually top, right, bottom and right). If one value is provided, it is used for all four sides. If two values are provided, the first is used for top and bottom and the second for left and right. | `0 20 5 10`, `0`, `0 10` | - -## General -The general section contains three options: -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| monitor | The monitor on which bar-rs should open. If this is set, bar-rs will override the default values of `width` and `height` (only the defaults, not the ones you specify). | String | / | -| hot_reloading | Whether bar-rs should monitor the config file for changes | bool | true | -| hard_reloading | Whether bar-rs should reopen and reload all modules (required for `anchor`, `width`, `height`, `margin` and e.g. workspace names set in the `niri.workspaces` module to be hot-reloadable) | bool | false | -| anchor | The anchor to use. Can be `top`, `bottom`, `left` or `right`. This decides whether the bar is vertical or not. | String | top | -| kb_focus | Defines whether bar-rs should be focusable. Can be `none` (no focus), `on_demand` (when you click on it) or `exclusive` (always stay focused). | String | none | - -**Example:** -```ini -[general] -monitor = DP-1 -hot_reloading = true -hard_reloading = false -anchor = top -``` - -## General Styling -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| background | Background color of the status bar | Color | rgba(0, 0, 0, 0.5) | -| width | The total width of the bar. The default depends on whether the bar is vertical or horizontal. | u32 | 30 or 1920 | -| height | The total height of the bar. The default depends on whether the bar is vertical or horizontal. | u32 | 1080 or 30 | -| margin | The margin between the bar and the screen edge, depending on the anchor. | float | 0 | -| padding | The padding between the bar edges and the actual contents of the bar. | Insets (float) | 0 | -| spacing | Space between the modules, can be different for left, center and right | Value list (float) | 20 10 15 | - -**Example:** -```ini -[style] -background = rgba(0, 0, 0, 0.5) -width = 1890 -height = 30 -margin = 5 -padding = 0 -spacing = 20 5 20 -``` diff --git a/wiki/Modules.md b/wiki/Modules.md deleted file mode 100644 index 289647b..0000000 --- a/wiki/Modules.md +++ /dev/null @@ -1,76 +0,0 @@ -# Modules -The `[module]` section sets the enabled modules for each side: - -**Example:** -```ini -[modules] -left = workspaces, window -center = date, time -right = media, volume, cpu, memory -``` - -The following modules are currently available: - -| Module | Description | -| ------ | ----------- | -| [cpu](./Modules:-CPU.md) | Shows the current CPU usage | -| [memory](./Modules:-Memory.md) | Shows the current memory usage | -| [time](./Modules:-Date-and-Time.md) | Shows the local time | -| [date](./Modules:-Date-and-Time.md) | Shows the local date | -| [battery](./Modules:-Battery.md) | Shows the current capacity and remaining time | -| [media](./Modules:-Media.md) | Shows the currently playing media as reported by `playerctl` | -| [volume](./Modules:-Volume.md) | Shows the current audio volume as reported by `wpctl`, updated by `pactl` | -| [disk_usage](./Modules:-Disk-usage.md) | Shows filesystem statistics fetched by the `statvfs` syscall | -| [hyprland.window](./Modules:-Hyprland.md) | Shows the title of the currently focused window | -| [hyprland.workspaces](./Modules:-Hyprland.md) | Shows the currently open workspaces | -| [wayfire.window](./Modules:-Wayfire.md) | Shows the title of the currently focused window | -| [wayfire.workspaces](./Modules:-Wayfire.md) | Shows the currently open workspace | -| [niri.window](./Modules:-Niri.md) | Shows the title or app_id of the currently focused window | -| [niri.workspaces](./Modules:-Niri.md) | Shows the currently open workspaces | - -To configure modules individually use a section name like this: -```ini -[module:{{name}}] -``` -where `{{name}}` is the name of the module, e. g. `cpu` - -**Example:** -```ini -[module:time] -icon_size = 24 -format = %H:%M - -[module:hyprland.workspaces] -active_color = black -active_background = rgba(255, 255, 255, 0.5) -``` - -## Module Styling -section name: `[module_style]` -This section sets default values for all modules, which can be overridden for each module individually. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| background | Background color of the status bar | Color | None | -| spacing | Space between the modules, can be different for left, center and right | Value list (float) | 10 | -| margin | The margin around this module. | Insets (float) | 0 | -| padding | The padding surrounding the module content. | Insets (float) | 0 | -| font_size | Default font size | float | 16 | -| icon_size | Default icon size | float | 20 | -| text_color | Default text color | Color | white | -| icon_color | Default icon color | Color | white | -| text_margin | The margin around the text of this module (can be used adjust the text position, negative values allowed). | Insets (float) | 0 | -| icon_margin | The margin around the icon of this module (can be used adjust the icon position, negative values allowed). | Insets (float) | 0 | -| border_color | The color of the border around this module. | Color | None | -| border_width | The width of the border. | float | 1 | -| border_radius | The radius (corner rounding) of the border. | Insets (float) | 0 | -| on_click | A command to be executed when you click the module with the left mouse button. | String | / | -| on_middle_click | A command to be executed when you click the module with the middle mouse button. | String | / | -| on_right_click | A command to be executed when you click the module with the right mouse button. | String | / | - -### Resolvers -Resolvers are can be used instead of module names and are mapped to modules on specific conditions. - -Currently bar-rs has two resolvers: **window** and **workspaces**, which map to `hyprland.window`, `wayfire.window` or `niri.window` or `hyprland.workspaces`, `wayfire.workspaces` or `niri.workspaces`, respectively, depending on the environment variable `XDG_CURRENT_DESKTOP`. - -Defined in [src/resolvers.rs](https://github.com/Faervan/bar-rs/blob/main/src/resolvers.rs) - diff --git a/wiki/Modules:-Battery.md b/wiki/Modules:-Battery.md deleted file mode 100644 index 2de1c43..0000000 --- a/wiki/Modules:-Battery.md +++ /dev/null @@ -1,28 +0,0 @@ -# Battery -Name: `battery` - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:battery`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | The format of this module | String | {{capacity}}% ({{hours}}h {{minutes}}min left) | - -## Popup configuration -You can override the default settings defined in [Popup Styling](./Popups.md) by setting them in this section: `module_popup:battery`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | the format of the popup text | String | `{{name}}: {{state}}\n\t{{icon}} {{capacity}}% ({{energy}} Wh)\n\thealth: {{health}}%{{time_remaining}}\n\tmodel: {{model}}` | -| format_time | the format of the remaining battery time left (to full or to empty) | String | `\n\t{{hours}}h {{minutes}}min remaining` | - -`format` supports: -- `name` (The name of the battery) -- `state` (The charging state of the battery) -- `icon` (The icon of the battery) -- `capacity` (The capacity of the battery) -- `energy` (The energy of the battery, in `Wh`) -- `health` (The health of the battery: energy_full / energy_full_design) -- `time_remaining` (The remaining battery time left (to full or to empty)) -- `model` (The battery model) - -`format_time` supports: -- `hours` -- `minutes` diff --git a/wiki/Modules:-CPU.md b/wiki/Modules:-CPU.md deleted file mode 100644 index 486e4b6..0000000 --- a/wiki/Modules:-CPU.md +++ /dev/null @@ -1,29 +0,0 @@ -# Cpu -Name: `cpu` - -Shows the cpu usage, also has a popup which can show more stats, including each core individually.
-This module reads stats from `/proc/stat`, see [kernel.org](https://docs.kernel.org/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat) - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:cpu`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String | 󰻠 | - -## Popup configuration -You can override the default settings defined in [Popup Styling](./Popups.md) by setting them in this section: `module_popup:cpu`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | the format of the popup text | String | `Total: {{total}}%\nUser: {{user}}%\nSystem: {{system}}%\nGuest: {{guest}}%\n{{cores}}` | -| format_core | the format of the cpu core | String | `Core {{index}}: {{total}}%` | - -both `format` and `format_core` support: -- `total`: The total cpu/core usage -- `user`: The userspace cpu/core usage -- `system`: the kernelspace cpu/core usage -- `guest`: the usage of processes running in a guest session - -`format` additionally supports: -- `cores`: all cores ordered by their id (ascending), separated by line breaks - -`format_core` additionally supports: -- `index`: The index of the core diff --git a/wiki/Modules:-Date-and-Time.md b/wiki/Modules:-Date-and-Time.md deleted file mode 100644 index b3ee6be..0000000 --- a/wiki/Modules:-Date-and-Time.md +++ /dev/null @@ -1,24 +0,0 @@ -# Date and time modules -These modules are basically identical. - -## Date -Name: `date` - -Shows the date. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String |  | -| format | How to format the date. See [chrono](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for the syntax. | String | `%a, %d. %b` | - -## Time -Name: `time` - -Shows the time. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String |  | -| format | How to format the time. See [chrono](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for the syntax. | String | `%H:%M` | diff --git a/wiki/Modules:-Disk-usage.md b/wiki/Modules:-Disk-usage.md deleted file mode 100644 index bc9fa18..0000000 --- a/wiki/Modules:-Disk-usage.md +++ /dev/null @@ -1,28 +0,0 @@ -# Disk usage -Name: `disk_usage` - -Shows the disk usage, also has a popup which can show more stats.
-This module obtains the stats via the `statvfs` syscall, see [man7.org](https://man7.org/linux/man-pages/man3/statvfs.3.html) - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:disk_usage`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String | 󰦚 | -| path | some directory, which determines the filesystem of interest | String | `/` | -| format | the content of the module text | String | `{{used_perc}}%` | - -## Popup configuration -You can override the default settings defined in [Popup Styling](./Popups.md) by setting them in this section: `module_popup:disk_usage`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | the format of the popup text | String | `Total: {{total_gb}} GB\nUsed: {{used_gb}} GB ({{used_perc}}%)\nFree: {{free_gb}} GB ({{free_perc}}%)` | - -`format` provides the following variables: -- `total`: The total filesystem space in mb -- `total_gb`: The total filesystem space in gb -- `used`: The used space in mb -- `used_gb`: The used space in gb -- `free`: The free space in mb -- `free_gb`: The free space in gb -- `used_perc`: the percentage of used space in the filesystem -- `free_perc`: the percentage of free space in the filesystem diff --git a/wiki/Modules:-Hyprland.md b/wiki/Modules:-Hyprland.md deleted file mode 100644 index 77d4710..0000000 --- a/wiki/Modules:-Hyprland.md +++ /dev/null @@ -1,42 +0,0 @@ -# Hyprland modules -Add this line to your `~/.config/hypr/hyprland.conf` to launch bar-rs on startup: -``` -exec-once = bar-rs open -``` - -bar-rs supports two modules for the [Hyprland](https://github.com/hyprwm/Hyprland/) wayland compositor: - -## Hyprland window -Name: `hyprland.window` - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:hyprland.window`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| max_length | the maximum character length of the title | usize | 25 | - -## Hyprland workspaces -Name: `hyprland.workspaces` - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:hyprland.workspaces`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon_padding | Padding for the icon, only useful with a background or border. | Insets (float) | 0 | -| icon_background | Background of the icons. | Color | None | -| icon_border_color | Color of the border around the icons. | Color | / | -| icon_border_width | Width of the border around the icons. | float | 1 | -| icon_border_radius | Radius of the border around the icons. | Insets (float) | 0 | -| active_padding | Padding for the active icon, only useful with a background or border. | Insets (float) | 0 | -| active_size | Size of the currently active icon. | float | 20 | -| active_color | the color for the currently focused workspace | Color | black | -| active_background | the background color for the currently focused workspace | Color | rgba(255, 255, 255, 0.5) | -| active_border_color | Color of the border around the active icon. | Color | / | -| active_border_width | Width of the border around the active icon. | float | 1 | -| active_border_radius | Radius of the border around the active icon. | Insets (float) | 0 | - -To have the `hyprland.workspaces` module show some nice workspace icons, set rules for your workspaces like this: -``` -workspace = 1, defaultName:󰈹 -``` - -> \[!TIP] -> Find some nice icons to use as workspace names [here](https://www.nerdfonts.com/cheat-sheet) diff --git a/wiki/Modules:-Media.md b/wiki/Modules:-Media.md deleted file mode 100644 index 82c6448..0000000 --- a/wiki/Modules:-Media.md +++ /dev/null @@ -1,30 +0,0 @@ -# Media -Name: `media` - -Shows the currently playing media title and artist and offers basic playback control using a popup.
-This module depends on `playerctl`. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:media`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String |  | -| max_length | the maximum character length to show | usize | 35 | -| max_title_length | the maximum character length of the title part of the media. Only applies if `max_length` is reached and the media has an artist | usize | 20 | - -## Popup configuration -You can override the default settings defined in [Popup Styling](./Popups.md) by setting them in this section: `module_popup:media`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | the format of the popup text | String | `{{title}}{{status}}\nin: {{album}}\nby: {{artist}}\n{{length}}` | -| format_length | the format of length of the media | String | `{{minutes}}min {{seconds}}sec` | - -`format` supports: -- `title` (The title of the playing media) -- `artist` (The artist of the playing media) -- `album` (The album of the playing media) -- `status` (The status of the playing media: empty if playing, `" (paused)"` if paused) -- `length` (The length of the playing media, it's format is determined by `format_length`) - -`format_length` supports: -- `minutes` -- `seconds` diff --git a/wiki/Modules:-Memory.md b/wiki/Modules:-Memory.md deleted file mode 100644 index 28fa5fa..0000000 --- a/wiki/Modules:-Memory.md +++ /dev/null @@ -1,10 +0,0 @@ -# Memory -Name: `memory` - -This module shows the percentage of memory usage.
-Depends on `free`, `grep`, `awk` and `printf`. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:memory`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon | the icon to use | String | 󰍛 | diff --git a/wiki/Modules:-Niri.md b/wiki/Modules:-Niri.md deleted file mode 100644 index ea33a05..0000000 --- a/wiki/Modules:-Niri.md +++ /dev/null @@ -1,79 +0,0 @@ -# Niri modules -Add this to your `~/.config/niri/config.kdl` to launch bar-rs on startup: -```kdl -spawn-at-startup "bar-rs" "open" -``` - -bar-rs supports two modules for the [Niri](https://github.com/YaLTeR/niri) wayland compositor: - -## Niri window -Name: `niri.window` - -This module shows the name or app_id of the currently focused window. A popup is also available. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:niri.window`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| max_length | the maximum character length of the title | usize | 25 | -| show_app_id | Show the app_id instead of the window title | bool | false | - -### Popup configuration -You can override the default settings defined in [Popup Styling](./Popups.md) by setting them in this section: `module_popup:niri.window`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| format | the format of the popup text | String | `Title: {{title}}\nApplication ID: {{app_id}}\nWindow ID: {{window_id}}\nWorkspace ID: {{workspace_id}}` | - -```ini -[module_popup:niri.window] -format = {{title}}\n{{app_id}} -``` - -This supports: -- `title` (The active window title) -- `app_id` (The active window's application id) -- `window_id` (The active window's id) -- `workspace_id` (The id of the active workspace) - -## Niri workspaces -Name: `niri.workspaces` - -This module shows the currently open workspaces and allows to change your workspace by clicking on a workspace icon. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:niri.workspaces`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon_padding | Padding for the icon, only useful with a background or border. | Insets (float) | 0 | -| icon_background | Background of the icons. | Color | None | -| icon_border_color | Color of the border around the icons. | Color | / | -| icon_border_width | Width of the border around the icons. | float | 1 | -| icon_border_radius | Radius of the border around the icons. | Insets (float) | 0 | -| active_padding | Padding for the active icon, only useful with a background or border. | Insets (float) | 0 | -| active_size | Size of the currently active icon. | float | 20 | -| active_color | the color for the currently focused workspace | Color | black | -| active_background | the background color for the currently focused workspace | Color | rgba(255, 255, 255, 0.5) | -| active_border_color | Color of the border around the active icon. | Color | / | -| active_border_width | Width of the border around the active icon. | float | 1 | -| active_border_radius | Radius of the border around the active icon. | Insets (float) | 0 | -| Output: n | the name of the nth workspace on the given output (monitor) | String | / | -| output_order | the order of the workspaces, depending on their output (monitor) | Value list (String) | / | -| fallback_icon | the icon to use for unnamed workspaces | String |  | -| active_fallback_icon | the icon to use for unnamed workspaces when active | String |  | - -> \[!TIP] -> Find some nice icons to use as workspace names [here](https://www.nerdfonts.com/cheat-sheet) - -**Example:** -```ini -[module:niri.workspaces] -spacing = 15 -padding = 0 12 0 6 -icon_margin = -2 0 0 0 -icon_size = 25 -active_size = 25 -output_order = DP-1, HDMI-A-1 -DP-1: 1 = 󰈹 -DP-1: 2 =  -DP-1: 3 = 󰓓 -DP-1: 4 =  -DP-1: 5 =  -``` diff --git a/wiki/Modules:-Volume.md b/wiki/Modules:-Volume.md deleted file mode 100644 index 4417644..0000000 --- a/wiki/Modules:-Volume.md +++ /dev/null @@ -1,7 +0,0 @@ -# Volume -Name: `volume` - -This module shows the audio volume. Sound can be toggled (muted or unmuted) by clicking on the volume icon.
-This module depends on `wpctl` and `pactl`. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:volume`. diff --git a/wiki/Modules:-Wayfire.md b/wiki/Modules:-Wayfire.md deleted file mode 100644 index 5c2fbe2..0000000 --- a/wiki/Modules:-Wayfire.md +++ /dev/null @@ -1,44 +0,0 @@ -# Wayfire modules -Add this to your `~/.config/wayfire.ini` to launch bar-rs on startup: -```ini -[autostart] -bar = bar-rs open -``` - -bar-rs supports two modules for the [Wayfire](https://github.com/WayfireWM/wayfire/) wayland compositor: - -## Wayfire window -Name: `wayfire.window` - -Shows the name of the currently open window. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:wayfire.window`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| max_length | the maximum character length of the title | usize | 25 | - -## Wayfire workspaces -Name: `wayfire.workspaces` - -Shows the name of the currently focused workspace. - -You can override the default settings defined in [Module Styling](./Modules.md) by setting them in this section: `module:wayfire.workspaces`. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| icon_padding | Padding for the icon, useful to adjust the icon position. | Insets (float) | 0 | -| fallback_icon | Default icon to use | String | / | -| (row, column) | the name of the workspace | String | fallback_icon or `row/column` | - -> \[!TIP] -> Find some nice icons to use as workspace names [here](https://www.nerdfonts.com/cheat-sheet) - -**Example:** -```ini -[module:wayfire.workspaces] -fallback_icon =  -(0, 0) = 󰈹 -(1, 0) =  -(2, 0) = 󰓓 -(0, 1) =  -(1, 1) =  -``` diff --git a/wiki/Popups.md b/wiki/Popups.md deleted file mode 100644 index d4baad0..0000000 --- a/wiki/Popups.md +++ /dev/null @@ -1,23 +0,0 @@ -# Popups -Extra windows that open on click to show some more info or allow for additional actions. - -## Popup Styling -section name: `[popup_style]` -This section sets default values for all module popups, which can be overridden for each module individually. -| Option | Description | Data type | Default | -| ------ | ----------- | --------- | ------- | -| width | The width of the popup | i32 | 300 | -| height | The height of the popup | i32 | 300 | -| fill_content_to_size | Whether the content of the module should fill the entire width and height | bool | false | -| padding | The padding surrounding the popup content. | Insets (float) | 10 20 | -| text_color | Default text color | Color | white | -| icon_color | Default icon color | Color | white | -| font_size | Default font size | float | 14 | -| icon_size | Default icon size | float | 24 | -| text_margin | The margin around the text of this popup (can be used adjust the text position, negative values allowed). | Insets (float) | 0 | -| icon_margin | The margin around the icon of this popup (can be used adjust the icon position, negative values allowed). | Insets (float) | 0 | -| spacing | Space between elements in the popup | float | 0 | -| background | Background color of the popup | Color | rgba(255, 255, 255, 0.8) | -| border_color | The color of the border around this popup. | Color | None | -| border_width | The width of the border. | float | 0 | -| border_radius | The radius (corner rounding) of the border. | Insets (float) | 8 |