diff --git a/.forgejo/workflows/calamares.yml b/.forgejo/workflows/calamares.yml index 80637f9..26b90b0 100644 --- a/.forgejo/workflows/calamares.yml +++ b/.forgejo/workflows/calamares.yml @@ -24,7 +24,12 @@ jobs: kcoreaddons kpmcore libpwquality qt6-declarative qt6-svg yaml-cpp useradd -m builder git config --global --add safe.directory '*' - git clone --depth 1 "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src + # Clone the branch/tag that triggered this run (not the default + # branch) — same as bibata.yml/powerlevel10k.yml/yay-bin.yml, so a + # push to a feature branch (or a release tag) builds and publishes + # from that ref, not whatever happens to be on the default branch. + git clone --depth 1 --branch "${GITHUB_REF_NAME}" \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src chown -R builder:builder /home/builder/src su builder -c "cd /home/builder/src/packaging/calamares && makepkg -f --noconfirm --nocheck" PKG=$(find /home/builder/src/packaging/calamares -name '*.pkg.tar.zst' | head -1) diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml deleted file mode 100644 index aaf7eb7..0000000 --- a/.forgejo/workflows/package.yml +++ /dev/null @@ -1,40 +0,0 @@ -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 glib2 - 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="bos-settings-${VERSION}/" HEAD \ - > packaging/arch/bos-settings-${VERSION}.tar.gz - SHA=$(sha256sum packaging/arch/bos-settings-${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/release-iso.yml b/.forgejo/workflows/release-iso.yml index 27c2a34..862368e 100644 --- a/.forgejo/workflows/release-iso.yml +++ b/.forgejo/workflows/release-iso.yml @@ -111,8 +111,18 @@ jobs: run: | set -euo pipefail # bread-theme is not in the bakery index; build it at the tag pinned - # in bos-settings/Cargo.toml so the CLI matches the library version. - THEME_TAG=$(grep 'bread-theme.*tag' /bos/bos-settings/Cargo.toml \ + # in bos-settings' Cargo.toml so the CLI matches the library version + # the bos-settings package (and breadbar/breadbox/breadpad) were + # built against. bos-settings used to live at bos-settings/Cargo.toml + # inside this repo; it's since been split into its own repo + # (git.breadway.dev/Breadway/bos-settings), so fetch its Cargo.toml + # from there instead of a path that no longer exists in this + # checkout. Uses bos-settings' default branch (dev) — the branch its + # own CI actually publishes the `bos-settings` pacman package from. + REPO_OWNER="${GITHUB_REPOSITORY%%/*}" + curl -fsSL "https://git.breadway.dev/${REPO_OWNER}/bos-settings/raw/branch/dev/Cargo.toml" \ + -o /tmp/bos-settings-Cargo.toml + THEME_TAG=$(grep 'bread-theme.*tag' /tmp/bos-settings-Cargo.toml \ | grep -oP '"v[^"]+"' | tr -d '"') echo "Building bread-theme @ $THEME_TAG" git clone --branch "$THEME_TAG" --depth 1 \ diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index bc9c049..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,1039 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - -[[package]] -name = "bos-settings" -version = "0.6.0" -dependencies = [ - "async-channel", - "bread-theme", - "glib", - "gtk4", - "serde", - "serde_json", - "toml 0.8.23", - "toml_edit 0.22.27", -] - -[[package]] -name = "bread-theme" -version = "0.2.3" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.10#17d1bb85801b9a8c195b64c02d288cd662c9c780" -dependencies = [ - "dirs", - "gtk4", - "serde", - "serde_json", -] - -[[package]] -name = "cairo-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" -dependencies = [ - "bitflags", - "cairo-sys-rs", - "glib", - "libc", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "cfg-expr" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk4" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk4-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk4-sys" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gio" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.59.0", -] - -[[package]] -name = "glib" -version = "0.22.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "graphene-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" -dependencies = [ - "glib", - "graphene-sys", - "libc", -] - -[[package]] -name = "graphene-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" -dependencies = [ - "glib-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gsk4" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" -dependencies = [ - "cairo-rs", - "gdk4", - "glib", - "graphene-rs", - "gsk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gsk4-sys" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" -dependencies = [ - "cairo-sys-rs", - "gdk4-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk4" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2" -dependencies = [ - "cairo-rs", - "field-offset", - "futures-channel", - "gdk-pixbuf", - "gdk4", - "gio", - "glib", - "graphene-rs", - "gsk4", - "gtk4-macros", - "gtk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gtk4-macros" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gtk4-sys" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "gsk4-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libredox" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "pango" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438" -dependencies = [ - "gio", - "glib", - "libc", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "system-deps" -version = "7.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml 1.1.2+spec-1.1.0", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.3", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_edit" -version = "0.25.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.3", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.3", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" -dependencies = [ - "memchr", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index af9532d..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[workspace] -members = ["bos-settings"] -resolver = "2" diff --git a/README.md b/README.md index b736e5d..7eb401b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # BOS — Bread Operating System An Arch-based, Hyprland desktop distribution that ships the [bread -ecosystem](https://github.com/Breadway) preconfigured. One Calamares install +ecosystem](https://git.breadway.dev/Breadway) preconfigured. One Calamares install produces a themed, bootable Wayland desktop — no manual Arch bootstrap, no wiring up dotfiles, no per-tool bakery installs. @@ -14,9 +14,15 @@ wiring up dotfiles, no per-tool bakery installs. - **Compositor**: Hyprland with a native-Lua config (`hyprland.lua`), curated keybinds, snappy animations, blur, and pywal-driven colours on a black base. - **bread ecosystem**, baked into `/etc/skel` from bakery-managed binaries - (no network needed at install time): `bread`/`breadd`, `breadbar` (status bar - + notification daemon), `breadbox` (launcher), `breadcrumbs` (Wi-Fi profiles), - `breadpad` (notes/reminders), `breadman`, and the `bakery` package manager. + (no network needed at install time): the `bread`/`breadd` automation daemon, + `breadbar` (status bar + notifications), `breadbox` (launcher), `breadclip` + (clipboard history), `breadcrumbs` (Wi-Fi profiles), `breadpad`/`breadman` + (notes), `breadpaper` (wallpaper + theme), `breadsearch` (system search), + `breadmon` (monitor layout TUI), `breadshot` (screenshots), `bread-theme` + (the shared palette engine), and the `bakery` package manager. `breadlock` + (lock screen + greeter) ships as its own pacman package alongside + `bos-settings`, not through bakery. See [below](#the-bread-ecosystem) for + what each one actually does. - **bos-settings**: a GTK4 control panel that configures every bread\* app's config from a GUI (non-destructively), plus snapshot rollback and bakery updates. See below. @@ -162,18 +168,49 @@ cargo build --release -p bos-settings cargo test -p bos-settings # includes config round-trip tests ``` -## The bread ecosystem at a glance +## The bread ecosystem + +Everything below is a separate bakery-distributed project with its own repo +and release cadence, baked into `/etc/skel` at ISO build time so a fresh +install has them all with no network round-trip. Some ship more than one +binary from a single package — that's noted where it applies. Most have a +corresponding **bos-settings** panel for configuration; this table is about +*using* the app directly. + +**Desktop shell** | Tool | Role | Launch | |------|------|--------| -| `bread` / `breadd` | Reactive automation daemon — normalises hardware/compositor signals into events dispatched to Lua modules | runs at login | -| `breadbar` | Top status bar (workspaces, clock, stats, tray) **and** the notification daemon | runs at login | -| `breadbox` | Application launcher | `SUPER+Space` | -| `breadpad` | Notes & reminders (AI-classified, optional CalDAV sync) | `SUPER+U` | -| `breadman` | Package-manager UI | `SUPER+M` | -| `breadcrumbs` | Wi-Fi profile state machine (location-aware) | CLI / BOS Settings | -| `bakery` | CLI package manager for the ecosystem | `bakery` | -| `bos-settings` | Unified GTK4 control panel for all of the above + snapshots + updates | `SUPER+,` | +| `bread` / `breadd` | Reactive automation daemon — normalises hardware/compositor/power/network signals into events dispatched to Lua modules (`~/.config/bread/`). Everything else in the ecosystem can subscribe to its events. | runs at login (`breadd.service`) | +| `breadbar` | Top status bar: workspaces, clock, system stats, tray, **and** the notification daemon — one process, not two | runs at login | +| `breadbox` | Application launcher (fuzzy search, per-context results via `breadbox-sync`) | `SUPER+Space` | +| `breadlock` | Idle lock screen. Also provides `breadgreet`, the login greeter hosted under `cage` via greetd — same project, two binaries, one visual identity from login to lock | `SUPER+L` (via `loginctl lock-session`, picked up by `hypridle`); `breadgreet` runs automatically at boot | +| `bread-theme` | The shared palette engine every bread app renders through: fixed dark base colors, with only the accent slots following the current wallpaper's pywal palette. `bread-theme generate` regenerates the stylesheet; hyprland.lua calls it automatically on wallpaper change. | invoked automatically, rarely run by hand | + +**Productivity** + +| Tool | Role | Launch | +|------|------|--------| +| `breadpad` | Quick-capture scratchpad/notes popup with AI classification and optional CalDAV calendar sync | `SUPER+U` | +| `breadman` | The fuller notes manager view (browse/organize) — ships from the same `breadpad` package as a second binary | `SUPER+M` | +| `breadclip` | Clipboard history. `breadclipd` is the background daemon that actually records history; `breadclip` is the GTK4 popup that browses it | `SUPER+V` / `SUPER+Shift+V` | +| `breadsearch` | Semantic system-wide search (indexes files/notes, embeds locally — CPU/ROCm/CUDA backend configurable). `breadmill` is its indexing daemon. | via breadbox, or BOS Settings → File Search | + +**System** + +| Tool | Role | Launch | +|------|------|--------| +| `breadcrumbs` | Location-aware Wi-Fi profile state machine, with optional Tailscale integration — switches network behavior based on which saved network you're on | CLI, or BOS Settings → Wi-Fi Profiles | +| `breadpaper` | Wallpaper manager — sets the wallpaper via `awww`, generates the pywal accent palette from it, and reloads every bread-theme app | BOS Settings → Wallpaper | +| `breadmon` | TUI monitor layout manager (resolution/position/scaling) — the interactive counterpart to BOS Settings' read-only Display panel | `breadmon` in a terminal | +| `breadshot` | Screenshot utility wrapping `grim`/`slurp`/`wl-copy` with Hyprland-aware geometry (multi-monitor-safe region select) | `breadshot`, or `SUPER+Shift+S/C/P` | + +**Tooling** + +| Tool | Role | Launch | +|------|------|--------| +| `bakery` | CLI package manager for the whole ecosystem — install/update/list, tracks installed binaries + versions independently of pacman | `bakery` | +| `bos-settings` | Unified GTK4 control panel: live system state + control (network, power, firewall, users, packages, firmware, AUR, snapshots) plus non-destructive config editing for every app above | `SUPER+,` | ## Keyboard shortcuts @@ -185,12 +222,16 @@ cheatsheet in-session; first boot shows a short welcome (once). | `SUPER+Return` | Terminal (kitty) | | `SUPER+Space` | App launcher (breadbox) | | `SUPER+E` / `SUPER+B` | Files (nautilus) / Browser (zen) | -| `SUPER+U` / `SUPER+M` | breadpad / breadman | +| `SUPER+U` / `SUPER+M` | Notes (breadpad) / notes manager (breadman) | | `SUPER+,` / `SUPER+/` | BOS Settings / keybind cheatsheet | | `SUPER+L` / `SUPER+N` | Lock / log out | | `SUPER+Backspace` | Close window | -| `SUPER+F` / `SUPER+V` / `SUPER+T` | Fullscreen / float / toggle split | -| `SUPER+Shift+V` | Clipboard history | +| `SUPER+F` | Fullscreen | +| `SUPER+I` | Toggle floating | +| `SUPER+P` | Toggle pseudotile | +| `SUPER+R` | Resize mode | +| `SUPER+T` | Toggle split direction | +| `SUPER+V` / `SUPER+Shift+V` | Clipboard history (breadclip) | | `SUPER+Tab` | Last window | | `SUPER+Shift+S/C/P` | Screenshot region→file / region→clipboard / screen→file | | `SUPER+arrows` | Move focus | @@ -199,7 +240,11 @@ cheatsheet in-session; first boot shows a short welcome (once). | `SUPER+1..0` | Switch to workspace 1–10 | | `SUPER+Shift+1..0` | Move window to workspace | | `SUPER+[ / ]` | Previous / next workspace | +| `SUPER+Shift+[ / ]` | Move window to previous / next workspace | +| `SUPER+scroll` | Cycle workspaces | | `SUPER+left/right-drag` | Move / resize window with the mouse | +| Volume / brightness / play-pause / next / prev | Media keys — work even on the lock screen | +| Calculator key | Opens gnome-calculator | ## Known limitations diff --git a/bakery.toml b/bakery.toml deleted file mode 100644 index 632de76..0000000 --- a/bakery.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "bos-settings" -description = "System settings app for Bread OS" -binaries = ["bos-settings"] -system_deps = ["gtk4", "glib2"] -optional_system_deps = ["snapper"] -bread_deps = [] - -[config] -dir = "~/.config" - -[install] -post_install = [] diff --git a/bos-settings/Cargo.toml b/bos-settings/Cargo.toml deleted file mode 100644 index 61bb665..0000000 --- a/bos-settings/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "bos-settings" -version = "0.6.0" -edition = "2021" - -[dependencies] -gtk4 = { version = "0.11", features = ["v4_12"] } -glib = "0.22" -# Shared ecosystem theming — bos-settings loads the same generated stylesheet as -# breadbar/breadbox/breadpad so the whole desktop looks consistent. -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.10", features = ["gtk"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -toml = "0.8" -# toml_edit drives non-destructive config editing: it preserves comments and -# any keys the UI doesn't model, so saving a single field never rewrites or -# drops the rest of the user's config file. -toml_edit = "0.22" -async-channel = "2" diff --git a/bos-settings/src/config/mod.rs b/bos-settings/src/config/mod.rs deleted file mode 100644 index 4cc3266..0000000 --- a/bos-settings/src/config/mod.rs +++ /dev/null @@ -1,213 +0,0 @@ -//! Non-destructive config editing. -//! -//! Every bread* app owns a TOML config that may contain keys, sections, and -//! comments this settings app does not model (e.g. breadpad's calendar -//! credentials, breadcrumbs' saved-network passwords). To edit safely we parse -//! the file into a `toml_edit::DocumentMut`, mutate only the specific keys the -//! UI exposes, and write the document back — preserving everything else, -//! formatting and comments included. - -use std::error::Error; -use std::path::{Path, PathBuf}; - -use toml_edit::{value, Array, DocumentMut, Item, Table, Value}; - -/// Load a TOML file into an editable document. A missing file yields an -/// empty document so the UI still renders with defaults — normal for a fresh -/// install. A file that *exists* but fails to parse is far more dangerous: -/// falling back to an empty document there means the next Save (see -/// `save_doc`) overwrites it with only the UI-modelled keys, silently -/// destroying anything else in the file (breadpad's calendar credentials, -/// breadcrumbs' saved network passwords, ...). Back up the unparseable file -/// once before falling back, so a bad edit is always recoverable. -pub fn load_doc(path: &Path) -> DocumentMut { - let Ok(text) = std::fs::read_to_string(path) else { - return DocumentMut::default(); - }; - match text.parse::() { - Ok(doc) => doc, - Err(e) => { - let backup = PathBuf::from(format!("{}.bak", path.display())); - eprintln!( - "bos-settings: {} failed to parse ({e}); backed up to {} before falling back to defaults", - path.display(), - backup.display() - ); - let _ = std::fs::write(&backup, &text); - DocumentMut::default() - } - } -} - -/// Write the document back to disk, creating parent dirs as needed. -pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box> { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::write(path, doc.to_string())?; - Ok(()) -} - -pub fn config_dir() -> PathBuf { - // Honour XDG_CONFIG_HOME if set; otherwise fall back to $HOME/.config. - if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { - let p = PathBuf::from(xdg); - if p.is_absolute() { - return p; - } - } - let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); - PathBuf::from(home).join(".config") -} - -// --- typed readers (walk a dotted path, return None if absent/wrong type) --- - -fn get<'a>(doc: &'a DocumentMut, path: &[&str]) -> Option<&'a Item> { - let mut tbl = doc.as_table(); - let (last, parents) = path.split_last()?; - for key in parents { - tbl = tbl.get(key)?.as_table()?; - } - tbl.get(last) -} - -pub fn get_bool(doc: &DocumentMut, path: &[&str]) -> Option { - get(doc, path)?.as_bool() -} -pub fn get_str(doc: &DocumentMut, path: &[&str]) -> Option { - get(doc, path)?.as_str().map(str::to_string) -} -pub fn get_i64(doc: &DocumentMut, path: &[&str]) -> Option { - get(doc, path)?.as_integer() -} -pub fn get_f64(doc: &DocumentMut, path: &[&str]) -> Option { - let item = get(doc, path)?; - item.as_float().or_else(|| item.as_integer().map(|i| i as f64)) -} -/// Read an array of strings (e.g. modules.disable, contexts[].priority). -pub fn get_str_list(doc: &DocumentMut, path: &[&str]) -> Vec { - match get(doc, path).and_then(Item::as_array) { - Some(arr) => arr - .iter() - .filter_map(|v| v.as_str().map(str::to_string)) - .collect(), - None => Vec::new(), - } -} - -// --- setters (auto-create intermediate tables, replace only the leaf) --- - -fn table_at_mut<'a>(doc: &'a mut DocumentMut, parents: &[&str]) -> &'a mut Table { - let mut tbl = doc.as_table_mut(); - for key in parents { - let entry = tbl.entry(key).or_insert_with(|| Item::Table(Table::new())); - if !entry.is_table() { - *entry = Item::Table(Table::new()); - } - tbl = entry.as_table_mut().expect("just ensured table"); - } - tbl -} - -fn set_item(doc: &mut DocumentMut, path: &[&str], item: Item) { - let Some((last, parents)) = path.split_last() else { - return; - }; - table_at_mut(doc, parents).insert(last, item); -} - -pub fn set_bool(doc: &mut DocumentMut, path: &[&str], v: bool) { - set_item(doc, path, value(v)); -} -pub fn set_str(doc: &mut DocumentMut, path: &[&str], v: &str) { - set_item(doc, path, value(v)); -} -pub fn set_i64(doc: &mut DocumentMut, path: &[&str], v: i64) { - set_item(doc, path, value(v)); -} -pub fn set_f64(doc: &mut DocumentMut, path: &[&str], v: f64) { - set_item(doc, path, value(v)); -} -pub fn set_str_list(doc: &mut DocumentMut, path: &[&str], items: &[String]) { - let mut arr = Array::new(); - for s in items { - arr.push(s.as_str()); - } - set_item(doc, path, Item::Value(Value::Array(arr))); -} - -/// Set a string key, or remove it entirely when the value is empty — keeps -/// optional fields out of the file rather than persisting `key = ""`. -pub fn set_str_or_remove(doc: &mut DocumentMut, path: &[&str], v: &str) { - if v.is_empty() { - remove(doc, path); - } else { - set_str(doc, path, v); - } -} - -pub fn remove(doc: &mut DocumentMut, path: &[&str]) { - if let Some((last, parents)) = path.split_last() { - let mut tbl = doc.as_table_mut(); - for key in parents { - match tbl.get_mut(key).and_then(Item::as_table_mut) { - Some(t) => tbl = t, - None => return, - } - } - tbl.remove(last); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn edits_preserve_unmodelled_keys_and_comments() { - let src = "\ -# a leading comment -[daemon] -log_level = \"info\" - -[calendar] -password = \"secret\" # keep me -"; - let mut doc: DocumentMut = src.parse().unwrap(); - // Modify a single modelled key. - set_str(&mut doc, &["daemon", "log_level"], "debug"); - // A key/section the UI never touches must survive untouched. - let out = doc.to_string(); - assert!(out.contains("log_level = \"debug\"")); - assert!(out.contains("password = \"secret\"")); - assert!(out.contains("# keep me")); - assert!(out.contains("# a leading comment")); - } - - #[test] - fn setters_create_missing_tables() { - let mut doc = DocumentMut::new(); - set_bool(&mut doc, &["adapters", "power", "enabled"], false); - set_i64(&mut doc, &["adapters", "power", "poll_interval_secs"], 45); - assert_eq!(get_bool(&doc, &["adapters", "power", "enabled"]), Some(false)); - assert_eq!( - get_i64(&doc, &["adapters", "power", "poll_interval_secs"]), - Some(45) - ); - } - - #[test] - fn empty_string_removes_key() { - let mut doc: DocumentMut = "[calendar]\nurl = \"x\"\n".parse().unwrap(); - set_str_or_remove(&mut doc, &["calendar", "url"], ""); - assert_eq!(get_str(&doc, &["calendar", "url"]), None); - } - - #[test] - fn str_list_roundtrips() { - let mut doc = DocumentMut::new(); - let items = vec!["a".to_string(), "b".to_string()]; - set_str_list(&mut doc, &["modules", "disable"], &items); - assert_eq!(get_str_list(&doc, &["modules", "disable"]), items); - } -} diff --git a/bos-settings/src/main.rs b/bos-settings/src/main.rs deleted file mode 100644 index a5e73fd..0000000 --- a/bos-settings/src/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod config; -mod theme; -mod ui; - -use gtk4::prelude::*; - -fn main() { - let app = gtk4::Application::builder() - .application_id("com.breadway.bos-settings") - .build(); - app.connect_activate(ui::window::build_ui); - app.run(); -} diff --git a/bos-settings/src/theme.rs b/bos-settings/src/theme.rs deleted file mode 100644 index 5b57ca8..0000000 --- a/bos-settings/src/theme.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Theming for bos-settings. -//! -//! bos-settings deliberately owns almost no styling: it loads the ecosystem's -//! shared stylesheet (the same one breadbar/breadbox/breadpad use, generated by -//! `bread-theme` from the pywal palette) and adds only the few layout rules -//! specific to this app's sidebar + content shell. This keeps it visually -//! identical to the rest of the bread desktop and live-recolouring for free. - -use gtk4::CssProvider; -use std::cell::RefCell; - -// App-specific layout only — everything visual (colours, buttons, entries, -// switches, sidebar/row styling, cards, scrollbars) comes from the shared sheet. -const APP_CSS: &str = "\ -.view-content { padding: 24px; }\n\ -.view-content > label.title { margin-bottom: 16px; }\n\ -/* Sidebar row sub-labels (the underlying binary/config name under a row's \ - human label) — smaller than the shared sheet's default dim-label size. */\n\ -.caption { font-size: 11px; }\n\ -/* bread-theme's shared sheet only overrides background-color on \ - suggested/destructive buttons, not background-image — so Adwaita's \ - built-in gradient (bright blue/red) paints over our flat colour \ - underneath it. Belongs upstream in bread-theme; patched locally here \ - until that's worth its own release. */\n\ -button.suggested-action, button.destructive-action { background-image: none; }\n\ -/* Same upstream gap for scale (Sound's volume sliders) — the shared sheet \ - has no `scale` rules at all, so they render in Adwaita's default blue. */\n\ -scale trough { background-color: alpha(@on-surface, 0.15); border-radius: 999px; min-height: 6px; background-image: none; }\n\ -scale highlight { background-color: @accent; background-image: none; border-radius: 999px; }\n\ -scale slider { background-color: @on-surface; border-radius: 999px; }\n\ -/* Destructive actions must not follow the wallpaper palette: @red is \ - pywal's color1, which can land on gold/yellow/anything depending on the \ - wallpaper (it did, this session) — making Delete/Remove look like a \ - primary action instead of a dangerous one. Fixed regardless of palette. */\n\ -button.destructive-action { background-color: #c0392b; color: #ffffff; }\n\ -button.destructive-action:hover { background-color: #d64535; }\n\ -/* Adwaita's default switch slider (the knob) carries a box-shadow used for \ - its 3D bevel look — bread-theme's override only sets background-color, \ - so that shadow still renders as a pale ring around the knob on top of \ - our flat colour. */\n\ -switch slider { box-shadow: none; outline: none; border: none; background-image: none; }\n\ -switch { box-shadow: none; outline: none; border: none; background-image: none; }\n\ -"; - -thread_local! { - static APP_PROVIDER: RefCell> = const { RefCell::new(None) }; -} - -pub fn load(_display: >k4::gdk::Display) { - // Shared ecosystem stylesheet (loads the generated file or a rendered - // fallback, and live-reloads when the palette changes). - bread_theme::gtk::apply_shared(); - - // bos-settings layout, layered on top at APPLICATION priority. - APP_PROVIDER.with(|cell| bread_theme::gtk::apply_css(APP_CSS, cell)); -} diff --git a/bos-settings/src/ui/mod.rs b/bos-settings/src/ui/mod.rs deleted file mode 100644 index 3867fcd..0000000 --- a/bos-settings/src/ui/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod sidebar; -pub mod views; -pub mod widgets; -pub mod window; diff --git a/bos-settings/src/ui/sidebar.rs b/bos-settings/src/ui/sidebar.rs deleted file mode 100644 index 5b2ef8e..0000000 --- a/bos-settings/src/ui/sidebar.rs +++ /dev/null @@ -1,141 +0,0 @@ -use gtk4::prelude::*; -use gtk4::{Box as GBox, Image, Label, ListBox, ListBoxRow, Orientation}; - -pub struct SidebarItem { - /// Must match the `Stack` page name registered in `window.rs`. - pub id: &'static str, - pub label: &'static str, - /// Dim second line — the underlying binary/config name, for items whose - /// human label doesn't already make that obvious. - pub sublabel: Option<&'static str>, - /// A `-symbolic` icon name from the system icon theme (Papirus-Dark ships - /// the full Adwaita-compatible symbolic set this app relies on). - pub icon: &'static str, -} - -const fn item(id: &'static str, label: &'static str, icon: &'static str) -> SidebarItem { - SidebarItem { id, label, sublabel: None, icon } -} - -const fn item_sub( - id: &'static str, - label: &'static str, - sublabel: &'static str, - icon: &'static str, -) -> SidebarItem { - SidebarItem { id, label, sublabel: Some(sublabel), icon } -} - -// Grouped by task, not by "app vs system internals" — a user thinks "I want -// to change my Wi-Fi" or "I want to change my wallpaper", not "which of -// these is a bread-ecosystem app". breadcrumbs (Wi-Fi profiles) moves out of -// the old "Apps" bucket into System for the same reason. -pub const SYSTEM_ITEMS: &[SidebarItem] = &[ - item("network", "Network", "network-wireless-symbolic"), - item_sub("breadcrumbs", "Wi-Fi Profiles", "breadcrumbs", "network-workgroup-symbolic"), - item("firewall", "Firewall", "security-high-symbolic"), - item("sound", "Sound", "audio-volume-high-symbolic"), - item("power", "Power", "battery-good-symbolic"), - item("datetime", "Date & Time", "preferences-system-time-symbolic"), - item_sub("hyprland", "Display", "hyprland.lua", "video-display-symbolic"), - item("users", "Users", "system-users-symbolic"), -]; - -pub const PERSONALIZATION_ITEMS: &[SidebarItem] = &[ - item_sub("breadpaper", "Wallpaper", "breadpaper", "preferences-desktop-wallpaper-symbolic"), - item_sub("breadbar", "Bar", "breadbar", "view-grid-symbolic"), - item_sub("breadbox", "Launcher", "breadbox", "view-app-grid-symbolic"), - item_sub("breadclip", "Clipboard", "breadclipd", "edit-paste-symbolic"), - item_sub("breadpad", "Notes", "breadpad", "text-editor-symbolic"), - item_sub("breadsearch", "File Search", "breadsearch", "system-search-symbolic"), - item_sub("bread", "Daemon", "breadd", "applications-system-symbolic"), -]; - -pub const MAINTENANCE_ITEMS: &[SidebarItem] = &[ - item("packages", "Packages", "package-x-generic-symbolic"), - item("aur", "AUR", "system-search-symbolic"), - item("firmware", "Firmware", "software-update-available-symbolic"), - item("snapshots", "Snapshots", "document-open-recent-symbolic"), -]; - -pub const ABOUT_ITEMS: &[SidebarItem] = &[item("about", "About", "help-about-symbolic")]; - -/// `default_id` must match whatever page `window.rs` sets as the `Stack`'s -/// initial visible child — previously these were two independent hardcoded -/// "about" literals in different files with no link between them, so -/// changing one without the other silently desynced the sidebar highlight -/// from the actually-displayed page. -pub fn build(default_id: &str) -> (GBox, ListBox) { - let vbox = GBox::new(Orientation::Vertical, 0); - vbox.add_css_class("sidebar"); - vbox.set_width_request(210); - - let list = ListBox::new(); - list.set_selection_mode(gtk4::SelectionMode::Single); - list.add_css_class("sidebar"); - - append_section(&list, "System", SYSTEM_ITEMS); - append_section(&list, "Personalization", PERSONALIZATION_ITEMS); - append_section(&list, "Maintenance", MAINTENANCE_ITEMS); - append_section(&list, None, ABOUT_ITEMS); - - let mut i = 0; - loop { - match list.row_at_index(i) { - None => break, - Some(row) if row.widget_name() == default_id => { - list.select_row(Some(&row)); - break; - } - _ => i += 1, - } - } - - vbox.append(&list); - (vbox, list) -} - -fn append_section(list: &ListBox, title: impl Into>, items: &[SidebarItem]) { - if let Some(title) = title.into() { - let header_row = ListBoxRow::new(); - header_row.set_selectable(false); - header_row.set_activatable(false); - let header_lbl = Label::new(Some(title)); - header_lbl.add_css_class("section-header"); - header_lbl.set_xalign(0.0); - header_row.set_child(Some(&header_lbl)); - list.append(&header_row); - } - - for entry in items { - let row = ListBoxRow::new(); - row.set_widget_name(entry.id); - - let hbox = GBox::new(Orientation::Horizontal, 10); - hbox.set_margin_top(4); - hbox.set_margin_bottom(4); - - let icon = Image::from_icon_name(entry.icon); - icon.set_pixel_size(16); - hbox.append(&icon); - - let labels = GBox::new(Orientation::Vertical, 0); - let lbl = Label::new(Some(entry.label)); - lbl.set_xalign(0.0); - labels.append(&lbl); - if let Some(sub) = entry.sublabel { - let sub_lbl = Label::new(Some(sub)); - sub_lbl.add_css_class("dim-label"); - sub_lbl.set_xalign(0.0); - // Match the sidebar's smaller "section-header" scale rather than - // the shared sheet's default dim-label size, so it reads as a - // caption under the row label, not a second full-size label. - sub_lbl.add_css_class("caption"); - labels.append(&sub_lbl); - } - hbox.append(&labels); - - row.set_child(Some(&hbox)); - list.append(&row); - } -} diff --git a/bos-settings/src/ui/views/about.rs b/bos-settings/src/ui/views/about.rs deleted file mode 100644 index 8f4cdcb..0000000 --- a/bos-settings/src/ui/views/about.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Read-only system info, plus the one thing worth making writable: hostname. -//! BOS is a rolling release (no fixed version number to show — `os-release` -//! ships `BUILD_ID=rolling` on purpose), so there's no "BOS 1.2.3" readout -//! here the way a point-release distro's About panel would have one. - -use gtk4::prelude::*; -use gtk4::{Box as GBox, Button, Entry, Label, Orientation}; -use std::fs; -use std::process::Command; - -use crate::ui::widgets as w; - -fn os_pretty_name() -> String { - fs::read_to_string("/etc/os-release") - .ok() - .and_then(|s| { - s.lines() - .find_map(|l| l.strip_prefix("PRETTY_NAME=").map(|v| v.trim_matches('"').to_string())) - }) - .unwrap_or_else(|| "BOS".to_string()) -} - -fn hostname() -> String { - fs::read_to_string("/etc/hostname") - .map(|s| s.trim().to_string()) - .unwrap_or_else(|_| "unknown".to_string()) -} - -fn kernel() -> String { - Command::new("uname") - .arg("-r") - .output() - .ok() - .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) - .unwrap_or_else(|| "unknown".to_string()) -} - -fn cpu() -> String { - let model = fs::read_to_string("/proc/cpuinfo") - .ok() - .and_then(|s| { - s.lines() - .find_map(|l| l.strip_prefix("model name").map(|v| v.trim_start_matches([':', ' ', '\t']).to_string())) - }) - .unwrap_or_else(|| "unknown".to_string()); - let cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0); - if cores > 0 { - format!("{model} ({cores} threads)") - } else { - model - } -} - -fn memory() -> String { - let kb = fs::read_to_string("/proc/meminfo") - .ok() - .and_then(|s| { - s.lines() - .find(|l| l.starts_with("MemTotal:")) - .and_then(|l| l.split_whitespace().nth(1)) - .and_then(|v| v.parse::().ok()) - }); - match kb { - Some(kb) => format!("{:.1} GiB", kb as f64 / 1024.0 / 1024.0), - None => "unknown".to_string(), - } -} - -fn gpu() -> String { - let Ok(output) = Command::new("lspci").output() else { - return "unknown".to_string(); - }; - let text = String::from_utf8_lossy(&output.stdout); - text.lines() - // "Display controller" covers integrated GPUs some laptop chipsets - // (this dev laptop's AMD Radeon 860M included) report under instead - // of "VGA compatible controller" — without it those show "unknown". - .find(|l| { - l.contains("VGA compatible controller") - || l.contains("3D controller") - || l.contains("Display controller") - }) - .and_then(|l| l.split(": ").nth(1)) - .unwrap_or("unknown") - .to_string() -} - -fn disk_usage() -> String { - let Ok(output) = Command::new("df").args(["-h", "--output=used,size,pcent", "/"]).output() else { - return "unknown".to_string(); - }; - let text = String::from_utf8_lossy(&output.stdout); - text.lines() - .nth(1) - .map(|l| { - let cols: Vec<&str> = l.split_whitespace().collect(); - match cols.as_slice() { - [used, size, pcent] => format!("{used} of {size} used ({pcent})"), - _ => l.trim().to_string(), - } - }) - .unwrap_or_else(|| "unknown".to_string()) -} - -fn uptime() -> String { - Command::new("uptime") - .arg("-p") - .output() - .ok() - .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) - .unwrap_or_else(|| "unknown".to_string()) -} - -pub fn build() -> GBox { - let (outer, content) = w::view_scaffold("About"); - - content.append(&w::info_row("Operating system", &os_pretty_name())); - content.append(&w::info_row("Kernel", &kernel())); - content.append(&w::info_row("CPU", &cpu())); - content.append(&w::info_row("GPU", &gpu())); - content.append(&w::info_row("Memory", &memory())); - content.append(&w::info_row("Disk (/)", &disk_usage())); - content.append(&w::info_row("Uptime", &uptime())); - - content.append(&w::section("Hostname")); - content.append(&w::hint( - "Changes the machine's network name. Takes effect immediately; \ - needs your password (polkit).", - )); - - let hn_row = GBox::new(Orientation::Horizontal, 12); - let entry = Entry::new(); - entry.set_text(&hostname()); - entry.set_hexpand(true); - let apply_btn = Button::with_label("Apply"); - let status = Label::new(None); - status.add_css_class("dim-label"); - - { - let entry = entry.clone(); - let status = status.clone(); - apply_btn.connect_clicked(move |_| { - let name = entry.text().to_string(); - if name.trim().is_empty() { - status.set_text("Hostname can't be empty"); - return; - } - let log_buf = gtk4::TextBuffer::new(None); - let status2 = status.clone(); - status.set_text("Applying…"); - w::stream_command_then( - &["pkexec", "hostnamectl", "set-hostname", name.trim()], - log_buf.clone(), - move || { - let text = log_buf.text(&log_buf.start_iter(), &log_buf.end_iter(), false); - if text.trim().is_empty() { - status2.set_text("Applied"); - } else { - status2.set_text(&format!("Error: {}", text.trim())); - } - }, - ); - }); - } - - hn_row.append(&entry); - hn_row.append(&apply_btn); - content.append(&hn_row); - content.append(&status); - - outer -} diff --git a/bos-settings/src/ui/views/aur.rs b/bos-settings/src/ui/views/aur.rs deleted file mode 100644 index b526224..0000000 --- a/bos-settings/src/ui/views/aur.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! AUR search via yay — graphical discovery for the wider AUR beyond -//! bakery's bread ecosystem and [breadway]'s own republished packages. -//! -//! Search and browsing are fully graphical; actually installing a package -//! opens a terminal running `yay -S ` instead of a silent `--noconfirm` -//! install. That's deliberate, not a shortcut we didn't get around to: -//! AUR packages run arbitrary maintainer-supplied build scripts, and yay's -//! interactive PKGBUILD diff review (plus the sudo password prompt) is the -//! actual safety mechanism against a malicious/compromised AUR package — -//! automating it away in the name of "no terminal" would remove the one -//! step that exists to catch that. - -use gtk4::prelude::*; -use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow}; -use std::process::Command; - -use crate::ui::widgets as w; - -#[derive(Clone)] -struct AurResult { - name: String, - version: String, - description: String, -} - -fn search(query: &str) -> Vec { - let Ok(output) = Command::new("yay").args(["-Ss", "--aur", query]).output() else { - return Vec::new(); - }; - let text = String::from_utf8_lossy(&output.stdout); - let mut results = Vec::new(); - let mut lines = text.lines().peekable(); - while let Some(header) = lines.next() { - // "aur/name version (+votes score) [Orphaned]" — name/version are - // always the first two whitespace-separated fields after the repo/. - let Some(rest) = header.strip_prefix("aur/") else { continue }; - let mut parts = rest.split_whitespace(); - let Some(name) = parts.next() else { continue }; - let version = parts.next().unwrap_or("").to_string(); - let description = lines.next().unwrap_or("").trim().to_string(); - results.push(AurResult { name: name.to_string(), version, description }); - } - results -} - -fn install_in_terminal(pkg: &str) { - let _ = Command::new("kitty").args(["-e", "yay", "-S", pkg]).spawn(); -} - -pub fn build() -> GBox { - let (outer, content) = w::view_scaffold("AUR"); - content.append(&w::hint( - "Search the Arch User Repository via yay. Installing opens a \ - terminal — AUR packages run arbitrary build scripts, and reviewing \ - what yay is about to do (and entering your password) is a real \ - safety step, not just a formality.", - )); - - let search_row = GBox::new(Orientation::Horizontal, 8); - let search_entry = Entry::new(); - search_entry.set_hexpand(true); - search_entry.set_placeholder_text(Some("Search the AUR…")); - let search_btn = Button::with_label("Search"); - search_btn.add_css_class("suggested-action"); - search_row.append(&search_entry); - search_row.append(&search_btn); - content.append(&search_row); - - let status = w::hint("Search for a package to see results here."); - content.append(&status); - - let list = ListBox::new(); - list.set_selection_mode(gtk4::SelectionMode::None); - let scroll = ScrolledWindow::new(); - scroll.set_vexpand(true); - scroll.set_min_content_height(320); - scroll.set_child(Some(&list)); - content.append(&scroll); - - let run_search = { - let list = list.clone(); - let status = status.clone(); - let search_entry = search_entry.clone(); - move |btn: Option