Refactor theme onto bread-theme; add bakery.toml and release workflow

- breadbox/Cargo.toml: depend on bread-theme with gtk feature
- breadbox/src/main.rs: remove local Palette + hex_to_rgba; use
  bread_theme::{load_palette, hex_to_rgba, gtk::apply_user_css}
- bakery.toml: describes breadbox for bakery install
- release.yml: builds on hestia self-hosted runner, publishes binaries to
  dl.breadway.dev and GitHub Releases on v* tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Breadway 2026-06-06 22:31:20 +08:00
parent 81319dd584
commit 53756e840a
5 changed files with 251 additions and 73 deletions

62
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,62 @@
name: release
on:
push:
tags: ["v*"]
env:
DL_DIR: /srv/breadway-dl
ECOSYSTEM_DIR: /home/breadway/Projects/bread-ecosystem
jobs:
build:
runs-on: [self-hosted, hestia]
steps:
- uses: actions/checkout@v4
- name: install system deps
run: sudo pacman -S --noconfirm gtk4 gtk4-layer-shell librsvg 2>/dev/null || true
- name: build
run: cargo build --release --locked
- name: prepare artifacts
run: |
VERSION="${GITHUB_REF_NAME#v}"
PKG_DIR="${DL_DIR}/breadbox/${VERSION}"
mkdir -p "${PKG_DIR}"
for bin in breadbox breadbox-sync; do
cp "target/release/${bin}" "${PKG_DIR}/${bin}-x86_64"
strip "${PKG_DIR}/${bin}-x86_64"
sha256sum "${PKG_DIR}/${bin}-x86_64" | awk '{print $1}' \
> "${PKG_DIR}/${bin}-x86_64.sha256"
done
cp packaging/breadbox-sync.service "${PKG_DIR}/"
cp config.example.toml "${PKG_DIR}/"
cp bakery.toml "${PKG_DIR}/bakery.toml"
ln -sfn "${PKG_DIR}" "${DL_DIR}/breadbox/latest"
- name: ensure bread-ecosystem
run: |
if [[ -d "${ECOSYSTEM_DIR}/.git" ]]; then
git -C "${ECOSYSTEM_DIR}" pull --ff-only
else
mkdir -p "$(dirname "${ECOSYSTEM_DIR}")"
git clone https://github.com/Breadway/bread-ecosystem.git "${ECOSYSTEM_DIR}"
fi
- name: regenerate index.json
run: bash "${ECOSYSTEM_DIR}/scripts/gen-index.sh"
- name: upload to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
PKG_DIR="${DL_DIR}/breadbox/${VERSION}"
gh release upload "${GITHUB_REF_NAME}" \
"${PKG_DIR}/breadbox-x86_64" \
"${PKG_DIR}/breadbox-sync-x86_64" \
"${PKG_DIR}/breadbox-x86_64.sha256" \
"${PKG_DIR}/breadbox-sync-x86_64.sha256" \
--clobber

160
Cargo.lock generated
View file

