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)