Compare commits
No commits in common. "034e948abb1817b7d07ef9d2a68855294b3bba24" and "2c6feb4ea0dd2b6be0293aa825731efccb693748" have entirely different histories.
034e948abb
...
2c6feb4ea0
62 changed files with 3366 additions and 18 deletions
20
.forgejo/workflows/mirror.yml
Normal file
20
.forgejo/workflows/mirror.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
name: Mirror to GitHub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['**']
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: [self-hosted, hestia]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Push to GitHub
|
||||
run: |
|
||||
git remote add github \
|
||||
"https://x-access-token:${{ secrets.GITHUB_MIRROR_TOKEN }}@github.com/Breadway/bos.git"
|
||||
git push github --mirror
|
||||
46
.forgejo/workflows/package.yml
Normal file
46
.forgejo/workflows/package.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Build and publish package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
package:
|
||||
runs-on: [self-hosted, hestia]
|
||||
container:
|
||||
image: archlinux:latest
|
||||
options: --privileged
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set version
|
||||
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install build dependencies
|
||||
run: pacman -Syu --noconfirm base-devel git rust cargo gtk4 glib2
|
||||
|
||||
- name: Create builder user
|
||||
run: useradd -m builder
|
||||
|
||||
- name: Prepare source
|
||||
run: |
|
||||
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
|
||||
cp -r . /home/builder/src
|
||||
chown -R builder:builder /home/builder/src
|
||||
|
||||
- name: Build package
|
||||
run: su builder -c "cd /home/builder/src/packaging/arch && makepkg -sf --noconfirm"
|
||||
|
||||
- name: Publish to Forgejo registry
|
||||
run: |
|
||||
PKG=$(find /home/builder/src/packaging/arch -name '*.pkg.tar.zst' | head -1)
|
||||
curl -fsS -X PUT \
|
||||
-H "Authorization: token ${{ secrets.FORGEJO_TOKEN }}" \
|
||||
--upload-file "${PKG}" \
|
||||
"https://git.breadway.dev/api/packages/breadway/arch/push?distrib=breadway"
|
||||
45
.gitignore
vendored
45
.gitignore
vendored
|
|
@ -1,16 +1,37 @@
|
|||
# ---> Rust
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
# Rust build artifacts
|
||||
/target/
|
||||
**/*.pdb
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
# Editor / IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.direnv/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
# OS artifacts
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
# Environment / secrets
|
||||
.env
|
||||
.env.local
|
||||
*.env.*
|
||||
secrets/
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
|
||||
# archiso build artifacts (these are large and reproducible)
|
||||
/iso-build/
|
||||
/iso-out/
|
||||
*.iso
|
||||
*.img
|
||||
|
||||
# Runtime / logs
|
||||
*.log
|
||||
logs/
|
||||
*.pid
|
||||
*.sock
|
||||
|
|
|
|||
816
Cargo.lock
generated
Normal file
816
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[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.1.0"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gtk4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b"
|
||||
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 = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[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.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk-pixbuf",
|
||||
"gdk4-sys",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pango-sys",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"gio-sys",
|
||||
"glib",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683"
|
||||
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.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
"glib",
|
||||
"graphene-rs",
|
||||
"gsk4-sys",
|
||||
"libc",
|
||||
"pango",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"graphene-sys",
|
||||
"libc",
|
||||
"pango-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6"
|
||||
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.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6"
|
||||
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 = "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 = "pango"
|
||||
version = "0.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[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 = "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 = "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 = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[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.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[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.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[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.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[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"
|
||||
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[workspace]
|
||||
members = ["bos-settings"]
|
||||
resolver = "2"
|
||||
195
DESIGN.md
Normal file
195
DESIGN.md
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# BOS — Bread Operating System Plan
|
||||
|
||||
## Context
|
||||
|
||||
The bread ecosystem (bread, breadbar, breadbox, breadcrumbs, breadpad/breadman, bakery) is a cohesive set of Arch/Hyprland-specific tools with a shared theme system, unified package manager, and consistent config conventions. Currently, getting to a working system requires installing Arch, Hyprland, each tool via bakery, and wiring up dotfiles manually. BOS eliminates that — one ISO install produces a fully working desktop with everything preconfigured.
|
||||
|
||||
Goals:
|
||||
- **Install and be done**: Calamares GUI installer → reboot → working Hyprland + full bread stack
|
||||
- **Rollback safety**: Btrfs subvolumes + snapper + snap-pac; every pacman transaction is snapshotted
|
||||
- **Unified config**: `bos-settings` GTK4 app surfaces all app configs + snapshot management + bakery updates
|
||||
- **Future-compatible**: Btrfs layout is designed to allow A/B partition migration later (SteamOS model)
|
||||
|
||||
---
|
||||
|
||||
## Repo Structure
|
||||
|
||||
Single new repo: `Breadway/bos` — a Cargo workspace.
|
||||
|
||||
```
|
||||
bos/
|
||||
├── Cargo.toml # Workspace (members: [bos-settings])
|
||||
├── bos-settings/ # GTK4 unified settings app
|
||||
│ ├── Cargo.toml
|
||||
│ └── src/
|
||||
│ ├── main.rs
|
||||
│ ├── state.rs
|
||||
│ ├── theme.rs
|
||||
│ ├── ui/
|
||||
│ │ ├── window.rs # Sidebar + content shell (port breadman pattern)
|
||||
│ │ ├── sidebar.rs
|
||||
│ │ └── views/
|
||||
│ │ ├── bread.rs
|
||||
│ │ ├── breadbar.rs
|
||||
│ │ ├── breadbox.rs
|
||||
│ │ ├── breadcrumbs.rs
|
||||
│ │ ├── breadpad.rs
|
||||
│ │ ├── snapshots.rs
|
||||
│ │ ├── packages.rs
|
||||
│ │ └── hyprland.rs
|
||||
│ └── config/
|
||||
│ └── mod.rs # Per-app config loaders
|
||||
├── iso/ # archiso profile
|
||||
│ ├── profiledef.sh
|
||||
│ ├── packages.x86_64 # Live ISO + installed system package list
|
||||
│ ├── airootfs/ # Files overlaid onto live ISO root
|
||||
│ │ └── etc/
|
||||
│ │ ├── calamares/ # Calamares YAML configuration
|
||||
│ │ └── skel/ # Default user dotfiles
|
||||
└── dotfiles/ # Default configs deployed at install time
|
||||
├── hyprland/ # hyprland.conf, keybinds, autostart
|
||||
├── bread/ # breadd.toml, init.lua, devices.lua
|
||||
├── breadbar/ # (no config needed; zero-config by default)
|
||||
├── breadbox/ # config.toml with default context priorities
|
||||
└── breadcrumbs/ # breadcrumbs.toml with default home profile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component 1: Btrfs Layout + Snapshot Infrastructure
|
||||
|
||||
### Partition/subvolume layout (set up by Calamares)
|
||||
|
||||
| Subvolume | Mount point | Notes |
|
||||
|-----------|-------------|-------|
|
||||
| `@` | `/` | Root — snapshotted by snapper |
|
||||
| `@home` | `/home` | User data — separate from root snapshots |
|
||||
| `@snapshots` | `/.snapshots` | Snapper snapshot dir |
|
||||
| `@log` | `/var/log` | Excluded from root snapshots (prevents bloat) |
|
||||
| `@cache` | `/var/cache` | Excluded from root snapshots |
|
||||
|
||||
Mount options: `noatime,compress=zstd,space_cache=v2` on all subvolumes.
|
||||
|
||||
**A/B compatibility note:** The `@` subvolume is self-contained and can be swapped atomically — this is the design property needed for a future A/B upgrade path. The layout does not need to change to adopt it.
|
||||
|
||||
### Snapshot tooling (installed + configured during post-install)
|
||||
|
||||
- `snapper` — snapshot manager; configured for root (`snapper -c root create-config /`)
|
||||
- `snap-pac` — pacman hooks that call `snapper pre`/`snapper post` around every transaction
|
||||
- `grub-btrfs` — regenerates GRUB entries from snapper snapshots; hook runs on `snapper post`
|
||||
|
||||
**snapper root config defaults** (written to `/etc/snapper/configs/root`):
|
||||
```
|
||||
TIMELINE_CREATE="no" # timeline snapshots off; snap-pac handles it
|
||||
NUMBER_CLEANUP="yes"
|
||||
NUMBER_MIN_AGE="1800"
|
||||
NUMBER_LIMIT="10" # keep last 10 pacman snapshots
|
||||
NUMBER_LIMIT_IMPORTANT="5"
|
||||
```
|
||||
|
||||
No user-facing CLI needed for this component — `bos-settings` is the interface.
|
||||
|
||||
---
|
||||
|
||||
## Component 2: ISO + Calamares Installer
|
||||
|
||||
### archiso profile (`iso/`)
|
||||
|
||||
- Derives from `/usr/share/archiso/configs/releng/` (the standard baseline)
|
||||
- `packages.x86_64` includes: base, linux, grub, btrfs-progs, snapper, snap-pac, grub-btrfs, hyprland, pipewire, wireplumber, networkmanager, gtk4, gtk4-layer-shell, iw, librsvg, libpulse, bluez, bluez-utils, calamares, calamares-qt6
|
||||
- `airootfs/etc/skel/` contains the default dotfiles (symlinked from `dotfiles/`)
|
||||
- Live session autologs into a `liveuser` and launches Calamares automatically
|
||||
|
||||
### Calamares modules (in order)
|
||||
|
||||
1. **welcome** — system checks (RAM ≥ 2GB, internet, disk space)
|
||||
2. **locale** — timezone + locale selection
|
||||
3. **keyboard** — layout selection
|
||||
4. **partition** — custom `btrfs` mode: creates EFI partition + single btrfs pool with the subvolume layout above
|
||||
5. **users** — create main user, set password
|
||||
6. **packages** — install package list (reuses `packages.x86_64`)
|
||||
7. **bootloader** — install GRUB to EFI, `grub-mkconfig` with grub-btrfs hook
|
||||
8. **shellprocess (post-install)** — runs `iso/post-install.sh`:
|
||||
- Configures snapper root config
|
||||
- Enables services: `NetworkManager`, `bluetooth`, `breadd` (user), `breadbox-sync` (user)
|
||||
- Runs `bakery install bread breadbar breadbox breadcrumbs breadpad` (or `bakery install --all`)
|
||||
- Copies `dotfiles/` into `/home/$USER/.config/` (skips any file that already exists)
|
||||
9. **finished** — reboot prompt
|
||||
|
||||
---
|
||||
|
||||
## Component 3: `bos-settings` GTK4 App
|
||||
|
||||
### Tech choices
|
||||
- **gtk4-rs** (v0.11, v4_12 feature), no relm4 — plain GTK4 following breadman's pattern
|
||||
- **bread-theme** for palette + CSS (git dep: `github.com/Breadway/bread-ecosystem`)
|
||||
- Reads/writes each tool's own config file directly (no unified intermediate config)
|
||||
- Window: 960×640, sidebar 190px, `gtk4::Stack` for view switching — identical structure to breadman
|
||||
|
||||
### Sidebar sections + views
|
||||
|
||||
| Section | View | What it does |
|
||||
|---------|------|--------------|
|
||||
| **Apps** | bread | Edit `~/.config/bread/breadd.toml` |
|
||||
| | breadbar | Edit `~/.config/breadbar/` (style.css override, no TOML needed) |
|
||||
| | breadbox | Edit `~/.config/breadbox/config.toml` (context priority lists) |
|
||||
| | breadcrumbs | Edit `~/.config/breadcrumbs/breadcrumbs.toml` (profiles, networks) |
|
||||
| | breadpad | Edit `~/.config/breadpad/breadpad.toml` (model, reminders, calendar) |
|
||||
| **System** | Snapshots | `snapper list` output; rollback button calls `snapper rollback N` |
|
||||
| | Packages | `bakery list --installed`; update buttons call `bakery update <pkg>` |
|
||||
| | Hyprland | "Open config in editor" + monitor list from `bread.state.monitors()` |
|
||||
|
||||
### Config loading pattern
|
||||
|
||||
Each view has a dedicated `load_config(path) -> Result<T>` and `save_config(path, T) -> Result<()>` using `toml` crate. Config structs mirror each app's existing types (no duplication — import the `*-shared` crate where it exists, e.g. `breadpad-shared`). For apps without a shared crate (breadbox, breadcrumbs), define minimal local structs.
|
||||
|
||||
### Snapshots view specifics
|
||||
|
||||
- On open: runs `snapper list --output-cols number,date,description,pre-post` via `std::process::Command`, parses into table rows
|
||||
- Rollback: confirmation dialog → `snapper rollback <N>` → notify user to reboot
|
||||
- Delete: `snapper delete <N>`
|
||||
- No write access to `/` needed for list/rollback since snapper is configured with `ALLOW_USERS` for the main user
|
||||
|
||||
### Packages view specifics
|
||||
|
||||
- On open: reads `~/.local/state/bakery/installed.json` directly (no network)
|
||||
- "Check for updates": runs `bakery list` (triggers index refresh), compares versions
|
||||
- "Update all": runs `bakery update --all` in a subprocess, streams stdout to a log TextView
|
||||
|
||||
### Distribution
|
||||
|
||||
`bos-settings` gets a `bakery.toml` and is added to the `bread-ecosystem` registry — installable standalone on any Arch/Hyprland system via `bakery install bos-settings`, not only as part of a BOS install.
|
||||
|
||||
---
|
||||
|
||||
## Component 4: Default Dotfiles
|
||||
|
||||
Minimal but functional defaults deployed at install time. These are opinionated starting points, not locked configs — users edit freely after install.
|
||||
|
||||
| File | Key content |
|
||||
|------|-------------|
|
||||
| `dotfiles/hyprland/hyprland.conf` | Monitor auto-detect, default keybinds, `exec-once` for breadd/breadbar/breadbox-sync |
|
||||
| `dotfiles/hyprland/keybinds.conf` | `$mod+Space` → breadbox, `$mod+N` → breadpad, `$mod+M` → breadman, `$mod+S` → bos-settings |
|
||||
| `dotfiles/bread/breadd.toml` | All adapters enabled, log_level=info |
|
||||
| `dotfiles/bread/init.lua` | Minimal: activates "default" profile on startup |
|
||||
| `dotfiles/breadbox/config.toml` | Single default context with common apps |
|
||||
| `dotfiles/breadcrumbs/breadcrumbs.toml` | Placeholder home profile (user fills in SSIDs) |
|
||||
|
||||
---
|
||||
|
||||
## Build Order
|
||||
|
||||
1. **Dotfiles** — write default configs; these unblock installer testing immediately
|
||||
2. **Btrfs + snapper config** — write `post-install.sh`; test in a VM with `archiso` livecdbase
|
||||
3. **ISO profile** — archiso profiledef + package list + Calamares YAML; iterate in a VM
|
||||
4. **bos-settings** — start with Snapshots and Packages views (highest value, no app-specific config parsing needed), then add per-app views one at a time
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
- **ISO**: Build with `mkarchiso -v -w /tmp/bos-work -o /tmp/bos-out iso/`; boot in QEMU (`qemu-system-x86_64 -cdrom bos.iso -m 4G -enable-kvm`); complete install; reboot into installed system; confirm all services running and bakery packages present
|
||||
- **btrfs layout**: `btrfs subvolume list /` after install; confirm `@`, `@home`, `@snapshots`, `@log`, `@cache` exist
|
||||
- **snapper**: `snapper list`; run `pacman -Syu` and confirm two new snapshots appear
|
||||
- **grub-btrfs**: Reboot and confirm snapshot submenu in GRUB
|
||||
- **bos-settings**: `cargo build --release`; launch; confirm each view loads its config file; edit a value, save, re-open and confirm persistence; test rollback button in Snapshots view
|
||||
20
LICENSE
20
LICENSE
|
|
@ -1,9 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Breadway
|
||||
Copyright (c) 2025 Breadway
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
# bos
|
||||
|
||||
12
bakery.toml
Normal file
12
bakery.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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 = []
|
||||
12
bos-settings/Cargo.toml
Normal file
12
bos-settings/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "bos-settings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gtk4 = { version = "0.9", features = ["v4_12"] }
|
||||
glib = "0.20"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.8"
|
||||
async-channel = "2"
|
||||
27
bos-settings/src/config/mod.rs
Normal file
27
bos-settings/src/config/mod.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn load<T: for<'de> serde::Deserialize<'de>>(path: &Path) -> Result<T, Box<dyn Error>> {
|
||||
let text = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str(&text)?)
|
||||
}
|
||||
|
||||
pub fn save<T: serde::Serialize>(path: &Path, val: &T) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::write(path, toml::to_string_pretty(val)?)?;
|
||||
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")
|
||||
}
|
||||
11
bos-settings/src/main.rs
Normal file
11
bos-settings/src/main.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod config;
|
||||
mod theme;
|
||||
mod ui;
|
||||
|
||||
fn main() {
|
||||
let app = gtk4::Application::builder()
|
||||
.application_id("com.breadway.bos-settings")
|
||||
.build();
|
||||
app.connect_activate(ui::window::build_ui);
|
||||
app.run();
|
||||
}
|
||||
88
bos-settings/src/theme.rs
Normal file
88
bos-settings/src/theme.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::CssProvider;
|
||||
|
||||
const CSS: &str = r#"
|
||||
window {
|
||||
background-color: #2e3440;
|
||||
color: #eceff4;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: #3b4252;
|
||||
border-right: 1px solid #434c5e;
|
||||
}
|
||||
|
||||
.sidebar row {
|
||||
padding: 8px 12px;
|
||||
color: #d8dee9;
|
||||
}
|
||||
|
||||
.sidebar row:selected {
|
||||
background-color: #5e81ac;
|
||||
color: #eceff4;
|
||||
}
|
||||
|
||||
.sidebar .section-header {
|
||||
padding: 12px 12px 4px 12px;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: #616e88;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.view-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.view-content label.title {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
color: #eceff4;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #5e81ac;
|
||||
color: #eceff4;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 6px 16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #81a1c1;
|
||||
}
|
||||
|
||||
button.destructive-action {
|
||||
background-color: #bf616a;
|
||||
}
|
||||
|
||||
button.destructive-action:hover {
|
||||
background-color: #d08770;
|
||||
}
|
||||
|
||||
entry {
|
||||
background-color: #434c5e;
|
||||
color: #eceff4;
|
||||
border: 1px solid #4c566a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
textview {
|
||||
background-color: #272c36;
|
||||
color: #a3be8c;
|
||||
font-family: monospace;
|
||||
padding: 8px;
|
||||
}
|
||||
"#;
|
||||
|
||||
pub fn load(display: >k4::gdk::Display) {
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_string(CSS);
|
||||
gtk4::style_context_add_provider_for_display(
|
||||
display,
|
||||
&provider,
|
||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
3
bos-settings/src/ui/mod.rs
Normal file
3
bos-settings/src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod sidebar;
|
||||
pub mod views;
|
||||
pub mod window;
|
||||
72
bos-settings/src/ui/sidebar.rs
Normal file
72
bos-settings/src/ui/sidebar.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Label, ListBox, ListBoxRow, Orientation};
|
||||
|
||||
pub struct SidebarItem {
|
||||
pub id: &'static str,
|
||||
pub label: &'static str,
|
||||
}
|
||||
|
||||
pub const APPS_ITEMS: &[SidebarItem] = &[
|
||||
SidebarItem { id: "bread", label: "bread" },
|
||||
SidebarItem { id: "breadbar", label: "breadbar" },
|
||||
SidebarItem { id: "breadbox", label: "breadbox" },
|
||||
SidebarItem { id: "breadcrumbs", label: "breadcrumbs" },
|
||||
SidebarItem { id: "breadpad", label: "breadpad" },
|
||||
];
|
||||
|
||||
pub const SYSTEM_ITEMS: &[SidebarItem] = &[
|
||||
SidebarItem { id: "snapshots", label: "Snapshots" },
|
||||
SidebarItem { id: "packages", label: "Packages" },
|
||||
SidebarItem { id: "hyprland", label: "Hyprland" },
|
||||
];
|
||||
|
||||
pub fn build() -> (GBox, ListBox) {
|
||||
let vbox = GBox::new(Orientation::Vertical, 0);
|
||||
vbox.add_css_class("sidebar");
|
||||
vbox.set_width_request(190);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::Single);
|
||||
list.add_css_class("sidebar");
|
||||
|
||||
append_section(&list, "Apps", APPS_ITEMS);
|
||||
append_section(&list, "System", SYSTEM_ITEMS);
|
||||
|
||||
// Select the snapshots row so it matches the default stack page
|
||||
let mut i = 0;
|
||||
loop {
|
||||
match list.row_at_index(i) {
|
||||
None => break,
|
||||
Some(row) if row.widget_name() == "snapshots" => {
|
||||
list.select_row(Some(&row));
|
||||
break;
|
||||
}
|
||||
_ => i += 1,
|
||||
}
|
||||
}
|
||||
|
||||
vbox.append(&list);
|
||||
(vbox, list)
|
||||
}
|
||||
|
||||
fn append_section(list: &ListBox, title: &str, items: &[SidebarItem]) {
|
||||
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 item in items {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_widget_name(item.id);
|
||||
let lbl = Label::new(Some(item.label));
|
||||
lbl.set_xalign(0.0);
|
||||
lbl.set_margin_top(2);
|
||||
lbl.set_margin_bottom(2);
|
||||
row.set_child(Some(&lbl));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
155
bos-settings/src/ui/views/bread.rs
Normal file
155
bos-settings/src/ui/views/bread.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, DropDown, Label, Orientation, StringList, Switch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct BreadConfig {
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: String,
|
||||
#[serde(default)]
|
||||
pub adapters: AdaptersConfig,
|
||||
}
|
||||
|
||||
fn default_log_level() -> String { "info".to_string() }
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct AdaptersConfig {
|
||||
#[serde(default = "default_true")] pub keyboard: bool,
|
||||
#[serde(default = "default_true")] pub mouse: bool,
|
||||
#[serde(default = "default_true")] pub touchpad: bool,
|
||||
#[serde(default = "default_true")] pub bluetooth: bool,
|
||||
#[serde(default = "default_true")] pub gamepad: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
impl Default for BreadConfig {
|
||||
fn default() -> Self {
|
||||
Self { log_level: default_log_level(), adapters: AdaptersConfig::default() }
|
||||
}
|
||||
}
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("bread/breadd.toml")
|
||||
}
|
||||
|
||||
fn adapter_row(
|
||||
label: &str,
|
||||
active: bool,
|
||||
cfg: Rc<RefCell<BreadConfig>>,
|
||||
field: &'static str,
|
||||
) -> GBox {
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some(label));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(active);
|
||||
sw.connect_active_notify(move |s| {
|
||||
let val = s.is_active();
|
||||
let mut c = cfg.borrow_mut();
|
||||
match field {
|
||||
"keyboard" => c.adapters.keyboard = val,
|
||||
"mouse" => c.adapters.mouse = val,
|
||||
"touchpad" => c.adapters.touchpad = val,
|
||||
"bluetooth" => c.adapters.bluetooth = val,
|
||||
"gamepad" => c.adapters.gamepad = val,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
row
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("bread"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
// Log level
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
row.set_margin_bottom(8);
|
||||
let lbl = Label::new(Some("Log level"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let levels = StringList::new(&["error", "warn", "info", "debug", "trace"]);
|
||||
let dropdown = DropDown::new(Some(levels), gtk4::Expression::NONE);
|
||||
let pos = match cfg.borrow().log_level.as_str() {
|
||||
"error" => 0u32, "warn" => 1, "info" => 2, "debug" => 3, "trace" => 4, _ => 2,
|
||||
};
|
||||
dropdown.set_selected(pos);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
dropdown.connect_selected_notify(move |dd| {
|
||||
let levels = ["error", "warn", "info", "debug", "trace"];
|
||||
if let Some(&level) = levels.get(dd.selected() as usize) {
|
||||
cfg.borrow_mut().log_level = level.to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&dropdown);
|
||||
vbox.append(&row);
|
||||
|
||||
let adapter_label = Label::new(Some("Adapters"));
|
||||
adapter_label.set_xalign(0.0);
|
||||
adapter_label.set_margin_top(8);
|
||||
adapter_label.set_margin_bottom(4);
|
||||
vbox.append(&adapter_label);
|
||||
|
||||
let (kbd, mouse, touchpad, bluetooth, gamepad) = {
|
||||
let c = cfg.borrow();
|
||||
(c.adapters.keyboard, c.adapters.mouse, c.adapters.touchpad,
|
||||
c.adapters.bluetooth, c.adapters.gamepad)
|
||||
};
|
||||
vbox.append(&adapter_row("Keyboard", kbd, cfg.clone(), "keyboard"));
|
||||
vbox.append(&adapter_row("Mouse", mouse, cfg.clone(), "mouse"));
|
||||
vbox.append(&adapter_row("Touchpad", touchpad, cfg.clone(), "touchpad"));
|
||||
vbox.append(&adapter_row("Bluetooth", bluetooth, cfg.clone(), "bluetooth"));
|
||||
vbox.append(&adapter_row("Gamepad", gamepad, cfg.clone(), "gamepad"));
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
76
bos-settings/src/ui/views/breadbar.rs
Normal file
76
bos-settings/src/ui/views/breadbar.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Label, Orientation, ScrolledWindow, TextView};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn css_path() -> PathBuf {
|
||||
crate::config::config_dir().join("breadbar/style.css")
|
||||
}
|
||||
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = css_path();
|
||||
let existing_css = std::fs::read_to_string(&path).unwrap_or_default();
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("breadbar"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some(
|
||||
"CSS overrides for breadbar. Leave empty to use the default bread theme.",
|
||||
));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(8);
|
||||
subtitle.set_wrap(true);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let buf = gtk4::TextBuffer::new(None);
|
||||
buf.set_text(&existing_css);
|
||||
|
||||
let text_view = TextView::with_buffer(&buf);
|
||||
text_view.set_monospace(true);
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&text_view));
|
||||
vbox.append(&scroll);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(12);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
let (start, end) = buf.bounds();
|
||||
let text = buf.text(&start, &end, false);
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
match std::fs::write(&path, text.as_str()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
162
bos-settings/src/ui/views/breadbox.rs
Normal file
162
bos-settings/src/ui/views/breadbox.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct BreadboxConfig {
|
||||
#[serde(default)]
|
||||
pub context: Vec<Context>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Context {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub apps: Vec<String>,
|
||||
}
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadbox/config.toml")
|
||||
}
|
||||
|
||||
fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadboxConfig>>) {
|
||||
while let Some(child) = list.first_child() {
|
||||
list.remove(&child);
|
||||
}
|
||||
for (i, ctx) in cfg.borrow().context.iter().enumerate() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
|
||||
let hbox = GBox::new(Orientation::Horizontal, 8);
|
||||
hbox.set_margin_top(6);
|
||||
hbox.set_margin_bottom(6);
|
||||
hbox.set_margin_start(8);
|
||||
hbox.set_margin_end(8);
|
||||
|
||||
let name_entry = Entry::new();
|
||||
name_entry.set_text(&ctx.name);
|
||||
name_entry.set_width_chars(14);
|
||||
name_entry.set_placeholder_text(Some("name"));
|
||||
|
||||
let apps_entry = Entry::new();
|
||||
apps_entry.set_text(&ctx.apps.join(", "));
|
||||
apps_entry.set_hexpand(true);
|
||||
apps_entry.set_placeholder_text(Some("app1, app2, ..."));
|
||||
|
||||
let remove_btn = Button::with_label("Remove");
|
||||
remove_btn.add_css_class("destructive-action");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
name_entry.connect_changed(move |e| {
|
||||
if let Some(c) = cfg.borrow_mut().context.get_mut(i) {
|
||||
c.name = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
apps_entry.connect_changed(move |e| {
|
||||
if let Some(c) = cfg.borrow_mut().context.get_mut(i) {
|
||||
c.apps = e.text()
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
remove_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().context.remove(i);
|
||||
rebuild_list(&list, &cfg);
|
||||
});
|
||||
}
|
||||
|
||||
hbox.append(&name_entry);
|
||||
hbox.append(&apps_entry);
|
||||
hbox.append(&remove_btn);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadboxConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("breadbox"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some("Context priority lists — apps shown in each context."));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(8);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
rebuild_list(&list, &cfg);
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&list));
|
||||
vbox.append(&scroll);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||
btn_row.set_margin_top(8);
|
||||
|
||||
let add_btn = Button::with_label("Add context");
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
add_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().context.push(Context {
|
||||
name: "new".to_string(),
|
||||
apps: Vec::new(),
|
||||
});
|
||||
rebuild_list(&list, &cfg);
|
||||
});
|
||||
}
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&add_btn);
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
162
bos-settings/src/ui/views/breadcrumbs.rs
Normal file
162
bos-settings/src/ui/views/breadcrumbs.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct BreadcrumbsConfig {
|
||||
#[serde(default)]
|
||||
pub profile: Vec<Profile>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Profile {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub ssids: Vec<String>,
|
||||
}
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadcrumbs/breadcrumbs.toml")
|
||||
}
|
||||
|
||||
fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadcrumbsConfig>>) {
|
||||
while let Some(child) = list.first_child() {
|
||||
list.remove(&child);
|
||||
}
|
||||
for (i, profile) in cfg.borrow().profile.iter().enumerate() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
|
||||
let hbox = GBox::new(Orientation::Horizontal, 8);
|
||||
hbox.set_margin_top(6);
|
||||
hbox.set_margin_bottom(6);
|
||||
hbox.set_margin_start(8);
|
||||
hbox.set_margin_end(8);
|
||||
|
||||
let name_entry = Entry::new();
|
||||
name_entry.set_text(&profile.name);
|
||||
name_entry.set_width_chars(14);
|
||||
name_entry.set_placeholder_text(Some("name"));
|
||||
|
||||
let ssids_entry = Entry::new();
|
||||
ssids_entry.set_text(&profile.ssids.join(", "));
|
||||
ssids_entry.set_hexpand(true);
|
||||
ssids_entry.set_placeholder_text(Some("SSID1, SSID2, ..."));
|
||||
|
||||
let remove_btn = Button::with_label("Remove");
|
||||
remove_btn.add_css_class("destructive-action");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
name_entry.connect_changed(move |e| {
|
||||
if let Some(p) = cfg.borrow_mut().profile.get_mut(i) {
|
||||
p.name = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
ssids_entry.connect_changed(move |e| {
|
||||
if let Some(p) = cfg.borrow_mut().profile.get_mut(i) {
|
||||
p.ssids = e.text()
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
remove_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().profile.remove(i);
|
||||
rebuild_list(&list, &cfg);
|
||||
});
|
||||
}
|
||||
|
||||
hbox.append(&name_entry);
|
||||
hbox.append(&ssids_entry);
|
||||
hbox.append(&remove_btn);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadcrumbsConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("breadcrumbs"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some("Network profiles — SSIDs associated with each location."));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(8);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
rebuild_list(&list, &cfg);
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&list));
|
||||
vbox.append(&scroll);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||
btn_row.set_margin_top(8);
|
||||
|
||||
let add_btn = Button::with_label("Add profile");
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
add_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().profile.push(Profile {
|
||||
name: "new".to_string(),
|
||||
ssids: Vec::new(),
|
||||
});
|
||||
rebuild_list(&list, &cfg);
|
||||
});
|
||||
}
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&add_btn);
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
122
bos-settings/src/ui/views/breadpad.rs
Normal file
122
bos-settings/src/ui/views/breadpad.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, Orientation, Switch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct BreadpadConfig {
|
||||
#[serde(default)]
|
||||
pub model: String,
|
||||
#[serde(default = "default_true")]
|
||||
pub reminders: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub calendar: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
impl Default for BreadpadConfig {
|
||||
fn default() -> Self {
|
||||
Self { model: String::new(), reminders: true, calendar: true }
|
||||
}
|
||||
}
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadpad/breadpad.toml")
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadpadConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("breadpad"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
// Model entry
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Model"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let model_entry = Entry::new();
|
||||
model_entry.set_text(&cfg.borrow().model);
|
||||
model_entry.set_placeholder_text(Some("e.g. claude-sonnet-4-6"));
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
model_entry.connect_changed(move |e| {
|
||||
cfg.borrow_mut().model = e.text().to_string();
|
||||
});
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&model_entry);
|
||||
vbox.append(&row);
|
||||
|
||||
// Reminders
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Reminders"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(cfg.borrow().reminders);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
sw.connect_active_notify(move |s| { cfg.borrow_mut().reminders = s.is_active(); });
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
vbox.append(&row);
|
||||
|
||||
// Calendar
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Calendar integration"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(cfg.borrow().calendar);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
sw.connect_active_notify(move |s| { cfg.borrow_mut().calendar = s.is_active(); });
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
vbox.append(&row);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
87
bos-settings/src/ui/views/hyprland.rs
Normal file
87
bos-settings/src/ui/views/hyprland.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Label, Orientation};
|
||||
use std::process::Command;
|
||||
|
||||
fn get_monitors() -> Vec<String> {
|
||||
let Ok(output) = Command::new("hyprctl").args(["monitors", "-j"]).output() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
let Ok(monitors) = serde_json::from_str::<Vec<serde_json::Value>>(&text) else {
|
||||
return Vec::new();
|
||||
};
|
||||
monitors
|
||||
.iter()
|
||||
.filter_map(|m| {
|
||||
let name = m.get("name")?.as_str()?;
|
||||
let w = m.get("width")?.as_u64()?;
|
||||
let h = m.get("height")?.as_u64()?;
|
||||
let refresh = m.get("refreshRate")?.as_f64()?;
|
||||
Some(format!("{name} {w}x{h} @ {refresh:.0}Hz"))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn hypr_path(name: &str) -> std::path::PathBuf {
|
||||
crate::config::config_dir().join("hypr").join(name)
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("Hyprland"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let monitors_lbl = Label::new(Some("Connected monitors"));
|
||||
monitors_lbl.set_xalign(0.0);
|
||||
monitors_lbl.set_margin_top(8);
|
||||
monitors_lbl.set_margin_bottom(4);
|
||||
vbox.append(&monitors_lbl);
|
||||
|
||||
let monitors = get_monitors();
|
||||
if monitors.is_empty() {
|
||||
let lbl = Label::new(Some("No monitors detected (is Hyprland running?)"));
|
||||
lbl.set_xalign(0.0);
|
||||
vbox.append(&lbl);
|
||||
} else {
|
||||
for mon in &monitors {
|
||||
let lbl = Label::new(Some(mon));
|
||||
lbl.set_xalign(0.0);
|
||||
lbl.set_monospace(true);
|
||||
vbox.append(&lbl);
|
||||
}
|
||||
}
|
||||
|
||||
let open_btn = Button::with_label("Open hyprland.conf in editor");
|
||||
open_btn.set_margin_top(16);
|
||||
open_btn.set_halign(gtk4::Align::Start);
|
||||
{
|
||||
let conf_path = hypr_path("hyprland.conf");
|
||||
open_btn.connect_clicked(move |_| {
|
||||
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "foot".to_string());
|
||||
if let Ok(mut child) = Command::new(&editor).arg(&conf_path).spawn() {
|
||||
std::thread::spawn(move || { let _ = child.wait(); });
|
||||
}
|
||||
});
|
||||
}
|
||||
vbox.append(&open_btn);
|
||||
|
||||
let keybinds_btn = Button::with_label("Open keybinds.conf in editor");
|
||||
keybinds_btn.set_margin_top(8);
|
||||
keybinds_btn.set_halign(gtk4::Align::Start);
|
||||
{
|
||||
let kb_path = hypr_path("keybinds.conf");
|
||||
keybinds_btn.connect_clicked(move |_| {
|
||||
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "foot".to_string());
|
||||
if let Ok(mut child) = Command::new(&editor).arg(&kb_path).spawn() {
|
||||
std::thread::spawn(move || { let _ = child.wait(); });
|
||||
}
|
||||
});
|
||||
}
|
||||
vbox.append(&keybinds_btn);
|
||||
|
||||
vbox
|
||||
}
|
||||
8
bos-settings/src/ui/views/mod.rs
Normal file
8
bos-settings/src/ui/views/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
pub mod bread;
|
||||
pub mod breadbar;
|
||||
pub mod breadbox;
|
||||
pub mod breadcrumbs;
|
||||
pub mod breadpad;
|
||||
pub mod hyprland;
|
||||
pub mod packages;
|
||||
pub mod snapshots;
|
||||
187
bos-settings/src/ui/views/packages.rs
Normal file
187
bos-settings/src/ui/views/packages.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use async_channel;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{
|
||||
Box as GBox, Button, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow, TextView,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn read_installed() -> HashMap<String, String> {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
|
||||
let path = std::path::Path::new(&home)
|
||||
.join(".local/state/bakery/installed.json");
|
||||
|
||||
let Ok(text) = std::fs::read_to_string(&path) else {
|
||||
return HashMap::new();
|
||||
};
|
||||
let Ok(parsed) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&text) else {
|
||||
return HashMap::new();
|
||||
};
|
||||
|
||||
parsed
|
||||
.into_iter()
|
||||
.filter_map(|(name, val)| {
|
||||
let version = val
|
||||
.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
Some((name, version))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn stream_command(args: &[&str], log_buf: gtk4::TextBuffer) {
|
||||
let (sender, receiver) = async_channel::bounded::<String>(256);
|
||||
let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut child = match Command::new(&args[0])
|
||||
.args(&args[1..])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let _ = sender.send_blocking(format!("Error: {e}"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Merge stderr into the channel too
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let stderr = child.stderr.take().unwrap();
|
||||
|
||||
let tx2 = sender.clone();
|
||||
std::thread::spawn(move || {
|
||||
for line in BufReader::new(stderr).lines().flatten() {
|
||||
let _ = tx2.send_blocking(line);
|
||||
}
|
||||
});
|
||||
|
||||
for line in BufReader::new(stdout).lines().flatten() {
|
||||
let _ = sender.send_blocking(line);
|
||||
}
|
||||
let _ = child.wait();
|
||||
});
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
while let Ok(line) = receiver.recv().await {
|
||||
let mut end = log_buf.end_iter();
|
||||
log_buf.insert(&mut end, &format!("{line}\n"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let vbox = GBox::new(Orientation::Vertical, 0);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("Packages"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some("Bread ecosystem packages installed via bakery."));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(16);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
|
||||
let packages = read_installed();
|
||||
if packages.is_empty() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
let lbl = Label::new(Some(
|
||||
"No bakery packages found (~/.local/state/bakery/installed.json)",
|
||||
));
|
||||
lbl.set_margin_top(8);
|
||||
lbl.set_margin_bottom(8);
|
||||
lbl.set_margin_start(8);
|
||||
row.set_child(Some(&lbl));
|
||||
list.append(&row);
|
||||
} else {
|
||||
let mut names: Vec<_> = packages.iter().collect();
|
||||
names.sort_by_key(|(k, _)| k.as_str());
|
||||
|
||||
for (name, version) in names {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
let hbox = GBox::new(Orientation::Horizontal, 16);
|
||||
hbox.set_margin_top(6);
|
||||
hbox.set_margin_bottom(6);
|
||||
hbox.set_margin_start(8);
|
||||
hbox.set_margin_end(8);
|
||||
|
||||
let name_lbl = Label::new(Some(name));
|
||||
name_lbl.set_hexpand(true);
|
||||
name_lbl.set_xalign(0.0);
|
||||
|
||||
let ver_lbl = Label::new(Some(version));
|
||||
ver_lbl.set_xalign(1.0);
|
||||
|
||||
// Spawn a thread to reap the child process — no zombies
|
||||
let pkg_name = name.clone();
|
||||
let update_btn = Button::with_label("Update");
|
||||
update_btn.connect_clicked(move |_| {
|
||||
match Command::new("bakery").args(["update", &pkg_name]).spawn() {
|
||||
Ok(mut child) => {
|
||||
std::thread::spawn(move || { let _ = child.wait(); });
|
||||
}
|
||||
Err(_) => {} // bakery not found; button is a no-op
|
||||
}
|
||||
});
|
||||
|
||||
hbox.append(&name_lbl);
|
||||
hbox.append(&ver_lbl);
|
||||
hbox.append(&update_btn);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&list));
|
||||
vbox.append(&scroll);
|
||||
|
||||
let log_buf = gtk4::TextBuffer::new(None);
|
||||
let log_view = TextView::with_buffer(&log_buf);
|
||||
log_view.set_editable(false);
|
||||
log_view.set_monospace(true);
|
||||
log_view.set_height_request(140);
|
||||
log_view.set_margin_top(8);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||
btn_row.set_margin_top(12);
|
||||
|
||||
let check_btn = Button::with_label("Check for updates");
|
||||
let update_all_btn = Button::with_label("Update all");
|
||||
|
||||
{
|
||||
let log_buf = log_buf.clone();
|
||||
check_btn.connect_clicked(move |_| {
|
||||
log_buf.set_text("");
|
||||
stream_command(&["bakery", "list"], log_buf.clone());
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let log_buf = log_buf.clone();
|
||||
update_all_btn.connect_clicked(move |_| {
|
||||
log_buf.set_text("");
|
||||
stream_command(&["bakery", "update", "--all"], log_buf.clone());
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&check_btn);
|
||||
btn_row.append(&update_all_btn);
|
||||
vbox.append(&btn_row);
|
||||
vbox.append(&log_view);
|
||||
|
||||
vbox
|
||||
}
|
||||
190
bos-settings/src/ui/views/snapshots.rs
Normal file
190
bos-settings/src/ui/views/snapshots.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{
|
||||
AlertDialog, Box as GBox, Button, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow,
|
||||
};
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SnapshotRow {
|
||||
number: String,
|
||||
date: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
fn list_snapshots() -> Vec<SnapshotRow> {
|
||||
let Ok(output) = Command::new("snapper")
|
||||
.args(["list", "--output-cols", "number,date,description"])
|
||||
.output()
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
text.lines()
|
||||
.skip(2) // header + separator
|
||||
.filter_map(|line| {
|
||||
let mut cols = line.splitn(3, '|');
|
||||
Some(SnapshotRow {
|
||||
number: cols.next()?.trim().to_string(),
|
||||
date: cols.next()?.trim().to_string(),
|
||||
description: cols.next()?.trim().to_string(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn populate_list(list: &ListBox) {
|
||||
while let Some(child) = list.first_child() {
|
||||
list.remove(&child);
|
||||
}
|
||||
let snapshots = list_snapshots();
|
||||
if snapshots.is_empty() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
let lbl = Label::new(Some("No snapshots found (snapper may not be configured yet)"));
|
||||
lbl.set_margin_top(8);
|
||||
lbl.set_margin_bottom(8);
|
||||
lbl.set_margin_start(8);
|
||||
row.set_child(Some(&lbl));
|
||||
list.append(&row);
|
||||
return;
|
||||
}
|
||||
for snap in &snapshots {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_widget_name(&snap.number);
|
||||
|
||||
let hbox = GBox::new(Orientation::Horizontal, 16);
|
||||
hbox.set_margin_top(6);
|
||||
hbox.set_margin_bottom(6);
|
||||
hbox.set_margin_start(8);
|
||||
hbox.set_margin_end(8);
|
||||
|
||||
let num_lbl = Label::new(Some(&snap.number));
|
||||
num_lbl.set_width_chars(4);
|
||||
num_lbl.set_xalign(0.0);
|
||||
|
||||
let date_lbl = Label::new(Some(&snap.date));
|
||||
date_lbl.set_width_chars(22);
|
||||
date_lbl.set_xalign(0.0);
|
||||
|
||||
let desc_lbl = Label::new(Some(&snap.description));
|
||||
desc_lbl.set_hexpand(true);
|
||||
desc_lbl.set_xalign(0.0);
|
||||
|
||||
hbox.append(&num_lbl);
|
||||
hbox.append(&date_lbl);
|
||||
hbox.append(&desc_lbl);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let vbox = GBox::new(Orientation::Vertical, 0);
|
||||
vbox.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("Snapshots"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some(
|
||||
"System snapshots created by snap-pac on each pacman transaction.",
|
||||
));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(16);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::Single);
|
||||
populate_list(&list);
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&list));
|
||||
vbox.append(&scroll);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||
btn_row.set_margin_top(12);
|
||||
|
||||
let refresh_btn = Button::with_label("Refresh");
|
||||
let rollback_btn = Button::with_label("Rollback to selected");
|
||||
let delete_btn = Button::with_label("Delete selected");
|
||||
delete_btn.add_css_class("destructive-action");
|
||||
|
||||
{
|
||||
let list = list.clone();
|
||||
refresh_btn.connect_clicked(move |_| {
|
||||
populate_list(&list);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let list = list.clone();
|
||||
rollback_btn.connect_clicked(move |btn| {
|
||||
let Some(row) = list.selected_row() else { return };
|
||||
let number = row.widget_name().to_string();
|
||||
if number.is_empty() { return }
|
||||
|
||||
let window = btn
|
||||
.root()
|
||||
.and_then(|r| r.downcast::<gtk4::Window>().ok());
|
||||
|
||||
let dialog = AlertDialog::builder()
|
||||
.message(&format!("Roll back to snapshot #{number}?"))
|
||||
.detail("The current system state will be replaced on next boot. \
|
||||
A polkit prompt will ask for your password.")
|
||||
.buttons(["Cancel", "Roll back"])
|
||||
.cancel_button(0)
|
||||
.default_button(0)
|
||||
.build();
|
||||
|
||||
dialog.choose(window.as_ref(), gtk4::gio::Cancellable::NONE, move |result| {
|
||||
if result == Ok(1) {
|
||||
// pkexec so polkit handles the privilege escalation
|
||||
std::thread::spawn(move || {
|
||||
let _ = Command::new("pkexec")
|
||||
.args(["snapper", "rollback", &number])
|
||||
.status();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let list = list.clone();
|
||||
delete_btn.connect_clicked(move |btn| {
|
||||
let Some(row) = list.selected_row() else { return };
|
||||
let number = row.widget_name().to_string();
|
||||
if number.is_empty() { return }
|
||||
|
||||
let window = btn
|
||||
.root()
|
||||
.and_then(|r| r.downcast::<gtk4::Window>().ok());
|
||||
|
||||
let list = list.clone();
|
||||
let dialog = AlertDialog::builder()
|
||||
.message(&format!("Delete snapshot #{number}?"))
|
||||
.detail("This cannot be undone.")
|
||||
.buttons(["Cancel", "Delete"])
|
||||
.cancel_button(0)
|
||||
.default_button(0)
|
||||
.build();
|
||||
|
||||
dialog.choose(window.as_ref(), gtk4::gio::Cancellable::NONE, move |result| {
|
||||
if result == Ok(1) {
|
||||
let _ = Command::new("snapper").args(["delete", &number]).status();
|
||||
populate_list(&list);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&refresh_btn);
|
||||
btn_row.append(&rollback_btn);
|
||||
btn_row.append(&delete_btn);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
}
|
||||
57
bos-settings/src/ui/window.rs
Normal file
57
bos-settings/src/ui/window.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Application, ApplicationWindow, Box as GBox, Orientation, Paned, Stack};
|
||||
|
||||
use super::sidebar;
|
||||
use super::views;
|
||||
|
||||
pub fn build_ui(app: &Application) {
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("BOS Settings")
|
||||
.default_width(960)
|
||||
.default_height(640)
|
||||
.build();
|
||||
|
||||
crate::theme::load(&window.display());
|
||||
|
||||
let hpaned = Paned::new(Orientation::Horizontal);
|
||||
hpaned.set_position(190);
|
||||
hpaned.set_shrink_start_child(false);
|
||||
hpaned.set_resize_start_child(false);
|
||||
|
||||
let (sidebar_box, list) = sidebar::build();
|
||||
|
||||
let stack = Stack::new();
|
||||
stack.set_hexpand(true);
|
||||
stack.set_vexpand(true);
|
||||
|
||||
stack.add_named(&views::snapshots::build(), Some("snapshots"));
|
||||
stack.add_named(&views::packages::build(), Some("packages"));
|
||||
stack.add_named(&views::bread::build(), Some("bread"));
|
||||
stack.add_named(&views::breadbar::build(), Some("breadbar"));
|
||||
stack.add_named(&views::breadbox::build(), Some("breadbox"));
|
||||
stack.add_named(&views::breadcrumbs::build(), Some("breadcrumbs"));
|
||||
stack.add_named(&views::breadpad::build(), Some("breadpad"));
|
||||
stack.add_named(&views::hyprland::build(), Some("hyprland"));
|
||||
|
||||
// Default to snapshots view
|
||||
stack.set_visible_child_name("snapshots");
|
||||
|
||||
{
|
||||
let stack = stack.clone();
|
||||
list.connect_row_selected(move |_, row| {
|
||||
if let Some(row) = row {
|
||||
let name = row.widget_name();
|
||||
if !name.is_empty() {
|
||||
stack.set_visible_child_name(&name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hpaned.set_start_child(Some(&sidebar_box));
|
||||
hpaned.set_end_child(Some(&stack));
|
||||
|
||||
window.set_child(Some(&hpaned));
|
||||
window.present();
|
||||
}
|
||||
8
dotfiles/bread/breadd.toml
Normal file
8
dotfiles/bread/breadd.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
log_level = "info"
|
||||
|
||||
[adapters]
|
||||
keyboard = true
|
||||
mouse = true
|
||||
touchpad = true
|
||||
bluetooth = true
|
||||
gamepad = true
|
||||
1
dotfiles/bread/init.lua
Normal file
1
dotfiles/bread/init.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
bread.activate_profile("default")
|
||||
3
dotfiles/breadbox/config.toml
Normal file
3
dotfiles/breadbox/config.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[[context]]
|
||||
name = "default"
|
||||
apps = ["firefox", "foot", "nautilus", "code"]
|
||||
3
dotfiles/breadcrumbs/breadcrumbs.toml
Normal file
3
dotfiles/breadcrumbs/breadcrumbs.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[[profile]]
|
||||
name = "home"
|
||||
ssids = []
|
||||
56
dotfiles/hypr/hyprland.conf
Normal file
56
dotfiles/hypr/hyprland.conf
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
monitor=,preferred,auto,1
|
||||
|
||||
exec-once = breadd
|
||||
exec-once = breadbar
|
||||
exec-once = breadbox-sync
|
||||
|
||||
source = ~/.config/hypr/keybinds.conf
|
||||
|
||||
general {
|
||||
gaps_in = 5
|
||||
gaps_out = 10
|
||||
border_size = 2
|
||||
col.active_border = rgba(88c0d0ff)
|
||||
col.inactive_border = rgba(4c566aff)
|
||||
layout = dwindle
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 8
|
||||
blur {
|
||||
enabled = true
|
||||
size = 6
|
||||
passes = 2
|
||||
}
|
||||
shadow {
|
||||
enabled = true
|
||||
range = 12
|
||||
render_power = 3
|
||||
}
|
||||
}
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = ease, 0.25, 0.1, 0.25, 1.0
|
||||
animation = windows, 1, 4, ease
|
||||
animation = fade, 1, 4, ease
|
||||
animation = workspaces, 1, 5, ease
|
||||
}
|
||||
|
||||
input {
|
||||
kb_layout = us
|
||||
follow_mouse = 1
|
||||
touchpad {
|
||||
natural_scroll = true
|
||||
}
|
||||
}
|
||||
|
||||
dwindle {
|
||||
pseudotile = true
|
||||
preserve_split = true
|
||||
}
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
disable_splash_rendering = true
|
||||
}
|
||||
58
dotfiles/hypr/keybinds.conf
Normal file
58
dotfiles/hypr/keybinds.conf
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
$mod = SUPER
|
||||
|
||||
# App launchers
|
||||
bind = $mod, Space, exec, breadbox
|
||||
bind = $mod, N, exec, breadpad
|
||||
bind = $mod, M, exec, breadman
|
||||
bind = $mod, S, exec, bos-settings
|
||||
|
||||
# Core
|
||||
bind = $mod, Return, exec, foot
|
||||
bind = $mod, Q, killactive
|
||||
bind = $mod SHIFT, E, exit
|
||||
bind = $mod, F, fullscreen
|
||||
|
||||
# Focus
|
||||
bind = $mod, H, movefocus, l
|
||||
bind = $mod, L, movefocus, r
|
||||
bind = $mod, K, movefocus, u
|
||||
bind = $mod, J, movefocus, d
|
||||
|
||||
# Move windows
|
||||
bind = $mod SHIFT, H, movewindow, l
|
||||
bind = $mod SHIFT, L, movewindow, r
|
||||
bind = $mod SHIFT, K, movewindow, u
|
||||
bind = $mod SHIFT, J, movewindow, d
|
||||
|
||||
# Workspaces
|
||||
bind = $mod, 1, workspace, 1
|
||||
bind = $mod, 2, workspace, 2
|
||||
bind = $mod, 3, workspace, 3
|
||||
bind = $mod, 4, workspace, 4
|
||||
bind = $mod, 5, workspace, 5
|
||||
|
||||
bind = $mod SHIFT, 1, movetoworkspace, 1
|
||||
bind = $mod SHIFT, 2, movetoworkspace, 2
|
||||
bind = $mod SHIFT, 3, movetoworkspace, 3
|
||||
bind = $mod SHIFT, 4, movetoworkspace, 4
|
||||
bind = $mod SHIFT, 5, movetoworkspace, 5
|
||||
|
||||
# Scroll through workspaces
|
||||
bind = $mod, mouse_down, workspace, e+1
|
||||
bind = $mod, mouse_up, workspace, e-1
|
||||
|
||||
# Mouse binds
|
||||
bindm = $mod, mouse:272, movewindow
|
||||
bindm = $mod, mouse:273, resizewindow
|
||||
|
||||
# Volume
|
||||
bind = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
|
||||
bind = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||
bind = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||
|
||||
# Brightness
|
||||
bind = , XF86MonBrightnessUp, exec, brightnessctl set 5%+
|
||||
bind = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
|
||||
|
||||
# Screenshot
|
||||
bind = , Print, exec, grimblast copy area
|
||||
29
iso/airootfs/etc/calamares/branding/bos/branding.desc
Normal file
29
iso/airootfs/etc/calamares/branding/bos/branding.desc
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
componentName: bos
|
||||
|
||||
strings:
|
||||
productName: "Bread Operating System"
|
||||
shortProductName: "BOS"
|
||||
version: "rolling"
|
||||
shortVersion: "rolling"
|
||||
versionedName: "BOS (rolling)"
|
||||
shortVersionedName: "BOS"
|
||||
bootloaderEntryName: "BOS"
|
||||
productUrl: "https://github.com/Breadway/bos"
|
||||
supportUrl: "https://github.com/Breadway/bos/issues"
|
||||
knownIssuesUrl: "https://github.com/Breadway/bos/issues"
|
||||
releaseNotesUrl: "https://github.com/Breadway/bos/releases"
|
||||
|
||||
images:
|
||||
productLogo: "logo.png"
|
||||
productIcon: "logo.png"
|
||||
productWelcome: "languages.png"
|
||||
|
||||
slideshow: "show.qml"
|
||||
slideshowAPI: 2
|
||||
|
||||
style:
|
||||
sidebarBackground: "#3b4252"
|
||||
sidebarText: "#eceff4"
|
||||
sidebarTextSelect: "#5e81ac"
|
||||
sidebarTextHighlight: "#eceff4"
|
||||
43
iso/airootfs/etc/calamares/branding/bos/show.qml
Normal file
43
iso/airootfs/etc/calamares/branding/bos/show.qml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* BOS installer slideshow */
|
||||
import QtQuick 2.15
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
Presentation {
|
||||
id: presentation
|
||||
|
||||
Slide {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#2e3440"
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 20
|
||||
|
||||
Text {
|
||||
text: "Bread Operating System"
|
||||
color: "#eceff4"
|
||||
font.pointSize: 28
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Installing your system…"
|
||||
color: "#88c0d0"
|
||||
font.pointSize: 16
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Hyprland · bread · bakery · snapshots"
|
||||
color: "#616e88"
|
||||
font.pointSize: 12
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
iso/airootfs/etc/calamares/modules/bootloader.conf
Normal file
9
iso/airootfs/etc/calamares/modules/bootloader.conf
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
efiBootloaderId: "BOS"
|
||||
installEFIFallback: true
|
||||
grubInstall: "grub-install"
|
||||
grubMkconfig: "grub-mkconfig"
|
||||
grubCfg: "/boot/grub/grub.cfg"
|
||||
grubProbe: "grub-probe"
|
||||
efiDirectory: "/boot/efi"
|
||||
kernel: ""
|
||||
5
iso/airootfs/etc/calamares/modules/finished.conf
Normal file
5
iso/airootfs/etc/calamares/modules/finished.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
restartNowEnabled: true
|
||||
restartNowChecked: true
|
||||
restartNowCommand: "systemctl reboot"
|
||||
notifyOnFinished: false
|
||||
2
iso/airootfs/etc/calamares/modules/keyboard.conf
Normal file
2
iso/airootfs/etc/calamares/modules/keyboard.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
xorgConfDir: "/etc/X11/xorg.conf.d"
|
||||
5
iso/airootfs/etc/calamares/modules/locale.conf
Normal file
5
iso/airootfs/etc/calamares/modules/locale.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
region: "America"
|
||||
zone: "New_York"
|
||||
localeGenPath: "/etc/locale.gen"
|
||||
geoipUrl: "https://geoip.kde.org/v1/calamares"
|
||||
10
iso/airootfs/etc/calamares/modules/mount.conf
Normal file
10
iso/airootfs/etc/calamares/modules/mount.conf
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Extra mount options applied by filesystem type.
|
||||
# Btrfs subvolume mounts are already configured in partition.conf.
|
||||
mountOptions:
|
||||
- filesystem: default
|
||||
options: [noatime]
|
||||
- filesystem: btrfs
|
||||
options: [noatime, "compress=zstd", "space_cache=v2"]
|
||||
- filesystem: vfat
|
||||
options: [umask=0077]
|
||||
10
iso/airootfs/etc/calamares/modules/packages.conf
Normal file
10
iso/airootfs/etc/calamares/modules/packages.conf
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
backend: pacman
|
||||
|
||||
options:
|
||||
- update_db: true
|
||||
|
||||
operations:
|
||||
- try_install:
|
||||
- pipewire-pulse
|
||||
- pipewire-alsa
|
||||
29
iso/airootfs/etc/calamares/modules/partition.conf
Normal file
29
iso/airootfs/etc/calamares/modules/partition.conf
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
efiSystemPartition: "/boot/efi"
|
||||
efiSystemPartitionSize: "512M"
|
||||
efiSystemPartitionName: "EFI"
|
||||
|
||||
defaultFileSystemType: "btrfs"
|
||||
|
||||
btrfsSubvolumes:
|
||||
- mountPoint: /
|
||||
subvolume: "@"
|
||||
mountOptions: "noatime,compress=zstd,space_cache=v2"
|
||||
- mountPoint: /home
|
||||
subvolume: "@home"
|
||||
mountOptions: "noatime,compress=zstd,space_cache=v2"
|
||||
- mountPoint: /.snapshots
|
||||
subvolume: "@snapshots"
|
||||
mountOptions: "noatime,compress=zstd,space_cache=v2"
|
||||
- mountPoint: /var/log
|
||||
subvolume: "@log"
|
||||
mountOptions: "noatime,compress=zstd,space_cache=v2"
|
||||
- mountPoint: /var/cache
|
||||
subvolume: "@cache"
|
||||
mountOptions: "noatime,compress=zstd,space_cache=v2"
|
||||
|
||||
userSwapChoices:
|
||||
- none
|
||||
- small
|
||||
- suspend
|
||||
- file
|
||||
3
iso/airootfs/etc/calamares/modules/shellprocess.conf
Normal file
3
iso/airootfs/etc/calamares/modules/shellprocess.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
script:
|
||||
- "-/usr/bin/bash /etc/calamares/post-install.sh"
|
||||
7
iso/airootfs/etc/calamares/modules/unpackfs.conf
Normal file
7
iso/airootfs/etc/calamares/modules/unpackfs.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
# Unpack the live squashfs onto the target partition.
|
||||
# "arch" matches profiledef.sh install_dir; adjust if that changes.
|
||||
unpack:
|
||||
- source: "/run/archiso/bootmnt/arch/x86_64/airootfs.sfs"
|
||||
sourcefs: "squashfs"
|
||||
destination: ""
|
||||
40
iso/airootfs/etc/calamares/modules/users.conf
Normal file
40
iso/airootfs/etc/calamares/modules/users.conf
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
defaultGroups:
|
||||
- name: users
|
||||
must_exist: true
|
||||
system: false
|
||||
- name: lp
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: video
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: network
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: storage
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: wheel
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: audio
|
||||
must_exist: false
|
||||
system: true
|
||||
- name: input
|
||||
must_exist: false
|
||||
system: true
|
||||
|
||||
autologinGroup: autologin
|
||||
doAutologin: false
|
||||
sudoersGroup: wheel
|
||||
setRootPassword: false
|
||||
doReusePassword: true
|
||||
|
||||
passwordRequirements:
|
||||
minLength: 6
|
||||
maxLength: -1
|
||||
libpwquality:
|
||||
- minlen=6
|
||||
|
||||
allowWeakPasswords: false
|
||||
11
iso/airootfs/etc/calamares/modules/welcome.conf
Normal file
11
iso/airootfs/etc/calamares/modules/welcome.conf
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
showSupportUrl: false
|
||||
showKnownIssuesUrl: false
|
||||
showReleaseNotesUrl: false
|
||||
|
||||
requirements:
|
||||
requiredStorage: 20
|
||||
requiredRam: 2.0
|
||||
checkInternet: true
|
||||
checkPower: true
|
||||
internetCheckUrl: "https://archlinux.org"
|
||||
41
iso/airootfs/etc/calamares/post-install.sh
Normal file
41
iso/airootfs/etc/calamares/post-install.sh
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# --- Snapper root config ---
|
||||
snapper -c root create-config /
|
||||
sed -i 's/TIMELINE_CREATE="yes"/TIMELINE_CREATE="no"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_CLEANUP="no"/NUMBER_CLEANUP="yes"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_MIN_AGE="[^"]*"/NUMBER_MIN_AGE="1800"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_LIMIT="[^"]*"/NUMBER_LIMIT="10"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_LIMIT_IMPORTANT="[^"]*"/NUMBER_LIMIT_IMPORTANT="5"/' /etc/snapper/configs/root
|
||||
|
||||
# Allow main user to list/create/delete snapshots without sudo
|
||||
MAIN_USER=$(getent passwd 1000 | cut -d: -f1)
|
||||
sed -i "s/ALLOW_USERS=\"\"/ALLOW_USERS=\"$MAIN_USER\"/" /etc/snapper/configs/root
|
||||
|
||||
# --- System services ---
|
||||
systemctl enable NetworkManager
|
||||
systemctl enable bluetooth
|
||||
systemctl enable snapper-cleanup.timer
|
||||
systemctl enable grub-btrfs.path
|
||||
|
||||
# --- Bakery: install bread ecosystem ---
|
||||
# Requires [breadway] repo in /etc/pacman.conf — see iso/pacman.conf
|
||||
if command -v bakery &>/dev/null; then
|
||||
sudo -u "$MAIN_USER" bakery install bread breadbar breadbox breadcrumbs breadpad bos-settings
|
||||
fi
|
||||
|
||||
# --- Deploy dotfiles into user home (skip any file that already exists) ---
|
||||
SKEL_SRC="/etc/skel/.config"
|
||||
DOTFILES_DEST="/home/$MAIN_USER/.config"
|
||||
|
||||
if [[ -d "$SKEL_SRC" ]]; then
|
||||
mkdir -p "$DOTFILES_DEST"
|
||||
cp -rn "$SKEL_SRC/." "$DOTFILES_DEST/"
|
||||
chown -R "$MAIN_USER:$MAIN_USER" "$DOTFILES_DEST"
|
||||
fi
|
||||
|
||||
# --- XDG user dirs ---
|
||||
sudo -u "$MAIN_USER" xdg-user-dirs-update
|
||||
|
||||
echo "BOS post-install complete. Reboot to start your system."
|
||||
36
iso/airootfs/etc/calamares/settings.conf
Normal file
36
iso/airootfs/etc/calamares/settings.conf
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
modules-search: [/etc/calamares/modules, /usr/lib/calamares/modules]
|
||||
|
||||
sequence:
|
||||
- show:
|
||||
- welcome
|
||||
- locale
|
||||
- keyboard
|
||||
- partition
|
||||
- users
|
||||
- summary
|
||||
- exec:
|
||||
- partition
|
||||
- mount
|
||||
- unpackfs
|
||||
- machineid
|
||||
- fstab
|
||||
- locale
|
||||
- keyboard
|
||||
- localecfg
|
||||
- users
|
||||
- networkcfg
|
||||
- hwclock
|
||||
- packages
|
||||
- bootloader
|
||||
- shellprocess
|
||||
- umount
|
||||
- show:
|
||||
- finished
|
||||
|
||||
branding: bos
|
||||
prompt-install: true
|
||||
dont-chroot: false
|
||||
oem-setup: false
|
||||
disable-cancel: false
|
||||
disable-cancel-during-exec: true
|
||||
11
iso/airootfs/etc/polkit-1/rules.d/10-snapper.rules
Normal file
11
iso/airootfs/etc/polkit-1/rules.d/10-snapper.rules
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Allow members of the wheel group to perform snapper rollback via pkexec
|
||||
// without a password prompt. Other snapper operations (list/create/delete)
|
||||
// are controlled by ALLOW_USERS in /etc/snapper/configs/root.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "io.opensuse.Snapper.Rollback" &&
|
||||
subject.local &&
|
||||
subject.active &&
|
||||
subject.isInGroup("wheel")) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
8
iso/airootfs/etc/skel/.config/bread/breadd.toml
Normal file
8
iso/airootfs/etc/skel/.config/bread/breadd.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
log_level = "info"
|
||||
|
||||
[adapters]
|
||||
keyboard = true
|
||||
mouse = true
|
||||
touchpad = true
|
||||
bluetooth = true
|
||||
gamepad = true
|
||||
1
iso/airootfs/etc/skel/.config/bread/init.lua
Normal file
1
iso/airootfs/etc/skel/.config/bread/init.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
bread.activate_profile("default")
|
||||
3
iso/airootfs/etc/skel/.config/breadbox/config.toml
Normal file
3
iso/airootfs/etc/skel/.config/breadbox/config.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[[context]]
|
||||
name = "default"
|
||||
apps = ["firefox", "foot", "nautilus", "code"]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[[profile]]
|
||||
name = "home"
|
||||
ssids = []
|
||||
56
iso/airootfs/etc/skel/.config/hypr/hyprland.conf
Normal file
56
iso/airootfs/etc/skel/.config/hypr/hyprland.conf
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
monitor=,preferred,auto,1
|
||||
|
||||
exec-once = breadd
|
||||
exec-once = breadbar
|
||||
exec-once = breadbox-sync
|
||||
|
||||
source = ~/.config/hypr/keybinds.conf
|
||||
|
||||
general {
|
||||
gaps_in = 5
|
||||
gaps_out = 10
|
||||
border_size = 2
|
||||
col.active_border = rgba(88c0d0ff)
|
||||
col.inactive_border = rgba(4c566aff)
|
||||
layout = dwindle
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 8
|
||||
blur {
|
||||
enabled = true
|
||||
size = 6
|
||||
passes = 2
|
||||
}
|
||||
shadow {
|
||||
enabled = true
|
||||
range = 12
|
||||
render_power = 3
|
||||
}
|
||||
}
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = ease, 0.25, 0.1, 0.25, 1.0
|
||||
animation = windows, 1, 4, ease
|
||||
animation = fade, 1, 4, ease
|
||||
animation = workspaces, 1, 5, ease
|
||||
}
|
||||
|
||||
input {
|
||||
kb_layout = us
|
||||
follow_mouse = 1
|
||||
touchpad {
|
||||
natural_scroll = true
|
||||
}
|
||||
}
|
||||
|
||||
dwindle {
|
||||
pseudotile = true
|
||||
preserve_split = true
|
||||
}
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
disable_splash_rendering = true
|
||||
}
|
||||
58
iso/airootfs/etc/skel/.config/hypr/keybinds.conf
Normal file
58
iso/airootfs/etc/skel/.config/hypr/keybinds.conf
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
$mod = SUPER
|
||||
|
||||
# App launchers
|
||||
bind = $mod, Space, exec, breadbox
|
||||
bind = $mod, N, exec, breadpad
|
||||
bind = $mod, M, exec, breadman
|
||||
bind = $mod, S, exec, bos-settings
|
||||
|
||||
# Core
|
||||
bind = $mod, Return, exec, foot
|
||||
bind = $mod, Q, killactive
|
||||
bind = $mod SHIFT, E, exit
|
||||
bind = $mod, F, fullscreen
|
||||
|
||||
# Focus
|
||||
bind = $mod, H, movefocus, l
|
||||
bind = $mod, L, movefocus, r
|
||||
bind = $mod, K, movefocus, u
|
||||
bind = $mod, J, movefocus, d
|
||||
|
||||
# Move windows
|
||||
bind = $mod SHIFT, H, movewindow, l
|
||||
bind = $mod SHIFT, L, movewindow, r
|
||||
bind = $mod SHIFT, K, movewindow, u
|
||||
bind = $mod SHIFT, J, movewindow, d
|
||||
|
||||
# Workspaces
|
||||
bind = $mod, 1, workspace, 1
|
||||
bind = $mod, 2, workspace, 2
|
||||
bind = $mod, 3, workspace, 3
|
||||
bind = $mod, 4, workspace, 4
|
||||
bind = $mod, 5, workspace, 5
|
||||
|
||||
bind = $mod SHIFT, 1, movetoworkspace, 1
|
||||
bind = $mod SHIFT, 2, movetoworkspace, 2
|
||||
bind = $mod SHIFT, 3, movetoworkspace, 3
|
||||
bind = $mod SHIFT, 4, movetoworkspace, 4
|
||||
bind = $mod SHIFT, 5, movetoworkspace, 5
|
||||
|
||||
# Scroll through workspaces
|
||||
bind = $mod, mouse_down, workspace, e+1
|
||||
bind = $mod, mouse_up, workspace, e-1
|
||||
|
||||
# Mouse binds
|
||||
bindm = $mod, mouse:272, movewindow
|
||||
bindm = $mod, mouse:273, resizewindow
|
||||
|
||||
# Volume
|
||||
bind = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
|
||||
bind = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||
bind = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||
|
||||
# Brightness
|
||||
bind = , XF86MonBrightnessUp, exec, brightnessctl set 5%+
|
||||
bind = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
|
||||
|
||||
# Screenshot
|
||||
bind = , Print, exec, grimblast copy area
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin root - $TERM
|
||||
4
iso/airootfs/root/.bash_profile
Normal file
4
iso/airootfs/root/.bash_profile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Auto-start Hyprland on tty1 in the live session
|
||||
if [[ "$(tty)" == "/dev/tty1" ]] && [[ -z "$WAYLAND_DISPLAY" ]]; then
|
||||
exec Hyprland
|
||||
fi
|
||||
28
iso/airootfs/root/.config/hypr/hyprland.conf
Normal file
28
iso/airootfs/root/.config/hypr/hyprland.conf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Live-session Hyprland config — launches Calamares on start.
|
||||
# This is NOT the installed system config; that lives in dotfiles/hypr/.
|
||||
|
||||
monitor=,preferred,auto,1
|
||||
|
||||
exec-once = calamares
|
||||
|
||||
general {
|
||||
border_size = 2
|
||||
col.active_border = rgba(88c0d0ff)
|
||||
col.inactive_border = rgba(4c566aff)
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 4
|
||||
}
|
||||
|
||||
input {
|
||||
kb_layout = us
|
||||
follow_mouse = 1
|
||||
}
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
disable_splash_rendering = true
|
||||
# Keep compositor running if calamares exits (user can relaunch)
|
||||
exit_window_request_force = false
|
||||
}
|
||||
91
iso/packages.x86_64
Normal file
91
iso/packages.x86_64
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Base system
|
||||
base
|
||||
base-devel
|
||||
linux
|
||||
linux-firmware
|
||||
linux-headers
|
||||
|
||||
# Bootloader + filesystem
|
||||
grub
|
||||
efibootmgr
|
||||
btrfs-progs
|
||||
dosfstools
|
||||
mtools
|
||||
|
||||
# Snapshot infrastructure
|
||||
snapper
|
||||
snap-pac
|
||||
grub-btrfs
|
||||
inotify-tools
|
||||
|
||||
# Wayland / Hyprland
|
||||
hyprland
|
||||
xdg-desktop-portal-hyprland
|
||||
xdg-utils
|
||||
xdg-user-dirs
|
||||
polkit
|
||||
polkit-gnome
|
||||
|
||||
# Audio
|
||||
pipewire
|
||||
wireplumber
|
||||
pipewire-pulse
|
||||
pipewire-alsa
|
||||
pipewire-jack
|
||||
|
||||
# Network
|
||||
networkmanager
|
||||
network-manager-applet
|
||||
iw
|
||||
iwd
|
||||
bluez
|
||||
bluez-utils
|
||||
|
||||
# GTK4 runtime
|
||||
gtk4
|
||||
gtk4-layer-shell
|
||||
librsvg
|
||||
libpulse
|
||||
|
||||
# Display (wlroots is bundled with Hyprland; don't list separately)
|
||||
wayland
|
||||
wayland-protocols
|
||||
|
||||
# Fonts
|
||||
noto-fonts
|
||||
noto-fonts-emoji
|
||||
ttf-jetbrains-mono
|
||||
|
||||
# Terminal
|
||||
foot
|
||||
|
||||
# File manager
|
||||
nautilus
|
||||
|
||||
# Installer — sourced from [breadway] repo (see pacman.conf)
|
||||
calamares
|
||||
calamares-qt6
|
||||
|
||||
# Bread ecosystem — sourced from [breadway] repo
|
||||
bakery
|
||||
|
||||
# Input / screen utilities
|
||||
brightnessctl
|
||||
grim
|
||||
slurp
|
||||
|
||||
# Utilities
|
||||
sudo
|
||||
git
|
||||
curl
|
||||
wget
|
||||
unzip
|
||||
tar
|
||||
gzip
|
||||
which
|
||||
man-db
|
||||
man-pages
|
||||
less
|
||||
|
||||
# Dev tools (for bos-settings standalone install)
|
||||
rustup
|
||||
39
iso/pacman.conf
Normal file
39
iso/pacman.conf
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# BOS pacman.conf — used during ISO build and installed to the target system.
|
||||
# Based on the standard Arch Linux pacman.conf.
|
||||
#
|
||||
|
||||
[options]
|
||||
HoldPkg = pacman glibc
|
||||
Architecture = auto
|
||||
CheckSpace
|
||||
ParallelDownloads = 5
|
||||
|
||||
Color
|
||||
VerbosePkgLists
|
||||
ILoveCandy
|
||||
|
||||
SigLevel = Required DatabaseOptional
|
||||
LocalFileSigLevel = Optional
|
||||
|
||||
[core]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[extra]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[multilib]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Breadway custom repo — provides: bakery, calamares (pre-built), and the
|
||||
# bread ecosystem packages (bread, breadbar, breadbox, breadcrumbs, breadpad,
|
||||
# bos-settings).
|
||||
#
|
||||
# Packages are published here by the Forgejo Actions package.yml workflow
|
||||
# in each repo. See git.breadway.dev/api/packages/breadway/arch for the
|
||||
# package registry.
|
||||
# -----------------------------------------------------------------------
|
||||
[breadway]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://git.breadway.dev/api/packages/breadway/arch/breadway/$arch
|
||||
24
iso/profiledef.sh
Normal file
24
iso/profiledef.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
iso_name="bos"
|
||||
iso_label="BOS_$(date +%Y%m)"
|
||||
iso_publisher="Breadway"
|
||||
iso_application="Bread Operating System"
|
||||
iso_version="$(date +%Y.%m.%d)"
|
||||
install_dir="arch"
|
||||
buildmodes=('iso')
|
||||
bootmodes=(
|
||||
'bios.syslinux.mbr'
|
||||
'bios.syslinux.eltorito'
|
||||
'uefi-x64.systemd-boot.esp'
|
||||
'uefi-x64.systemd-boot.eltorito'
|
||||
)
|
||||
arch="x86_64"
|
||||
pacman_conf="pacman.conf"
|
||||
airootfs_image_type="squashfs"
|
||||
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M')
|
||||
file_permissions=(
|
||||
["/etc/shadow"]="0:0:400"
|
||||
["/etc/calamares/post-install.sh"]="0:0:755"
|
||||
)
|
||||
34
packaging/arch/PKGBUILD
Normal file
34
packaging/arch/PKGBUILD
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Maintainer: Breadway <rileyhorsham@gmail.com>
|
||||
|
||||
pkgname=bos-settings
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="System settings app for Bread OS"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/Breadway/bos"
|
||||
license=('MIT')
|
||||
depends=('gtk4' 'glib2' 'hicolor-icon-theme')
|
||||
optdepends=(
|
||||
'snapper: snapshot management view'
|
||||
)
|
||||
makedepends=('rust' 'cargo')
|
||||
source=("${pkgname}-${pkgver}.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
cargo build --release --locked -p bos-settings
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
cargo test --release --locked -p bos-settings
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
install -Dm755 target/release/bos-settings "${pkgdir}/usr/bin/bos-settings"
|
||||
install -Dm644 packaging/arch/bos-settings.desktop \
|
||||
"${pkgdir}/usr/share/applications/bos-settings.desktop"
|
||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||
}
|
||||
25
packaging/arch/README.md
Normal file
25
packaging/arch/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Arch packaging
|
||||
==============
|
||||
|
||||
`PKGBUILD` builds and installs `bos-settings` from source.
|
||||
|
||||
## Local build
|
||||
|
||||
```bash
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
## Before publishing to [breadway] repo
|
||||
|
||||
1. Tag a release on GitHub.
|
||||
2. Update `pkgver` to match the tag.
|
||||
3. Update `source` to the release tarball URL.
|
||||
4. Run `updpkgsums` (or manually set `sha256sums`).
|
||||
|
||||
## Runtime dependencies
|
||||
|
||||
| Package | Required | Notes |
|
||||
|---------|----------|-------|
|
||||
| `gtk4` | yes | UI toolkit |
|
||||
| `glib2` | yes | always |
|
||||
| `snapper` | optional | snapshot management view |
|
||||
9
packaging/arch/bos-settings.desktop
Normal file
9
packaging/arch/bos-settings.desktop
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Name=BOS Settings
|
||||
Comment=System settings for Bread OS
|
||||
Exec=bos-settings
|
||||
Icon=preferences-system
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Settings;System;
|
||||
StartupWMClass=bos-settings
|
||||
Loading…
Add table
Add a link
Reference in a new issue