Compare commits

...

8 commits
v0.3.1 ... main

Author SHA1 Message Date
Breadway
7c55ea4756 Adopt bread_utils::gtk_popup for the launcher overlay
All checks were successful
check / check (push) Successful in 54s
dev release / build (push) Successful in 1m19s
Replace the hand-rolled layer-shell window, visible-row Up/Down
navigation, and click-outside-to-close gesture with the shared
helpers already used by breadclip. Keep singleton::toggle_or_kill
and bind_window_auto.
2026-08-23 14:39:11 +08:00
b52bf77ed3 Merge pull request 'can't be bothered writing a commit message' (#3) from feature/liquid-motion into main
All checks were successful
dev release / build (push) Successful in 2m14s
Reviewed-on: #3
2026-08-16 14:31:10 +08:00
Breadway
28ff4bc982 can't be bothered writing a commit message
All checks were successful
check / check (push) Successful in 8m6s
2026-08-16 14:29:24 +08:00
Breadway
b9abafe572 Bump version to v0.3.3
All checks were successful
dev release / build (push) Successful in 46s
beta (rc) release / build (push) Has been skipped
release / build (push) Successful in 1m11s
2026-08-16 14:09:13 +08:00
Breadway
06015b2627 Bind the launcher to the current monitor's bread-theme palette
All checks were successful
dev release / build (push) Successful in 4m5s
Pin bread-theme to v0.7.4.
2026-08-16 13:25:53 +08:00
Breadway
6330ad1951 CI: refuse unsigned bakery index on stable tag releases
All checks were successful
check / check (push) Successful in 1m36s
dev release / build (push) Successful in 1m46s
beta (rc) release / build (push) Has been skipped
release / build (push) Successful in 34s
2026-08-16 00:50:22 +08:00
Breadway
c359fd74be Adopt bread_utils::screenshot_cli for --screenshot flags
All checks were successful
check / check (push) Successful in 19s
dev release / build (push) Successful in 53s
Replace the local settle delay, canvas defaults, and pair-validation
error path with bread-utils v0.7.2. Clap parsing stays in-tree.
2026-08-16 00:26:06 +08:00
Breadway
4d0c0288b7 Honor bread.command.box.open via breadbox listen
All checks were successful
dev release / build (push) Successful in 40s
2026-08-15 23:11:28 +08:00
10 changed files with 646 additions and 340 deletions

View file

@ -17,7 +17,16 @@ jobs:
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
- name: build
run: cd src && bash ci/build.sh cargo build --release --locked
run: |
set -euo pipefail
if [ ! -f src/ci/build.sh ]; then
echo "::error::ci/build.sh is missing — bakery release builds must go through the shared CI wrapper"
exit 1
fi
cd src && bash ci/build.sh cargo build --release --locked || {
echo "::error::cargo build --release --locked failed. If Cargo.lock drifted, update and commit it; do not drop --locked."
exit 1
}
- name: prepare artifacts
run: |
@ -38,8 +47,14 @@ jobs:
ln -sfn "${VERSION}" "/srv/breadway-dl/breadbox/latest"
- name: regenerate index.json
env:
MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }}
run: |
set -euo pipefail
if [ -z "${MINISIGN_SEC_KEY:-}" ]; then
echo "::error::BAKERY_MINISIGN_SEC_KEY_PATH secret not set — refusing to regenerate index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone)"
exit 1
fi
rm -rf /tmp/bread-ecosystem-ci
git clone https://git.breadway.dev/Breadway/bread-ecosystem.git /tmp/bread-ecosystem-ci
bash /tmp/bread-ecosystem-ci/scripts/gen-index.sh

View file

@ -7,7 +7,7 @@ Follow [`CONTRIBUTING.md`](CONTRIBUTING.md). Single-trunk: `main` plus short-liv
- `github` — mirror. Day-to-day push `origin` only.
## Product
GTK4 app launcher + `breadbox-sync` icon cache. Theme via `bread-theme` (pin by tag on `git.breadway.dev`). Toggle uses `bread-utils::singleton`, not a homegrown PID file. `EVENTS.md` is the bread-event contract (app id `box`); emit `bread.box.launched` after a successful launch. No command verbs.
GTK4 app launcher + `breadbox-sync` icon cache. Theme via `bread-theme` (pin by tag on `git.breadway.dev`). Toggle uses `bread-utils::singleton`, not a homegrown PID file. `EVENTS.md` is the bread-event contract (app id `box`); emit `bread.box.launched` after a successful launch. `breadbox listen` honors `bread.command.box.open`.
## Distribution
Bakery (`bakery.toml`). Forgejo `.forgejo/workflows/` is canonical; do not re-add a GitHub Actions release workflow.

363
Cargo.lock generated
View file

