diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml deleted file mode 100644 index b547c34..0000000 --- a/.forgejo/workflows/check.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: check - -# Fast-fail lint/test on short-lived work branches, before it ever reaches -# main and triggers a dev-track release build. -on: - push: - branches: ['feature/**', 'fix/**'] - -jobs: - check: - runs-on: [self-hosted, hestia] - steps: - - name: checkout - run: | - set -euo pipefail - rm -rf src && mkdir src - git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ - "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - - name: clippy - run: cd src && bash ci/build.sh cargo clippy --workspace --all-targets --locked -- -D warnings - - - name: test - run: cd src && bash ci/build.sh cargo test --workspace --locked diff --git a/.forgejo/workflows/dev-release.yml b/.forgejo/workflows/dev-release.yml deleted file mode 100644 index 5d6839e..0000000 --- a/.forgejo/workflows/dev-release.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: dev release - -# Publishes a dev-track build on every push to `main` (the trunk -# branch — there is no separate `dev` branch). See bread-ecosystem's -# docs/release-channels.md for the release-track policy this is part of. -on: - push: - branches: ['main'] - -jobs: - build: - runs-on: [self-hosted, hestia] - steps: - - name: checkout - run: | - set -euo pipefail - rm -rf src && mkdir src - git clone --branch main --depth 1 \ - "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - - name: build - run: cd src && bash ci/build.sh cargo build --release --locked - - - name: compute dev version - run: | - set -euo pipefail - cd src - # Base the dev version off the latest published stable tag, - # not Cargo.toml — Cargo.toml can go stale relative to the last - # real release (seen in practice: breadbox/breadpad/breadcrumbs/ - # breadpaper), which would make a dev build sort as OLDER than - # what's already installed and bakery would correctly refuse it. - LATEST_TAG="$(git ls-remote --tags --refs \ - "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" 'v*' \ - | awk -F/ '{print $NF}' | sed 's/^v//' | (grep -v -- '-' || true) | sort -V | tail -1)" - if [ -n "${LATEST_TAG}" ]; then - CUR="${LATEST_TAG}" - else - CUR="$(grep -m1 '^version' breadbox/Cargo.toml | sed -E 's/.*"(.*)".*/\1/')" - fi - IFS='.' read -r MA MI PA <<< "${CUR}" - SHA="$(git rev-parse --short HEAD)" - TS="$(date -u +%Y%m%d%H%M%S)" - echo "VERSION=${MA}.${MI}.$((PA + 1))-dev.${TS}+${SHA}" >> "$GITHUB_ENV" - - - name: prepare artifacts - run: | - set -euo pipefail - PKG_DIR="/srv/breadway-dl/dev/breadbox/${VERSION}" - mkdir -p "${PKG_DIR}" - for bin in breadbox breadbox-sync; do - cp "src/target/release/${bin}" "${PKG_DIR}/${bin}-x86_64" - strip "${PKG_DIR}/${bin}-x86_64" - sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \ - > "${PKG_DIR}/${bin}-x86_64.sha256" - done - cp src/packaging/breadbox-sync.service "${PKG_DIR}/" - cp src/config.example.toml "${PKG_DIR}/" - cp src/LICENSE "${PKG_DIR}/" - cp src/bakery.toml "${PKG_DIR}/bakery.toml" - ln -sfn "${VERSION}" "/srv/breadway-dl/dev/breadbox/latest" - - # No GitHub Release upload — dev, like the other non-stable track, - # is only distributed via dl.breadway.dev/dev/. - - name: regenerate dev 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 dev index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone on the dev track)" - exit 1 - fi - rm -rf /tmp/bread-ecosystem-ci-* 2>/dev/null || true - # mktemp: a fixed clone path races when multiple repos' dev/beta - # workflows run close together on the same self-hosted runner. - ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" - git clone --branch main https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" - TRACK=dev bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" - rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml new file mode 100644 index 0000000..4c186c9 --- /dev/null +++ b/.forgejo/workflows/mirror.yml @@ -0,0 +1,19 @@ +name: Mirror to GitHub + +on: + push: + branches: ['**'] + tags: ['**'] + +jobs: + mirror: + runs-on: [self-hosted, hestia] + steps: + - name: Mirror to GitHub + run: | + set -euo pipefail + git clone --mirror "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" repo.git + cd repo.git + git push --prune \ + "https://x-access-token:${{ secrets.MIRROR_TOKEN }}@github.com/Breadway/breadbox.git" \ + '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml new file mode 100644 index 0000000..fecfe9d --- /dev/null +++ b/.forgejo/workflows/package.yml @@ -0,0 +1,40 @@ +name: Build and publish package + +on: + push: + tags: ['v*'] + +jobs: + package: + runs-on: [self-hosted, hestia] + container: + image: archlinux:latest + steps: + # Note: no actions/checkout — the archlinux image has no Node, which JS + # actions require. Everything runs as shell steps and clones manually. + - name: Build and publish + env: + PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + pacman -Syu --noconfirm base-devel git rust cargo gtk4 gtk4-layer-shell librsvg + useradd -m builder + git config --global --add safe.directory '*' + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src + cd /home/builder/src + git archive --format=tar.gz --prefix="breadbox-${VERSION}/" HEAD \ + > packaging/arch/breadbox-${VERSION}.tar.gz + SHA=$(sha256sum packaging/arch/breadbox-${VERSION}.tar.gz | awk '{print $1}') + sed -i "s/^pkgver=.*/pkgver=${VERSION}/" packaging/arch/PKGBUILD + sed -i "s/^sha256sums=.*/sha256sums=('${SHA}')/" packaging/arch/PKGBUILD + chown -R builder:builder /home/builder/src + # --nocheck: packaging builds the artifact; tests belong in a CI job. + su builder -c "cd /home/builder/src/packaging/arch && makepkg -f --noconfirm --nocheck" + PKG=$(find /home/builder/src/packaging/arch -name '*.pkg.tar.zst' | head -1) + curl -fsS -X PUT \ + -H "Authorization: token ${PUBLISH_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@${PKG}" \ + "https://git.breadway.dev/api/packages/Breadway/arch/os" diff --git a/.forgejo/workflows/rc-release.yml b/.forgejo/workflows/rc-release.yml deleted file mode 100644 index 5ab74d0..0000000 --- a/.forgejo/workflows/rc-release.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: beta (rc) release - -# Publishes a beta-track build for any `vX.Y.Z-rc.N` prerelease tag -# pushed to `main` — there is no separate `beta` branch; "freezing" is -# just pausing pushes to main while an RC gets tested. See -# bread-ecosystem's docs/release-channels.md for the release-track policy. -on: - push: - tags: ['v*'] - -jobs: - build: - if: ${{ contains(github.ref_name, '-rc.') }} - runs-on: [self-hosted, hestia] - steps: - - name: checkout - run: | - set -euo pipefail - rm -rf src && mkdir src - git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ - "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - - name: build - run: cd src && bash ci/build.sh cargo build --release --locked - - - name: prepare artifacts - run: | - set -euo pipefail - VERSION="${GITHUB_REF_NAME#v}" - PKG_DIR="/srv/breadway-dl/beta/breadbox/${VERSION}" - mkdir -p "${PKG_DIR}" - for bin in breadbox breadbox-sync; do - cp "src/target/release/${bin}" "${PKG_DIR}/${bin}-x86_64" - strip "${PKG_DIR}/${bin}-x86_64" - sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \ - > "${PKG_DIR}/${bin}-x86_64.sha256" - done - cp src/packaging/breadbox-sync.service "${PKG_DIR}/" - cp src/config.example.toml "${PKG_DIR}/" - cp src/LICENSE "${PKG_DIR}/" - cp src/bakery.toml "${PKG_DIR}/bakery.toml" - ln -sfn "${VERSION}" "/srv/breadway-dl/beta/breadbox/latest" - - # No GitHub Release upload — beta, like dev, is only distributed via - # dl.breadway.dev/beta/. - - name: regenerate beta 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 beta index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone on the beta track)" - exit 1 - fi - rm -rf /tmp/bread-ecosystem-ci-* 2>/dev/null || true - # mktemp: a fixed clone path races when multiple repos' dev/beta - # workflows run close together on the same self-hosted runner. - ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" - git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" - TRACK=beta bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" - rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 8f8c6dd..de309ae 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -6,7 +6,6 @@ on: jobs: build: - if: ${{ !contains(github.ref_name, '-rc.') }} runs-on: [self-hosted, hestia] steps: - name: checkout @@ -17,16 +16,7 @@ jobs: "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - name: build - 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 - } + run: cd src && cargo build --release --locked - name: prepare artifacts run: | @@ -42,19 +32,12 @@ jobs: done cp src/packaging/breadbox-sync.service "${PKG_DIR}/" cp src/config.example.toml "${PKG_DIR}/" - cp src/LICENSE "${PKG_DIR}/" cp src/bakery.toml "${PKG_DIR}/bakery.toml" 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 diff --git a/.github/README.md b/.github/README.md deleted file mode 100644 index 4cb7ee5..0000000 --- a/.github/README.md +++ /dev/null @@ -1 +0,0 @@ -Forgejo (`.forgejo/workflows`) is the canonical CI. This `.github` tree is unused. diff --git a/.gitignore b/.gitignore index ec10cb0..f3bd9af 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,3 @@ Thumbs.db # Claude Code session data .claude/ - -# Local hygiene notes (not for commit) -CLAUDE.md - -# graphify knowledge-graph output (local tool cache, not for commit) -graphify-out/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 6911f08..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,13 +0,0 @@ -# AGENTS.md — Repo hygiene - -Follow [`CONTRIBUTING.md`](CONTRIBUTING.md). Single-trunk: `main` plus short-lived `feature/` / `fix/` branches. `dev`/`beta` are bakery tracks (tags / main), not git branches. - -## Remotes -- `origin` — Forgejo (`git.breadway.dev`) — authoritative. -- `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. `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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5ae0190..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,84 +0,0 @@ -# Contributing - -`breadbox` — App launcher for Hyprland / Wayland. - -Part of the bread ecosystem; this repo follows the same branch/release -workflow as every other ecosystem product. - -## Branches - -There is one long-lived branch: **`main`**. All day-to-day work lands here. -Every push to `main` automatically builds and publishes a **dev-track** -build (see Tracks below) — a real install you can test before cutting -anything more formal. - -New work — features and bug fixes alike — goes on a short-lived branch: - -``` -feature/ -fix/ -``` - -Branch off `main`, open a PR/push back into `main` when ready. Short-lived -branches get deleted on merge — they never accumulate the kind of drift a -second long-lived branch does. - -## The release cycle - -There's no separate `beta` or release branch — "stable" and "beta" are both -just **tags** on `main`, not branches that need to be kept in sync: - -1. Work accumulates on `main` via `feature/x` / `fix/x` branches. Each push - auto-publishes a dev build — install it with `bakery track set dev` and - `bakery update --all`, then fix anything broken with another push. -2. When you want to stabilize before a real release, tag a release - candidate: `git tag vX.Y.Z-rc.1 && git push origin vX.Y.Z-rc.1` (push to - both remotes). That tag alone triggers a beta-track build — - "freezing" is just pausing pushes to `main` while you test it, not a - branch operation. Cut `-rc.2`, `-rc.3`, etc. for further fixes. -3. Once an RC has gone without issues, tag the real release: - `git tag vX.Y.Z && git push origin vX.Y.Z` — that's what triggers the - signed stable release build. - -## Tracks, from a user's perspective - -``` -bakery track show # what you're currently on (defaults to stable) -bakery track set dev # or beta, or stable -bakery update --all # pull the latest build on your current track -``` - -| Track | What it is | Published from | -|--------|-----------|-----------------| -| `stable` | The last tagged release | a `vX.Y.Z` tag | -| `beta` | Latest release candidate | a `vX.Y.Z-rc.N` tag | -| `dev` | Bleeding edge | `main`, on every push | - -Dev versions are auto-computed (`X.Y.Z-dev.+`) from the -latest published stable tag, so they always sort as newer than what you -have installed — no manual version bumping needed. Beta versions are just -the RC tag itself (already valid semver, already sorts below the real -release it's a candidate for). - -## Local development - -```sh -cargo build --release --workspace -cargo test --release --workspace -``` - -## CI - -- `dev-release.yml` — triggered on push to `main`. -- `rc-release.yml` — triggered on any `vX.Y.Z-rc.N` tag push. -- `release.yml` — triggered on any other `v*` tag push, cuts the actual - stable release. - -All CI runs on a self-hosted runner; nothing runs automatically on plain -commits or PRs beyond the track builds above. See -[bread-ecosystem's docs/release-channels.md](https://git.breadway.dev/Breadway/bread-ecosystem/src/branch/main/docs/release-channels.md) -for the full policy, including how a new product gets wired onto these tracks. - -## Questions - -Open an issue on this repo's Forgejo tracker. diff --git a/Cargo.lock b/Cargo.lock index b47dd23..a82d64b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,62 +8,6 @@ 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 = "autocfg" version = "1.5.1" @@ -78,35 +22,14 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "bread-screenshots" -version = "0.7.2" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.2#30517f161724132cdeb658c04cf5e490be07ee73" -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", - "serde", - "serde_json", - "toml 0.8.23", -] +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bread-theme" -version = "0.7.4" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.4#fcba3760387e2523edb71350f8efea3bc851b21e" +version = "0.2.3" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.8#77417d552130281ff787e07d52541eb25e9d533b" dependencies = [ "dirs", "gtk4", @@ -114,29 +37,12 @@ dependencies = [ "serde_json", ] -[[package]] -name = "bread-utils" -version = "0.7.2" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.2#30517f161724132cdeb658c04cf5e490be07ee73" -dependencies = [ - "bread-shared", - "dirs", - "gtk4", - "gtk4-layer-shell", - "serde", - "serde_json", -] - [[package]] name = "breadbox" -version = "0.3.3" +version = "0.2.6" dependencies = [ - "anyhow", - "bread-screenshots", "bread-theme", - "bread-utils", "breadbox-shared", - "clap", "gtk4", "gtk4-layer-shell", "serde_json", @@ -144,7 +50,7 @@ dependencies = [ [[package]] name = "breadbox-shared" -version = "0.3.3" +version = "0.2.6" dependencies = [ "serde", "serde_json", @@ -153,7 +59,7 @@ dependencies = [ [[package]] name = "breadbox-sync" -version = "0.3.3" +version = "0.2.6" dependencies = [ "breadbox-shared", "serde_json", @@ -180,14 +86,14 @@ checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" dependencies = [ "glib-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] name = "cc" -version = "1.4.3" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -209,52 +115,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "clap" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" -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 = "crc32fast" version = "1.5.0" @@ -287,13 +147,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] @@ -314,9 +174,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -339,24 +199,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -365,32 +225,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "futures-task" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.34" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", @@ -421,14 +281,14 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] name = "gdk4" -version = "0.11.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39" +checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -442,9 +302,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.11.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13" +checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -454,7 +314,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -470,9 +330,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.22.8" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" +checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6" dependencies = [ "futures-channel", "futures-core", @@ -487,14 +347,14 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.22.8" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" +checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 7.0.8", + "system-deps", "windows-sys 0.61.2", ] @@ -520,9 +380,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.22.8" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" +checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" dependencies = [ "bitflags", "futures-channel", @@ -548,17 +408,17 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "glib-sys" -version = "0.22.8" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" +checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445" dependencies = [ "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -569,35 +429,37 @@ checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" dependencies = [ "glib-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] name = "graphene-rs" -version = "0.22.8" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff" +checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" dependencies = [ "glib", "graphene-sys", + "libc", ] [[package]] name = "graphene-sys" -version = "0.22.8" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04" +checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" dependencies = [ "glib-sys", "libc", - "system-deps 7.0.8", + "pkg-config", + "system-deps", ] [[package]] name = "gsk4" -version = "0.11.4" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff" +checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" dependencies = [ "cairo-rs", "gdk4", @@ -610,9 +472,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.11.4" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088" +checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -621,14 +483,14 @@ dependencies = [ "graphene-sys", "libc", "pango-sys", - "system-deps 7.0.8", + "system-deps", ] [[package]] name = "gtk4" -version = "0.11.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9" +checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2" dependencies = [ "cairo-rs", "field-offset", @@ -647,9 +509,9 @@ dependencies = [ [[package]] name = "gtk4-layer-shell" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c28ea0f4676fdaaae7ff2413a24d0d35c8657424f84856c1103c73454c9da4" +checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a" dependencies = [ "bitflags", "gdk4", @@ -662,34 +524,34 @@ dependencies = [ [[package]] name = "gtk4-layer-shell-sys" -version = "0.6.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf19bb884ef0ef55b9e6b2b369c39b4fcc0c41e3a0c1cbc8c267720338b690b" +checksum = "8f566a5ec5bcc454e7fcf2ab76930887ced5365afce12c1e5201bb296b95f1b9" dependencies = [ "gdk4-sys", "glib-sys", "gtk4-sys", "libc", - "system-deps 8.0.0", + "system-deps", ] [[package]] name = "gtk4-macros" -version = "0.11.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e" +checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "gtk4-sys" -version = "0.11.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01" +checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -701,7 +563,7 @@ dependencies = [ "gsk4-sys", "libc", "pango-sys", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -718,9 +580,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", @@ -732,9 +594,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -745,9 +607,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -759,17 +621,16 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -780,15 +641,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -830,12 +691,6 @@ 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" @@ -850,36 +705,36 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "libc" -version = "0.2.189" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] [[package]] name = "litemap" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.33" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "memchr" -version = "2.8.3" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memoffset" @@ -906,12 +761,6 @@ 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" @@ -920,12 +769,13 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "pango" -version = "0.22.8" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" +checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438" dependencies = [ "gio", "glib", + "libc", "pango-sys", ] @@ -938,7 +788,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -955,15 +805,15 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "potential_utf" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -974,23 +824,23 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] name = "proc-macro2" -version = "1.0.107" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.47" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1031,9 +881,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -1046,18 +896,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.1" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.14" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -1072,9 +922,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1082,29 +932,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1139,9 +989,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -1151,9 +1001,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" @@ -1161,12 +1011,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" @@ -1175,20 +1019,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.119" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1203,7 +1036,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] @@ -1215,20 +1048,7 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "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", + "toml 1.1.2+spec-1.1.0", "version-compare", ] @@ -1255,14 +1075,14 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -1282,9 +1102,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", @@ -1292,7 +1112,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] @@ -1329,23 +1149,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] @@ -1356,40 +1176,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "unicode-ident" @@ -1437,12 +1226,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "version-compare" version = "0.2.1" @@ -1461,14 +1244,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.9", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.9" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -1638,24 +1421,24 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] name = "writeable" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xml-rs" -version = "0.8.29" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "yoke" @@ -1676,7 +1459,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", "synstructure", ] @@ -1697,21 +1480,21 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -1720,9 +1503,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.7" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -1731,17 +1514,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "zmij" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/EVENTS.md b/EVENTS.md deleted file mode 100644 index 3e7064a..0000000 --- a/EVENTS.md +++ /dev/null @@ -1,65 +0,0 @@ -# breadbox — bread event integration - -breadbox is a standalone app launcher: it works exactly the same with or -without `breadd` running. When breadd *is* present, the GTK launcher -publishes a single event into the shared bread automation fabric after a -successful launch. See the parent `bread` repo's `Documentation.md` — -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. 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": "", "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": "" }` | `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 -for the exec line's arguments or the resulting process. - -## Commands honored (`bread.command.box.*`) - -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) 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. diff --git a/README.md b/README.md index b2d4f32..b829c6c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ breadbox GTK4 layer-shell launcher - Layer-shell window, centered 600 px wide, keyboard-exclusive - Reads the active Hyprland workspace and sorts apps by context priority -- Fuzzy filtering as you type; Enter launches, Escape closes +- Launch history: non-priority apps sort by most-launched first, then alphabetically +- Fuzzy filtering as you type; Enter or click to launch, Escape or click outside to close - App icons loaded from the resolved icon cache (see `breadbox-sync`) -- pywal accents from `~/.cache/wal/colors.json`; background/surface/overlay/foreground stay fixed BOS dark +- pywal palette auto-detected from `~/.cache/wal/colors.json`, falls back to Catppuccin Mocha - User CSS override at `~/.config/breadbox/style.css` - Toggle/dismiss: running a second instance kills the first @@ -88,10 +89,9 @@ breadbox-sync ``` Icon resolution order: -1. System icon theme (`~/.local/share/icons`, `/usr/share/icons`, `/usr/share/pixmaps`) — 64 px > 48 px PNG, then SVG -2. Flathub media server — for reverse-DNS app IDs (e.g. `org.gnome.Gedit`) -3. icon.horse — downloaded and cached -4. `application-x-executable` fallback from system theme +1. System icon theme (`~/.local/share/icons`, `/usr/share/icons`, `/usr/share/pixmaps`) — 64 px > 48 px > 128 px > 32 px > 256 px PNG, then SVG +2. Flathub appstream CDN — for reverse-DNS app IDs (e.g. `org.gnome.Gedit`) +3. `application-x-executable` fallback from system theme ### Systemd service (run on login) diff --git a/bakery.toml b/bakery.toml index 6ad1675..f2abae4 100644 --- a/bakery.toml +++ b/bakery.toml @@ -4,7 +4,6 @@ binaries = ["breadbox", "breadbox-sync"] system_deps = ["gtk4", "gtk4-layer-shell", "librsvg"] optional_system_deps = ["hyprland"] bread_deps = [] -license_file = "LICENSE" [[service]] unit = "breadbox-sync.service" diff --git a/breadbox-shared/Cargo.toml b/breadbox-shared/Cargo.toml index 2c583be..f69d7a1 100644 --- a/breadbox-shared/Cargo.toml +++ b/breadbox-shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbox-shared" -version = "0.3.3" +version = "0.2.6" edition = "2021" license = "MIT" diff --git a/breadbox-shared/src/lib.rs b/breadbox-shared/src/lib.rs index 92c573b..5286729 100644 --- a/breadbox-shared/src/lib.rs +++ b/breadbox-shared/src/lib.rs @@ -54,9 +54,6 @@ pub fn app_dirs() -> Vec { #[derive(Debug, Clone)] pub struct DesktopEntry { - /// Desktop file id (the `.desktop` filename, e.g. `firefox.desktop`). - /// Empty only if the path had no file name; callers fall back to `exec`. - pub id: String, pub name: String, pub exec: String, pub icon_name: String, @@ -158,14 +155,7 @@ pub fn parse_desktop(path: &Path) -> Option { .map(|s| s.to_string()) .collect(); - let id = path - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .filter(|s| !s.is_empty()) - .unwrap_or_default(); - Some(DesktopEntry { - id, name, exec, icon_name, diff --git a/breadbox-sync/Cargo.toml b/breadbox-sync/Cargo.toml index 218db09..d85f1dc 100644 --- a/breadbox-sync/Cargo.toml +++ b/breadbox-sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbox-sync" -version = "0.3.3" +version = "0.2.6" edition = "2021" license = "MIT" diff --git a/breadbox/Cargo.toml b/breadbox/Cargo.toml index fde186f..9d3f28b 100644 --- a/breadbox/Cargo.toml +++ b/breadbox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadbox" -version = "0.3.3" +version = "0.2.6" edition = "2021" license = "MIT" @@ -9,13 +9,8 @@ name = "breadbox" path = "src/main.rs" [dependencies] -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" } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.8", features = ["gtk"] } breadbox-shared = { path = "../breadbox-shared" } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" serde_json = "1" -clap = { version = "4", features = ["derive"] } -anyhow = "1" diff --git a/breadbox/src/listen.rs b/breadbox/src/listen.rs deleted file mode 100644 index 9aeb367..0000000 --- a/breadbox/src/listen.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! 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 { - 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); - } -} diff --git a/breadbox/src/main.rs b/breadbox/src/main.rs index b29b80c..e5e5840 100644 --- a/breadbox/src/main.rs +++ b/breadbox/src/main.rs @@ -1,31 +1,27 @@ use bread_theme::{hex_to_rgba, ink_on, load_palette, Palette}; -use bread_utils::bread_client::BreadClient; use std::{ - cell::{Cell, RefCell}, + 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 -/// (`bread_shared::apps::KNOWN_APPS`) — events publish as `bread.box.*`. -const APP_ID: &str = "box"; - use breadbox_shared::{ config_dir, load_all_desktop_entries, Config, DesktopEntry, IconCache, LaunchHistory, }; use gtk4::{ - glib, pango::EllipsizeMode, prelude::*, Application, Box as GBox, CssProvider, Entry, - EventControllerKey, Label, ListBox, Orientation, PolicyType, ScrolledWindow, SelectionMode, + glib, + pango::EllipsizeMode, + prelude::*, + Application, ApplicationWindow, Box as GBox, CssProvider, EventControllerKey, Label, + ListBox, Orientation, PolicyType, ScrolledWindow, SearchEntry, SelectionMode, }; - -mod listen; -mod screenshot; +use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell}; // ---- Hyprland IPC ----------------------------------------------------------- @@ -86,9 +82,7 @@ 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())) } } @@ -136,171 +130,58 @@ 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.68); + let bg_panel = hex_to_rgba(&p.background, 0.60); // 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, hovered/selected row) so + // Colour is set on each surface (panel, search box, hovered/selected row) so // child labels inherit the legible ink for that background. `on_*` are - // 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::>() - .join(""); + // luminance-picked black/white — the pywal hues are untouched. Without this a + // light `surface` slot makes the selected row's text vanish. format!( - "\ -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, + "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), ) } -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(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"); +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); img } @@ -323,42 +204,24 @@ fn pick_terminal() -> String { fn do_launch(entry: &DesktopEntry) { let cmd = entry.exec.trim(); - let spawned = if entry.terminal { + if entry.terminal { let term = pick_terminal(); - Command::new(&term) + let _ = Command::new(&term) .args(["-e", "bash", "-c", cmd]) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn() + .spawn(); } else { - Command::new("bash") + let _ = Command::new("bash") .args(["-c", cmd]) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn() - }; - if spawned.is_ok() { - emit_launched(entry); + .spawn(); } } -/// Publishes `bread.box.launched` after a successful spawn. Fire-and-forget -/// and non-fatal (`BreadClient::emit` never blocks or errors this caller) — -/// breadd being absent must never affect launching itself. -fn emit_launched(entry: &DesktopEntry) { - let id = if entry.id.is_empty() { - entry.exec.as_str() - } else { - entry.id.as_str() - }; - BreadClient::connect(APP_ID).emit( - "bread.box.launched", - serde_json::json!({ "id": id, "name": entry.name }), - ); -} - // ---- Fuzzy matching --------------------------------------------------------- fn fuzzy_matches(pattern: &str, text: &str) -> bool { @@ -379,22 +242,84 @@ fn fuzzy_matches(pattern: &str, text: &str) -> bool { } 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; } + 4 // subsequence match +} + +/// Same tiers as `fuzzy_score`, but `None` when nothing matches at all +/// (rather than falling through to a bare-subsequence score), and folds in +/// `exec` as a tier-4 (weakest) match too — used for filtering, not sorting. +/// Tier 4 is loose enough that e.g. querying "zen" matches "Avahi Zeroconf +/// Browser" (z…e…n as a subsequence) alongside the real "Zen Browser" hit; +/// the filter hides tier-4-only rows whenever a tier ≤2 (name-based) match +/// exists elsewhere in the list, so that kind of noise only shows up when +/// it's the best any entry can do. +fn match_tier(query: &str, entry: &DesktopEntry) -> Option { + if query.is_empty() { + return Some(0); + } 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; + return Some(0); } if name.starts_with(&q) { - return 1; + return Some(1); } if name.contains(&q) { - return 2; + return Some(2); } if wm.starts_with(&q) || wm.contains(&q) { - return 3; + return Some(3); } - 4 // subsequence match + if fuzzy_matches(query, &entry.name) + || entry.wm_class.as_deref().is_some_and(|w| fuzzy_matches(query, w)) + || fuzzy_matches(query, &entry.exec) + { + return Some(4); + } + None +} + +// ---- PID file toggle -------------------------------------------------------- + +fn pid_file() -> PathBuf { + env::var("XDG_RUNTIME_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("/tmp")) + .join("breadbox.pid") +} + +fn is_breadbox_pid(pid: u32) -> bool { + fs::read_to_string(format!("/proc/{}/comm", pid)) + .map(|s| s.trim() == "breadbox") + .unwrap_or(false) +} + +// Returns false if an existing instance was killed (caller should exit). +fn toggle_or_continue() -> bool { + let pf = pid_file(); + if let Ok(content) = fs::read_to_string(&pf) { + if let Ok(pid) = content.trim().parse::() { + if is_breadbox_pid(pid) { + let _ = Command::new("kill").arg(pid.to_string()).status(); + return false; + } + } + } + let _ = fs::write(&pf, std::process::id().to_string()); + true +} + +fn cleanup_pid() { + let _ = fs::remove_file(pid_file()); } // ---- UI --------------------------------------------------------------------- @@ -406,44 +331,13 @@ fn get_row_entry(row: >k4::ListBoxRow) -> Option { } } -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, - history: LaunchHistory, - screenshot_req: Option, -) { - let mut builder = Application::builder().application_id("com.breadway.breadbox"); - if screenshot_req.is_some() { - // GApplication is single-instance by default; this machine typically - // already has a real breadbox instance, so without this a screenshot - // run would just message the *existing* instance instead of - // starting a fresh one that ever sees `screenshot_req`. - builder = builder.flags(gtk4::gio::ApplicationFlags::NON_UNIQUE); - } - let app = builder.build(); +fn run_ui(entries: Vec, history: LaunchHistory) { + let app = Application::builder() + .application_id("com.breadway.breadbox") + .build(); let history_rc = Rc::new(RefCell::new(history)); let query_rc: Rc> = Rc::new(RefCell::new(String::new())); - let is_screenshot_run = screenshot_req.is_some(); app.connect_activate(move |app| { // Shared ecosystem base (fonts, palette, generic widgets) first, then @@ -459,13 +353,21 @@ fn run_ui( bread_theme::gtk::apply_user_css(&user_css_path, &user_cell); } - // 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); + // 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); let close_all: Rc = Rc::new({ let w = window.clone(); move || { + cleanup_pid(); w.close(); } }); @@ -474,25 +376,13 @@ 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(88); + vbox.set_margin_top(120); 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 = Entry::new(); - search.set_placeholder_text(Some("Search")); - search.set_has_frame(false); + let search = SearchEntry::new(); + search.set_placeholder_text(Some("Search apps…")); 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); @@ -503,44 +393,28 @@ 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.add_css_class("app-row"); + hbox.set_margin_start(6); + hbox.set_margin_end(6); hbox.set_valign(gtk4::Align::Center); - 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 icon = make_icon(&entry.icon_name, entry.icon_path.as_deref()); + hbox.append(&icon); 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); - text.append(&name_lbl); + hbox.append(&name_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); + 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); + } - hbox.append(&text); row.set_child(Some(&hbox)); unsafe { row.set_data("entry", entry.clone()) }; unsafe { row.set_data("initial_order", idx as u32) }; @@ -554,16 +428,8 @@ 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::("initial_order") - .map_or(u32::MAX, |p| *p.as_ref()) - }; - let ob = unsafe { - row_b - .data::("initial_order") - .map_or(u32::MAX, |p| *p.as_ref()) - }; + let oa = unsafe { row_a.data::("initial_order").map_or(u32::MAX, |p| *p.as_ref()) }; + let ob = unsafe { row_b.data::("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 { @@ -586,66 +452,40 @@ 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)); - 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. + // Filter on keystroke 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(); *filter_query.borrow_mut() = query.to_string(); + + // Two passes: first collect each row's match tier, then decide + // visibility — a tier-4 (bare subsequence) row only gets hidden + // once we know whether some *other* row has a real tier ≤2 hit. + let mut rows = Vec::new(); let mut i = 0i32; while let Some(row) = list_f.row_at_index(i) { - 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)) - || fuzzy_matches(query, &e.exec) - }) - .unwrap_or(false); - row.set_visible(vis); + let tier = get_row_entry(&row).and_then(|e| match_tier(query, &e)); + rows.push((row, tier)); i += 1; } - list_f.invalidate_sort(); - 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"); - } - }); + let has_direct_hit = rows.iter().any(|(_, t)| matches!(t, Some(0..=2))); + for (row, tier) in &rows { + let vis = match tier { + None => false, + Some(4) if has_direct_hit => false, + Some(_) => true, + }; + row.set_visible(vis); } + list_f.invalidate_sort(); + 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()); }); // Keyboard handling — capture phase on window @@ -673,11 +513,36 @@ fn run_ui( glib::Propagation::Stop } Key::Down => { - bread_utils::gtk_popup::select_next_visible(&list_k); + 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, + } + } glib::Propagation::Stop } Key::Up => { - bread_utils::gtk_popup::select_prev_visible(&list_k); + 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, + } + } glib::Propagation::Stop } _ => glib::Propagation::Proceed, @@ -698,65 +563,38 @@ fn run_ui( }); // Click outside launcher panel → close - { - 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); - } + 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); + window.connect_destroy(|_| cleanup_pid()); window.present(); search.grab_focus(); }); - if is_screenshot_run { - // GLib's own option parser otherwise rejects --screenshot/--output - // before clap ever sees them (`Cli::parse()` already ran in `main`, - // over the real argv). - app.run_with_args(&[] as &[&str]); - } else { - app.run(); - } + app.run(); } // ---- Main ------------------------------------------------------------------- fn main() { - if std::env::args().nth(1).as_deref() == Some("listen") { - listen::run(); + if !toggle_or_continue() { return; } - use clap::Parser; - let cli = screenshot::Cli::parse(); - let screenshot_req = cli.screenshot_request(); - - // `toggle_or_kill` kills whatever's holding the single-instance lock — - // a real, already-running breadbox included. A screenshot run must - // never touch it: it's a separate, disposable instance by design (same - // reasoning as breadbar's `allow_multiple_instances`), not a toggle of - // the operator's real launcher. - // - // Kept alive for the rest of `main` — dropping it releases the - // single-instance lock and removes the pid file, which happens - // naturally once `run_ui` returns (after the window closes). - let _singleton_guard = if screenshot_req.is_some() { - None - } else { - match bread_utils::singleton::toggle_or_kill("breadbox") { - 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" - ); - None - } - } - }; - let config = Config::load(); let workspace = get_active_workspace().unwrap_or_default(); let priority = config @@ -768,5 +606,5 @@ fn main() { let manifest = load_manifest(); let entries = load_sorted_entries(&manifest, &priority, &history); - run_ui(entries, history, screenshot_req); + run_ui(entries, history); } diff --git a/breadbox/src/screenshot.rs b/breadbox/src/screenshot.rs deleted file mode 100644 index a1b4edc..0000000 --- a/breadbox/src/screenshot.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! `--screenshot` CLI mode: render breadbox's launcher panel, capture it via -//! `bread-screenshots`, then exit — driven by `bread-ecosystem`'s -//! `bread-capture` orchestrator, or run standalone for one-off captures. -//! -//! breadbox has only one view worth capturing: the launcher panel itself -//! (search box + app list). It's a `halign: Center` panel over a full-screen -//! transparent overlay window, not its own layer surface, so — same -//! reasoning as breadbar's control-panel view — the simplest reliable -//! 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; - -#[derive(Parser)] -#[command(name = "breadbox")] -pub struct Cli { - /// Render the named view, capture it, then exit instead of running - /// normally. Known views: "launcher". - #[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`). - #[arg(long, default_value_t = DEFAULT_WIDTH)] - pub width: u32, - - /// Capture canvas height — see `width`. - #[arg(long, default_value_t = DEFAULT_HEIGHT)] - pub height: u32, -} - -#[derive(Clone)] -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 the - /// `--screenshot` / `--output` pair is incomplete, before any GTK setup - /// happens. - pub fn screenshot_request(&self) -> Option { - if let Err(e) = validate_pair(self.screenshot.as_deref(), self.output.as_deref()) { - eprintln!("breadbox: {e}"); - std::process::exit(1); - } - Some(ScreenshotRequest { - view: self.screenshot.clone()?, - output: self.output.clone()?, - width: self.width, - height: self.height, - }) - } -} - -/// Wire up the given view's screenshot sequence against an already-built, -/// not-yet-presented window. Every path here ends by exiting the process — -/// it never returns control to the normal launcher UI. -pub fn dispatch(window: >k4::ApplicationWindow, req: ScreenshotRequest) { - match req.view.as_str() { - "launcher" => { - let output = req.output; - let (width, height) = (req.width as i32, req.height as 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)); - }); - }); - } - other => { - eprintln!("breadbox: unknown screenshot view '{other}' (known: launcher)"); - std::process::exit(1); - } - } -} - -fn finish(result: anyhow::Result<()>) { - match result { - Ok(()) => std::process::exit(0), - Err(e) => { - eprintln!("breadbox: screenshot capture failed: {e}"); - std::process::exit(1); - } - } -} diff --git a/ci/bread-ecosystem.rev b/ci/bread-ecosystem.rev deleted file mode 100644 index 34e7aa9..0000000 --- a/ci/bread-ecosystem.rev +++ /dev/null @@ -1 +0,0 @@ -147cfbbf96ae4b171027defa1130d2caddb934b1 diff --git a/ci/build.sh b/ci/build.sh deleted file mode 100755 index a877acf..0000000 --- a/ci/build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -# Delegates to bread-ecosystem's shared CI build image/script, pinned to -# the commit in ci/bread-ecosystem.rev — not `main`. bread-ecosystem's CI -# files now affect every product's release pipeline, so bumping the pin -# is a deliberate act instead of silent drift (see the bread-theme test -# that broke here for exactly that reason, before it was pinned by rev). -# -# Usage: ci/build.sh cargo build --release --locked -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -REV="$(cat "${ROOT}/ci/bread-ecosystem.rev")" - -CACHE_DIR="/tmp/bread-ecosystem-ci-${REV}" -if [ ! -d "$CACHE_DIR" ]; then - rm -rf /tmp/bread-ecosystem-ci-* - git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "$CACHE_DIR" - git -C "$CACHE_DIR" checkout --quiet "$REV" -fi - -bash "${CACHE_DIR}/ci/build.sh" breadbox "$ROOT" "$@" diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD new file mode 100644 index 0000000..1df5ba5 --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,39 @@ +# Maintainer: Breadway + +pkgname=breadbox +pkgver=0.1.0 +pkgrel=1 +pkgdesc="App launcher for Hyprland / Wayland" +arch=('x86_64') +url="https://github.com/Breadway/breadbox" +license=('MIT') +# Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's +# default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read, +# causing undefined-symbol errors. Disable LTO. +options=(!lto !debug) +depends=('gtk4' 'gtk4-layer-shell' 'librsvg') +optdepends=( + 'hyprland: window and workspace integration' +) +makedepends=('rust' 'cargo') +source=("${pkgname}-${pkgver}.tar.gz") +sha256sums=('SKIP') + +build() { + cd "${srcdir}/${pkgname}-${pkgver}" + cargo build --release --locked +} + +check() { + cd "${srcdir}/${pkgname}-${pkgver}" + cargo test --release --locked --workspace +} + +package() { + cd "${srcdir}/${pkgname}-${pkgver}" + install -Dm755 target/release/breadbox "${pkgdir}/usr/bin/breadbox" + install -Dm755 target/release/breadbox-sync "${pkgdir}/usr/bin/breadbox-sync" + install -Dm644 packaging/breadbox-sync.service \ + "${pkgdir}/usr/lib/systemd/user/breadbox-sync.service" + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" +}