diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a8c150b --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..af9532d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["bos-settings"] +resolver = "2" diff --git a/bos-settings/Cargo.toml b/bos-settings/Cargo.toml new file mode 100644 index 0000000..d906354 --- /dev/null +++ b/bos-settings/Cargo.toml @@ -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" diff --git a/bos-settings/src/config/mod.rs b/bos-settings/src/config/mod.rs new file mode 100644 index 0000000..da3b8eb --- /dev/null +++ b/bos-settings/src/config/mod.rs @@ -0,0 +1,24 @@ +use std::error::Error; +use std::path::{Path, PathBuf}; + +pub fn load serde::Deserialize<'de>>(path: &Path) -> Result> { + let text = std::fs::read_to_string(path)?; + Ok(toml::from_str(&text)?) +} + +pub fn save(path: &Path, val: &T) -> Result<(), Box> { + 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") +} diff --git a/bos-settings/src/main.rs b/bos-settings/src/main.rs new file mode 100644 index 0000000..fc13dc2 --- /dev/null +++ b/bos-settings/src/main.rs @@ -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(); +} diff --git a/bos-settings/src/state.rs b/bos-settings/src/state.rs new file mode 100644 index 0000000..e0e760e --- /dev/null +++ b/bos-settings/src/state.rs @@ -0,0 +1,11 @@ +pub struct AppState { + pub current_view: String, +} + +impl AppState { + pub fn new() -> Self { + Self { + current_view: "snapshots".to_string(), + } + } +} diff --git a/bos-settings/src/theme.rs b/bos-settings/src/theme.rs new file mode 100644 index 0000000..ae850d0 --- /dev/null +++ b/bos-settings/src/theme.rs @@ -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, + ); +} diff --git a/bos-settings/src/ui/mod.rs b/bos-settings/src/ui/mod.rs new file mode 100644 index 0000000..1a2b383 --- /dev/null +++ b/bos-settings/src/ui/mod.rs @@ -0,0 +1,3 @@ +pub mod sidebar; +pub mod views; +pub mod window; diff --git a/bos-settings/src/ui/sidebar.rs b/bos-settings/src/ui/sidebar.rs new file mode 100644 index 0000000..4395591 --- /dev/null +++ b/bos-settings/src/ui/sidebar.rs @@ -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); + } +} diff --git a/bos-settings/src/ui/views/bread.rs b/bos-settings/src/ui/views/bread.rs new file mode 100644 index 0000000..27d0596 --- /dev/null +++ b/bos-settings/src/ui/views/bread.rs @@ -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>, + 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 +} diff --git a/bos-settings/src/ui/views/breadbar.rs b/bos-settings/src/ui/views/breadbar.rs new file mode 100644 index 0000000..9f1ceb7 --- /dev/null +++ b/bos-settings/src/ui/views/breadbar.rs @@ -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 +} diff --git a/bos-settings/src/ui/views/breadbox.rs b/bos-settings/src/ui/views/breadbox.rs new file mode 100644 index 0000000..36f88ac --- /dev/null +++ b/bos-settings/src/ui/views/breadbox.rs @@ -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, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct Context { + pub name: String, + #[serde(default)] + pub apps: Vec, +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadbox/config.toml") +} + +fn rebuild_list(list: &ListBox, cfg: &Rc>) { + 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 +} diff --git a/bos-settings/src/ui/views/breadcrumbs.rs b/bos-settings/src/ui/views/breadcrumbs.rs new file mode 100644 index 0000000..f165f43 --- /dev/null +++ b/bos-settings/src/ui/views/breadcrumbs.rs @@ -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, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct Profile { + pub name: String, + #[serde(default)] + pub ssids: Vec, +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadcrumbs/breadcrumbs.toml") +} + +fn rebuild_list(list: &ListBox, cfg: &Rc>) { + 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 +} diff --git a/bos-settings/src/ui/views/breadpad.rs b/bos-settings/src/ui/views/breadpad.rs new file mode 100644 index 0000000..6e24346 --- /dev/null +++ b/bos-settings/src/ui/views/breadpad.rs @@ -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 +} diff --git a/bos-settings/src/ui/views/hyprland.rs b/bos-settings/src/ui/views/hyprland.rs new file mode 100644 index 0000000..0ed704d --- /dev/null +++ b/bos-settings/src/ui/views/hyprland.rs @@ -0,0 +1,88 @@ +use gtk4::prelude::*; +use gtk4::{Box as GBox, Button, Label, Orientation}; +use std::process::Command; + +fn get_monitors() -> Vec { + 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::>(&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 +} diff --git a/bos-settings/src/ui/views/mod.rs b/bos-settings/src/ui/views/mod.rs new file mode 100644 index 0000000..67763f0 --- /dev/null +++ b/bos-settings/src/ui/views/mod.rs @@ -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; diff --git a/bos-settings/src/ui/views/packages.rs b/bos-settings/src/ui/views/packages.rs new file mode 100644 index 0000000..6b3aedc --- /dev/null +++ b/bos-settings/src/ui/views/packages.rs @@ -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 { + 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::>(&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::(256); + let args: Vec = 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 +} diff --git a/bos-settings/src/ui/views/snapshots.rs b/bos-settings/src/ui/views/snapshots.rs new file mode 100644 index 0000000..d3ac8b7 --- /dev/null +++ b/bos-settings/src/ui/views/snapshots.rs @@ -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 { + 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::().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::().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 +} diff --git a/bos-settings/src/ui/window.rs b/bos-settings/src/ui/window.rs new file mode 100644 index 0000000..0088675 --- /dev/null +++ b/bos-settings/src/ui/window.rs @@ -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(); +} diff --git a/dotfiles/bread/breadd.toml b/dotfiles/bread/breadd.toml new file mode 100644 index 0000000..8473fe3 --- /dev/null +++ b/dotfiles/bread/breadd.toml @@ -0,0 +1,8 @@ +log_level = "info" + +[adapters] +keyboard = true +mouse = true +touchpad = true +bluetooth = true +gamepad = true diff --git a/dotfiles/bread/init.lua b/dotfiles/bread/init.lua new file mode 100644 index 0000000..270ee7e --- /dev/null +++ b/dotfiles/bread/init.lua @@ -0,0 +1 @@ +bread.activate_profile("default") diff --git a/dotfiles/breadbox/config.toml b/dotfiles/breadbox/config.toml new file mode 100644 index 0000000..797b3cc --- /dev/null +++ b/dotfiles/breadbox/config.toml @@ -0,0 +1,3 @@ +[[context]] +name = "default" +apps = ["firefox", "foot", "nautilus", "code"] diff --git a/dotfiles/breadcrumbs/breadcrumbs.toml b/dotfiles/breadcrumbs/breadcrumbs.toml new file mode 100644 index 0000000..46e26f3 --- /dev/null +++ b/dotfiles/breadcrumbs/breadcrumbs.toml @@ -0,0 +1,3 @@ +[[profile]] +name = "home" +ssids = [] diff --git a/dotfiles/hypr/hyprland.conf b/dotfiles/hypr/hyprland.conf new file mode 100644 index 0000000..e509b63 --- /dev/null +++ b/dotfiles/hypr/hyprland.conf @@ -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 +} diff --git a/dotfiles/hypr/keybinds.conf b/dotfiles/hypr/keybinds.conf new file mode 100644 index 0000000..7cf8cdd --- /dev/null +++ b/dotfiles/hypr/keybinds.conf @@ -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 diff --git a/iso/airootfs/etc/calamares/branding/bos/branding.desc b/iso/airootfs/etc/calamares/branding/bos/branding.desc new file mode 100644 index 0000000..d3034ab --- /dev/null +++ b/iso/airootfs/etc/calamares/branding/bos/branding.desc @@ -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" diff --git a/iso/airootfs/etc/calamares/branding/bos/show.qml b/iso/airootfs/etc/calamares/branding/bos/show.qml new file mode 100644 index 0000000..446c624 --- /dev/null +++ b/iso/airootfs/etc/calamares/branding/bos/show.qml @@ -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 + } + } + } + } +} diff --git a/iso/airootfs/etc/calamares/modules/bootloader.conf b/iso/airootfs/etc/calamares/modules/bootloader.conf new file mode 100644 index 0000000..d392b23 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/bootloader.conf @@ -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: "" diff --git a/iso/airootfs/etc/calamares/modules/finished.conf b/iso/airootfs/etc/calamares/modules/finished.conf new file mode 100644 index 0000000..08cbc62 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/finished.conf @@ -0,0 +1,5 @@ +--- +restartNowEnabled: true +restartNowChecked: true +restartNowCommand: "systemctl reboot" +notifyOnFinished: false diff --git a/iso/airootfs/etc/calamares/modules/keyboard.conf b/iso/airootfs/etc/calamares/modules/keyboard.conf new file mode 100644 index 0000000..cc51f14 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/keyboard.conf @@ -0,0 +1,2 @@ +--- +xorgConfDir: "/etc/X11/xorg.conf.d" diff --git a/iso/airootfs/etc/calamares/modules/locale.conf b/iso/airootfs/etc/calamares/modules/locale.conf new file mode 100644 index 0000000..d7d3ba3 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/locale.conf @@ -0,0 +1,5 @@ +--- +region: "America" +zone: "New_York" +localeGenPath: "/etc/locale.gen" +geoipUrl: "https://geoip.kde.org/v1/calamares" diff --git a/iso/airootfs/etc/calamares/modules/mount.conf b/iso/airootfs/etc/calamares/modules/mount.conf new file mode 100644 index 0000000..767bf57 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/mount.conf @@ -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] diff --git a/iso/airootfs/etc/calamares/modules/packages.conf b/iso/airootfs/etc/calamares/modules/packages.conf new file mode 100644 index 0000000..c327cb6 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/packages.conf @@ -0,0 +1,10 @@ +--- +backend: pacman + +options: + - update_db: true + +operations: + - try_install: + - pipewire-pulse + - pipewire-alsa diff --git a/iso/airootfs/etc/calamares/modules/partition.conf b/iso/airootfs/etc/calamares/modules/partition.conf new file mode 100644 index 0000000..a33f199 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/partition.conf @@ -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 diff --git a/iso/airootfs/etc/calamares/modules/shellprocess.conf b/iso/airootfs/etc/calamares/modules/shellprocess.conf new file mode 100644 index 0000000..3aef052 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/shellprocess.conf @@ -0,0 +1,3 @@ +--- +script: + - "-/usr/bin/bash /etc/calamares/post-install.sh" diff --git a/iso/airootfs/etc/calamares/modules/unpackfs.conf b/iso/airootfs/etc/calamares/modules/unpackfs.conf new file mode 100644 index 0000000..7d9589e --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/unpackfs.conf @@ -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: "" diff --git a/iso/airootfs/etc/calamares/modules/users.conf b/iso/airootfs/etc/calamares/modules/users.conf new file mode 100644 index 0000000..8f2d422 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/users.conf @@ -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 diff --git a/iso/airootfs/etc/calamares/modules/welcome.conf b/iso/airootfs/etc/calamares/modules/welcome.conf new file mode 100644 index 0000000..33bf7ad --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/welcome.conf @@ -0,0 +1,11 @@ +--- +showSupportUrl: false +showKnownIssuesUrl: false +showReleaseNotesUrl: false + +requirements: + requiredStorage: 20 + requiredRam: 2.0 + checkInternet: true + checkPower: true + internetCheckUrl: "https://archlinux.org" diff --git a/iso/airootfs/etc/calamares/post-install.sh b/iso/airootfs/etc/calamares/post-install.sh new file mode 100644 index 0000000..967292b --- /dev/null +++ b/iso/airootfs/etc/calamares/post-install.sh @@ -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." diff --git a/iso/airootfs/etc/calamares/settings.conf b/iso/airootfs/etc/calamares/settings.conf new file mode 100644 index 0000000..ea17565 --- /dev/null +++ b/iso/airootfs/etc/calamares/settings.conf @@ -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 diff --git a/iso/airootfs/etc/polkit-1/rules.d/10-snapper.rules b/iso/airootfs/etc/polkit-1/rules.d/10-snapper.rules new file mode 100644 index 0000000..dc2b538 --- /dev/null +++ b/iso/airootfs/etc/polkit-1/rules.d/10-snapper.rules @@ -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; + } +}); diff --git a/iso/airootfs/etc/skel/.config/bread/breadd.toml b/iso/airootfs/etc/skel/.config/bread/breadd.toml new file mode 100644 index 0000000..8473fe3 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/bread/breadd.toml @@ -0,0 +1,8 @@ +log_level = "info" + +[adapters] +keyboard = true +mouse = true +touchpad = true +bluetooth = true +gamepad = true diff --git a/iso/airootfs/etc/skel/.config/bread/init.lua b/iso/airootfs/etc/skel/.config/bread/init.lua new file mode 100644 index 0000000..270ee7e --- /dev/null +++ b/iso/airootfs/etc/skel/.config/bread/init.lua @@ -0,0 +1 @@ +bread.activate_profile("default") diff --git a/iso/airootfs/etc/skel/.config/breadbox/config.toml b/iso/airootfs/etc/skel/.config/breadbox/config.toml new file mode 100644 index 0000000..797b3cc --- /dev/null +++ b/iso/airootfs/etc/skel/.config/breadbox/config.toml @@ -0,0 +1,3 @@ +[[context]] +name = "default" +apps = ["firefox", "foot", "nautilus", "code"] diff --git a/iso/airootfs/etc/skel/.config/breadcrumbs/breadcrumbs.toml b/iso/airootfs/etc/skel/.config/breadcrumbs/breadcrumbs.toml new file mode 100644 index 0000000..46e26f3 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/breadcrumbs/breadcrumbs.toml @@ -0,0 +1,3 @@ +[[profile]] +name = "home" +ssids = [] diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprland.conf b/iso/airootfs/etc/skel/.config/hypr/hyprland.conf new file mode 100644 index 0000000..e509b63 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/hyprland.conf @@ -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 +} diff --git a/iso/airootfs/etc/skel/.config/hypr/keybinds.conf b/iso/airootfs/etc/skel/.config/hypr/keybinds.conf new file mode 100644 index 0000000..7cf8cdd --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/keybinds.conf @@ -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 diff --git a/iso/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf b/iso/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf new file mode 100644 index 0000000..b9d22eb --- /dev/null +++ b/iso/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin root - $TERM diff --git a/iso/airootfs/root/.bash_profile b/iso/airootfs/root/.bash_profile new file mode 100644 index 0000000..fd13f6a --- /dev/null +++ b/iso/airootfs/root/.bash_profile @@ -0,0 +1,4 @@ +# Auto-start Hyprland on tty1 in the live session +if [[ "$(tty)" == "/dev/tty1" ]] && [[ -z "$WAYLAND_DISPLAY" ]]; then + exec Hyprland +fi diff --git a/iso/airootfs/root/.config/hypr/hyprland.conf b/iso/airootfs/root/.config/hypr/hyprland.conf new file mode 100644 index 0000000..8adb80a --- /dev/null +++ b/iso/airootfs/root/.config/hypr/hyprland.conf @@ -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 +} diff --git a/iso/packages.x86_64 b/iso/packages.x86_64 new file mode 100644 index 0000000..32c97d4 --- /dev/null +++ b/iso/packages.x86_64 @@ -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 diff --git a/iso/pacman.conf b/iso/pacman.conf new file mode 100644 index 0000000..c071a87 --- /dev/null +++ b/iso/pacman.conf @@ -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 diff --git a/iso/profiledef.sh b/iso/profiledef.sh new file mode 100644 index 0000000..8f5cb15 --- /dev/null +++ b/iso/profiledef.sh @@ -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" +)