diff --git a/Cargo.lock b/Cargo.lock index a90294c..b48bacd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,62 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "arrayref" version = "0.3.9" @@ -40,7 +96,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -62,18 +118,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -94,12 +150,22 @@ version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "bread-screenshots" +version = "0.3.1" +source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev#27b3b17c58b81f947a40cc0d3f9cd0862e7885ff" +dependencies = [ + "anyhow", + "bread-utils", + "tracing", +] + [[package]] name = "bread-shared" version = "0.7.0" source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b" dependencies = [ - "dirs", + "dirs 5.0.1", "serde", "serde_json", "toml 0.8.23", @@ -108,9 +174,9 @@ dependencies = [ [[package]] name = "bread-shared" version = "0.7.0" -source = "git+https://git.breadway.dev/Breadway/bread?branch=dev#8a794c03bfc7f2f294ff89d92494ad324afc30f5" +source = "git+https://git.breadway.dev/Breadway/bread?branch=dev#34854f147135143ca4baf1d628e8c609e2c1cc8c" dependencies = [ - "dirs", + "dirs 6.0.0", "serde", "serde_json", "toml 0.8.23", @@ -119,9 +185,9 @@ dependencies = [ [[package]] name = "bread-theme" version = "0.3.1" -source = "git+https://github.com/Breadway/bread-ecosystem?branch=dev#77bca8a1cf90d7fd38745270d2deb291b8b22bfe" +source = "git+https://github.com/Breadway/bread-ecosystem?branch=dev#686af0d3dc93bf8039d62fbbd1e04a8b2db90154" dependencies = [ - "dirs", + "dirs 5.0.1", "gtk4", "serde", "serde_json", @@ -130,10 +196,10 @@ dependencies = [ [[package]] name = "bread-utils" version = "0.3.1" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev#77bca8a1cf90d7fd38745270d2deb291b8b22bfe" +source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev#27b3b17c58b81f947a40cc0d3f9cd0862e7885ff" dependencies = [ "bread-shared 0.7.0 (git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0)", - "dirs", + "dirs 5.0.1", "serde", "serde_json", ] @@ -142,9 +208,12 @@ dependencies = [ name = "breadbar" version = "0.3.0" dependencies = [ + "anyhow", + "bread-screenshots", "bread-shared 0.7.0 (git+https://git.breadway.dev/Breadway/bread?branch=dev)", "bread-theme", "bread-utils", + "clap", "futures-lite", "gtk4", "gtk4-layer-shell", @@ -165,9 +234,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.25.1" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "bytes" @@ -215,14 +284,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "clap" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ - "crossbeam-utils", + "clap_builder", + "clap_derive", ] +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "convert_case" version = "0.10.0" @@ -241,12 +347,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - [[package]] name = "data-url" version = "0.3.2" @@ -272,7 +372,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -282,7 +382,16 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", ] [[package]] @@ -293,15 +402,27 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] [[package]] -name = "either" -version = "1.16.0" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "endi" @@ -327,7 +448,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -357,11 +478,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -378,11 +498,11 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", ] [[package]] @@ -510,7 +630,7 @@ checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -611,20 +731,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -632,8 +738,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", + "wasm-bindgen", ] [[package]] @@ -716,7 +824,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -850,7 +958,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -916,7 +1024,7 @@ checksum = "31157e6ccefbad4b0cd7e549db6696691a70c11b108f26bf6bf76eef26af8c10" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -935,6 +1043,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -972,9 +1086,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libredox" @@ -1057,6 +1171,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "option-ext" version = "0.2.0" @@ -1159,28 +1279,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -1195,7 +1309,18 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.19", ] [[package]] @@ -1229,7 +1354,7 @@ checksum = "36c9dbf50a60c82375e66b61d522c936b187a11b25c0a42e91c516326ad24a4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1306,9 +1431,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1316,29 +1441,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -1349,13 +1474,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1447,6 +1572,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "svgtypes" version = "0.16.1" @@ -1468,6 +1599,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "system-deps" version = "7.0.8" @@ -1477,7 +1619,7 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "version-compare", ] @@ -1506,7 +1648,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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", ] [[package]] @@ -1517,7 +1668,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] @@ -1548,9 +1710,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -1571,7 +1733,7 @@ checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1588,9 +1750,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.3+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -1647,9 +1809,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow 1.0.4", ] @@ -1685,7 +1847,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1748,6 +1910,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.24.0" @@ -1771,15 +1939,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -1812,7 +1971,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1924,12 +2083,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "xml-rs" version = "0.8.28" @@ -1981,7 +2134,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "zbus_names", "zvariant", "zvariant_utils", @@ -2027,7 +2180,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "zvariant_utils", ] @@ -2040,6 +2193,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.119", "winnow 1.0.4", ] diff --git a/Cargo.toml b/Cargo.toml index ad660fe..23acc2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", branch = "d # instead of hand-parsing serde_json::Value. See src/widgets/. bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "dev", features = ["bread-client"] } bread-shared = { git = "https://git.breadway.dev/Breadway/bread", branch = "dev" } +# Capture primitives for `--screenshot` mode — see src/screenshot.rs. +bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "dev" } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" relm4 = { version = "0.11", features = ["macros"] } @@ -26,6 +28,8 @@ zbus = { version = "5", default-features = false, features = ["tokio"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "process", "signal", "sync"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +clap = { version = "4", features = ["derive"] } +anyhow = "1" # Pure-Rust SVG rasteriser (default features off → no text/font deps; the icons # are vector-only). Needed because librsvg dropped its gdk-pixbuf SVG loader. resvg = { version = "0.47", default-features = false } diff --git a/src/main.rs b/src/main.rs index 4bdb45e..6ea7ded 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ macro_rules! asset { mod bar; mod notifications; mod osd; +mod screenshot; mod theme; mod widgets; @@ -124,7 +125,7 @@ pub enum AppInput { #[relm4::component(pub)] impl SimpleComponent for App { - type Init = (); + type Init = Option; type Input = AppInput; type Output = (); @@ -141,7 +142,7 @@ impl SimpleComponent for App { } fn init( - _: Self::Init, + screenshot_req: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { @@ -644,6 +645,17 @@ impl SimpleComponent for App { widgets.center_box.set_center_widget(Some(¢er_area)); widgets.center_box.set_end_widget(Some(&stats_box)); + // Captured before these move into `model` (or are otherwise dropped + // as bare locals, never stored on `App` at all) — needed by the + // screenshot dispatch just before this function returns. + let control_popover_for_screenshot = control_popover.clone(); + let connectivity_popover_for_screenshot = connectivity_popover.clone(); + let wifi_tab_btn_for_screenshot = wifi_tab_btn.clone(); + let bt_tab_btn_for_screenshot = bt_tab_btn.clone(); + let media_popover_for_screenshot = media_popover.clone(); + let media_widget_for_screenshot = media_widget.clone(); + let media_track_lbl_for_screenshot = media_track_lbl.clone(); + let model = App { workspaces: vec![], active_ws: 1, @@ -710,8 +722,44 @@ impl SimpleComponent for App { bar::wifi::spawn_status_poller(sender.clone()); bar::media::spawn_poller(sender.clone()); widgets::client::spawn(sender.clone()); - notifications::spawn(); - osd::spawn(); + + // Screenshot mode primes these with sample content instead of the + // real D-Bus/pactl/backlight sources — see notifications::SampleKind + // and osd::SampleKind's doc comments. + let notif_sample = screenshot_req.as_ref().and_then(|r| match r.view.as_str() { + "notification" => Some(notifications::SampleKind::Normal), + "notification-critical" => Some(notifications::SampleKind::Critical), + _ => None, + }); + let notification_window = notifications::spawn(notif_sample); + let osd_sample = screenshot_req.as_ref().and_then(|r| match r.view.as_str() { + "osd-volume" => Some(osd::SampleKind::Volume), + "osd-brightness" => Some(osd::SampleKind::Brightness), + _ => None, + }); + let osd_window = osd::spawn(osd_sample); + + if let Some(req) = screenshot_req { + let notification_window = matches!(req.view.as_str(), "notification" | "notification-critical") + .then_some(notification_window); + let osd_window = matches!(req.view.as_str(), "osd-volume" | "osd-brightness") + .then_some(osd_window); + screenshot::dispatch( + &root, + req, + screenshot::Handles { + control_popover: control_popover_for_screenshot, + connectivity_popover: connectivity_popover_for_screenshot, + wifi_tab_btn: wifi_tab_btn_for_screenshot, + bt_tab_btn: bt_tab_btn_for_screenshot, + media_popover: media_popover_for_screenshot, + media_widget: media_widget_for_screenshot, + media_track_lbl: media_track_lbl_for_screenshot, + notification_window, + osd_window, + }, + ); + } ComponentParts { model, widgets } } @@ -1136,7 +1184,7 @@ impl App { if saved { bar::wifi::spawn_join(ssid_clone.clone()); } else { - show_add_network_dialog(btn, ssid_clone.clone()); + show_add_network_dialog(btn, ssid_clone.clone(), |_| {}); } close_parent_popover(btn); }); @@ -1293,8 +1341,11 @@ fn close_parent_popover(widget: >k4::Button) { } /// Small modal prompting for a password, then saves + joins the network via -/// `breadcrumbs add` + `breadcrumbs join`. -fn show_add_network_dialog(anchor: >k4::Button, ssid: String) { +/// `breadcrumbs add` + `breadcrumbs join`. `on_build` runs on the freshly +/// built dialog *before* it's presented — screenshot mode's only hook point, +/// since `connect_map` registered any later would miss a map that already +/// happened. The real call site passes a no-op. +fn show_add_network_dialog(anchor: &impl IsA, ssid: String, on_build: impl FnOnce(>k4::Window)) { let dialog = gtk4::Window::new(); dialog.set_title(Some(&format!("Add “{ssid}”"))); dialog.set_resizable(false); @@ -1358,6 +1409,7 @@ fn show_add_network_dialog(anchor: >k4::Button, ssid: String) { dialog_for_activate.close(); }); + on_build(&dialog); dialog.present(); entry.grab_focus(); } @@ -1411,6 +1463,10 @@ fn stat_label() -> gtk4::Label { } fn main() { + use clap::Parser; + let cli = screenshot::Cli::parse(); + let screenshot_req = cli.screenshot_request(); + relm4::spawn(async { use tokio::signal::unix::{signal, SignalKind}; let mut stream = signal(SignalKind::hangup()).expect("SIGHUP handler"); @@ -1420,6 +1476,17 @@ fn main() { } }); - let app = RelmApp::new("sh.breadway.breadbar"); - app.run::(()); + // `with_args(vec![])` stops relm4 from handing our own --screenshot/ + // --output flags to GLib's option parser (`app.run()`'s default), which + // would otherwise reject them as unrecognized before Cli::parse() above + // ever sees argv. allow_multiple_instances is needed for screenshot runs + // specifically: GApplication is single-instance by default, and a normal + // breadbar is typically already running, so without this a screenshot + // invocation would just activate that existing instance instead of + // starting a fresh one whose `init()` receives the request at all. + let app = RelmApp::new("sh.breadway.breadbar").with_args(vec![]); + if screenshot_req.is_some() { + app.allow_multiple_instances(true); + } + app.run::(screenshot_req); } diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs index b1f1760..0c25c0d 100644 --- a/src/notifications/mod.rs +++ b/src/notifications/mod.rs @@ -172,38 +172,90 @@ impl NotifServer { } } -pub fn spawn() { - let (tx, rx) = mpsc::channel(32); - let (conn_tx, conn_rx) = tokio::sync::oneshot::channel(); +/// A fixed sample notification for `--screenshot notification`/ +/// `notification-critical` — substitutes for a real `Notify` D-Bus call so a +/// capture doesn't depend on some external sender firing one at just the +/// right moment. +pub enum SampleKind { + Normal, + Critical, +} - relm4::spawn(async move { - let server = NotifServer { - tx, - next_id: AtomicU32::new(1), - sync_tags: Mutex::new(HashMap::new()), +impl SampleKind { + fn sample_event(&self) -> NotifEvent { + let urgency = match self { + SampleKind::Normal => Urgency::Normal, + SampleKind::Critical => Urgency::Critical, }; - // Builder failures here would only occur with invalid static strings — safe to unwrap. - let conn = zbus::connection::Builder::session() - .unwrap() - .name("org.freedesktop.Notifications") - .unwrap() - .serve_at("/org/freedesktop/Notifications", server) - .unwrap() - .build() - .await - .expect("failed to claim org.freedesktop.Notifications on D-Bus session bus"); - // Hand the connection to popup::run so it can emit `NotificationClosed` - // (spec-mandated whenever a notification actually goes away) — the - // dismiss decisions all happen over there, not in this interface impl. - let _ = conn_tx.send(conn); - std::future::pending::<()>().await - }); - - relm4::spawn_local(async move { - if let Ok(conn) = conn_rx.await { - popup::run(rx, conn).await; + NotifEvent::Show { + id: 1, + app_name: "Sample App".into(), + summary: "Sample notification".into(), + body: "This is what a notification card looks like.".into(), + urgency, + expire: Expire::Never, } - }); + } +} + +/// Builds the notification window synchronously (see +/// `popup::build_window`'s doc comment) and spawns the event loop that +/// shows/updates/hides it. +/// +/// `sample`: `Some` skips real D-Bus registration entirely and seeds the +/// loop with one fixed sample event instead — screenshot mode only. Doing +/// the real `org.freedesktop.Notifications` registration in every +/// screenshot run would race the real breadbar (if running) for the same +/// well-known name for no benefit, since nothing needs to reach this +/// instance externally. +pub fn spawn(sample: Option) -> gtk4::Window { + let (window, cards_box) = popup::build_window(); + let (tx, rx) = mpsc::channel(32); + + match sample { + Some(kind) => { + let _ = tx.try_send(kind.sample_event()); + let window_for_loop = window.clone(); + relm4::spawn_local(async move { + popup::run(window_for_loop, cards_box, rx, None).await; + }); + } + None => { + let (conn_tx, conn_rx) = tokio::sync::oneshot::channel(); + + relm4::spawn(async move { + let server = NotifServer { + tx, + next_id: AtomicU32::new(1), + sync_tags: Mutex::new(HashMap::new()), + }; + // Builder failures here would only occur with invalid static strings — safe to unwrap. + let conn = zbus::connection::Builder::session() + .unwrap() + .name("org.freedesktop.Notifications") + .unwrap() + .serve_at("/org/freedesktop/Notifications", server) + .unwrap() + .build() + .await + .expect("failed to claim org.freedesktop.Notifications on D-Bus session bus"); + // Hand the connection to popup::run so it can emit `NotificationClosed` + // (spec-mandated whenever a notification actually goes away) — the + // dismiss decisions all happen over there, not in this interface impl. + let _ = conn_tx.send(conn); + std::future::pending::<()>().await + }); + + let window_for_loop = window.clone(); + relm4::spawn_local(async move { + if let Ok(conn) = conn_rx.await { + popup::run(window_for_loop, cards_box, rx, Some(conn)).await; + } + }); + } + } + + window } #[cfg(test)] diff --git a/src/notifications/popup.rs b/src/notifications/popup.rs index 69d1f6c..91adec0 100644 --- a/src/notifications/popup.rs +++ b/src/notifications/popup.rs @@ -23,7 +23,11 @@ mod close_reason { pub const CLOSE_NOTIFICATION_CALL: u32 = 3; } -pub async fn run(mut rx: Receiver, conn: zbus::Connection) { +/// Builds the notification window synchronously — so a caller (screenshot +/// mode in particular) has a real window to hook `connect_map` on before +/// `run`'s event loop, which needs an async `zbus::Connection` handshake in +/// the real path, ever starts. +pub fn build_window() -> (gtk4::Window, gtk4::Box) { let window = create_window(); let cards_box = gtk4::Box::new(gtk4::Orientation::Vertical, 4); cards_box.set_margin_top(8); @@ -31,7 +35,21 @@ pub async fn run(mut rx: Receiver, conn: zbus::Connection) { cards_box.set_margin_start(8); cards_box.set_margin_end(8); window.set_child(Some(&cards_box)); + (window, cards_box) +} +/// `conn`: `None` in screenshot mode, which skips real D-Bus registration +/// entirely (see `super::spawn`) — there's no external client that needs to +/// reach a screenshot-only instance, and registering the well-known name +/// would just race the real breadbar for it. `NotificationClosed` is a +/// spec-mandated signal for real clients only, so it's simply not emitted +/// when there's no real connection to emit it on. +pub async fn run( + window: gtk4::Window, + cards_box: gtk4::Box, + mut rx: Receiver, + conn: Option, +) { let cards: Cards = Rc::new(RefCell::new(HashMap::new())); let generations: Generations = Rc::new(RefCell::new(HashMap::new())); @@ -109,8 +127,10 @@ fn dismiss(cards_box: >k4::Box, window: >k4::Window, cards: &Cards, id: u32) /// Emits the spec-mandated `NotificationClosed(id, reason)` signal. Sent /// directly over the connection rather than through the zbus interface /// macro's generated helper, since the dismiss decision happens here in the -/// popup task, not inside `NotifServer`'s own method bodies. -async fn emit_closed(conn: &zbus::Connection, id: u32, reason: u32) { +/// popup task, not inside `NotifServer`'s own method bodies. No-op when +/// `conn` is `None` (screenshot mode — see `run`'s doc comment). +async fn emit_closed(conn: &Option, id: u32, reason: u32) { + let Some(conn) = conn else { return }; let result = conn .emit_signal( None::<&str>, diff --git a/src/osd.rs b/src/osd.rs index d7520fc..5deb1d4 100644 --- a/src/osd.rs +++ b/src/osd.rs @@ -9,14 +9,50 @@ enum OsdEvent { Brightness { pct: u8 }, } -pub fn spawn() { +/// A fixed sample event for `--screenshot osd-volume`/`osd-brightness` — +/// substitutes for the real `pactl subscribe`/backlight-sysfs watchers so a +/// capture doesn't depend on this machine's actual volume/brightness at +/// capture time. +pub enum SampleKind { + Volume, + Brightness, +} + +impl SampleKind { + fn sample_event(&self) -> OsdEvent { + match self { + SampleKind::Volume => OsdEvent::Volume { pct: 65, muted: false }, + SampleKind::Brightness => OsdEvent::Brightness { pct: 80 }, + } + } +} + +/// Builds the OSD window synchronously (so a caller — screenshot mode, via +/// `sample`, in particular — has a real window to hook `connect_map` on +/// before the async event loop below ever runs) and spawns the event loop +/// that shows/updates/hides it. +/// +/// `sample`: `Some` skips the real volume/brightness watchers entirely and +/// seeds the loop with one fixed sample event instead — screenshot mode +/// only, so a capture never depends on (or is disrupted by) this machine's +/// actual audio/backlight state. +pub fn spawn(sample: Option) -> gtk4::Window { let (tx, rx) = mpsc::channel::(8); - let tx1 = tx.clone(); - std::thread::spawn(move || volume_watcher(tx1)); - std::thread::spawn(move || brightness_watcher(tx)); + match sample { + Some(kind) => { + let _ = tx.try_send(kind.sample_event()); + } + None => { + let tx1 = tx.clone(); + std::thread::spawn(move || volume_watcher(tx1)); + std::thread::spawn(move || brightness_watcher(tx)); + } + } - relm4::spawn_local(run_osd(rx)); + let window = create_window(); + relm4::spawn_local(run_osd(window.clone(), rx)); + window } fn volume_watcher(tx: mpsc::Sender) { @@ -119,9 +155,7 @@ fn brightness_watcher(tx: mpsc::Sender) { } } -async fn run_osd(mut rx: mpsc::Receiver) { - let window = create_window(); - +async fn run_osd(window: gtk4::Window, mut rx: mpsc::Receiver) { let container = gtk4::Box::new(gtk4::Orientation::Horizontal, 0); container.set_margin_top(10); container.set_margin_bottom(10); diff --git a/src/screenshot.rs b/src/screenshot.rs new file mode 100644 index 0000000..8ccdd87 --- /dev/null +++ b/src/screenshot.rs @@ -0,0 +1,244 @@ +//! `--screenshot` CLI mode: render a specific view, capture it via +//! `bread-screenshots`, then exit — driven by `bread-ecosystem`'s +//! `bread-capture` orchestrator, or run standalone for one-off captures. +//! +//! Capture waits on GTK's `map` signal rather than a blind sleep before +//! grabbing pixels — the surface (or, for popover views, the popover itself) +//! genuinely isn't on screen yet before that fires, so a fixed delay would +//! either race a slow first paint or pad every fast one for nothing. +//! +//! breadbar is "a bar + the notification daemon + the OSD" (see its own +//! module docs), so its screenshot views span three separate top-level +//! surfaces, not just the bar: the bar itself and its popovers (this +//! module, anchored off `root`), plus the standalone notification and OSD +//! windows (`notifications::spawn`/`osd::spawn`, built and primed with +//! sample data by `main.rs` before `dispatch` runs — see [`Handles`]). + +use clap::Parser; +use gtk4::prelude::*; +use std::path::PathBuf; +use std::time::Duration; + +/// Extra settle time after `map` for the first frame to actually paint +/// before grim runs — `map` fires once the surface exists, not once +/// anything has been drawn into it. +const SETTLE_DELAY: Duration = Duration::from_millis(300); + +/// Settle time for views whose content depends on `bar::stats::spawn_poller`'s +/// 2-second background loop (control-panel's CPU/RAM/PWR/GPU/network labels, +/// gated on popover visibility) or a similar live-data popover load +/// (connectivity's wifi/bluetooth scan) — capturing any sooner leaves +/// placeholder dashes/"Scanning…" instead of real content. +const LIVE_DATA_SETTLE_DELAY: Duration = Duration::from_millis(2_200); + +/// Delay between the bar's own `map` and calling `popover.popup()`. Calling +/// `popup()` synchronously from inside the root window's `map` handler +/// produces a popover that reports itself `map`ped but never actually paints +/// (confirmed by an independent `grim` capture taken mid-sequence, showing no +/// popover at all) — presumably the parent widget's own allocation isn't +/// settled yet at that exact point. Giving the initial layout pass a beat to +/// finish first is what makes it actually render. +const PRE_POPUP_DELAY: Duration = Duration::from_millis(300); + +const KNOWN_VIEWS: &[&str] = &[ + "bar", + "control-panel", + "connectivity-wifi", + "connectivity-bluetooth", + "media-popover", + "notification", + "notification-critical", + "osd-volume", + "osd-brightness", + "wifi-add-dialog", +]; + +#[derive(Parser)] +#[command(name = "breadbar")] +pub struct Cli { + /// Render the named view, capture it, then exit instead of running + /// normally. See `screenshot::KNOWN_VIEWS` for the full list. + #[arg(long)] + pub screenshot: Option, + + /// PNG path to write the capture to. Required together with --screenshot. + #[arg(long)] + pub output: Option, + + /// Capture canvas width — matches the isolated compositor's output width + /// (`bread-capture --isolate-width`) so the geometry passed to `grim` + /// doesn't depend on querying anything at capture time. + #[arg(long, default_value_t = 1920)] + pub width: u32, + + /// Capture canvas height — see `width`. + #[arg(long, default_value_t = 1080)] + pub height: u32, +} + +pub struct ScreenshotRequest { + pub view: String, + pub output: PathBuf, + pub width: u32, + pub height: u32, +} + +impl Cli { + /// `None` for a normal run. Exits the process with an error if + /// `--screenshot` was given without `--output`, before any GTK/relm4 + /// setup happens. + pub fn screenshot_request(&self) -> Option { + let view = self.screenshot.clone()?; + let Some(output) = self.output.clone() else { + eprintln!("breadbar: --screenshot requires --output"); + std::process::exit(1); + }; + Some(ScreenshotRequest { view, output, width: self.width, height: self.height }) + } +} + +/// Every widget/window `dispatch` might need, gathered by `main.rs`'s +/// `init()` — most of these are plain locals there that never otherwise +/// outlive `init()` (never stored on `App`), so they have to be cloned out +/// before dispatch time same as `control_popover` always was. +pub struct Handles { + pub control_popover: gtk4::Popover, + pub connectivity_popover: gtk4::Popover, + pub wifi_tab_btn: gtk4::ToggleButton, + pub bt_tab_btn: gtk4::ToggleButton, + pub media_popover: gtk4::Popover, + pub media_widget: gtk4::Box, + pub media_track_lbl: gtk4::Label, + /// Already built and primed with sample content by `main.rs` (via + /// `notifications::spawn(Some(kind))`) when `req.view` calls for it — + /// `None` otherwise. + pub notification_window: Option, + /// Same deal as `notification_window`, via `osd::spawn(Some(kind))`. + pub osd_window: Option, +} + +/// The bar's fixed height — matches `root.set_exclusive_zone(32)` / +/// `set_default_height: 32` in `main.rs`. Unlike the other views' full +/// canvas, this never varies with `--width`/`--height`. +const BAR_HEIGHT: i32 = 32; + +pub fn dispatch(root: >k4::ApplicationWindow, req: ScreenshotRequest, handles: Handles) { + let output = req.output; + let (width, height) = (req.width as i32, req.height as i32); + + match req.view.as_str() { + "bar" => { + root.connect_map(move |_| { + let output = output.clone(); + gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || { + finish(bread_screenshots::capture_region(0, 0, width, BAR_HEIGHT, &output)); + }); + }); + } + "control-panel" => { + open_popover_on_root_map(root, handles.control_popover, LIVE_DATA_SETTLE_DELAY, output, width, height); + } + "connectivity-wifi" => { + handles.wifi_tab_btn.set_active(true); + open_popover_on_root_map(root, handles.connectivity_popover, LIVE_DATA_SETTLE_DELAY, output, width, height); + } + "connectivity-bluetooth" => { + handles.bt_tab_btn.set_active(true); + open_popover_on_root_map(root, handles.connectivity_popover, LIVE_DATA_SETTLE_DELAY, output, width, height); + } + "media-popover" => { + // Real media state only shows the widget/text when something's + // actually playing (see AppInput::MediaUpdate) — an automated + // run has nothing playing, so fake enough of it directly on the + // widgets to get a representative capture. + handles.media_widget.set_visible(true); + handles.media_track_lbl.set_text("Sample Track — Sample Artist"); + open_popover_on_root_map(root, handles.media_popover, SETTLE_DELAY, output, width, height); + } + "notification" | "notification-critical" => { + let Some(window) = handles.notification_window else { + eprintln!("breadbar: internal error — no notification window built for '{}'", req.view); + std::process::exit(1); + }; + capture_standalone_window(window, output, width, height); + } + "osd-volume" | "osd-brightness" => { + let Some(window) = handles.osd_window else { + eprintln!("breadbar: internal error — no OSD window built for '{}'", req.view); + std::process::exit(1); + }; + capture_standalone_window(window, output, width, height); + } + "wifi-add-dialog" => { + let anchor = handles.wifi_tab_btn; + root.connect_map(move |_| { + let output = output.clone(); + let anchor = anchor.clone(); + gtk4::glib::timeout_add_local_once(PRE_POPUP_DELAY, move || { + crate::show_add_network_dialog(&anchor, "Sample Network".to_string(), move |dialog| { + capture_standalone_window(dialog.clone(), output.clone(), width, height); + }); + }); + }); + } + other => { + eprintln!( + "breadbar: unknown screenshot view '{other}' (known: {})", + KNOWN_VIEWS.join(", ") + ); + std::process::exit(1); + } + } +} + +/// Shared shape for every popover view: force it open shortly after the bar +/// maps (autohide disabled — a programmatic `popup()` has no real input +/// event serial to grab the Wayland seat with), then capture the whole +/// canvas after `settle` once the popover itself maps. +fn open_popover_on_root_map( + root: >k4::ApplicationWindow, + popover: gtk4::Popover, + settle: Duration, + output: PathBuf, + width: i32, + height: i32, +) { + let popover_to_open = popover.clone(); + root.connect_map(move |_| { + popover_to_open.set_autohide(false); + let popover_to_open = popover_to_open.clone(); + gtk4::glib::timeout_add_local_once(PRE_POPUP_DELAY, move || { + popover_to_open.popup(); + }); + }); + popover.connect_map(move |_| { + let output = output.clone(); + gtk4::glib::timeout_add_local_once(settle, move || { + finish(bread_screenshots::capture_region(0, 0, width, height, &output)); + }); + }); +} + +/// Shared shape for the standalone notification/OSD windows and the wifi +/// add-network dialog: wait for `map`, settle, capture, exit. These are +/// already-visible-or-about-to-be windows by the time this is called (their +/// sample event is queued before `dispatch` even runs), so this is just the +/// capture half. +fn capture_standalone_window(window: gtk4::Window, output: PathBuf, width: i32, height: i32) { + window.connect_map(move |_| { + let output = output.clone(); + gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || { + finish(bread_screenshots::capture_region(0, 0, width, height, &output)); + }); + }); +} + +fn finish(result: anyhow::Result<()>) { + match result { + Ok(()) => std::process::exit(0), + Err(e) => { + eprintln!("breadbar: screenshot capture failed: {e}"); + std::process::exit(1); + } + } +}