diff --git a/.gitignore b/.gitignore
index 2f7896d..b83d222 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-target/
+/target/
diff --git a/Cargo.lock b/Cargo.lock
index 0b0b2cf..5abc856 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -35,7 +35,7 @@ dependencies = [
"atspi-common",
"serde",
"thiserror 1.0.69",
- "zvariant 4.2.0",
+ "zvariant",
]
[[package]]
@@ -77,7 +77,7 @@ dependencies = [
"futures-lite",
"futures-util",
"serde",
- "zbus 4.4.0",
+ "zbus",
]
[[package]]
@@ -431,11 +431,11 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
- "zbus 4.4.0",
+ "zbus",
"zbus-lockstep",
"zbus-lockstep-macros",
- "zbus_names 3.0.0",
- "zvariant 4.2.0",
+ "zbus_names",
+ "zvariant",
]
[[package]]
@@ -447,7 +447,7 @@ dependencies = [
"atspi-common",
"atspi-proxies",
"futures-lite",
- "zbus 4.4.0",
+ "zbus",
]
[[package]]
@@ -458,8 +458,8 @@ checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496"
dependencies = [
"atspi-common",
"serde",
- "zbus 4.4.0",
- "zvariant 4.2.0",
+ "zbus",
+ "zvariant",
]
[[package]]
@@ -501,6 +501,7 @@ version = "0.1.0"
dependencies = [
"ab_glyph",
"anyhow",
+ "bitflags 2.11.0",
"clap",
"dirs",
"eframe",
@@ -510,7 +511,6 @@ dependencies = [
"image",
"libc",
"memmap2",
- "notify-rust",
"png 0.17.16",
"rand",
"serde",
@@ -549,15 +549,6 @@ dependencies = [
"objc2 0.5.2",
]
-[[package]]
-name = "block2"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
-dependencies = [
- "objc2 0.6.4",
-]
-
[[package]]
name = "blocking"
version = "1.6.2"
@@ -910,15 +901,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
-[[package]]
-name = "deranged"
-version = "0.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
-dependencies = [
- "powerfmt",
-]
-
[[package]]
name = "digest"
version = "0.10.7"
@@ -1481,7 +1463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix 1.1.4",
- "windows-link 0.2.1",
+ "windows-link",
]
[[package]]
@@ -1959,7 +1941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
- "windows-link 0.2.1",
+ "windows-link",
]
[[package]]
@@ -2019,18 +2001,6 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
-[[package]]
-name = "mac-notification-sys"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621"
-dependencies = [
- "cc",
- "objc2 0.6.4",
- "objc2-foundation 0.3.2",
- "time",
-]
-
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -2205,26 +2175,6 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "notify-rust"
-version = "4.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21af20a1b50be5ac5861f74af1a863da53a11c38684d9818d82f1c42f7fdc6c2"
-dependencies = [
- "futures-lite",
- "log",
- "mac-notification-sys",
- "serde",
- "tauri-winrt-notification",
- "zbus 5.14.0",
-]
-
-[[package]]
-name = "num-conv"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
-
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -2297,7 +2247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"libc",
"objc2 0.5.2",
"objc2-core-data",
@@ -2326,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
@@ -2338,7 +2288,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@@ -2350,7 +2300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@@ -2385,7 +2335,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
@@ -2397,7 +2347,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-contacts",
"objc2-foundation 0.2.2",
@@ -2416,7 +2366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"dispatch",
"libc",
"objc2 0.5.2",
@@ -2429,8 +2379,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
- "block2 0.6.2",
- "libc",
"objc2 0.6.4",
"objc2-core-foundation",
]
@@ -2452,7 +2400,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
@@ -2465,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@@ -2477,7 +2425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
@@ -2500,7 +2448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-cloud-kit",
"objc2-core-data",
@@ -2520,7 +2468,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@@ -2532,7 +2480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
@@ -2621,7 +2569,7 @@ dependencies = [
"libc",
"redox_syscall 0.5.18",
"smallvec",
- "windows-link 0.2.1",
+ "windows-link",
]
[[package]]
@@ -2789,12 +2737,6 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "powerfmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -2860,15 +2802,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "quick-xml"
-version = "0.37.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "quick-xml"
version = "0.39.2"
@@ -3315,18 +3248,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "tauri-winrt-notification"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
-dependencies = [
- "quick-xml 0.37.5",
- "thiserror 2.0.18",
- "windows 0.61.3",
- "windows-version",
-]
-
[[package]]
name = "tempfile"
version = "3.26.0"
@@ -3389,25 +3310,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "time"
-version = "0.3.47"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
-dependencies = [
- "deranged",
- "num-conv",
- "powerfmt",
- "serde_core",
- "time-core",
-]
-
-[[package]]
-name = "time-core"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
-
[[package]]
name = "tiny-skia"
version = "0.11.4"
@@ -3602,17 +3504,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-[[package]]
-name = "uuid"
-version = "1.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
-dependencies = [
- "js-sys",
- "serde_core",
- "wasm-bindgen",
-]
-
[[package]]
name = "version_check"
version = "0.9.5"
@@ -4115,28 +4006,6 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows"
-version = "0.61.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
-dependencies = [
- "windows-collections",
- "windows-core 0.61.2",
- "windows-future",
- "windows-link 0.1.3",
- "windows-numerics",
-]
-
-[[package]]
-name = "windows-collections"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
-dependencies = [
- "windows-core 0.61.2",
-]
-
[[package]]
name = "windows-core"
version = "0.52.0"
@@ -4152,37 +4021,13 @@ version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
- "windows-implement 0.58.0",
- "windows-interface 0.58.0",
- "windows-result 0.2.0",
- "windows-strings 0.1.0",
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows-core"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
-dependencies = [
- "windows-implement 0.60.2",
- "windows-interface 0.59.3",
- "windows-link 0.1.3",
- "windows-result 0.3.4",
- "windows-strings 0.4.2",
-]
-
-[[package]]
-name = "windows-future"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
-dependencies = [
- "windows-core 0.61.2",
- "windows-link 0.1.3",
- "windows-threading",
-]
-
[[package]]
name = "windows-implement"
version = "0.58.0"
@@ -4194,17 +4039,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "windows-implement"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
[[package]]
name = "windows-interface"
version = "0.58.0"
@@ -4216,39 +4050,12 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "windows-interface"
-version = "0.59.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "windows-link"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
-
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-[[package]]
-name = "windows-numerics"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
-dependencies = [
- "windows-core 0.61.2",
- "windows-link 0.1.3",
-]
-
[[package]]
name = "windows-result"
version = "0.2.0"
@@ -4258,34 +4065,16 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows-result"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
-dependencies = [
- "windows-link 0.1.3",
-]
-
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
- "windows-result 0.2.0",
+ "windows-result",
"windows-targets 0.52.6",
]
-[[package]]
-name = "windows-strings"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
-dependencies = [
- "windows-link 0.1.3",
-]
-
[[package]]
name = "windows-sys"
version = "0.45.0"
@@ -4337,7 +4126,7 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
- "windows-link 0.2.1",
+ "windows-link",
]
[[package]]
@@ -4392,7 +4181,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
- "windows-link 0.2.1",
+ "windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
@@ -4403,24 +4192,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
-[[package]]
-name = "windows-threading"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
-dependencies = [
- "windows-link 0.1.3",
-]
-
-[[package]]
-name = "windows-version"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631"
-dependencies = [
- "windows-link 0.2.1",
-]
-
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@@ -4611,7 +4382,7 @@ dependencies = [
"android-activity",
"atomic-waker",
"bitflags 2.11.0",
- "block2 0.5.1",
+ "block2",
"bytemuck",
"calloop 0.13.0",
"cfg_aliases 0.2.1",
@@ -4903,44 +4674,9 @@ dependencies = [
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
- "zbus_macros 4.4.0",
- "zbus_names 3.0.0",
- "zvariant 4.2.0",
-]
-
-[[package]]
-name = "zbus"
-version = "5.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
-dependencies = [
- "async-broadcast",
- "async-executor",
- "async-io",
- "async-lock",
- "async-process",
- "async-recursion",
- "async-task",
- "async-trait",
- "blocking",
- "enumflags2",
- "event-listener",
- "futures-core",
- "futures-lite",
- "hex",
- "libc",
- "ordered-stream",
- "rustix 1.1.4",
- "serde",
- "serde_repr",
- "tracing",
- "uds_windows",
- "uuid",
- "windows-sys 0.61.2",
- "winnow",
- "zbus_macros 5.14.0",
- "zbus_names 4.3.1",
- "zvariant 5.10.0",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
]
[[package]]
@@ -4950,7 +4686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d"
dependencies = [
"zbus_xml",
- "zvariant 4.2.0",
+ "zvariant",
]
[[package]]
@@ -4964,7 +4700,7 @@ dependencies = [
"syn 2.0.117",
"zbus-lockstep",
"zbus_xml",
- "zvariant 4.2.0",
+ "zvariant",
]
[[package]]
@@ -4977,22 +4713,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "zvariant_utils 2.1.0",
-]
-
-[[package]]
-name = "zbus_macros"
-version = "5.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
- "zbus_names 4.3.1",
- "zvariant 5.10.0",
- "zvariant_utils 3.3.0",
+ "zvariant_utils",
]
[[package]]
@@ -5003,18 +4724,7 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
dependencies = [
"serde",
"static_assertions",
- "zvariant 4.2.0",
-]
-
-[[package]]
-name = "zbus_names"
-version = "4.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
-dependencies = [
- "serde",
- "winnow",
- "zvariant 5.10.0",
+ "zvariant",
]
[[package]]
@@ -5026,8 +4736,8 @@ dependencies = [
"quick-xml 0.30.0",
"serde",
"static_assertions",
- "zbus_names 3.0.0",
- "zvariant 4.2.0",
+ "zbus_names",
+ "zvariant",
]
[[package]]
@@ -5120,21 +4830,7 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
- "zvariant_derive 4.2.0",
-]
-
-[[package]]
-name = "zvariant"
-version = "5.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b"
-dependencies = [
- "endi",
- "enumflags2",
- "serde",
- "winnow",
- "zvariant_derive 5.10.0",
- "zvariant_utils 3.3.0",
+ "zvariant_derive",
]
[[package]]
@@ -5147,20 +4843,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "zvariant_utils 2.1.0",
-]
-
-[[package]]
-name = "zvariant_derive"
-version = "5.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
- "zvariant_utils 3.3.0",
+ "zvariant_utils",
]
[[package]]
@@ -5173,16 +4856,3 @@ dependencies = [
"quote",
"syn 2.0.117",
]
-
-[[package]]
-name = "zvariant_utils"
-version = "3.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
-dependencies = [
- "proc-macro2",
- "quote",
- "serde",
- "syn 2.0.117",
- "winnow",
-]
diff --git a/Cargo.toml b/Cargo.toml
index 0356d59..967daab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,6 @@ clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
wl-clipboard-rs = "0.9.3"
-notify-rust = "4"
wayland-client = "0.31"
wayland-protocols-wlr = { version = "0.2", features = ["client"] }
wayland-protocols = { version = "0.31", features = ["client", "staging", "unstable"] }
@@ -38,9 +37,16 @@ thiserror = "1"
anyhow = "1"
libc = "0.2"
tempfile = "3"
+bitflags = "2"
gbm = "0.18"
memmap2 = "0.9"
ab_glyph = { version = "0.2", optional = true }
eframe = { version = "0.29", features = ["wayland"], optional = true }
egui = { version = "0.29", optional = true }
egui_extras = { version = "0.29", features = ["image"], optional = true }
+
+[profile.release]
+lto = "fat"
+codegen-units = 1
+strip = true
+panic = "abort"
diff --git a/protocols/hyprland-toplevel-export-v1.xml b/protocols/hyprland-toplevel-export-v1.xml
new file mode 100644
index 0000000..b1185aa
--- /dev/null
+++ b/protocols/hyprland-toplevel-export-v1.xml
@@ -0,0 +1,228 @@
+
+
+
+ Copyright © 2022 Vaxry
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+ This protocol allows clients to ask for exporting another toplevel's
+ surface(s) to a buffer.
+
+ Particularly useful for sharing a single window.
+
+
+
+
+ This object is a manager which offers requests to start capturing from a
+ source.
+
+
+
+
+ Capture the next frame of a toplevel. (window)
+
+ The captured frame will not contain any server-side decorations and will
+ ignore the compositor-set geometry, like e.g. rounded corners.
+
+ It will contain all the subsurfaces and popups, however the latter will be clipped
+ to the geometry of the base surface.
+
+ The handle parameter refers to the address of the window as seen in `hyprctl clients`.
+ For example, for d161e7b0 it would be 3512854448.
+
+
+
+
+
+
+
+
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+
+
+
+
+
+
+ Same as capture_toplevel, but with a zwlr_foreign_toplevel_handle_v1 handle.
+
+
+
+
+
+
+
+
+
+
+ This object represents a single frame.
+
+ When created, a series of buffer events will be sent, each representing a
+ supported buffer type. The "buffer_done" event is sent afterwards to
+ indicate that all supported buffer types have been enumerated. The client
+ will then be able to send a "copy" request. If the capture is successful,
+ the compositor will send a "flags" followed by a "ready" event.
+
+ wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent.
+
+ If the capture failed, the "failed" event is sent. This can happen anytime
+ before the "ready" event.
+
+ Once either a "ready" or a "failed" event is received, the client should
+ destroy the frame.
+
+
+
+
+ Provides information about wl_shm buffer parameters that need to be
+ used for this frame. This event is sent once after the frame is created
+ if wl_shm buffers are supported.
+
+
+
+
+
+
+
+
+
+ Copy the frame to the supplied buffer. The buffer must have the
+ correct size, see hyprland_toplevel_export_frame_v1.buffer and
+ hyprland_toplevel_export_frame_v1.linux_dmabuf. The buffer needs to have a
+ supported format.
+
+ If the frame is successfully copied, a "flags" and a "ready" event is
+ sent. Otherwise, a "failed" event is sent.
+
+ This event will wait for appropriate damage to be copied, unless the ignore_damage
+ arg is set to a non-zero value.
+
+
+
+
+
+
+
+ This event is sent right before the ready event when ignore_damage was
+ not set. It may be generated multiple times for each copy
+ request.
+
+ The arguments describe a box around an area that has changed since the
+ last copy request that was derived from the current screencopy manager
+ instance.
+
+ The union of all regions received between the call to copy
+ and a ready event is the total damage since the prior ready event.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Provides flags about the frame. This event is sent once before the
+ "ready" event.
+
+
+
+
+
+
+ Called as soon as the frame is copied, indicating it is available
+ for reading. This event includes the time at which presentation happened
+ at.
+
+ The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
+ each component being an unsigned 32-bit value. Whole seconds are in
+ tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
+ and the additional fractional part in tv_nsec as nanoseconds. Hence,
+ for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
+ may have an arbitrary offset at start.
+
+ After receiving this event, the client should destroy the object.
+
+
+
+
+
+
+
+
+ This event indicates that the attempted frame copy has failed.
+
+ After receiving this event, the client should destroy the object.
+
+
+
+
+
+ Destroys the frame. This request can be sent at any time by the client.
+
+
+
+
+
+ Provides information about linux-dmabuf buffer parameters that need to
+ be used for this frame. This event is sent once after the frame is
+ created if linux-dmabuf buffers are supported.
+
+
+
+
+
+
+
+
+ This event is sent once after all buffer events have been sent.
+
+ The client should proceed to create a buffer of one of the supported
+ types, and send a "copy" request.
+
+
+
+
diff --git a/src/capture.rs b/src/capture.rs
index 5be796e..2d05ec4 100644
--- a/src/capture.rs
+++ b/src/capture.rs
@@ -314,60 +314,51 @@ impl AppState {
}
fn run_capture_all(self, opts: &CaptureOptions) -> Result {
+ // Hyprland IPC path
if let Ok(mons) = crate::hyprland::monitors() {
if !mons.is_empty() {
let logical_total_w = mons
.iter()
.map(|m| m.x + (m.width as f64 / m.scale).round() as i64)
.max()
- .unwrap_or(0) as u32;
+ .unwrap_or(0)
+ .max(1) as u32;
let logical_total_h = mons
.iter()
.map(|m| m.y + (m.height as f64 / m.scale).round() as i64)
.max()
- .unwrap_or(0) as u32;
+ .unwrap_or(0)
+ .max(1) as u32;
+
+ // Capture all monitors in parallel — each thread opens its own
+ // Wayland connection. This turns N sequential captures (each
+ // gated on a compositor roundtrip) into N concurrent ones.
+ let captures: Vec>> =
+ std::thread::scope(|s| {
+ let handles: Vec<_> = mons
+ .iter()
+ .map(|mon| {
+ s.spawn(move || {
+ let lw = (mon.width as f64 / mon.scale).round() as u32;
+ let lh = (mon.height as f64 / mon.scale).round() as u32;
+ capture_and_resize(&mon.name, opts, lw, lh, false)
+ })
+ })
+ .collect();
+ handles.into_iter().map(|h| h.join().unwrap()).collect()
+ });
+
let mut canvas = image::RgbaImage::new(logical_total_w, logical_total_h);
- for mon in &mons {
- let state = AppState::connect()?;
- let out = state
- .outputs
- .iter()
- .find(|o| o.name == mon.name)
- .map(|o| o.handle.clone());
- let out = match out {
- Some(o) => o,
- None => continue,
- };
- let overlay = if opts.include_cursor { 1 } else { 0 };
- let mut state = state;
- let frame = state.screencopy_mgr.as_ref().unwrap().capture_output(
- overlay,
- &out,
- &state.qh,
- (),
- );
- state.frame = Some(frame);
- state.frame_state = FrameState::Negotiating;
- state.dispatch_to_ready()?;
- let mon_img = state.read_pixels()?;
- let logical_w = (mon.width as f64 / mon.scale).round() as u32;
- let logical_h = (mon.height as f64 / mon.scale).round() as u32;
- let scaled = mon_img.resize_exact(
- logical_w,
- logical_h,
- image::imageops::FilterType::Lanczos3,
- );
- image::imageops::overlay(
- &mut canvas,
- &scaled.to_rgba8(),
- mon.x as i64,
- mon.y as i64,
- );
+ for (mon, c) in mons.iter().zip(captures.into_iter()) {
+ if let Some(img) = c? {
+ image::imageops::overlay(&mut canvas, &img, mon.x as i64, mon.y as i64);
+ }
}
return Ok(image::DynamicImage::ImageRgba8(canvas));
}
}
+ // Fallback: Wayland xdg_output info from the current connection.
if self.outputs.is_empty() {
return Err(BlastError::Capture("no monitors found".into()));
}
@@ -385,7 +376,6 @@ impl AppState {
.max()
.unwrap_or(0)
.max(1) as u32;
- let mut canvas = image::RgbaImage::new(logical_total_w, logical_total_h);
let out_info: Vec<(String, i32, i32, i32, i32)> = self
.outputs
@@ -400,35 +390,26 @@ impl AppState {
)
})
.collect();
+ // Close this connection before spawning per-thread connections.
+ drop(self);
- for (name, lx, ly, lw, lh) in &out_info {
- let state = AppState::connect()?;
- let out = state
- .outputs
+ let captures: Vec>> = std::thread::scope(|s| {
+ let handles: Vec<_> = out_info
.iter()
- .find(|o| &o.name == name)
- .or_else(|| state.outputs.first())
- .map(|o| o.handle.clone());
- let out = match out {
- Some(o) => o,
- None => continue,
- };
- let overlay = if opts.include_cursor { 1 } else { 0 };
- let mut state = state;
- let frame =
- state
- .screencopy_mgr
- .as_ref()
- .unwrap()
- .capture_output(overlay, &out, &state.qh, ());
- state.frame = Some(frame);
- state.frame_state = FrameState::Negotiating;
- state.dispatch_to_ready()?;
- let mon_img = state.read_pixels()?;
- let lw = (*lw as u32).max(1);
- let lh = (*lh as u32).max(1);
- let scaled = mon_img.resize_exact(lw, lh, image::imageops::FilterType::Lanczos3);
- image::imageops::overlay(&mut canvas, &scaled.to_rgba8(), *lx as i64, *ly as i64);
+ .map(|(name, _lx, _ly, lw, lh)| {
+ let lw = (*lw as u32).max(1);
+ let lh = (*lh as u32).max(1);
+ s.spawn(move || capture_and_resize(name, opts, lw, lh, true))
+ })
+ .collect();
+ handles.into_iter().map(|h| h.join().unwrap()).collect()
+ });
+
+ let mut canvas = image::RgbaImage::new(logical_total_w, logical_total_h);
+ for ((_, lx, ly, _, _), c) in out_info.iter().zip(captures.into_iter()) {
+ if let Some(img) = c? {
+ image::imageops::overlay(&mut canvas, &img, *lx as i64, *ly as i64);
+ }
}
Ok(image::DynamicImage::ImageRgba8(canvas))
@@ -636,92 +617,22 @@ impl AppState {
.map_err(|e| BlastError::Capture(format!("shm read: {e}")))?;
let (width, height, stride) = (offer.width, offer.height, offer.stride);
- let mut rgba: Vec = Vec::with_capacity((width * height * 4) as usize);
- // All formats below are packed 32-bit little-endian values.
- // The DRM name describes channel order from MSB to LSB, so in memory
- // (little-endian) the channel order is reversed.
- // e.g. ARGB8888 = [31:0] A:R:G:B -> memory bytes [B, G, R, A]
- // ABGR8888 = [31:0] A:B:G:R -> memory bytes [R, G, B, A]
- match offer.format {
- wl_shm::Format::Argb8888 => {
- // memory: [B, G, R, A]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base + 2]); // R
- rgba.push(raw[base + 1]); // G
- rgba.push(raw[base]); // B
- rgba.push(raw[base + 3]); // A
- }
- }
- }
- wl_shm::Format::Xrgb8888 => {
- // memory: [B, G, R, X]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base + 2]); // R
- rgba.push(raw[base + 1]); // G
- rgba.push(raw[base]); // B
- rgba.push(255); // A
- }
- }
- }
- wl_shm::Format::Abgr8888 => {
- // memory: [R, G, B, A]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base]); // R
- rgba.push(raw[base + 1]); // G
- rgba.push(raw[base + 2]); // B
- rgba.push(raw[base + 3]); // A
- }
- }
- }
- wl_shm::Format::Xbgr8888 => {
- // memory: [R, G, B, X]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base]); // R
- rgba.push(raw[base + 1]); // G
- rgba.push(raw[base + 2]); // B
- rgba.push(255); // A
- }
- }
- }
- wl_shm::Format::Rgba8888 => {
- // memory: [A, B, G, R]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base + 3]); // R
- rgba.push(raw[base + 2]); // G
- rgba.push(raw[base + 1]); // B
- rgba.push(raw[base]); // A
- }
- }
- }
- wl_shm::Format::Bgra8888 => {
- // memory: [A, R, G, B]
- for row in 0..height {
- for col in 0..width {
- let base = (row * stride + col * 4) as usize;
- rgba.push(raw[base + 1]); // R
- rgba.push(raw[base + 2]); // G
- rgba.push(raw[base + 3]); // B
- rgba.push(raw[base]); // A
- }
- }
- }
+ // DRM name describes channels MSB->LSB, so little-endian memory order is reversed.
+ // ARGB8888 mem = [B, G, R, A], XBGR8888 mem = [R, G, B, X], etc.
+ let rgba = match offer.format {
+ wl_shm::Format::Argb8888 => swizzle8::<2, 1, 0, 3>(&raw, width, height, stride),
+ wl_shm::Format::Xrgb8888 => swizzle8::<2, 1, 0, 4>(&raw, width, height, stride),
+ wl_shm::Format::Abgr8888 => swizzle8::<0, 1, 2, 3>(&raw, width, height, stride),
+ wl_shm::Format::Xbgr8888 => swizzle8::<0, 1, 2, 4>(&raw, width, height, stride),
+ wl_shm::Format::Rgba8888 => swizzle8::<3, 2, 1, 0>(&raw, width, height, stride),
+ wl_shm::Format::Bgra8888 => swizzle8::<1, 2, 3, 0>(&raw, width, height, stride),
other => {
return Err(BlastError::Capture(format!(
"unhandled pixel format {other:?} ->>> please report this"
)));
}
- }
+ };
let img: ImageBuffer, Vec> = ImageBuffer::from_raw(width, height, rgba)
.ok_or_else(|| BlastError::Capture("image buffer construction failed".into()))?;
@@ -729,6 +640,61 @@ impl AppState {
}
}
+// Multi-monitor capture helpers
+
+/// Capture a single output by name and resize the result to (target_w, target_h),
+/// skipping the resize entirely when dimensions already match. Triangle filter
+/// is used when a resize is needed — it's ~10x faster than Lanczos3 and the
+/// quality difference is invisible on screenshots.
+///
+/// `fallback_to_first` mirrors a quirk of the non-Hyprland code path: if the
+/// named output isn't found, use the first available output instead. The
+/// Hyprland path returns `Ok(None)` instead (silent skip).
+fn capture_and_resize(
+ name: &str,
+ opts: &CaptureOptions,
+ target_w: u32,
+ target_h: u32,
+ fallback_to_first: bool,
+) -> Result