@ -26,10 +26,21 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "bread-theme"
version = "0.1.0"
dependencies = [
"dirs",
"gtk4",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "breadbox" name = "breadbox"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bread-theme",
"breadbox-shared", "breadbox-shared",
"gtk4", "gtk4",
"gtk4-layer-shell", "gtk4-layer-shell",
@ -111,6 +122,27 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@ -675,6 +707,15 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.2"
@ -718,6 +759,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.22.6" version = "0.22.6"
@ -796,6 +843,17 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@ -998,6 +1056,26 @@ version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tinystr" name = "tinystr"
version = "0.8.3" version = "0.8.3"
@ -1182,13 +1260,22 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -1200,34 +1287,67 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm",
"windows_i686_msvc", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@ -1240,24 +1360,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"

18
bakery.toml Normal file
View file

@ -0,0 +1,18 @@
name = "breadbox"
description = "App launcher for Hyprland / Wayland"
binaries = ["breadbox", "breadbox-sync"]
system_deps = ["gtk4", "gtk4-layer-shell", "librsvg"]
bread_deps = []
[[service]]
unit = "breadbox-sync.service"
enable = true
[config]
dir = "~/.config/breadbox"
example = "config.example.toml"
[install]
post_install = [
"systemctl --user start breadbox-sync.service 2>/dev/null || breadbox-sync",
]

View file

@ -9,6 +9,9 @@ name = "breadbox"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
# Path dep for local dev; replace with git dep on first tag:
# bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "theme-v0.1.0", features = ["gtk"] }
bread-theme = { path = "../../bread-ecosystem/bread-theme", features = ["gtk"] }
breadbox-shared = { path = "../breadbox-shared" } breadbox-shared = { path = "../breadbox-shared" }
gtk4 = { version = "0.11", features = ["v4_12"] } gtk4 = { version = "0.11", features = ["v4_12"] }
gtk4-layer-shell = "0.8" gtk4-layer-shell = "0.8"

View file

@ -1,3 +1,4 @@
use bread_theme::{hex_to_rgba, load_palette, Palette};
use std::{ use std::{
collections::HashMap, collections::HashMap,
env, env,
@ -10,7 +11,7 @@ use std::{
}; };
use breadbox_shared::{ use breadbox_shared::{
config_dir, home_dir, load_all_desktop_entries, Config, DesktopEntry, IconCache, config_dir, load_all_desktop_entries, Config, DesktopEntry, IconCache,
}; };
use gtk4::{ use gtk4::{
gdk::Display, gdk::Display,
@ -124,74 +125,28 @@ fn matches_term(field: &str, term: &str) -> bool {
// ---- Theming ---------------------------------------------------------------- // ---- Theming ----------------------------------------------------------------
#[derive(Debug)]
struct Palette {
bg: String,
surface: String,
fg: String,
accent: String,
}
impl Palette {
fn catppuccin_mocha() -> Self {
Palette {
bg: "#1e1e2e".into(),
surface: "#181825".into(),
fg: "#cdd6f4".into(),
accent: "#89b4fa".into(),
}
}
fn from_wal() -> Option<Self> {
let path = env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| home_dir().join(".cache"))
.join("wal/colors.json");
let content = fs::read_to_string(&path).ok()?;
let v: serde_json::Value = serde_json::from_str(&content).ok()?;
let spec = &v["special"];
let cols = &v["colors"];
let bg = spec["background"].as_str()?.to_string();
let surface = cols["color0"].as_str().unwrap_or(&bg).to_string();
let fg = cols["color15"].as_str().unwrap_or("#cdd6f4").to_string();
let accent = cols["color1"].as_str().unwrap_or("#89b4fa").to_string();
Some(Palette { bg, surface, fg, accent })
}
}
fn hex_to_rgba(hex: &str, alpha: f32) -> String {
let h = hex.trim_start_matches('#');
let r = u8::from_str_radix(h.get(0..2).unwrap_or("00"), 16).unwrap_or(0);
let g = u8::from_str_radix(h.get(2..4).unwrap_or("00"), 16).unwrap_or(0);
let b = u8::from_str_radix(h.get(4..6).unwrap_or("00"), 16).unwrap_or(0);
format!("rgba({r}, {g}, {b}, {alpha})")
}
fn build_css(p: &Palette) -> String { fn build_css(p: &Palette) -> String {
let bg_panel = hex_to_rgba(&p.bg, 0.60); let bg_panel = hex_to_rgba(&p.background, 0.60);
format!( format!(
"* {{ font-family: 'JetBrainsMono Nerd Font Mono', monospace; font-size: 14px; }}\ "* {{ font-family: 'Varela Round', sans-serif; font-size: 14px; }}\
window {{ background-color: transparent; }}\ window {{ background-color: transparent; }}\
.launcher-bg {{ background-color: {bg_panel}; border-radius: 8px;\ .launcher-bg {{ background-color: {bg_panel}; border-radius: 8px;\
box-shadow: 0 8px 32px rgba(0,0,0,0.6); }}\ box-shadow: 0 8px 32px rgba(0,0,0,0.6); }}\
searchentry {{ background-color: {surface}; color: {fg}; caret-color: {accent};\ searchentry {{ background-color: {surface}; color: {fg}; caret-color: {accent};\
border: none; outline: none; box-shadow: none;\ border: none; outline: none; box-shadow: none;\
padding: 12px 16px; border-radius: 4px 4px 0 0; }}\ padding: 12px 16px; border-radius: 6px 6px 0 0; }}\
listbox {{ background-color: transparent; padding: 4px; }}\ listbox {{ background-color: transparent; padding: 4px; }}\
row {{ padding: 5px 10px; color: {fg}; background-color: transparent;\ row {{ padding: 8px 12px; color: {fg}; background-color: transparent;\
border-radius: 4px; }}\ border-radius: 6px; }}\
row:hover {{ background-color: {surface}; }}\ row:hover {{ background-color: {surface}; }}\
row:selected {{ background-color: {surface}; }}\ row:selected {{ background-color: {surface}; }}\
.app-name {{ font-size: 14px; }}\ .app-name {{ font-size: 14px; }}\
.app-muted {{ color: {fg}; opacity: 0.6; font-size: 12px; }}\ .app-muted {{ color: {fg}; opacity: 0.6; font-size: 12px; }}\
image {{ margin-right: 8px; }}", image {{ margin-right: 8px; }}",
bg_panel = bg_panel, bg_panel = bg_panel,
surface = p.surface, surface = p.color0,
fg = p.fg, fg = p.foreground,
accent = p.accent, accent = p.color4,
) )
} }
@ -334,15 +289,11 @@ fn run_ui(entries: Vec<DesktopEntry>, css: String) {
); );
// User CSS override // User CSS override
let user_css_path = config_dir().join("style.css"); {
if user_css_path.exists() { use std::cell::RefCell;
let user_provider = CssProvider::new(); let user_css_path = config_dir().join("style.css");
user_provider.load_from_path(&user_css_path); let user_cell: RefCell<Option<CssProvider>> = RefCell::new(None);
gtk4::style_context_add_provider_for_display( bread_theme::gtk::apply_user_css(&user_css_path, &user_cell);
&Display::default().expect("no display"),
&user_provider,
gtk4::STYLE_PROVIDER_PRIORITY_USER,
);
} }
// Full-screen transparent window; clicks outside the launcher panel close it. // Full-screen transparent window; clicks outside the launcher panel close it.
@ -557,7 +508,7 @@ fn main() {
let manifest = load_manifest(); let manifest = load_manifest();
let entries = load_sorted_entries(&manifest, &priority); let entries = load_sorted_entries(&manifest, &priority);
let palette = Palette::from_wal().unwrap_or_else(Palette::catppuccin_mocha); let palette = load_palette();
let css = build_css(&palette); let css = build_css(&palette);
run_ui(entries, css); run_ui(entries, css);