Merge pull request #1 from Breadway/scaffold/bos-initial
Scaffold/bos initial
This commit is contained in:
commit
c744e45c90
53 changed files with 2979 additions and 0 deletions
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"
|
||||||
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"
|
||||||
24
bos-settings/src/config/mod.rs
Normal file
24
bos-settings/src/config/mod.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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 {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| {
|
||||||
|
std::env::var("XDG_CONFIG_HOME")
|
||||||
|
.map(|p| PathBuf::from(p).parent().unwrap_or(Path::new("/")).to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|_| "/home/user".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();
|
||||||
|
}
|
||||||
11
bos-settings/src/state.rs
Normal file
11
bos-settings/src/state.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
pub struct AppState {
|
||||||
|
pub current_view: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_view: "snapshots".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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 {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string());
|
||||||
|
PathBuf::from(home).join(".config/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
|
||||||
|
}
|
||||||
88
bos-settings/src/ui/views/hyprland.rs
Normal file
88
bos-settings/src/ui/views/hyprland.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
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 {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string());
|
||||||
|
std::path::PathBuf::from(home).join(".config/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(|_| "/home/user".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(e) => eprintln!("bakery update failed: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
86
iso/packages.x86_64
Normal file
86
iso/packages.x86_64
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
sudo
|
||||||
|
git
|
||||||
|
curl
|
||||||
|
wget
|
||||||
|
unzip
|
||||||
|
tar
|
||||||
|
gzip
|
||||||
|
which
|
||||||
|
man-db
|
||||||
|
man-pages
|
||||||
|
less
|
||||||
|
|
||||||
|
# Dev tools (for bos-settings standalone install)
|
||||||
|
rustup
|
||||||
38
iso/pacman.conf
Normal file
38
iso/pacman.conf
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#
|
||||||
|
# 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).
|
||||||
|
#
|
||||||
|
# TODO: Replace this URL with the actual hosted repo before building.
|
||||||
|
# See: https://github.com/Breadway/repo for setup instructions.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
[breadway]
|
||||||
|
SigLevel = Optional TrustAll
|
||||||
|
Server = https://repo.breadway.dev/$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"
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue