diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d918b1..e450d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Multi monitor support + ### Changed - Update to pop-os Iced 14.0-dev diff --git a/Cargo.lock b/Cargo.lock index 3537095..ba54381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ "accesskit_consumer", "atspi-common", "serde", - "thiserror", + "thiserror 1.0.69", "zvariant 3.15.2", ] @@ -160,7 +160,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -196,9 +196,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "approx" @@ -257,7 +257,7 @@ dependencies = [ "tokio", "wayland-client", "wayland-protocols", - "zbus 5.1.1", + "zbus 5.2.0", ] [[package]] @@ -302,7 +302,7 @@ checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-lite 2.5.0", "slab", ] @@ -351,7 +351,7 @@ dependencies = [ "futures-lite 2.5.0", "parking", "polling 3.7.4", - "rustix 0.38.41", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -390,7 +390,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.41", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -409,7 +409,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.5.0", - "rustix 0.38.41", + "rustix 0.38.42", "tracing", ] @@ -436,7 +436,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.41", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -694,9 +694,9 @@ dependencies = [ "bitflags 2.6.0", "log", "polling 3.7.4", - "rustix 0.38.41", + "rustix 0.38.42", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -706,16 +706,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.41", + "rustix 0.38.42", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", @@ -767,9 +767,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -824,7 +824,7 @@ name = "clipboard_x11" version = "0.4.2" source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ - "thiserror", + "thiserror 1.0.69", "x11rb", ] @@ -1045,9 +1045,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1064,9 +1064,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -1206,7 +1206,7 @@ dependencies = [ "bytemuck", "drm-ffi", "drm-fourcc", - "rustix 0.38.41", + "rustix 0.38.42", ] [[package]] @@ -1216,7 +1216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ "drm-sys", - "rustix 0.38.41", + "rustix 0.38.42", ] [[package]] @@ -1364,15 +1364,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -1389,15 +1389,15 @@ dependencies = [ [[package]] name = "flexi_logger" -version = "0.29.6" +version = "0.29.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26948e37cfcb1f2c2cd38e0602d3a8ab6b9472c0c6eff4516fc8def9a3124d7" +checksum = "4613c3fa90ebf91dff72ff383a9324329c017819711bda86142be81368ac6fad" dependencies = [ "chrono", "log", "nu-ansi-term", "regex", - "thiserror", + "thiserror 2.0.7", ] [[package]] @@ -1529,7 +1529,7 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -1685,7 +1685,7 @@ checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "winapi", "windows 0.52.0", ] @@ -1756,7 +1756,7 @@ dependencies = [ "com", "libc", "libloading", - "thiserror", + "thiserror 1.0.69", "widestring", "winapi", ] @@ -1871,7 +1871,7 @@ dependencies = [ "iced_widget", "iced_winit", "mime", - "thiserror", + "thiserror 1.0.69", "window_clipboard", ] @@ -1902,7 +1902,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 2.1.0", "smol_str", - "thiserror", + "thiserror 1.0.69", "web-time", "window_clipboard", ] @@ -1948,7 +1948,7 @@ dependencies = [ "once_cell", "raw-window-handle", "rustc-hash 2.1.0", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", ] @@ -1961,7 +1961,7 @@ dependencies = [ "iced_tiny_skia", "iced_wgpu", "log", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1975,7 +1975,7 @@ dependencies = [ "iced_core", "iced_futures", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", "window_clipboard", ] @@ -2012,8 +2012,8 @@ dependencies = [ "once_cell", "raw-window-handle", "rustc-hash 2.1.0", - "rustix 0.38.41", - "thiserror", + "rustix 0.38.42", + "thiserror 1.0.69", "tiny-xlib", "wayland-backend", "wayland-client", @@ -2035,7 +2035,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 2.1.0", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", "window_clipboard", ] @@ -2053,7 +2053,7 @@ dependencies = [ "log", "raw-window-handle", "rustc-hash 2.1.0", - "thiserror", + "thiserror 1.0.69", "tracing", "wasm-bindgen-futures", "wayland-backend", @@ -2130,9 +2130,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" [[package]] name = "itertools" @@ -2169,7 +2169,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2191,9 +2191,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -2240,9 +2240,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" @@ -2295,7 +2295,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -2493,7 +2493,7 @@ dependencies = [ "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -2509,7 +2509,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2999,7 +2999,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -3097,7 +3097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-io", ] @@ -3115,7 +3115,7 @@ dependencies = [ "nix 0.27.1", "once_cell", "pipewire-sys", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3137,9 +3137,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3174,7 +3174,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.41", + "rustix 0.38.42", "tracing", "windows-sys 0.59.0", ] @@ -3316,9 +3316,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.22.5" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" dependencies = [ "bytemuck", "font-types", @@ -3344,9 +3344,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -3426,15 +3426,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3496,24 +3496,24 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -3652,8 +3652,8 @@ dependencies = [ "log", "memmap2 0.9.5", "pkg-config", - "rustix 0.38.41", - "thiserror", + "rustix 0.38.42", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3716,7 +3716,7 @@ dependencies = [ "cocoa", "core-graphics", "drm", - "fastrand 2.2.0", + "fastrand 2.3.0", "foreign-types", "js-sys", "log", @@ -3724,7 +3724,7 @@ dependencies = [ "objc", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.41", + "rustix 0.38.42", "tiny-xlib", "wasm-bindgen", "wayland-backend", @@ -3844,9 +3844,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.2.0", + "fastrand 2.3.0", "once_cell", - "rustix 0.38.41", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -3865,7 +3865,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +dependencies = [ + "thiserror-impl 2.0.7", ] [[package]] @@ -3879,6 +3888,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "thiserror-impl" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3935,9 +3955,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -3965,9 +3985,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -4187,9 +4207,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -4198,13 +4218,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -4213,9 +4232,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -4226,9 +4245,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4236,9 +4255,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -4249,9 +4268,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-timer" @@ -4276,7 +4295,7 @@ checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.41", + "rustix 0.38.42", "scoped-tls", "smallvec", "wayland-sys", @@ -4289,7 +4308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.41", + "rustix 0.38.42", "wayland-backend", "wayland-scanner", ] @@ -4311,7 +4330,7 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix 0.38.41", + "rustix 0.38.42", "wayland-client", "xcursor", ] @@ -4375,8 +4394,8 @@ checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" dependencies = [ "bitflags 2.6.0", "downcast-rs", - "io-lifetimes 2.0.3", - "rustix 0.38.41", + "io-lifetimes 2.0.4", + "rustix 0.38.42", "wayland-backend", "wayland-scanner", ] @@ -4395,9 +4414,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -4458,7 +4477,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "wgpu-hal", "wgpu-types", ] @@ -4501,7 +4520,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4568,7 +4587,7 @@ dependencies = [ "dnd", "mime", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4932,7 +4951,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.41", + "rustix 0.38.42", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -4992,7 +5011,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 0.38.41", + "rustix 0.38.42", "x11rb-protocol", ] @@ -5053,9 +5072,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" [[package]] name = "yansi-term" @@ -5110,9 +5129,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.1.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" +checksum = "fb67eadba43784b6fb14857eba0d8fc518686d3ee537066eb6086dc318e2c8a1" dependencies = [ "async-broadcast 0.7.1", "async-executor", @@ -5139,7 +5158,7 @@ dependencies = [ "windows-sys 0.59.0", "winnow 0.6.20", "xdg-home", - "zbus_macros 5.1.1", + "zbus_macros 5.2.0", "zbus_names 4.1.0", "zvariant 5.1.0", ] @@ -5160,9 +5179,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.1.1" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" +checksum = "2c9d49ebc960ceb660f2abe40a5904da975de6986f2af0d7884b39eec6528c57" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", diff --git a/README.md b/README.md index 0fccc88..ab01a44 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # Ashell -A ready to go Wayland status bar for Hyprland +A ready to go Wayland status bar for Hyprland. + +Feel free to fork this project and customize it for your needs or just open an +issue to request a particular feature. > If you have an issue with the transparency you could try launching ashell with WGPU_BACKEND=gl. This env var forces wgpu to use OpenGL instead of Vulkan. It seems that wgpu has some issues with AMD GPU and Vulkan transparency. ### Does it only work on Hyprland? -While it's currently tailored for Hyprland, it could work with other compositors. -However, it currently relies on [hyprland-rs](https://github.com/hyprland-community/hyprland-rs) -to gather information about the active window and workspaces. I haven't implemented any +While it's currently tailored for Hyprland, it could work with other compositors. + +However, it currently relies on [hyprland-rs](https://github.com/hyprland-community/hyprland-rs) +to gather information about the active window and workspaces. I haven't implemented any feature flags to disable these functionalities or alternative methods to obtain this data. ## Install @@ -20,13 +24,15 @@ feature flags to disable these functionalities or alternative methods to obtain You can get the official Arch Linux package from the AUR: #### Tagged release + ``` -paru/yay -S ashell +paru/yay -S ashell ``` #### Main branch + ``` -paru/yay -S ashell-git +paru/yay -S ashell-git ``` ### ALT Linux @@ -41,14 +47,15 @@ apt-get install ashell To install ashell using the nix package be sure to enable flakes and then run #### Tagged release + ``` -nix profile install github:MalpenZibo/ashell?ref=0.1.0 +nix profile install github:MalpenZibo/ashell?ref=0.3.1 ``` #### Main branch ``` -nix profile install github:MalpenZibo/ashell +nix profile install github:MalpenZibo/ashell ``` ### NixOS @@ -56,6 +63,7 @@ nix profile install github:MalpenZibo/ashell I haven't tested ashell on NixOS. To enable this flake use + ```nix { pkgs, ... }: @@ -72,8 +80,7 @@ To enable this flake use ``` > I'm not an expert and I haven't tested this configuration -but I'm quite sure that if you use NixOS you are smart enough to add ashell to your configuration :D - +> but I'm quite sure that if you use NixOS you are smart enough to add ashell to your configuration :D ## Features @@ -88,44 +95,51 @@ but I'm quite sure that if you use NixOS you are smart enough to add ashell to y - Date time - Privacy (check microphone, camera and screenshare usage) - Settings panel - - Power menu - - Battery information - - Audio sources and sinks - - Screen brightness - - Network stuff - - VPN - - Bluetooth - - Power profiles - - Idle inhibitor - - Airplane mode + - Power menu + - Battery information + - Audio sources and sinks + - Screen brightness + - Network stuff + - VPN + - Bluetooth + - Power profiles + - Idle inhibitor + - Airplane mode ## Configuration + The configuration uses the yaml file format and is named `~/.config/ashell.yml` -``` yaml +```yaml # Ashell log level filter, possible values "DEBUG" | "INFO" | "WARNING" | "ERROR". Needs reload logLevel: "INFO" # optional, default "INFO" -# Ashell bar position, possible values Top | Bottom. Needs reload +# List of outputs, example values: DP-1 | HDMI-1 | eDP-1. +# the status bar will be displayed on all the outputs listed here +# if the outputs is not available the bar will be displayed in the active output +outputs: # optional, default empty list (the bar will be displayed on the active output) + - eDP-1 + - DP-1 +# Bar position, possible values Top | Bottom. position: Top # optional, default Top # App lancher commanda, it will be used to open the launcher, # without a value the related button will not appear -appLauncherCmd: "~/.config/rofi/launcher.sh" # optional, default None +appLauncherCmd: "~/.config/rofi/launcher.sh" # optional, default None # Clipboard command, it will be used to open the clipboard menu, # without a value the related button will not appear -clipboardCmd: "cliphist-rofi-img | wl-copy" # optional, default None -# Update module configuration. +clipboardCmd: "cliphist-rofi-img | wl-copy" # optional, default None +# Update module configuration. # Without a value the related button will not appear. -updates: # optional, default None +updates: # optional, default None # The check command will be used to retrieve the update list. # It should return something like `package_name version_from -> version_to\n` checkCmd: "checkupdates; paru -Qua" # required # The update command is used to init the OS update process - updateCmd: "alacritty -e bash -c \"paru; echo Done - Press enter to exit; read\" &" # required + updateCmd: 'alacritty -e bash -c "paru; echo Done - Press enter to exit; read" &' # required # Maximum number of chars that can be present in the window title -# after that the title will be truncated +# after that the title will be truncated truncateTitleAfterLength: 150 # optional, default 150 # The system module configuration -system: +system: disabled: false # Enable or disable the system monitor module cpuWarnThreshold: 6O # cpu indicator warning level (default 60) cpuAlertThreshold: 8O # cpu indicator alert level (default 80) @@ -141,30 +155,30 @@ keyboard: disabled: false # Enable or disable the keyboard submap module # Clock module configuration clock: - # clock format see: https://docs.rs/chrono/latest/chrono/format/strftime/index.html + # clock format see: https://docs.rs/chrono/latest/chrono/format/strftime/index.html format: "%a %d %b %R" # optional, default: %a %d %b %R # Settings module configuration settings: # command used for lock the system - # without a value the related button will not appear - lockCmd: "hyprlock &" # optional, default None - # command used to open the sinks audio settings - # without a value the related button will not appear - audioSinksMoreCmd: "pavucontrol -t 3" # optional default None + # without a value the related button will not appear + lockCmd: "hyprlock &" # optional, default None + # command used to open the sinks audio settings + # without a value the related button will not appear + audioSinksMoreCmd: "pavucontrol -t 3" # optional default None # command used to open the sources audio settings - # without a value the related button will not appear - audioSourcesMoreCmd: "pavucontrol -t 4" # optional, default None - # command used to open the network settings - # without a value the related button will not appear + # without a value the related button will not appear + audioSourcesMoreCmd: "pavucontrol -t 4" # optional, default None + # command used to open the network settings + # without a value the related button will not appear wifiMoreCmd: "nm-connection-editor" # optional, default None - # command used to open the VPN settings - # without a value the related button will not appear - vpnMoreCmd: "nm-connection-editor" # optional, default None - # command used to open the Bluetooth settings - # without a value the related button will not appear - bluetoothMoreCmd: "blueman-manager" # optional, default None -# Appearance config -# Each color could be a simple hex color like #228800 or an + # command used to open the VPN settings + # without a value the related button will not appear + vpnMoreCmd: "nm-connection-editor" # optional, default None + # command used to open the Bluetooth settings + # without a value the related button will not appear + bluetoothMoreCmd: "blueman-manager" # optional, default None +# Appearance config +# Each color could be a simple hex color like #228800 or an # object that define a base hex color and two optional variant of that color (a strong one and a weak one) # and the text color that should be used with that base color # example: @@ -174,53 +188,34 @@ settings: # weak: #448855 -- optional default autogenarated from base color # text: #ffffff -- optional default base text color appearance: - backgroundColor: "#1e1e2e" # used as a base background color for header module button - primaryColor: "#fab387" # used as a accent color - secondaryColor: "#11111b" # used for darker background color - successColor: "#a6e3a1" # used for success message or happy state - dangerColor: "#f38ba8" # used for danger message or danger state (the weak version is used for the warning state - textColor: "#f38ba8" # base default text color - # this is a list of color that will be used in the workspace module (one color for each monitor) - workspaceColors: - - "#fab387" - - "#b4befe" - # this is a list of color that will be used in the workspace module - # for the special workspace (one color for each monitor) - # optional, default None - # without a value the workspaceColors list will be used - specialWorkspaceColors: - - "#a6e3a1" - - "#f38ba8" + backgroundColor: "#1e1e2e" # used as a base background color for header module button + primaryColor: "#fab387" # used as a accent color + secondaryColor: "#11111b" # used for darker background color + successColor: "#a6e3a1" # used for success message or happy state + dangerColor: "#f38ba8" # used for danger message or danger state (the weak version is used for the warning state + textColor: "#f38ba8" # base default text color + # this is a list of color that will be used in the workspace module (one color for each monitor) + workspaceColors: + - "#fab387" + - "#b4befe" + # this is a list of color that will be used in the workspace module + # for the special workspace (one color for each monitor) + # optional, default None + # without a value the workspaceColors list will be used + specialWorkspaceColors: + - "#a6e3a1" + - "#f38ba8" ``` -### So, what's the purpose of this project? -I could have used [waybar](https://github.com/Alexays/Waybar) that's for sure is a -a great project but I wanted something more sophisticated -with submenus and other stuff. - -I tried with other great projects like [eww](https://github.com/elkowar/eww) but -instead of writing or copy-paste eww configurations I prefered to create -my Wayland bar. - -So, I copy-pasted from iced pop-os fork created a layer to interact -with wayland layer shell protocol from iced [Iced SCTK](https://github.com/MalpenZibo/iced_sctk) -and I started to create this project. - -Feel free to fork this project and customize it for your needs or just open an -issue to request a particular feature. - ## Some screenshots I will try my best to keep these screenshots as updated as possible but some details could be different - - -| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/updates-panel.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/settings-panel.png) | -| --- | --- | -| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/power-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/sinks-selection.png) | -| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/network-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/bluetooth-menu.png) | -| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/vpn-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/airplane-mode.png) | - - + +| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/updates-panel.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/settings-panel.png) | +| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/power-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/sinks-selection.png) | +| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/network-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/bluetooth-menu.png) | +| ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/vpn-menu.png) | ![](https://raw.githubusercontent.com/MalpenZibo/ashell/main/screenshots/airplane-mode.png) | diff --git a/src/app.rs b/src/app.rs index f608f48..acb8493 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,13 +1,14 @@ use crate::{ centerbox, - config::{self, Config, Position}, + config::{self, Config}, get_log_spec, - menu::{menu_wrapper, Menu, MenuPosition}, + menu::{menu_wrapper, MenuPosition}, modules::{ self, clipboard, clock::Clock, keyboard_layout::KeyboardLayout, keyboard_submap::KeyboardSubmap, launcher, privacy::PrivacyMessage, settings::Settings, system_info::SystemInfo, title::Title, updates::Updates, workspaces::Workspaces, }, + outputs::{HasOutput, Outputs}, services::{privacy::PrivacyService, ReadOnlyService, ServiceEvent}, style::ashell_theme, utils, HEIGHT, @@ -15,49 +16,17 @@ use crate::{ use flexi_logger::LoggerHandle; use iced::{ daemon::Appearance, - platform_specific::shell::commands::layer_surface::{ - get_layer_surface, Anchor, KeyboardInteractivity, Layer, - }, - runtime::platform_specific::wayland::layer_surface::{IcedOutput, SctkLayerSurfaceSettings}, + event::{listen_with, wayland::Event as WaylandEvent}, widget::Row, window::Id, Alignment, Color, Element, Length, Subscription, Task, Theme, }; -use log::info; - -fn create_layer(pos: Position) -> (Id, Vec>) { - let main = get_layer_surface(SctkLayerSurfaceSettings { - size: Some((None, Some(HEIGHT))), - layer: Layer::Bottom, - pointer_interactivity: true, - keyboard_interactivity: KeyboardInteractivity::None, - exclusive_zone: HEIGHT as i32, - output: IcedOutput::All, - anchor: match pos { - Position::Top => Anchor::TOP, - Position::Bottom => Anchor::BOTTOM, - } | Anchor::LEFT - | Anchor::RIGHT, - ..Default::default() - }); - - let menu_id = Id::unique(); - let menu = get_layer_surface(SctkLayerSurfaceSettings { - id: menu_id, - size: Some((None, None)), - layer: Layer::Background, - pointer_interactivity: true, - keyboard_interactivity: KeyboardInteractivity::None, - anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT, - ..Default::default() - }); - - (menu_id, vec![main, menu]) -} +use log::{debug, info, warn}; pub struct App { logger: LoggerHandle, config: Config, + outputs: Outputs, updates: Updates, workspaces: Workspaces, window_title: Title, @@ -67,7 +36,6 @@ pub struct App { clock: Clock, privacy: Option, pub settings: Settings, - menu: Menu, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -80,7 +48,7 @@ pub enum MenuType { pub enum Message { None, ConfigChanged(Box), - CloseMenu, + CloseMenu(Id), OpenLauncher, OpenClipboard, Updates(modules::updates::Message), @@ -92,16 +60,18 @@ pub enum Message { Clock(modules::clock::Message), Privacy(modules::privacy::PrivacyMessage), Settings(modules::settings::Message), + WaylandEvent(WaylandEvent), } impl App { pub fn new((logger, config): (LoggerHandle, Config)) -> impl FnOnce() -> (Self, Task) { - let (menu_id, tasks) = create_layer(config.position); - move || { + || { + let (outputs, task) = Outputs::new(config.position); ( App { logger, config, + outputs, updates: Updates::default(), workspaces: Workspaces::default(), window_title: Title::default(), @@ -111,9 +81,8 @@ impl App { clock: Clock::default(), privacy: None, settings: Settings::default(), - menu: Menu::new(menu_id), }, - Task::batch(tasks), + task, ) } } @@ -139,15 +108,27 @@ impl App { Message::None => Task::none(), Message::ConfigChanged(config) => { info!("New config: {:?}", config); + let mut tasks = Vec::new(); + info!( + "Current outputs: {:?}, new outputs: {:?}", + self.config.outputs, config.outputs + ); + if self.config.outputs != config.outputs || self.config.position != config.position + { + warn!("Outputs changed, syncing"); + tasks.push(self.outputs.sync(&config.outputs, config.position)); + } self.config = *config; self.logger .set_new_spec(get_log_spec(&self.config.log_level)); - Task::none() + + Task::batch(tasks) } - Message::CloseMenu => self.menu.close(), + Message::CloseMenu(id) => self.outputs.close_menu(id), Message::Updates(message) => { if let Some(updates_config) = self.config.updates.as_ref() { - self.updates.update(message, updates_config, &mut self.menu) + self.updates + .update(message, updates_config, &mut self.outputs) } else { Task::none() } @@ -207,104 +188,132 @@ impl App { }, Message::Settings(message) => { self.settings - .update(message, &self.config.settings, &mut self.menu) + .update(message, &self.config.settings, &mut self.outputs) } + Message::WaylandEvent(event) => match event { + WaylandEvent::Output(event, wl_output) => match event { + iced::event::wayland::OutputEvent::Created(info) => { + info!("Output created: {:?}", info); + let name = info + .as_ref() + .and_then(|info| info.name.as_deref()) + .unwrap_or(""); + + self.outputs.add( + &self.config.outputs, + self.config.position, + name, + wl_output, + ) + } + iced::event::wayland::OutputEvent::Removed => { + info!("Output destroyed"); + self.outputs.remove(self.config.position, wl_output) + } + _ => Task::none(), + }, + _ => Task::none(), + }, } } pub fn view(&self, id: Id) -> Element { - if self.menu.is_menu(id) { - match self.menu.get_menu_type_to_render(id) { + match self.outputs.has(id) { + Some(HasOutput::Main) => { + let left = Row::new() + .push_maybe( + self.config + .app_launcher_cmd + .as_ref() + .map(|_| launcher::launcher()), + ) + .push_maybe( + self.config + .clipboard_cmd + .as_ref() + .map(|_| clipboard::clipboard()), + ) + .push_maybe( + self.config + .updates + .as_ref() + .map(|_| self.updates.view(id).map(Message::Updates)), + ) + .push( + self.workspaces + .view( + &self.config.appearance.workspace_colors, + self.config.appearance.special_workspace_colors.as_deref(), + ) + .map(Message::Workspaces), + ) + .height(Length::Shrink) + .align_y(Alignment::Center) + .spacing(4); + + let center = Row::new() + .push_maybe(self.window_title.view().map(|v| v.map(Message::Title))) + .spacing(4); + + let right = Row::new() + .push_maybe( + self.system_info + .view(&self.config.system) + .map(|c| c.map(Message::SystemInfo)), + ) + .push_maybe( + self.keyboard_submap + .view(&self.config.keyboard.submap) + .map(|l| l.map(Message::KeyboardSubmap)), + ) + .push_maybe( + self.keyboard_layout + .view(&self.config.keyboard.layout) + .map(|l| l.map(Message::KeyboardLayout)), + ) + .push( + Row::new() + .push( + self.clock + .view(&self.config.clock.format) + .map(Message::Clock), + ) + .push_maybe( + self.privacy + .as_ref() + .and_then(|privacy| privacy.view()) + .map(|e| e.map(Message::Privacy)), + ) + .push(self.settings.view(id).map(Message::Settings)), + ) + .spacing(4); + + centerbox::Centerbox::new([left.into(), center.into(), right.into()]) + .spacing(4) + .padding([0, 4]) + .width(Length::Fill) + .height(Length::Fixed(HEIGHT as f32)) + .align_items(Alignment::Center) + .into() + } + Some(HasOutput::Menu(menu_type)) => match menu_type { Some(MenuType::Updates) => menu_wrapper( - self.updates.menu_view().map(Message::Updates), + id, + self.updates.menu_view(id).map(Message::Updates), MenuPosition::Left, self.config.position, ), Some(MenuType::Settings) => menu_wrapper( + id, self.settings - .menu_view(&self.config.settings) + .menu_view(id, &self.config.settings) .map(Message::Settings), MenuPosition::Right, self.config.position, ), None => Row::new().into(), - } - } else { - let left = Row::new() - .push_maybe( - self.config - .app_launcher_cmd - .as_ref() - .map(|_| launcher::launcher()), - ) - .push_maybe( - self.config - .clipboard_cmd - .as_ref() - .map(|_| clipboard::clipboard()), - ) - .push_maybe( - self.config - .updates - .as_ref() - .map(|_| self.updates.view().map(Message::Updates)), - ) - .push( - self.workspaces - .view( - &self.config.appearance.workspace_colors, - self.config.appearance.special_workspace_colors.as_deref(), - ) - .map(Message::Workspaces), - ) - .height(Length::Shrink) - .align_y(Alignment::Center) - .spacing(4); - - let center = Row::new() - .push_maybe(self.window_title.view().map(|v| v.map(Message::Title))) - .spacing(4); - - let right = Row::new() - .push_maybe( - self.system_info - .view(&self.config.system) - .map(|c| c.map(Message::SystemInfo)), - ) - .push_maybe( - self.keyboard_submap - .view(&self.config.keyboard.submap) - .map(|l| l.map(Message::KeyboardSubmap)), - ) - .push_maybe( - self.keyboard_layout - .view(&self.config.keyboard.layout) - .map(|l| l.map(Message::KeyboardLayout)), - ) - .push( - Row::new() - .push( - self.clock - .view(&self.config.clock.format) - .map(Message::Clock), - ) - .push_maybe( - self.privacy - .as_ref() - .and_then(|privacy| privacy.view()) - .map(|e| e.map(Message::Privacy)), - ) - .push(self.settings.view().map(Message::Settings)), - ) - .spacing(4); - - centerbox::Centerbox::new([left.into(), center.into(), right.into()]) - .spacing(4) - .padding([0, 4]) - .width(Length::Fill) - .height(Length::Fixed(HEIGHT as f32)) - .align_items(Alignment::Center) - .into() + }, + None => Row::new().into(), } } @@ -335,6 +344,21 @@ impl App { ), Some(self.settings.subscription().map(Message::Settings)), Some(config::subscription()), + Some(listen_with(|evt, _, _| { + if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland( + evt, + )) = evt + { + if matches!(evt, WaylandEvent::Output(_, _)) { + debug!("Wayland event: {:?}", evt); + Some(Message::WaylandEvent(evt)) + } else { + None + } + } else { + None + } + })), ] .into_iter() .flatten() diff --git a/src/config.rs b/src/config.rs index 38c5fa2..79d2a48 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,9 +6,9 @@ use iced::{ Color, Subscription, }; use inotify::{EventMask, Inotify, WatchMask}; -use log::warn; -use serde::{Deserialize, Deserializer}; -use std::{env, fs::File, path::Path}; +use serde::Deserialize; +use std::{any::TypeId, env, fs::File, path::Path, time::Duration}; +use tokio::time::sleep; use crate::app::Message; @@ -289,7 +289,7 @@ impl Default for Appearance { } } -#[derive(Deserialize, Clone, Copy, Debug, Default)] +#[derive(Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Position { #[default] Top, @@ -303,11 +303,13 @@ pub struct Config { pub log_level: String, #[serde(default)] pub position: Position, + #[serde(default)] + pub outputs: Vec, pub app_launcher_cmd: Option, pub clipboard_cmd: Option, #[serde(default = "default_truncate_title_after_length")] pub truncate_title_after_length: u32, - #[serde(deserialize_with = "try_default")] + #[serde(default)] pub updates: Option, #[serde(default)] pub system: SystemModuleConfig, @@ -321,21 +323,6 @@ pub struct Config { pub appearance: Appearance, } -fn try_default<'de, T, D>(deserializer: D) -> Result -where - T: Deserialize<'de> + Default + std::fmt::Debug, - D: Deserializer<'de>, -{ - // Try to deserialize the UpdatesModuleConfig - let result: Result = T::deserialize(deserializer); - - // If it fails, return None - result.or_else(|err| { - warn!("error deserializing: {:?}", err); - Ok(T::default()) - }) -} - fn default_log_level() -> String { "warn".to_owned() } @@ -349,6 +336,7 @@ impl Default for Config { Self { log_level: default_log_level(), position: Position::Top, + outputs: vec![], app_launcher_cmd: None, clipboard_cmd: None, truncate_title_after_length: default_truncate_title_after_length(), @@ -376,8 +364,11 @@ pub fn read_config() -> Result { } pub fn subscription() -> Subscription { - Subscription::run(|| { - channel(100, move |mut output| async move { + let id = TypeId::of::(); + + Subscription::run_with_id( + id, + channel(100, |mut output| async move { let home_dir = env::var("HOME").expect("Could not get HOME environment variable"); let file_path = format!("{}{}", home_dir, CONFIG_PATH.replace('~', "")); @@ -416,6 +407,7 @@ pub fn subscription() -> Subscription { .expect("Failed to create event stream"); loop { + log::debug!("waiting for event"); let event = stream.next().await; match event { Some(Ok(inotify::Event { @@ -444,6 +436,8 @@ pub fn subscription() -> Subscription { })) => { log::info!("Config file modified"); + sleep(Duration::from_millis(500)).await; + let new_config = read_config(); if let Ok(new_config) = new_config { let _ = output @@ -452,6 +446,8 @@ pub fn subscription() -> Subscription { } else { log::warn!("Failed to read config file: {:?}", new_config); } + + break; } Some(Ok(inotify::Event { mask: EventMask::DELETE, @@ -462,10 +458,12 @@ pub fn subscription() -> Subscription { break; } - _ => {} + other => { + log::debug!("other event {:?}", other); + } } } } - }) - }) + }), + ) } diff --git a/src/main.rs b/src/main.rs index baa4f7d..41b680e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod components; mod config; mod menu; mod modules; +mod outputs; mod password_dialog; mod services; mod style; diff --git a/src/menu.rs b/src/menu.rs index 69d493a..247e263 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -12,8 +12,8 @@ use iced::{Border, Length, Padding}; #[derive(Debug, Clone)] pub struct Menu { - id: Id, - menu_type: Option, + pub id: Id, + pub menu_type: Option, } impl Menu { @@ -24,24 +24,29 @@ impl Menu { } } - pub fn open(&mut self, menu_type: MenuType) -> Task { - let task = set_layer(self.id, Layer::Overlay); - + pub fn open(&mut self, menu_type: MenuType) -> Task { self.menu_type.replace(menu_type); - task + Task::batch(vec![ + set_layer(self.id, Layer::Overlay), + set_keyboard_interactivity(self.id, KeyboardInteractivity::None), + ]) } pub fn close(&mut self) -> Task { if self.menu_type.is_some() { self.menu_type.take(); - set_layer(self.id, Layer::Background) + + Task::batch(vec![ + set_layer(self.id, Layer::Background), + set_keyboard_interactivity(self.id, KeyboardInteractivity::None), + ]) } else { Task::none() } } - pub fn toggle(&mut self, menu_type: MenuType) -> Task { + pub fn toggle(&mut self, menu_type: MenuType) -> Task { match self.menu_type.as_mut() { None => self.open(menu_type), Some(current) if *current == menu_type => self.close(), @@ -64,23 +69,11 @@ impl Menu { } } - pub fn is_menu(&self, id: Id) -> bool { - self.id == id - } - - pub fn get_menu_type_to_render(&self, id: Id) -> Option { - if self.id == id { - self.menu_type - } else { - None - } - } - - pub fn request_keyboard(&self) -> Task { + pub fn request_keyboard(&self) -> Task { set_keyboard_interactivity(self.id, KeyboardInteractivity::OnDemand) } - pub fn release_keyboard(&self) -> Task { + pub fn release_keyboard(&self) -> Task { set_keyboard_interactivity(self.id, KeyboardInteractivity::None) } } @@ -91,6 +84,7 @@ pub enum MenuPosition { } pub fn menu_wrapper( + id: Id, content: Element, position: MenuPosition, bar_position: Position, @@ -125,6 +119,6 @@ pub fn menu_wrapper( .width(Length::Fill) .height(Length::Fill), ) - .on_release(app::Message::CloseMenu) + .on_release(app::Message::CloseMenu(id)) .into() } diff --git a/src/modules/settings/audio.rs b/src/modules/settings/audio.rs index d75753b..f3a61b1 100644 --- a/src/modules/settings/audio.rs +++ b/src/modules/settings/audio.rs @@ -9,6 +9,7 @@ use crate::{ }; use iced::{ widget::{button, column, container, horizontal_rule, row, slider, text, Column, Row}, + window::Id, Alignment, Element, Length, Theme, }; @@ -21,8 +22,8 @@ pub enum AudioMessage { SinkVolumeChanged(i32), ToggleSourceMute, SourceVolumeChanged(i32), - SinksMore, - SourcesMore, + SinksMore(Id), + SourcesMore(Id), } impl AudioData { @@ -87,7 +88,7 @@ impl AudioData { } } - pub fn sinks_submenu(&self, show_more: bool) -> Element { + pub fn sinks_submenu(&self, id: Id, show_more: bool) -> Element { audio_submenu( self.sinks .iter() @@ -104,14 +105,14 @@ impl AudioData { }) .collect(), if show_more { - Some(Message::Audio(AudioMessage::SinksMore)) + Some(Message::Audio(AudioMessage::SinksMore(id))) } else { None }, ) } - pub fn sources_submenu(&self, show_more: bool) -> Element { + pub fn sources_submenu(&self, id: Id, show_more: bool) -> Element { audio_submenu( self.sources .iter() @@ -128,7 +129,7 @@ impl AudioData { }) .collect(), if show_more { - Some(Message::Audio(AudioMessage::SourcesMore)) + Some(Message::Audio(AudioMessage::SourcesMore(id))) } else { None }, diff --git a/src/modules/settings/bluetooth.rs b/src/modules/settings/bluetooth.rs index 18d2ff2..fcb1023 100644 --- a/src/modules/settings/bluetooth.rs +++ b/src/modules/settings/bluetooth.rs @@ -9,6 +9,7 @@ use crate::{ }; use iced::{ widget::{button, column, container, horizontal_rule, row, text, Column, Row}, + window::Id, Element, Length, Theme, }; @@ -16,12 +17,13 @@ use iced::{ pub enum BluetoothMessage { Event(ServiceEvent), Toggle, - More, + More(Id), } impl BluetoothData { pub fn get_quick_setting_button( &self, + id: Id, sub_menu: Option, show_more_button: bool, ) -> Option<(Element, Option>)> { @@ -41,11 +43,11 @@ impl BluetoothData { ), sub_menu .filter(|menu_type| *menu_type == SubMenu::Bluetooth) - .map(|_| sub_menu_wrapper(self.bluetooth_menu(show_more_button))), + .map(|_| sub_menu_wrapper(self.bluetooth_menu(id, show_more_button))), )) } - pub fn bluetooth_menu(&self, show_more_button: bool) -> Element { + pub fn bluetooth_menu(&self, id: Id, show_more_button: bool) -> Element { let main = if self.devices.is_empty() { text("No devices connected").into() } else { @@ -69,7 +71,7 @@ impl BluetoothData { main, horizontal_rule(1), button("More") - .on_press(Message::Bluetooth(BluetoothMessage::More)) + .on_press(Message::Bluetooth(BluetoothMessage::More(id))) .padding([4, 12]) .width(Length::Fill) .style(GhostButtonStyle.into_style()) diff --git a/src/modules/settings/mod.rs b/src/modules/settings/mod.rs index cbdd8cc..8b5ced9 100644 --- a/src/modules/settings/mod.rs +++ b/src/modules/settings/mod.rs @@ -5,8 +5,8 @@ use crate::{ app::MenuType, components::icons::{icon, Icons}, config::SettingsModuleConfig, - menu::Menu, modules::settings::power::power_menu, + outputs::Outputs, password_dialog, services::{ audio::{AudioCommand, AudioService}, @@ -28,6 +28,7 @@ use iced::{ widget::{ button, column, container, horizontal_space, row, text, vertical_rule, Column, Row, Space, }, + window::Id, Alignment, Background, Border, Element, Length, Padding, Subscription, Task, Theme, }; use log::info; @@ -68,7 +69,7 @@ impl Default for Settings { #[derive(Debug, Clone)] pub enum Message { - ToggleMenu, + ToggleMenu(Id), UPower(UPowerMessage), Network(NetworkMessage), Bluetooth(BluetoothMessage), @@ -96,13 +97,13 @@ impl Settings { &mut self, message: Message, config: &SettingsModuleConfig, - menu: &mut Menu, + outputs: &mut Outputs, ) -> Task { match message { - Message::ToggleMenu => { + Message::ToggleMenu(id) => { self.sub_menu = None; self.password_dialog = None; - Task::batch(vec![menu.toggle(MenuType::Settings)]) + outputs.toggle_menu(id, MenuType::Settings) } Message::Audio(msg) => match msg { AudioMessage::Event(event) => match event { @@ -154,18 +155,18 @@ impl Settings { } Task::none() } - AudioMessage::SinksMore => { + AudioMessage::SinksMore(id) => { if let Some(cmd) = &config.audio_sinks_more_cmd { crate::utils::launcher::execute_command(cmd.to_string()); - menu.close() + outputs.close_menu(id) } else { Task::none() } } - AudioMessage::SourcesMore => { + AudioMessage::SourcesMore(id) => { if let Some(cmd) = &config.audio_sources_more_cmd { crate::utils::launcher::execute_command(cmd.to_string()); - menu.close() + outputs.close_menu(id) } else { Task::none() } @@ -252,10 +253,10 @@ impl Settings { Task::none() } } - NetworkMessage::RequestWiFiPassword(ssid) => { + NetworkMessage::RequestWiFiPassword(id, ssid) => { info!("Requesting password for {}", ssid); self.password_dialog = Some((ssid, "".to_string())); - menu.request_keyboard() + outputs.request_keyboard(id) } NetworkMessage::ScanNearByWiFi => { if let Some(network) = self.network.as_mut() { @@ -270,18 +271,18 @@ impl Settings { Task::none() } } - NetworkMessage::WiFiMore => { + NetworkMessage::WiFiMore(id) => { if let Some(cmd) = &config.wifi_more_cmd { crate::utils::launcher::execute_command(cmd.to_string()); - menu.close() + outputs.close_menu(id) } else { Task::none() } } - NetworkMessage::VpnMore => { + NetworkMessage::VpnMore(id) => { if let Some(cmd) = &config.vpn_more_cmd { crate::utils::launcher::execute_command(cmd.to_string()); - menu.close() + outputs.close_menu(id) } else { Task::none() } @@ -325,10 +326,10 @@ impl Settings { Task::none() } } - BluetoothMessage::More => { + BluetoothMessage::More(id) => { if let Some(cmd) = &config.bluetooth_more_cmd { crate::utils::launcher::execute_command(cmd.to_string()); - menu.close() + outputs.close_menu(id) } else { Task::none() } @@ -407,7 +408,7 @@ impl Settings { Task::none() } - password_dialog::Message::DialogConfirmed => { + password_dialog::Message::DialogConfirmed(id) => { if let Some((ssid, password)) = self.password_dialog.take() { let network_command = if let Some(network) = self.network.as_mut() { let ap = network @@ -432,21 +433,21 @@ impl Settings { } else { Task::none() }; - Task::batch(vec![network_command, menu.release_keyboard()]) + Task::batch(vec![network_command, outputs.release_keyboard(id)]) } else { - menu.release_keyboard() + outputs.release_keyboard(id) } } - password_dialog::Message::DialogCancelled => { + password_dialog::Message::DialogCancelled(id) => { self.password_dialog = None; - menu.release_keyboard() + outputs.release_keyboard(id) } }, } } - pub fn view(&self) -> Element { + pub fn view(&self, id: Id) -> Element { button( Row::new() .push_maybe( @@ -488,13 +489,13 @@ impl Settings { ) .style(HeaderButtonStyle::Right.into_style()) .padding([2, 8]) - .on_press(Message::ToggleMenu) + .on_press(Message::ToggleMenu(id)) .into() } - pub fn menu_view(&self, config: &SettingsModuleConfig) -> Element { + pub fn menu_view(&self, id: Id, config: &SettingsModuleConfig) -> Element { if let Some((ssid, current_password)) = &self.password_dialog { - password_dialog::view(ssid, current_password).map(Message::PasswordDialog) + password_dialog::view(id, ssid, current_password).map(Message::PasswordDialog) } else { let battery_data = self .upower @@ -534,7 +535,7 @@ impl Settings { .unwrap_or((None, None)); let wifi_setting_button = self.network.as_ref().and_then(|n| { - n.get_wifi_quick_setting_button(self.sub_menu, config.wifi_more_cmd.is_some()) + n.get_wifi_quick_setting_button(id, self.sub_menu, config.wifi_more_cmd.is_some()) }); let quick_settings = quick_settings_section( vec![ @@ -544,12 +545,17 @@ impl Settings { .filter(|b| b.state != BluetoothState::Unavailable) .and_then(|b| { b.get_quick_setting_button( + id, self.sub_menu, config.bluetooth_more_cmd.is_some(), ) }), self.network.as_ref().map(|n| { - n.get_vpn_quick_setting_button(self.sub_menu, config.vpn_more_cmd.is_some()) + n.get_vpn_quick_setting_button( + id, + self.sub_menu, + config.vpn_more_cmd.is_some(), + ) }), self.network .as_ref() @@ -594,7 +600,7 @@ impl Settings { .and_then(|_| { self.audio.as_ref().map(|a| { sub_menu_wrapper( - a.sinks_submenu(config.audio_sinks_more_cmd.is_some()), + a.sinks_submenu(id, config.audio_sinks_more_cmd.is_some()), ) }) }), @@ -606,7 +612,7 @@ impl Settings { .and_then(|_| { self.audio.as_ref().map(|a| { sub_menu_wrapper( - a.sources_submenu(config.audio_sources_more_cmd.is_some()), + a.sources_submenu(id, config.audio_sources_more_cmd.is_some()), ) }) }), diff --git a/src/modules/settings/network.rs b/src/modules/settings/network.rs index bf72fb1..e19cc7e 100644 --- a/src/modules/settings/network.rs +++ b/src/modules/settings/network.rs @@ -13,6 +13,7 @@ use crate::{ }; use iced::{ widget::{button, column, container, horizontal_rule, row, scrollable, text, toggler, Column}, + window::Id, Alignment, Element, Length, Theme, }; @@ -21,10 +22,10 @@ pub enum NetworkMessage { Event(ServiceEvent), ToggleWiFi, ScanNearByWiFi, - WiFiMore, - VpnMore, + WiFiMore(Id), + VpnMore(Id), SelectAccessPoint(AccessPoint), - RequestWiFiPassword(String), + RequestWiFiPassword(Id, String), ToggleVpn(Vpn), ToggleAirplaneMode, } @@ -127,6 +128,7 @@ impl NetworkData { pub fn get_wifi_quick_setting_button( &self, + id: Id, sub_menu: Option, show_more_button: bool, ) -> Option<(Element, Option>)> { @@ -156,6 +158,7 @@ impl NetworkData { .filter(|menu_type| *menu_type == SubMenu::Wifi) .map(|_| { sub_menu_wrapper(self.wifi_menu( + id, active_connection.map(|(name, strengh, _)| (name.as_str(), *strengh)), show_more_button, )) @@ -169,6 +172,7 @@ impl NetworkData { pub fn get_vpn_quick_setting_button( &self, + id: Id, sub_menu: Option, show_more_button: bool, ) -> (Element, Option>) { @@ -185,12 +189,15 @@ impl NetworkData { ), sub_menu .filter(|menu_type| *menu_type == SubMenu::Vpn) - .map(|_| sub_menu_wrapper(self.vpn_menu(show_more_button)).map(Message::Network)), + .map(|_| { + sub_menu_wrapper(self.vpn_menu(id, show_more_button)).map(Message::Network) + }), ) } pub fn wifi_menu( &self, + id: Id, active_connection: Option<(&str, u8)>, show_more_button: bool, ) -> Element { @@ -260,7 +267,7 @@ impl NetworkData { Some(if is_known { NetworkMessage::SelectAccessPoint(ac.clone()) } else { - NetworkMessage::RequestWiFiPassword(ac.ssid.clone()) + NetworkMessage::RequestWiFiPassword(id, ac.ssid.clone()) }) } else { None @@ -281,7 +288,7 @@ impl NetworkData { main, horizontal_rule(1), button("More") - .on_press(NetworkMessage::WiFiMore) + .on_press(NetworkMessage::WiFiMore(id)) .padding([4, 12]) .width(Length::Fill) .style(GhostButtonStyle.into_style()), @@ -293,7 +300,7 @@ impl NetworkData { } } - pub fn vpn_menu(&self, show_more_button: bool) -> Element { + pub fn vpn_menu(&self, id: Id, show_more_button: bool) -> Element { let main = Column::with_children( self.known_connections .iter() @@ -323,7 +330,7 @@ impl NetworkData { main, horizontal_rule(1), button("More") - .on_press(NetworkMessage::VpnMore) + .on_press(NetworkMessage::VpnMore(id)) .padding([4, 12]) .width(Length::Fill) .style(GhostButtonStyle.into_style()), diff --git a/src/modules/updates.rs b/src/modules/updates.rs index 6eb5c34..a3ceceb 100644 --- a/src/modules/updates.rs +++ b/src/modules/updates.rs @@ -2,13 +2,14 @@ use crate::{ app::{self, MenuType}, components::icons::{icon, Icons}, config::UpdatesModuleConfig, - menu::Menu, + outputs::Outputs, style::{GhostButtonStyle, HeaderButtonStyle}, }; use iced::{ alignment::Horizontal, stream::channel, widget::{button, column, container, horizontal_rule, row, scrollable, text, Column}, + window::Id, Alignment, Element, Length, Padding, Subscription, Task, }; use log::error; @@ -70,12 +71,12 @@ async fn update(update_cmd: &str) { #[derive(Debug, Clone)] pub enum Message { - ToggleMenu, + ToggleMenu(Id), UpdatesCheckCompleted(Vec), UpdateFinished, ToggleUpdatesList, CheckNow, - Update, + Update(Id), } #[derive(Debug, Default, Clone, Eq, PartialEq)] @@ -97,7 +98,7 @@ impl Updates { &mut self, message: Message, config: &UpdatesModuleConfig, - menu: &mut Menu, + outputs: &mut Outputs, ) -> Task { match message { Message::UpdatesCheckCompleted(updates) => { @@ -106,9 +107,9 @@ impl Updates { Task::none() } - Message::ToggleMenu => { + Message::ToggleMenu(id) => { self.is_updates_list_open = false; - menu.toggle(MenuType::Updates) + outputs.toggle_menu(id, MenuType::Updates) } Message::UpdateFinished => { self.updates.clear(); @@ -129,7 +130,7 @@ impl Updates { move |updates| app::Message::Updates(Message::UpdatesCheckCompleted(updates)), ) } - Message::Update => { + Message::Update(id) => { let update_command = config.update_cmd.clone(); let mut cmds = vec![Task::perform( async move { @@ -143,14 +144,14 @@ impl Updates { move |_| app::Message::Updates(Message::UpdateFinished), )]; - cmds.push(menu.close_if(MenuType::Updates)); + cmds.push(outputs.close_menu_if(id, MenuType::Updates)); Task::batch(cmds) } } } - pub fn view(&self) -> Element { + pub fn view(&self, id: Id) -> Element { let mut content = row!(container(icon(match self.state { State::Checking => Icons::Refresh, State::Ready if self.updates.is_empty() => Icons::NoUpdatesAvailable, @@ -166,11 +167,11 @@ impl Updates { button(content) .padding([2, 7]) .style(HeaderButtonStyle::Full.into_style()) - .on_press(Message::ToggleMenu) + .on_press(Message::ToggleMenu(id)) .into() } - pub fn menu_view(&self) -> Element { + pub fn menu_view(&self, id: Id) -> Element { column!( if self.updates.is_empty() { convert::Into::>::into( @@ -237,7 +238,7 @@ impl Updates { button("Update") .style(GhostButtonStyle.into_style()) .padding([8, 8]) - .on_press(Message::Update) + .on_press(Message::Update(id)) .width(Length::Fill), button({ let mut content = row!(text("Check now").width(Length::Fill),); diff --git a/src/outputs.rs b/src/outputs.rs new file mode 100644 index 0000000..097cb83 --- /dev/null +++ b/src/outputs.rs @@ -0,0 +1,376 @@ +use iced::{ + platform_specific::shell::commands::layer_surface::{ + destroy_layer_surface, get_layer_surface, set_anchor, Anchor, KeyboardInteractivity, Layer, + }, + runtime::platform_specific::wayland::layer_surface::{IcedOutput, SctkLayerSurfaceSettings}, + window::Id, + Task, +}; +use log::debug; +use wayland_client::protocol::wl_output::WlOutput; + +use crate::{app::MenuType, config::Position, menu::Menu, HEIGHT}; + +static FALLBACK_LAYER: &str = "fallback"; + +#[derive(Debug, Clone)] +struct ShellInfo { + id: Id, + position: Position, + menu: Menu, +} + +#[derive(Debug, Clone)] +pub struct Outputs(Vec<(String, Option, Option)>); + +pub enum HasOutput { + Main, + Menu(Option), +} + +impl Outputs { + pub fn new(position: Position) -> (Self, Task) { + let (id, menu_id, task) = Self::create_output_layers(None, position); + + ( + Self(vec![( + FALLBACK_LAYER.to_owned(), + Some(ShellInfo { + id, + menu: Menu::new(menu_id), + position, + }), + None, + )]), + task, + ) + } + + fn create_output_layers( + wl_output: Option, + position: Position, + ) -> (Id, Id, Task) { + let id = Id::unique(); + let task = get_layer_surface(SctkLayerSurfaceSettings { + id, + size: Some((None, Some(HEIGHT))), + layer: Layer::Bottom, + pointer_interactivity: true, + keyboard_interactivity: KeyboardInteractivity::None, + exclusive_zone: HEIGHT as i32, + output: wl_output.clone().map_or(IcedOutput::Active, |wl_output| { + IcedOutput::Output(wl_output) + }), + anchor: match position { + Position::Top => Anchor::TOP, + Position::Bottom => Anchor::BOTTOM, + } | Anchor::LEFT + | Anchor::RIGHT, + ..Default::default() + }); + + let menu_id = Id::unique(); + let menu_task = get_layer_surface(SctkLayerSurfaceSettings { + id: menu_id, + size: Some((None, None)), + layer: Layer::Background, + pointer_interactivity: true, + keyboard_interactivity: KeyboardInteractivity::None, + output: wl_output.map_or(IcedOutput::Active, |wl_output| { + IcedOutput::Output(wl_output) + }), + anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT, + ..Default::default() + }); + + (id, menu_id, Task::batch(vec![task, menu_task])) + } + + pub fn has(&self, id: Id) -> Option { + self.0.iter().find_map(|(_, info, _)| { + if let Some(info) = info { + if info.id == id { + Some(HasOutput::Main) + } else if info.menu.id == id { + Some(HasOutput::Menu(info.menu.menu_type)) + } else { + None + } + } else { + None + } + }) + } + + pub fn add( + &mut self, + request_outputs: &[String], + position: Position, + name: &str, + wl_output: WlOutput, + ) -> Task { + let target = request_outputs.iter().any(|output| output.as_str() == name); + + if target { + debug!("Found target output, creating a new layer surface"); + + let (id, menu_id, task) = Self::create_output_layers(Some(wl_output.clone()), position); + + let destroy_task = + if let Some(index) = self.0.iter().position(|(key, _, _)| key == name) { + let old_output = self.0.swap_remove(index); + + if let Some(shell_info) = old_output.1 { + let destroy_main_task = destroy_layer_surface(shell_info.id); + let destroy_menu_task = destroy_layer_surface(shell_info.menu.id); + + Task::batch(vec![destroy_main_task, destroy_menu_task]) + } else { + Task::none() + } + } else { + Task::none() + }; + + self.0.push(( + name.to_owned(), + Some(ShellInfo { + id, + menu: Menu::new(menu_id), + position, + }), + Some(wl_output), + )); + + // remove fallback layer surface + let destroy_fallback_task = + if let Some(index) = self.0.iter().position(|(key, _, _)| key == FALLBACK_LAYER) { + let old_output = self.0.swap_remove(index); + + if let Some(shell_info) = old_output.1 { + let destroy_fallback_main_task = destroy_layer_surface(shell_info.id); + let destroy_fallback_menu_task = destroy_layer_surface(shell_info.menu.id); + + Task::batch(vec![destroy_fallback_main_task, destroy_fallback_menu_task]) + } else { + Task::none() + } + } else { + Task::none() + }; + + Task::batch(vec![destroy_task, destroy_fallback_task, task]) + } else { + self.0.push((name.to_owned(), None, Some(wl_output))); + + Task::none() + } + } + + pub fn remove( + &mut self, + position: Position, + wl_output: WlOutput, + ) -> Task { + if let Some(index_to_remove) = self.0.iter().position(|(_, _, assigned_wl_output)| { + assigned_wl_output + .as_ref() + .map(|assigned_wl_output| *assigned_wl_output == wl_output) + .unwrap_or_default() + }) { + debug!("Removing layer surface for output"); + + let (name, shell_info, wl_output) = self.0.swap_remove(index_to_remove); + + let destroy_task = if let Some(shell_info) = shell_info { + let destroy_main_task = destroy_layer_surface(shell_info.id); + let destroy_menu_task = destroy_layer_surface(shell_info.menu.id); + + Task::batch(vec![destroy_main_task, destroy_menu_task]) + } else { + Task::none() + }; + + self.0.push((name.to_owned(), None, wl_output)); + + if !self.0.iter().any(|(_, shell_info, _)| shell_info.is_some()) { + debug!("No outputs left, creating a fallback layer surface"); + + let (id, menu_id, task) = Self::create_output_layers(None, position); + + self.0.push(( + FALLBACK_LAYER.to_owned(), + Some(ShellInfo { + id, + menu: Menu::new(menu_id), + position, + }), + None, + )); + + Task::batch(vec![destroy_task, task]) + } else { + Task::batch(vec![destroy_task]) + } + } else { + Task::none() + } + } + + pub fn sync( + &mut self, + request_outputs: &[String], + position: Position, + ) -> Task { + debug!( + "Syncing outputs: {:?}, request_outputs: {:?}", + self, request_outputs + ); + + let to_remove = self + .0 + .iter() + .filter_map(|(name, shell_info, wl_output)| { + if !request_outputs.iter().any(|output| output.as_str() == name) + && shell_info.is_some() + { + Some(wl_output.clone()) + } else { + None + } + }) + .flatten() + .collect::>(); + debug!("Removing outputs: {:?}", to_remove); + + let to_add = self + .0 + .iter() + .filter_map(|(name, shell_info, wl_output)| { + if request_outputs.iter().any(|output| output.as_str() == name) + && shell_info.is_none() + { + Some((name.clone(), wl_output.clone())) + } else { + None + } + }) + .collect::>(); + debug!("Adding outputs: {:?}", to_add); + + let mut tasks = Vec::new(); + for (name, wl_output) in to_add { + if let Some(wl_output) = wl_output { + tasks.push(self.add(request_outputs, position, &name, wl_output)); + } + } + + for wl_output in to_remove { + tasks.push(self.remove(position, wl_output)); + } + + for shell_info in self.0.iter_mut().filter_map(|(_, shell_info, _)| { + if let Some(shell_info) = shell_info { + if shell_info.position != position { + Some(shell_info) + } else { + None + } + } else { + None + } + }) { + debug!( + "Repositioning output: {:?}, new position {:?}", + shell_info.id, position + ); + shell_info.position = position; + tasks.push(set_anchor( + shell_info.id, + match position { + Position::Top => Anchor::TOP, + Position::Bottom => Anchor::BOTTOM, + } | Anchor::LEFT + | Anchor::RIGHT, + )); + } + + Task::batch(tasks) + } + + pub fn toggle_menu(&mut self, id: Id, menu_type: MenuType) -> Task { + if let Some((_, Some(shell_info), _)) = self.0.iter_mut().find(|(_, shell_info, _)| { + shell_info.as_ref().map(|shell_info| shell_info.id) == Some(id) + || shell_info.as_ref().map(|shell_info| shell_info.menu.id) == Some(id) + }) { + let toggle_task = shell_info.menu.toggle(menu_type); + let mut tasks = self + .0 + .iter_mut() + .filter_map(|(_, shell_info, _)| { + if let Some(shell_info) = shell_info { + if shell_info.id != id && shell_info.menu.id != id { + Some(shell_info.menu.close()) + } else { + None + } + } else { + None + } + }) + .collect::>(); + tasks.push(toggle_task); + Task::batch(tasks) + } else { + Task::none() + } + } + + pub fn close_menu(&mut self, id: Id) -> Task { + if let Some((_, Some(shell_info), _)) = self.0.iter_mut().find(|(_, shell_info, _)| { + shell_info.as_ref().map(|shell_info| shell_info.id) == Some(id) + || shell_info.as_ref().map(|shell_info| shell_info.menu.id) == Some(id) + }) { + shell_info.menu.close() + } else { + Task::none() + } + } + + pub fn close_menu_if( + &mut self, + id: Id, + menu_type: MenuType, + ) -> Task { + if let Some((_, Some(shell_info), _)) = self.0.iter_mut().find(|(_, shell_info, _)| { + shell_info.as_ref().map(|shell_info| shell_info.id) == Some(id) + || shell_info.as_ref().map(|shell_info| shell_info.menu.id) == Some(id) + }) { + shell_info.menu.close_if(menu_type) + } else { + Task::none() + } + } + + pub fn request_keyboard(&self, id: Id) -> Task { + if let Some((_, Some(shell_info), _)) = self.0.iter().find(|(_, shell_info, _)| { + shell_info.as_ref().map(|shell_info| shell_info.id) == Some(id) + || shell_info.as_ref().map(|shell_info| shell_info.menu.id) == Some(id) + }) { + shell_info.menu.request_keyboard() + } else { + Task::none() + } + } + + pub fn release_keyboard(&self, id: Id) -> Task { + if let Some((_, Some(shell_info), _)) = self.0.iter().find(|(_, shell_info, _)| { + shell_info.as_ref().map(|shell_info| shell_info.id) == Some(id) + || shell_info.as_ref().map(|shell_info| shell_info.menu.id) == Some(id) + }) { + shell_info.menu.release_keyboard() + } else { + Task::none() + } + } +} diff --git a/src/password_dialog.rs b/src/password_dialog.rs index 2f14b4c..d29f7f3 100644 --- a/src/password_dialog.rs +++ b/src/password_dialog.rs @@ -1,6 +1,7 @@ use iced::{ alignment::Vertical, widget::{button, column, horizontal_space, row, text, text_input}, + window::Id, Alignment, Element, Length, }; @@ -12,11 +13,11 @@ use crate::{ #[derive(Debug, Clone)] pub enum Message { PasswordChanged(String), - DialogConfirmed, - DialogCancelled, + DialogConfirmed(Id), + DialogCancelled(Id), } -pub fn view<'a>(wifi_ssid: &str, current_password: &str) -> Element<'a, Message> { +pub fn view<'a>(id: Id, wifi_ssid: &str, current_password: &str) -> Element<'a, Message> { column!( row!( icon(Icons::WifiLock4).size(32), @@ -31,19 +32,19 @@ pub fn view<'a>(wifi_ssid: &str, current_password: &str) -> Element<'a, Message> .padding([8, 16]) .style(TextInputStyle.into_style()) .on_input(Message::PasswordChanged) - .on_submit(Message::DialogConfirmed), + .on_submit(Message::DialogConfirmed(id)), row!( horizontal_space(), button(text("Cancel").align_y(Vertical::Center)) .padding([4, 32]) .style(OutlineButtonStyle.into_style()) .height(Length::Fixed(50.)) - .on_press(Message::DialogCancelled), + .on_press(Message::DialogCancelled(id)), button(text("Confirm").align_y(Vertical::Center)) .padding([4, 32]) .height(Length::Fixed(50.)) .style(ConfirmButtonStyle.into_style()) - .on_press(Message::DialogConfirmed) + .on_press(Message::DialogConfirmed(id)) ) .spacing(8) .width(Length::Fill)