@ -78,9 +78,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.13.0"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bread-screenshots"
@ -105,8 +105,8 @@ dependencies = [
[[package]]
name = "bread-theme"
version = "0.7.2"
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.2#30517f161724132cdeb658c04cf5e490be07ee73"
version = "0.7.4"
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.4#fcba3760387e2523edb71350f8efea3bc851b21e"
dependencies = [
"dirs",
"gtk4",
@ -121,13 +121,15 @@ source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.2#30517
dependencies = [
"bread-shared",
"dirs",
"gtk4",
"gtk4-layer-shell",
"serde",
"serde_json",
]
[[package]]
name = "breadbox"
version = "0.3.1"
version = "0.3.3"
dependencies = [
"anyhow",
"bread-screenshots",
@ -142,7 +144,7 @@ dependencies = [
[[package]]
name = "breadbox-shared"
version = "0.3.1"
version = "0.3.3"
dependencies = [
"serde",
"serde_json",
@ -151,7 +153,7 @@ dependencies = [
[[package]]
name = "breadbox-sync"
version = "0.3.1"
version = "0.3.3"
dependencies = [
"breadbox-shared",
"serde_json",
@ -178,14 +180,14 @@ checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54"
dependencies = [
"glib-sys",
"libc",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
name = "cc"
version = "1.2.63"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [
"find-msvc-tools",
"shlex",
@ -209,9 +211,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.4"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
dependencies = [
"clap_builder",
"clap_derive",
@ -219,9 +221,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.2"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
dependencies = [
"anstream",
"anstyle",
@ -285,13 +287,13 @@ dependencies = [
[[package]]
name = "displaydoc"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 3.0.3",
]
[[package]]
@ -312,9 +314,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
@ -337,24 +339,24 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-executor"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [
"futures-core",
"futures-task",
@ -363,32 +365,32 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-macro"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 3.0.3",
]
[[package]]
name = "futures-task"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.32"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-macro",
@ -419,14 +421,14 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
name = "gdk4"
version = "0.11.2"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e"
checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39"
dependencies = [
"cairo-rs",
"gdk-pixbuf",
@ -440,9 +442,9 @@ dependencies = [
[[package]]
name = "gdk4-sys"
version = "0.11.2"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c"
checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -452,7 +454,7 @@ dependencies = [
"libc",
"pango-sys",
"pkg-config",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
@ -468,9 +470,9 @@ dependencies = [
[[package]]
name = "gio"
version = "0.22.6"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6"
checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded"
dependencies = [
"futures-channel",
"futures-core",
@ -485,15 +487,15 @@ dependencies = [
[[package]]
name = "gio-sys"
version = "0.22.0"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1"
checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.52.0",
"system-deps 7.0.8",
"windows-sys 0.61.2",
]
[[package]]
@ -518,9 +520,9 @@ dependencies = [
[[package]]
name = "glib"
version = "0.22.7"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100"
dependencies = [
"bitflags",
"futures-channel",
@ -546,17 +548,17 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
]
[[package]]
name = "glib-sys"
version = "0.22.6"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445"
checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233"
dependencies = [
"libc",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
@ -567,37 +569,35 @@ checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c"
dependencies = [
"glib-sys",
"libc",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
name = "graphene-rs"
version = "0.22.0"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1"
checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff"
dependencies = [
"glib",
"graphene-sys",
"libc",
]
[[package]]
name = "graphene-sys"
version = "0.22.0"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c"
checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04"
dependencies = [
"glib-sys",
"libc",
"pkg-config",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
name = "gsk4"
version = "0.11.1"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62"
checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff"
dependencies = [
"cairo-rs",
"gdk4",
@ -610,9 +610,9 @@ dependencies = [
[[package]]
name = "gsk4-sys"
version = "0.11.1"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a"
checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@ -621,14 +621,14 @@ dependencies = [
"graphene-sys",
"libc",
"pango-sys",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
name = "gtk4"
version = "0.11.3"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2"
checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9"
dependencies = [
"cairo-rs",
"field-offset",
@ -647,9 +647,9 @@ dependencies = [
[[package]]
name = "gtk4-layer-shell"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a"
checksum = "17c28ea0f4676fdaaae7ff2413a24d0d35c8657424f84856c1103c73454c9da4"
dependencies = [
"bitflags",
"gdk4",
@ -662,34 +662,34 @@ dependencies = [
[[package]]
name = "gtk4-layer-shell-sys"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f566a5ec5bcc454e7fcf2ab76930887ced5365afce12c1e5201bb296b95f1b9"
checksum = "bcf19bb884ef0ef55b9e6b2b369c39b4fcc0c41e3a0c1cbc8c267720338b690b"
dependencies = [
"gdk4-sys",
"glib-sys",
"gtk4-sys",
"libc",
"system-deps",
"system-deps 8.0.0",
]
[[package]]
name = "gtk4-macros"
version = "0.11.0"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87"
checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
]
[[package]]
name = "gtk4-sys"
version = "0.11.3"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b"
checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -701,7 +701,7 @@ dependencies = [
"gsk4-sys",
"libc",
"pango-sys",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
@ -718,9 +718,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "icu_collections"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [
"displaydoc",
"potential_utf",
@ -732,9 +732,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
@ -745,9 +745,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@ -759,16 +759,17 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_properties"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
@ -779,15 +780,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]]
name = "icu_provider"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428"
dependencies = [
"displaydoc",
"icu_locale_core",
@ -849,36 +850,36 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[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"
version = "0.1.17"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
dependencies = [
"libc",
]
[[package]]
name = "litemap"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]]
name = "log"
version = "0.4.32"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memoffset"
@ -919,13 +920,12 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "pango"
version = "0.22.6"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438"
checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c"
dependencies = [
"gio",
"glib",
"libc",
"pango-sys",
]
@ -938,7 +938,7 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"system-deps 7.0.8",
]
[[package]]
@ -955,15 +955,15 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "potential_utf"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [
"zerovec",
]
@ -974,23 +974,23 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit 0.25.12+spec-1.1.0",
"toml_edit 0.25.13+spec-1.1.0",
]
[[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.45"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@ -1031,9 +1031,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.40"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
"log",
"once_cell",
@ -1046,18 +1046,18 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
version = "0.103.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
dependencies = [
"ring",
"rustls-pki-types",
@ -1072,9 +1072,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",
@ -1082,29 +1082,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 2.0.117",
"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",
@ -1139,9 +1139,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd-adler32"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "slab"
@ -1151,9 +1151,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "stable_deref_trait"
@ -1175,9 +1175,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@ -1203,7 +1203,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
]
[[package]]
@ -1215,7 +1215,20 @@ dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml 1.1.2+spec-1.1.0",
"toml 1.1.4+spec-1.1.0",
"version-compare",
]
[[package]]
name = "system-deps"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83779a5c956bcb6ba627a4ecf0a9d7625db47d7537e0892d97f712ac995648a3"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml 1.1.4+spec-1.1.0",
"version-compare",
]
@ -1242,14 +1255,14 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
]
[[package]]
name = "tinystr"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"zerovec",
@ -1269,9 +1282,9 @@ dependencies = [
[[package]]
name = "toml"
version = "1.1.2+spec-1.1.0"
version = "1.1.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [
"indexmap",
"serde_core",
@ -1279,7 +1292,7 @@ dependencies = [
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.3",
"winnow 1.0.4",
]
[[package]]
@ -1316,23 +1329,23 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.25.12+spec-1.1.0"
version = "0.25.13+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
dependencies = [
"indexmap",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.3",
"winnow 1.0.4",
]
[[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.3",
"winnow 1.0.4",
]
[[package]]
@ -1343,9 +1356,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.1.1+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "tracing"
@ -1366,7 +1379,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
]
[[package]]
@ -1448,14 +1461,14 @@ version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.7",
"webpki-roots 1.0.9",
]
[[package]]
name = "webpki-roots"
version = "1.0.7"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
dependencies = [
"rustls-pki-types",
]
@ -1625,24 +1638,24 @@ dependencies = [
[[package]]
name = "winnow"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
dependencies = [
"memchr",
]
[[package]]
name = "writeable"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "xml-rs"
version = "0.8.28"
version = "0.8.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "yoke"
@ -1663,7 +1676,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
"synstructure",
]
@ -1684,21 +1697,21 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 2.0.119",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerotrie"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"yoke",
@ -1707,9 +1720,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.6"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8"
dependencies = [
"yoke",
"zerofrom",
@ -1718,17 +1731,17 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.3"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 3.0.3",
]
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View file

@ -8,16 +8,19 @@ specifically its "Namespaces" and "Integrating a bread\* app" sections —
for the general convention this follows.
App id: **`box`**. Transport: `bread-utils`'s `bread_client` module
(feature `bread-client`) — `breadbox` links it directly. breadbox is a
short-lived process (it exits when the launcher closes), so each `emit`
is its own short-lived connection. There is no long-running daemon and
therefore no command subscription.
(feature `bread-client`) — `breadbox` links it directly. One-shot
launcher invocations each `emit` on their own fire-and-forget
connection. Command verbs are only received while `breadbox listen` is
running — that process holds the `bread.command.box.**` subscription
open.
## Events published (`bread.box.*`)
| Event | Data | When |
|-------|------|------|
| `bread.box.launched` | `{ "id": "<desktop id or exec>", "name": "<display name>" }` | The user launched an app (Enter / keypad Enter on the selected row, or activating a row) **and** the spawn succeeded. Not emitted if `Command::spawn` fails (missing terminal, `exec` that cannot start). `id` is the desktop-file id (the `.desktop` filename, e.g. `firefox.desktop`), falling back to the stripped `Exec=` line when that id is empty. `name` is the desktop-entry display name. |
| `bread.box.open.done` | `{}` | `bread.command.box.open` was received and `breadbox` was spawned. This is the command confirmation, not proof the overlay mapped — the spawned process is the same toggle as a keybind. |
| `bread.box.open.failed` | `{ "error": "<message>" }` | `bread.command.box.open` was received but this binary could not be started. |
Launch history is local to breadbox (`~/.cache/breadbox/history.json`);
the event bus is a notification that a launch happened, not a channel
@ -25,17 +28,38 @@ for the exec line's arguments or the resulting process.
## Commands honored (`bread.command.box.*`)
None. breadbox is not a daemon — it is not running (and not subscribed)
except while the launcher overlay is open. There is no existing
"launch this desktop id from the bus" product surface, and inventing
`bread.command.box.launch` (or similar) without that surface would be a
stub. If/when breadbox grows a long-running piece that can honor a
verb, the corresponding `bread.command.box.*` command should be added
at the same time, not stubbed out ahead of it.
These are only received while `breadbox listen` is running. Publishing a
command with no subscriber is a silent no-op — that is the documented
bread convention, not a breadbox bug.
| Verb | Data | Effect |
|------|------|--------|
| `open` | none | Same as running `breadbox` (toggle the launcher overlay via the existing singleton). Emits `bread.box.open.done` / `.failed`. |
```lua
bread.spawn(function()
bread.emit("bread.command.box.open")
bread.wait("bread.box.open.done", { timeout = 5000 })
end)
```
### Not implemented: extra verbs
There is no `launch` / `close` / `query` command verb. Picking a desktop
id from the bus would be a new product surface. If/when that exists, add
the corresponding `bread.command.box.*` verb at the same time, not
stubbed as a no-op ahead of it.
## Fail-safe behavior
- If breadd isn't installed or isn't running, `emit` is a silent no-op
(`BreadClient::emit` never blocks or errors the caller) — launching,
(`BreadClient::emit` never blocks or errors the caller) and the
command subscription simply never receives anything — launching,
history, theming, and the singleton toggle are entirely unaffected.
- If breadd restarts, the command subscription reconnects automatically
(`BreadClient::subscribe`'s background thread has its own backoff
loop); no restart of `breadbox listen` is needed.
- If `breadbox listen` is not running, commands are a graceful no-op at
the bus (no subscriber). The CLI still works, and one-shot invocations
still emit `bread.box.launched` on their own short-lived connection.
- Closing the launcher without launching anything emits nothing.

View file

@ -1,6 +1,6 @@
[package]
name = "breadbox-shared"
version = "0.3.1"
version = "0.3.3"
edition = "2021"
license = "MIT"

View file

@ -1,6 +1,6 @@
[package]
name = "breadbox-sync"
version = "0.3.1"
version = "0.3.3"
edition = "2021"
license = "MIT"

View file

@ -1,6 +1,6 @@
[package]
name = "breadbox"
version = "0.3.1"
version = "0.3.3"
edition = "2021"
license = "MIT"
@ -9,8 +9,8 @@ name = "breadbox"
path = "src/main.rs"
[dependencies]
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2", features = ["gtk"] }
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2", features = ["bread-client"] }
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.4", features = ["gtk"] }
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2", features = ["bread-client", "gtk"] }
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2" }
breadbox-shared = { path = "../breadbox-shared" }

84
breadbox/src/listen.rs Normal file
View file

@ -0,0 +1,84 @@
//! Long-running command subscription for `bread.command.box.*`.
//!
//! `breadbox` is still a one-shot toggle overlay by default. `breadbox listen`
//! is the optional persistent process that can honor bus commands. See
//! `EVENTS.md`.
use bread_utils::bread_client::{BreadClient, BreadEvent};
/// Sibling-app id in `bread_shared::apps::KNOWN_APPS`.
const APP_ID: &str = "box";
/// Subscribe to `bread.command.box.**` and block until the process is killed.
///
/// breadd being absent is not an error: [`BreadClient::subscribe`] reconnects
/// with backoff, and `on_event` simply isn't called until the daemon is up.
pub fn run() {
let client = BreadClient::connect(APP_ID);
if client.health().is_none() {
eprintln!("breadbox: breadd unreachable; command subscription will connect when it comes back");
}
let _commands = client.subscribe("bread.command.box.**", |event| {
handle_command(&event);
});
eprintln!("breadbox: listening for bread.command.box.**");
loop {
std::thread::park();
}
}
/// Reacts to `bread.command.box.*` verbs. Only `open` is honored today —
/// other verbs are ignored, not stubbed as no-ops that pretend to succeed.
fn handle_command(event: &BreadEvent) {
let Some(verb) = command_verb(&event.event) else {
return;
};
match verb {
"open" => handle_open(),
other => {
eprintln!("breadbox: ignoring unrecognized bread.command.box.{other}");
}
}
}
fn handle_open() {
// Same as running `breadbox` from a keybind: toggle the overlay via the
// existing singleton. Spawn success is the command confirmation — we do
// not wait for the GTK window to map.
let result = spawn_self();
let client = BreadClient::connect(APP_ID);
match result {
Ok(_) => client.emit("bread.box.open.done", serde_json::json!({})),
Err(e) => {
eprintln!("breadbox: bread.command.box.open failed: {e}");
client.emit(
"bread.box.open.failed",
serde_json::json!({ "error": e.to_string() }),
);
}
}
}
fn spawn_self() -> std::io::Result<std::process::Child> {
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("breadbox"));
std::process::Command::new(exe).spawn()
}
fn command_verb(event_name: &str) -> Option<&str> {
event_name.strip_prefix("bread.command.box.")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_verb_strips_box_prefix() {
assert_eq!(command_verb("bread.command.box.open"), Some("open"));
assert_eq!(command_verb("bread.command.box.launch"), Some("launch"));
assert_eq!(command_verb("bread.command.clip.clear"), None);
assert_eq!(command_verb("bread.box.launched"), None);
}
}

View file

@ -1,15 +1,15 @@
use bread_theme::{hex_to_rgba, ink_on, load_palette, Palette};
use bread_utils::bread_client::BreadClient;
use std::{
cell::RefCell,
cell::{Cell, RefCell},
collections::HashMap,
env,
fs,
env, fs,
io::{Read, Write},
os::unix::net::UnixStream,
path::{Path, PathBuf},
process::{Command, Stdio},
rc::Rc,
time::Duration,
};
/// This app's id in bread's sibling-app namespace registry
@ -20,14 +20,11 @@ use breadbox_shared::{
config_dir, load_all_desktop_entries, Config, DesktopEntry, IconCache, LaunchHistory,
};
use gtk4::{
glib,
pango::EllipsizeMode,
prelude::*,
Application, ApplicationWindow, Box as GBox, CssProvider, EventControllerKey, Label,
ListBox, Orientation, PolicyType, ScrolledWindow, SearchEntry, SelectionMode,
glib, pango::EllipsizeMode, prelude::*, Application, Box as GBox, CssProvider, Entry,
EventControllerKey, Label, ListBox, Orientation, PolicyType, ScrolledWindow, SelectionMode,
};
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
mod listen;
mod screenshot;
// ---- Hyprland IPC -----------------------------------------------------------
@ -89,7 +86,9 @@ fn load_sorted_entries(
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => {
// Most-launched first, then alphabetical
history.count(&b.name).cmp(&history.count(&a.name))
history
.count(&b.name)
.cmp(&history.count(&a.name))
.then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))
}
}
@ -137,58 +136,171 @@ fn matches_term(field: &str, term: &str) -> bool {
// ---- Theming ----------------------------------------------------------------
const STAGGER_ROWS: usize = 12;
fn build_css(p: &Palette) -> String {
let bg_panel = hex_to_rgba(&p.background, 0.60);
let bg_panel = hex_to_rgba(&p.background, 0.68);
// breadbox-specific rules only — fonts, palette, and generic widgets come
// from the shared ecosystem stylesheet (applied first in connect_activate).
// Colour is set on each surface (panel, search box, hovered/selected row) so
// Colour is set on each surface (panel, search, hovered/selected row) so
// child labels inherit the legible ink for that background. `on_*` are
// luminance-picked black/white — the pywal hues are untouched. Without this a
// light `surface` slot makes the selected row's text vanish.
// luminance-picked black/white — the pywal hues are untouched.
//
// GTK4 ListBox's node is `list`, not `listbox`. These `list row:selected`
// rules beat the shared sheet's solid accent fill + on-accent ink so the
// glass card keeps a tinted selection and a left inset hairline.
let stagger = (0..STAGGER_ROWS)
.map(|i| {
format!(
".launcher-bg.just-opened list row.stagger-{i} {{ animation-delay: {}ms; }}",
i * 28
)
})
.collect::<Vec<_>>()
.join("");
format!(
"window {{ background-color: transparent; }}\
.launcher-bg {{ background-color: {bg_panel}; color: {on_bg}; border-radius: 8px;\
box-shadow: 0 8px 32px rgba(0,0,0,0.6); }}\
searchentry {{ background-color: {surface}; color: {on_surface}; caret-color: {accent};\
border: none; outline: none; box-shadow: none;\
padding: 12px 16px; border-radius: 6px 6px 0 0; }}\
listbox {{ background-color: transparent; padding: 4px; }}\
row {{ padding: 8px 12px; color: {on_bg}; background-color: transparent;\
border-radius: 6px; }}\
row:hover {{ background-color: {surface}; color: {on_surface}; }}\
row:selected {{ background-color: {surface}; color: {on_surface}; }}\
.app-name {{ font-size: 14px; }}\
.app-muted {{ opacity: 0.6; font-size: 12px; }}\
image {{ margin-right: 8px; }}",
bg_panel = bg_panel,
surface = p.color0,
accent = p.color4,
on_bg = ink_on(&p.background),
on_surface = ink_on(&p.color0),
"\
window {{ background-color: rgba(0, 0, 0, 0.28); animation: scrim-in 0.28s ease both; }}\
@keyframes scrim-in {{\
from {{ background-color: rgba(0, 0, 0, 0); }}\
to {{ background-color: rgba(0, 0, 0, 0.28); }}\
}}\
.launcher-bg {{\
background-color: {bg_panel}; color: {on_bg}; border-radius: 20px;\
border: 1px solid alpha({on_bg}, 0.14);\
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.50);\
animation: card-in 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;\
}}\
@keyframes card-in {{\
from {{ opacity: 0; margin-top: 112px; }}\
to {{ opacity: 1; margin-top: 88px; }}\
}}\
.launcher-bg entry {{\
background-color: transparent; color: {on_bg}; caret-color: {accent};\
border: none; outline: none; box-shadow: none;\
padding: 20px 22px 14px; border-radius: 20px 20px 0 0;\
font-size: 17px; min-height: 28px;\
}}\
.launcher-bg entry:focus, .launcher-bg entry:focus-within {{\
border: none; outline: none; box-shadow: none; background-color: transparent;\
}}\
entry > text {{ background: transparent; }}\
entry image {{ opacity: 0; min-width: 0; margin: 0; padding: 0; }}\
.launcher-caret {{\
min-height: 2px; max-height: 2px; margin: 0 20px; border-radius: 2px;\
background-color: {accent};\
background-image: linear-gradient(90deg, {accent}, {accent2});\
}}\
.launcher-bg.just-opened .launcher-caret {{\
animation: caret-draw 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;\
}}\
@keyframes caret-draw {{\
from {{ margin-right: 600px; opacity: 0.25; }}\
to {{ margin-right: 20px; opacity: 1; }}\
}}\
scrolledwindow {{ background: transparent; }}\
list {{ background-color: transparent; padding: 8px 8px 4px; }}\
list row {{\
padding: 6px 8px; color: {on_bg}; background-color: transparent;\
border-radius: 14px; margin: 1px 10px; outline: none;\
}}\
list row:hover {{ background-color: alpha({on_bg}, 0.07); color: {on_bg}; }}\
row:selected, list row:selected, list row:selected:focus,\
list row:selected:hover, list row:selected:focus:hover {{\
background-color: alpha({accent}, 0.22); color: {on_bg};\
outline: none; box-shadow: none;\
}}\
list row:selected label, list row:selected .app-name, list row:selected .app-muted {{\
color: {on_bg};\
}}\
.app-row {{ min-height: 48px; }}\
.app-icon-well {{\
min-width: 38px; min-height: 38px; margin-right: 12px;\
border-radius: 999px; background-color: alpha({on_bg}, 0.08);\
}}\
.app-icon {{ color: {on_bg}; opacity: 0.88; }}\
.app-name {{ font-size: 14px; font-weight: bold; }}\
.app-muted {{ opacity: 0.48; font-size: 11px; }}\
.launcher-footer {{\
padding: 8px 18px 12px; font-size: 11px; opacity: 0.40;\
letter-spacing: 0.08em; text-transform: uppercase;\
}}\
.launcher-bg.just-opened list row {{\
animation: row-in 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;\
}}\
@keyframes row-in {{\
from {{ opacity: 0; }}\
to {{ opacity: 1; }}\
}}\
{stagger}\
list.reflow row {{ animation: row-fade 0.16s ease both; }}\
@keyframes row-fade {{\
from {{ opacity: 0.40; }}\
to {{ opacity: 1; }}\
}}\
.no-motion, .no-motion * {{ animation: none; transition: none; }}",
bg_panel = bg_panel,
accent = p.color4,
accent2 = p.color5,
on_bg = ink_on(&p.background),
stagger = stagger,
)
}
fn category_label(entry: &DesktopEntry) -> &'static str {
let has = |needle: &str| {
entry
.categories
.iter()
.any(|c| c.eq_ignore_ascii_case(needle) || c.to_ascii_lowercase().contains(needle))
};
if entry.terminal || has("terminalemulator") {
"Terminal"
} else if has("webbrowser") {
"Browser"
} else if has("game") {
"Games"
} else if has("instantmessaging") || has("chat") || has("ircclient") {
"Chat"
} else if has("settings") || has("desktopsettings") || has("system") {
"System"
} else if has("ide") || has("development") {
"IDE"
} else if has("office") || has("wordprocessor") || has("texteditor") || has("notes") {
"Notes"
} else if has("audio") || has("player") || has("audiovideo") {
"Music"
} else if has("graphics") || has("photography") || has("camera") {
"Capture"
} else if has("filemanager") {
"Files"
} else {
"App"
}
}
fn symbolic_icon(entry: &DesktopEntry) -> &'static str {
match category_label(entry) {
"Browser" => "web-browser-symbolic",
"Terminal" => "utilities-terminal-symbolic",
"Notes" => "accessories-text-editor-symbolic",
"System" => "emblem-system-symbolic",
"Chat" => "user-available-symbolic",
"Games" => "applications-games-symbolic",
"Music" => "audio-x-generic-symbolic",
"Capture" => "camera-photo-symbolic",
"IDE" => "applications-engineering-symbolic",
"Files" => "folder-symbolic",
_ => "application-x-executable-symbolic",
}
}
// ---- Icon loading -----------------------------------------------------------
fn make_icon(icon_name: &str, icon_path: Option<&Path>) -> gtk4::Image {
// Try loading from resolved cached path via gio::File
if let Some(path) = icon_path {
let gio_file = gtk4::gio::File::for_path(path);
if let Ok(texture) = gtk4::gdk::Texture::from_file(&gio_file) {
let img = gtk4::Image::new();
img.set_paintable(Some(&texture));
img.set_pixel_size(32);
return img;
}
}
// Fall back to GTK icon theme lookup by name
let name = if icon_name.is_empty() {
"application-x-executable"
} else {
icon_name
};
let img = gtk4::Image::from_icon_name(name);
img.set_pixel_size(32);
fn make_icon(entry: &DesktopEntry) -> gtk4::Image {
let img = gtk4::Image::from_icon_name(symbolic_icon(entry));
img.set_pixel_size(18);
img.add_css_class("app-icon");
img
}
@ -270,10 +382,18 @@ fn fuzzy_score(query: &str, entry: &DesktopEntry) -> u32 {
let q = query.to_lowercase();
let name = entry.name.to_lowercase();
let wm = entry.wm_class.as_deref().unwrap_or("").to_lowercase();
if name == q || wm == q { return 0; }
if name.starts_with(&q) { return 1; }
if name.contains(&q) { return 2; }
if wm.starts_with(&q) || wm.contains(&q) { return 3; }
if name == q || wm == q {
return 0;
}
if name.starts_with(&q) {
return 1;
}
if name.contains(&q) {
return 2;
}
if wm.starts_with(&q) || wm.contains(&q) {
return 3;
}
4 // subsequence match
}
@ -286,6 +406,26 @@ fn get_row_entry(row: &gtk4::ListBoxRow) -> Option<DesktopEntry> {
}
}
fn visible_row_count(list: &ListBox) -> u32 {
let mut n = 0;
let mut i = 0;
while let Some(row) = list.row_at_index(i) {
if row.is_visible() {
n += 1;
}
i += 1;
}
n
}
fn set_footer_count(footer: &Label, n: u32) {
match n {
0 => footer.set_text("no match"),
1 => footer.set_text("1 app"),
n => footer.set_text(&format!("{n} apps")),
}
}
fn run_ui(
entries: Vec<DesktopEntry>,
history: LaunchHistory,
@ -319,16 +459,9 @@ fn run_ui(
bread_theme::gtk::apply_user_css(&user_css_path, &user_cell);
}
// Full-screen transparent window; clicks outside the launcher panel close it.
let window = ApplicationWindow::builder().application(app).build();
window.init_layer_shell();
window.set_namespace(Some("breadbox"));
window.set_layer(Layer::Overlay);
window.set_keyboard_mode(KeyboardMode::Exclusive);
for edge in [Edge::Top, Edge::Bottom, Edge::Left, Edge::Right] {
window.set_anchor(edge, true);
}
window.set_exclusive_zone(0);
// Full-screen transparent overlay; panel widget is positioned inside it.
let window = bread_utils::gtk_popup::new_overlay_window(app, "breadbox");
bread_theme::gtk::bind_window_auto(&window);
let close_all: Rc<dyn Fn()> = Rc::new({
let w = window.clone();
@ -341,13 +474,25 @@ fn run_ui(
vbox.add_css_class("launcher-bg");
vbox.set_halign(gtk4::Align::Center);
vbox.set_valign(gtk4::Align::Start);
vbox.set_margin_top(120);
vbox.set_margin_top(88);
vbox.set_size_request(600, -1);
if is_screenshot_run {
window.add_css_class("no-motion");
vbox.add_css_class("no-motion");
} else {
vbox.add_css_class("just-opened");
}
let search = SearchEntry::new();
search.set_placeholder_text(Some("breadbox"));
let search = Entry::new();
search.set_placeholder_text(Some("Search"));
search.set_has_frame(false);
vbox.append(&search);
let caret = GBox::new(Orientation::Horizontal, 0);
caret.add_css_class("launcher-caret");
caret.set_hexpand(true);
vbox.append(&caret);
let scroll = ScrolledWindow::new();
scroll.set_policy(PolicyType::Never, PolicyType::Automatic);
scroll.set_max_content_height(480);
@ -358,28 +503,44 @@ fn run_ui(
for (idx, entry) in entries.iter().enumerate() {
let row = gtk4::ListBoxRow::new();
if idx < STAGGER_ROWS {
row.add_css_class(&format!("stagger-{idx}"));
}
let hbox = GBox::new(Orientation::Horizontal, 0);
hbox.set_margin_start(6);
hbox.set_margin_end(6);
hbox.add_css_class("app-row");
hbox.set_valign(gtk4::Align::Center);
let icon = make_icon(&entry.icon_name, entry.icon_path.as_deref());
hbox.append(&icon);
let well = GBox::new(Orientation::Horizontal, 0);
well.add_css_class("app-icon-well");
well.set_size_request(38, 38);
well.set_halign(gtk4::Align::Center);
well.set_valign(gtk4::Align::Center);
well.set_hexpand(false);
let icon = make_icon(entry);
icon.set_halign(gtk4::Align::Center);
icon.set_valign(gtk4::Align::Center);
icon.set_hexpand(true);
well.append(&icon);
hbox.append(&well);
let text = GBox::new(Orientation::Vertical, 1);
text.add_css_class("app-text");
text.set_hexpand(true);
text.set_valign(gtk4::Align::Center);
let name_lbl = Label::new(Some(&entry.name));
name_lbl.add_css_class("app-name");
name_lbl.set_xalign(0.0);
name_lbl.set_hexpand(true);
name_lbl.set_ellipsize(EllipsizeMode::End);
hbox.append(&name_lbl);
text.append(&name_lbl);
if let Some(ref wm) = entry.wm_class {
let wm_lbl = Label::new(Some(wm));
wm_lbl.add_css_class("app-muted");
wm_lbl.set_xalign(1.0);
hbox.append(&wm_lbl);
}
let sub_lbl = Label::new(Some(category_label(entry)));
sub_lbl.add_css_class("app-muted");
sub_lbl.set_xalign(0.0);
sub_lbl.set_ellipsize(EllipsizeMode::End);
text.append(&sub_lbl);
hbox.append(&text);
row.set_child(Some(&hbox));
unsafe { row.set_data("entry", entry.clone()) };
unsafe { row.set_data("initial_order", idx as u32) };
@ -393,8 +554,16 @@ fn run_ui(
list.set_sort_func(move |row_a, row_b| {
let query = sort_query.borrow();
if query.is_empty() {
let oa = unsafe { row_a.data::<u32>("initial_order").map_or(u32::MAX, |p| *p.as_ref()) };
let ob = unsafe { row_b.data::<u32>("initial_order").map_or(u32::MAX, |p| *p.as_ref()) };
let oa = unsafe {
row_a
.data::<u32>("initial_order")
.map_or(u32::MAX, |p| *p.as_ref())
};
let ob = unsafe {
row_b
.data::<u32>("initial_order")
.map_or(u32::MAX, |p| *p.as_ref())
};
return oa.cmp(&ob).into();
}
let (Some(ea), Some(eb)) = (get_row_entry(row_a), get_row_entry(row_b)) else {
@ -417,11 +586,29 @@ fn run_ui(
scroll.set_child(Some(&list));
vbox.append(&scroll);
let footer = Label::new(None);
footer.add_css_class("launcher-footer");
footer.set_xalign(0.0);
set_footer_count(&footer, visible_row_count(&list));
vbox.append(&footer);
window.set_child(Some(&vbox));
// Filter on keystroke
if !is_screenshot_run {
let vbox_open = vbox.clone();
glib::timeout_add_local_once(Duration::from_millis(520), move || {
vbox_open.remove_css_class("just-opened");
});
}
// Filter on keystroke. ListBox keeps row identity across sort, so the
// reorder is already a cheap FLIP analog; a short CSS fade is the extra.
let list_f = list.clone();
let footer_f = footer.clone();
let vbox_f = vbox.clone();
let filter_query = Rc::clone(&query_rc);
let reflow_gen = Rc::new(Cell::new(0u32));
search.connect_changed(move |entry| {
let text = entry.text();
let query = text.as_str();
@ -431,6 +618,7 @@ fn run_ui(
let vis = get_row_entry(&row)
.map(|e| {
fuzzy_matches(query, &e.name)
|| fuzzy_matches(query, category_label(&e))
|| e.wm_class
.as_deref()
.is_some_and(|w| fuzzy_matches(query, w))
@ -441,10 +629,23 @@ fn run_ui(
i += 1;
}
list_f.invalidate_sort();
let first_vis = (0i32..).find_map(|j| {
list_f.row_at_index(j).filter(|r| r.is_visible())
});
let first_vis =
(0i32..).find_map(|j| list_f.row_at_index(j).filter(|r| r.is_visible()));
list_f.select_row(first_vis.as_ref());
set_footer_count(&footer_f, visible_row_count(&list_f));
if !vbox_f.has_css_class("just-opened") {
list_f.remove_css_class("reflow");
list_f.add_css_class("reflow");
let gen = reflow_gen.get().wrapping_add(1);
reflow_gen.set(gen);
let list_fade = list_f.clone();
let reflow_gen = Rc::clone(&reflow_gen);
glib::timeout_add_local_once(Duration::from_millis(180), move || {
if reflow_gen.get() == gen {
list_fade.remove_css_class("reflow");
}
});
}
});
// Keyboard handling — capture phase on window
@ -472,36 +673,11 @@ fn run_ui(
glib::Propagation::Stop
}
Key::Down => {
let cur = list_k.selected_row().map(|r| r.index()).unwrap_or(-1);
let mut i = cur + 1;
loop {
match list_k.row_at_index(i) {
Some(r) if r.is_visible() => {
list_k.select_row(Some(&r));
break;
}
Some(_) => i += 1,
None => break,
}
}
bread_utils::gtk_popup::select_next_visible(&list_k);
glib::Propagation::Stop
}
Key::Up => {
let cur = list_k.selected_row().map(|r| r.index()).unwrap_or(0);
let mut i = cur - 1;
loop {
if i < 0 {
break;
}
match list_k.row_at_index(i) {
Some(r) if r.is_visible() => {
list_k.select_row(Some(&r));
break;
}
Some(_) => i -= 1,
None => break,
}
}
bread_utils::gtk_popup::select_prev_visible(&list_k);
glib::Propagation::Stop
}
_ => glib::Propagation::Proceed,
@ -522,22 +698,10 @@ fn run_ui(
});
// Click outside launcher panel → close
let close_outside = Rc::clone(&close_all);
let vbox_ref = vbox.clone();
let win_ref = window.clone();
let outside_click = gtk4::GestureClick::new();
outside_click.connect_pressed(move |_, _, x, y| {
if let Some(b) = vbox_ref.compute_bounds(&win_ref) {
if x < b.x() as f64
|| x > (b.x() + b.width()) as f64
|| y < b.y() as f64
|| y > (b.y() + b.height()) as f64
{
close_outside();
}
}
});
window.add_controller(outside_click);
{
let close_outside = Rc::clone(&close_all);
bread_utils::gtk_popup::close_on_outside_click(&window, &vbox, move || close_outside());
}
if let Some(req) = screenshot_req.clone() {
screenshot::dispatch(&window, req);
@ -560,6 +724,11 @@ fn run_ui(
// ---- Main -------------------------------------------------------------------
fn main() {
if std::env::args().nth(1).as_deref() == Some("listen") {
listen::run();
return;
}
use clap::Parser;
let cli = screenshot::Cli::parse();
let screenshot_req = cli.screenshot_request();
@ -580,7 +749,9 @@ fn main() {
Ok(bread_utils::singleton::Toggle::Started(guard)) => Some(guard),
Ok(bread_utils::singleton::Toggle::KilledExisting) => return,
Err(e) => {
eprintln!("breadbox: single-instance lock unavailable ({e}); continuing without it");
eprintln!(
"breadbox: single-instance lock unavailable ({e}); continuing without it"
);
None
}
}

View file

@ -9,15 +9,10 @@
//! capture is the whole known-size canvas, not a hand-tracked panel
//! geometry.
use bread_utils::screenshot_cli::{validate_pair, DEFAULT_HEIGHT, DEFAULT_WIDTH, SETTLE_DELAY};
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);
#[derive(Parser)]
#[command(name = "breadbox")]
@ -33,11 +28,11 @@ pub struct Cli {
/// Capture canvas width — matches the isolated compositor's output width
/// (`bread-capture --isolate-width`).
#[arg(long, default_value_t = 1920)]
#[arg(long, default_value_t = DEFAULT_WIDTH)]
pub width: u32,
/// Capture canvas height — see `width`.
#[arg(long, default_value_t = 1080)]
#[arg(long, default_value_t = DEFAULT_HEIGHT)]
pub height: u32,
}
@ -50,16 +45,20 @@ pub struct ScreenshotRequest {
}
impl Cli {
/// `None` for a normal run. Exits the process with an error if
/// `--screenshot` was given without `--output`, before any GTK setup
/// `None` for a normal run. Exits the process with an error if the
/// `--screenshot` / `--output` pair is incomplete, before any GTK setup
/// happens.
pub fn screenshot_request(&self) -> Option<ScreenshotRequest> {
let view = self.screenshot.clone()?;
let Some(output) = self.output.clone() else {
eprintln!("breadbox: --screenshot requires --output");
if let Err(e) = validate_pair(self.screenshot.as_deref(), self.output.as_deref()) {
eprintln!("breadbox: {e}");
std::process::exit(1);
};
Some(ScreenshotRequest { view, output, width: self.width, height: self.height })
}
Some(ScreenshotRequest {
view: self.screenshot.clone()?,
output: self.output.clone()?,
width: self.width,
height: self.height,
})
}
}