system settings
git clone https://git.lucas.co/cce-system-interface.git
feat: remove the Fonts page — the DE's families live in config.kdl now
The page edited three foreign files, none of them cce's own config:
fontconfig's fonts.conf (XML), fuzzel.ini and foot.ini. Three of its five
sections had stopped controlling anything:
Borders `window-borders` alias — no reader anywhere in the tree.
Window borders lost their text when titlebars went away.
Status Interface `status-interface` alias — read_status_font_from_config
checks `module { font }`, then `/style/status/font`, and
only then this. Both KDL keys are set on this host, so the
dropdown wrote into a fallback nothing consulted.
Fuzzel fuzzel isn't installed; cce-cloud replaced it and reads
none of this.
The two live ones — sans/serif/mono, and cce-terminal's family — move to a
`fonts { }` block in the shared config, so no cce setting is left squatting
in fontconfig's family namespace. fonts.conf is still fontconfig's file and
still governs GTK/Electron apps; cce just no longer reads or writes it. That
also ends the page's habit of rewriting that file wholesale, preserving only
its `<dir>` lines — any hand-added hinting or antialias rule was destroyed by
touching a font dropdown.
There is deliberately no settings UI left for fonts, following input.kdl's
precedent. cce-fonts is unaffected: it previews faces, it never assigned them.
Page removal is the 9-site wiring in reverse (mod.rs enum + ALL[15→14] +
label, app.rs field/default/two match arms/AppAction, watchers.rs rx+spawn,
main.rs rx field/init/drain/action arm), plus read_preferred_fonts now coming
from cce_ui rather than the page's own copy.
Shadow-verified: the page dropdown lists 14 entries with no Fonts, and the
settings app still renders. 51 tests pass (53 minus the two that tested the
page's own gating).
Co-Authored-By: Claude <[email protected]>
src/app.rs | 6 -
src/main.rs | 24 +-
src/pages/fonts.rs | 725 -----------------------------------------------------
src/pages/mod.rs | 6 +-
src/watchers.rs | 5 +-
5 files changed, 4 insertions(+), 762 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 955498d..44bb171 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -10,7 +10,6 @@ use crate::pages::services;
use crate::pages::system_info;
use crate::pages::timers;
use crate::pages::storage;
-use crate::pages::fonts;
use crate::pages::accounts;
use crate::pages::packages;
use crate::pages::notifications;
@@ -30,7 +29,6 @@ pub struct AppState {
pub system_info: system_info::SystemState,
pub timers: timers::TimersState,
pub storage: storage::StorageState,
- pub fonts: fonts::FontsState,
pub accounts: accounts::AccountsState,
pub packages: packages::PackagesState,
pub notifications: notifications::NotificationsState,
@@ -51,7 +49,6 @@ impl Default for AppState {
system_info: system_info::SystemState::default(),
timers: timers::TimersState::default(),
storage: storage::StorageState::default(),
- fonts: fonts::FontsState::default(),
accounts: accounts::AccountsState::default_mock(),
packages: packages::PackagesState::default(),
notifications: notifications::NotificationsState::default(),
@@ -75,7 +72,6 @@ impl AppState {
Page::Storage => &self.storage,
Page::System => &self.system_info,
Page::Timers => &self.timers,
- Page::Fonts => &self.fonts,
Page::Notifications => &self.notifications,
}
}
@@ -95,7 +91,6 @@ impl AppState {
Page::Storage => &mut self.storage,
Page::System => &mut self.system_info,
Page::Timers => &mut self.timers,
- Page::Fonts => &mut self.fonts,
Page::Notifications => &mut self.notifications,
}
}
@@ -123,7 +118,6 @@ pub enum AppAction {
SystemInfo(system_info::SystemMessage),
Timers(timers::TimersMessage),
Storage(storage::StorageMessage),
- Fonts(fonts::FontsMessage),
Accounts(accounts::AccountsMessage),
Packages(packages::PackagesMessage),
Notifications(notifications::NotificationsMessage),
diff --git a/src/main.rs b/src/main.rs
index a5cf8d3..7cb2b69 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -68,7 +68,6 @@ struct SystemInterface {
rx_services: std::sync::mpsc::Receiver<Vec<pages::services::ServiceInfo>>,
rx_default_apps: std::sync::mpsc::Receiver<pages::default_apps::DefaultAppsInfo>,
rx_timers: std::sync::mpsc::Receiver<Vec<pages::timers::TimerInfo>>,
- rx_fonts: std::sync::mpsc::Receiver<pages::fonts::FontsState>,
rx_accounts: std::sync::mpsc::Receiver<Vec<pages::accounts::AccountInfo>>,
tx_backup: std::sync::mpsc::Sender<pages::storage::StorageMessage>,
rx_backup: std::sync::mpsc::Receiver<pages::storage::StorageMessage>,
@@ -130,10 +129,7 @@ impl cce_ui::engine::Application for SystemInterface {
fn new(_qh: &wayland_client::QueueHandle<cce_ui::engine::EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
cce_ui::scale::set_scale_factor(1.0);
- let app = AppState {
- fonts: pages::fonts::read_typeface_config(),
- ..Default::default()
- };
+ let app = AppState::default();
// ── Background refresh channels ──
let initial_page_idx = INITIAL_PAGE_INDEX.load(std::sync::atomic::Ordering::SeqCst);
@@ -142,7 +138,7 @@ impl cce_ui::engine::Application for SystemInterface {
let (watchers, tx_backup, rx_backup, tx_update, rx_update) =
cce_settings::watchers::spawn_all(current_page_shared.clone());
- let (sans_family, serif_family, monospace_family, _, _, _, _) = pages::fonts::read_preferred_fonts();
+ let (sans_family, serif_family, monospace_family, _) = cce_ui::layout::read_preferred_fonts();
let pages_names = Page::ALL.iter().map(|p| p.label().to_string()).collect::<Vec<_>>();
let page_dropdown = cce_ui::widget::input::Dropdown::new(pages_names, initial_page_idx)
@@ -181,7 +177,6 @@ impl cce_ui::engine::Application for SystemInterface {
rx_notifications: watchers.rx_notifications,
rx_browser: watchers.rx_browser,
rx_services: watchers.rx_services,
- rx_fonts: watchers.rx_fonts,
rx_accounts: watchers.rx_accounts,
tx_backup,
rx_backup,
@@ -598,15 +593,6 @@ impl SystemInterface {
self.needs_rebuild = true;
}
}
- while let Ok(s) = self.rx_fonts.try_recv() {
- self.sans_serif_family = s.sans_serif.clone();
- self.serif_family = s.serif.clone();
- self.monospace_family = s.monospace.clone();
- fonts::update(&mut self.app.fonts, fonts::FontsMessage::TypefaceRefreshed(s));
- if self.app.current_page == Page::Fonts {
- self.needs_rebuild = true;
- }
- }
while let Ok(s) = self.rx_default_apps.try_recv() {
pages::default_apps::update(&mut self.app.default_apps, pages::default_apps::DefaultAppsMessage::Refreshed(s));
if self.app.current_page == Page::DefaultApps {
@@ -674,12 +660,6 @@ impl SystemInterface {
},
- AppAction::Fonts(m) => {
- fonts::update(&mut self.app.fonts, m.clone());
- self.sans_serif_family = self.app.fonts.sans_serif.clone();
- self.serif_family = self.app.fonts.serif.clone();
- self.monospace_family = self.app.fonts.monospace.clone();
- }
AppAction::Accounts(m) => match m {
pages::accounts::AccountsMessage::GoogleLoginInit => {
// One listener at a time: port 36137 is fixed, so a second
diff --git a/src/pages/fonts.rs b/src/pages/fonts.rs
deleted file mode 100644
index 11466f2..0000000
--- a/src/pages/fonts.rs
+++ /dev/null
@@ -1,725 +0,0 @@
-use std::fs;
-use cce_ui::layout::LayoutStrategy;
-use cce_ui::widget::{TextBox, Dropdown, Spinbox};
-use crate::app::{PageContent, AppAction};
-use crate::pages::AppPage;
-
-fn fonts_conf_path() -> String {
- cce_ui::config::config_home().join("fontconfig").join("fonts.conf").to_string_lossy().into_owned()
-}
-fn fuzzel_ini_path() -> String {
- cce_ui::config::config_home().join("fuzzel").join("fuzzel.ini").to_string_lossy().into_owned()
-}
-fn foot_ini_path() -> String {
- cce_ui::config::config_home().join("foot").join("foot.ini").to_string_lossy().into_owned()
-}
-
-#[derive(Debug, Clone)]
-pub enum FontsMessage {
- TypefaceRefreshed(FontsState),
- Refreshed(FontsState),
- SetSans(String),
- SetSerif(String),
- SetMono(String),
- SetBorders(String),
- SetStatus(String),
- SetFuzzel(String),
- SetTerminal(String),
- SetBordersIdx(usize),
- SetStatusIdx(usize),
- SetFuzzelIdx(usize),
- SetTerminalIdx(usize),
- SetFuzzelSize(u16),
- SetTerminalSize(u16),
-}
-
-/// The "Other" entry in each font dropdown — the one selection whose section
-/// also paints a free-text box for a font name the presets don't cover.
-///
-/// Named because the gate now has to be stated in three places (the view, the
-/// dispatch-root report, and the manual registration) and a bare `== 3` drifting
-/// in one of them is exactly the bug this replaced. The boxes used to be
-/// registered by pointer unconditionally: an unpainted box kept its last-drawn
-/// rect, kept hit-testing there, and — because the registration *succeeded* —
-/// never tripped the `unregistered/stale root` warning that catches the same
-/// mistake on every other page.
-const FONT_OTHER: usize = 3;
-
-#[derive(Clone)]
-pub struct FontsState {
- pub typeface_loaded: bool,
- pub sans_serif: String,
- pub serif: String,
- pub monospace: String,
- pub window_borders: String,
- pub status_interface: String,
- pub fuzzel: String,
- pub terminal: String,
- pub all_fonts: Vec<String>,
- pub mono_fonts: Vec<String>,
- pub sans_box: cce_ui::widget::Adapted<TextBox>,
- pub serif_box: cce_ui::widget::Adapted<TextBox>,
- pub mono_box: cce_ui::widget::Adapted<TextBox>,
- pub borders_box: cce_ui::widget::Adapted<TextBox>,
- pub status_box: cce_ui::widget::Adapted<TextBox>,
- pub fuzzel_box: cce_ui::widget::Adapted<TextBox>,
- pub terminal_box: cce_ui::widget::Adapted<TextBox>,
- pub borders_menu: cce_ui::widget::Adapted<Dropdown>,
- pub status_menu: cce_ui::widget::Adapted<Dropdown>,
- pub fuzzel_menu: cce_ui::widget::Adapted<Dropdown>,
- pub terminal_menu: cce_ui::widget::Adapted<Dropdown>,
- pub fuzzel_size_box: cce_ui::widget::Adapted<cce_ui::widget::Spinbox>,
- pub terminal_size_box: cce_ui::widget::Adapted<cce_ui::widget::Spinbox>,
-}
-
-impl std::fmt::Debug for FontsState {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("FontsState").finish()
- }
-}
-
-impl Default for FontsState {
- fn default() -> Self {
- Self {
- typeface_loaded: false,
- sans_serif: "Noto Sans".to_string(),
- serif: "Noto Serif".to_string(),
- monospace: "Noto Sans Mono".to_string(),
- window_borders: "Noto Sans".to_string(),
- status_interface: "Noto Sans".to_string(),
- fuzzel: "Noto Sans".to_string(),
- terminal: "Noto Sans Mono".to_string(),
- all_fonts: Vec::new(),
- mono_fonts: Vec::new(),
- sans_box: TextBox::new(String::new()).with_label("Sans-Serif").with_config(&fonts_conf_path() , "sans-serif"),
- serif_box: TextBox::new(String::new()).with_label("Serif").with_config(&fonts_conf_path() , "serif"),
- mono_box: TextBox::new(String::new()).with_label("Monospace").with_config(&fonts_conf_path() , "monospace"),
- borders_box: TextBox::new(String::new()).with_label("Window Borders").with_config(&fonts_conf_path() , "window_borders"),
- status_box: TextBox::new(String::new()).with_label("Status Interface").with_config(&fonts_conf_path() , "status_interface"),
- fuzzel_box: TextBox::new(String::new()).with_label("Fuzzel").with_config(&fonts_conf_path() , "fuzzel"),
- terminal_box: TextBox::new(String::new()).with_label("Terminal").with_config(&fonts_conf_path() , "terminal"),
- borders_menu: Dropdown::new(Vec::new(), 0).with_config(&fonts_conf_path() , "window_borders"),
- status_menu: Dropdown::new(Vec::new(), 0).with_config(&fonts_conf_path() , "status_interface"),
- fuzzel_menu: Dropdown::new(Vec::new(), 0).with_config(&fonts_conf_path() , "fuzzel"),
- terminal_menu: Dropdown::new(Vec::new(), 0).with_config(&fonts_conf_path() , "terminal"),
- fuzzel_size_box: Spinbox::new(14, 6, 72, 1).with_config(&fuzzel_ini_path() , "size"),
- terminal_size_box: Spinbox::new(12, 6, 72, 1).with_config(&foot_ini_path() , "size"),
- }
- }
-}
-
-impl AppPage for FontsState {
- // Sections: [Preferred Fonts, Borders, Status Interface, Fuzzel, Terminal]
- // Each section's free-text box is reported only when its menu is on "Other",
- // mirroring the view below — the boxes are the only conditionally-painted
- // widgets here, and the groups stay in the view's paint order either way.
- fn section_widgets(&mut self) -> Vec<Vec<cce_ui::widget::WidgetId>> {
- let mut borders = vec![self.borders_menu.id()];
- if self.borders_menu.selected == FONT_OTHER {
- borders.push(self.borders_box.id());
- }
- let mut status = vec![self.status_menu.id()];
- if self.status_menu.selected == FONT_OTHER {
- status.push(self.status_box.id());
- }
- let mut fuzzel = vec![self.fuzzel_menu.id()];
- if self.fuzzel_menu.selected == FONT_OTHER {
- fuzzel.push(self.fuzzel_box.id());
- }
- fuzzel.push(self.fuzzel_size_box.id());
- let mut terminal = vec![self.terminal_menu.id()];
- if self.terminal_menu.selected == FONT_OTHER {
- terminal.push(self.terminal_box.id());
- }
- terminal.push(self.terminal_size_box.id());
- vec![
- vec![self.sans_box.id(), self.serif_box.id(), self.mono_box.id()],
- borders,
- status,
- fuzzel,
- terminal,
- ]
- }
-
- // Not every section widget passes through `render_widget`'s registration side
- // effect (the menus draw custom); the id-rooted router needs them all resolvable.
- //
- // The four "Other" boxes carry the same gate as `section_widgets` above and
- // the view below. Registering one the view did not paint does not merely
- // waste a slot — it resurrects a widget at its last-drawn rect, ahead of the
- // live widgets that now occupy that space in the dispatch order.
- fn register_extra_dispatch_roots(&mut self, ctx: &mut cce_ui::context::UiContext) {
- let (id, ptr) = (self.sans_box.id(), self.sans_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- let (id, ptr) = (self.serif_box.id(), self.serif_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- let (id, ptr) = (self.mono_box.id(), self.mono_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- let (id, ptr) = (self.borders_menu.id(), self.borders_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- if self.borders_menu.selected == FONT_OTHER {
- let (id, ptr) = (self.borders_box.id(), self.borders_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
- let (id, ptr) = (self.status_menu.id(), self.status_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- if self.status_menu.selected == FONT_OTHER {
- let (id, ptr) = (self.status_box.id(), self.status_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
- let (id, ptr) = (self.fuzzel_menu.id(), self.fuzzel_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- if self.fuzzel_menu.selected == FONT_OTHER {
- let (id, ptr) = (self.fuzzel_box.id(), self.fuzzel_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
- let (id, ptr) = (self.fuzzel_size_box.id(), self.fuzzel_size_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- let (id, ptr) = (self.terminal_menu.id(), self.terminal_menu.as_ptr_mut());
- ctx.register_widget(id, ptr);
- if self.terminal_menu.selected == FONT_OTHER {
- let (id, ptr) = (self.terminal_box.id(), self.terminal_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
- let (id, ptr) = (self.terminal_size_box.id(), self.terminal_size_box.as_ptr_mut());
- ctx.register_widget(id, ptr);
- }
-
- fn view(
- &mut self,
- cx: f32,
- cy: f32,
- cw: f32,
- ch: f32,
- _root_focused: bool,
- sec_focused: &[bool],
- layout: &mut dyn LayoutStrategy,
- ctx: &mut cce_ui::context::UiContext,
- ) -> PageContent {
- let mut final_pc = PageContent::new();
- let sec_w = 260.0f32;
- let mut builder = cce_ui::layout::PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(5);
-
- builder.add_section(&mut final_pc, "Preferred Fonts", sec_focused.get(0).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
- let sec_w = stack.context.cw;
- stack.add_widget(&mut self.sans_box, sec_w - 28.0, 44.0, ctx);
- stack.add_widget(&mut self.serif_box, sec_w - 28.0, 44.0, ctx);
- stack.add_widget(&mut self.mono_box, sec_w - 28.0, 44.0, ctx);
- });
-
- builder.add_section(&mut final_pc, "Borders", sec_focused.get(1).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
- let sec_w = stack.context.cw;
- stack.add_widget(&mut self.borders_menu, sec_w - 28.0, 44.0, ctx);
- if self.borders_menu.selected == FONT_OTHER {
- stack.add_widget(&mut self.borders_box, sec_w - 28.0, 44.0, ctx);
- }
- });
-
- builder.add_section(&mut final_pc, "Status Interface", sec_focused.get(2).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
- let sec_w = stack.context.cw;
- stack.add_widget(&mut self.status_menu, sec_w - 28.0, 44.0, ctx);
- if self.status_menu.selected == FONT_OTHER {
- stack.add_widget(&mut self.status_box, sec_w - 28.0, 44.0, ctx);
- }
- });
-
- builder.add_section(&mut final_pc, "Fuzzel", sec_focused.get(3).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
- let sec_w = stack.context.cw;
- stack.add_widget(&mut self.fuzzel_menu, sec_w - 28.0, 44.0, ctx);
- if self.fuzzel_menu.selected == FONT_OTHER {
- stack.add_widget(&mut self.fuzzel_box, sec_w - 28.0, 44.0, ctx);
- }
- stack.add_widget(&mut self.fuzzel_size_box, sec_w - 28.0, 44.0, ctx);
- });
-
- builder.add_section(&mut final_pc, "Terminal", sec_focused.get(4).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
- let sec_w = stack.context.cw;
- stack.add_widget(&mut self.terminal_menu, sec_w - 28.0, 44.0, ctx);
- if self.terminal_menu.selected == FONT_OTHER {
- stack.add_widget(&mut self.terminal_box, sec_w - 28.0, 44.0, ctx);
- }
- stack.add_widget(&mut self.terminal_size_box, sec_w - 28.0, 44.0, ctx);
- });
-
- final_pc
- }
-
- fn propagate_widget_changes(&mut self, actions: &mut Vec<AppAction>) {
- if self.sans_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetSans(self.sans_box.text.clone())));
- }
- if self.serif_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetSerif(self.serif_box.text.clone())));
- }
- if self.mono_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetMono(self.mono_box.text.clone())));
- }
- if self.borders_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetBorders(self.borders_box.text.clone())));
- }
- if self.status_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetStatus(self.status_box.text.clone())));
- }
- if self.fuzzel_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetFuzzel(self.fuzzel_box.text.clone())));
- }
- if self.terminal_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetTerminal(self.terminal_box.text.clone())));
- }
-
- if self.borders_menu.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetBordersIdx(self.borders_menu.selected)));
- }
- if self.status_menu.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetStatusIdx(self.status_menu.selected)));
- }
- if self.fuzzel_menu.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetFuzzelIdx(self.fuzzel_menu.selected)));
- }
- if self.terminal_menu.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetTerminalIdx(self.terminal_menu.selected)));
- }
-
- if self.fuzzel_size_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetFuzzelSize(self.fuzzel_size_box.value as u16)));
- }
- if self.terminal_size_box.take_change() {
- actions.push(AppAction::Fonts(FontsMessage::SetTerminalSize(self.terminal_size_box.value as u16)));
- }
- }
-}
-
-fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
- let lines: Vec<&str> = content.lines().collect();
- for i in 0..lines.len() {
- let line = lines[i].trim();
- if line.contains("<test") && line.contains("name=\"family\"") && line.contains(&format!("<string>{}</string>", alias)) {
- for j in (i + 1)..(i + 6).min(lines.len()) {
- let next_line = lines[j].trim();
- if next_line.contains("<edit") {
- for k in (j + 1)..(j + 6).min(lines.len()) {
- let str_line = lines[k].trim();
- if str_line.contains("<string>") && str_line.contains("</string>") {
- if let Some(start) = str_line.find("<string>") {
- if let Some(end) = str_line.find("</string>") {
- let font = &str_line[start + 8..end];
- return Some(font.to_string());
- }
- }
- }
- }
- }
- }
- }
- }
- None
-}
-
-pub fn read_preferred_fonts() -> (String, String, String, String, String, String, String) {
- let content = fs::read_to_string(&fonts_conf_path() ).unwrap_or_default();
-
- let sans = parse_font_for_alias(&content, "sans-serif").unwrap_or_else(|| "Noto Sans".to_string());
- let serif = parse_font_for_alias(&content, "serif").unwrap_or_else(|| "Noto Serif".to_string());
- let mono = parse_font_for_alias(&content, "monospace").unwrap_or_else(|| "Noto Sans Mono".to_string());
- let borders = parse_font_for_alias(&content, "window-borders").unwrap_or_else(|| "Noto Sans".to_string());
- let status = parse_font_for_alias(&content, "status-interface").unwrap_or_else(|| "Noto Sans".to_string());
- let fuzzel_font = parse_font_for_alias(&content, "fuzzel").unwrap_or_else(|| "Noto Sans".to_string());
- let term = parse_font_for_alias(&content, "terminal").unwrap_or_else(|| "Noto Sans Mono".to_string());
-
- (sans, serif, mono, borders, status, fuzzel_font, term)
-}
-
-pub fn save_preferred_fonts(
- sans: &str,
- serif: &str,
- mono: &str,
- borders: &str,
- status: &str,
- fuzzel: &str,
- terminal: &str,
-) {
- let content = fs::read_to_string(&fonts_conf_path() ).unwrap_or_default();
- let mut dirs = Vec::new();
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed.starts_with("<dir>") && trimmed.ends_with("</dir>") {
- dirs.push(trimmed.to_string());
- }
- }
- if dirs.is_empty() {
- dirs.push("<dir>~/Dropbox/Fonts</dir>".to_string());
- }
-
- let mut new_content = String::new();
- new_content.push_str("<?xml version=\"1.0\"?>\n");
- new_content.push_str("<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n");
- new_content.push_str("<fontconfig>\n");
-
- for dir in dirs {
- new_content.push_str(&format!(" {}\n", dir));
- }
-
- // Sans-Serif
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>sans-serif</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", sans));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Serif
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>serif</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", serif));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Monospace
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>monospace</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", mono));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Window Borders
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>window-borders</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", borders));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Status Interface
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>status-interface</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", status));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Fuzzel
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>fuzzel</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", fuzzel));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- // Terminal
- new_content.push_str(" <match target=\"pattern\">\n");
- new_content.push_str(" <test qual=\"any\" name=\"family\"><string>terminal</string></test>\n");
- new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
- new_content.push_str(&format!(" <string>{}</string>\n", terminal));
- new_content.push_str(" </edit>\n");
- new_content.push_str(" </match>\n");
-
- new_content.push_str("</fontconfig>\n");
- let _ = fs::write(&fonts_conf_path() , new_content);
-}
-
-fn read_fuzzel_size() -> Option<u16> {
- let ini = fs::read_to_string(&fuzzel_ini_path() ).ok()?;
- for line in ini.lines() {
- let trimmed = line.trim();
- if trimmed.starts_with("font") {
- if let Some(pos) = trimmed.find("size=") {
- let size_str = &trimmed[pos + 5..];
- let end_pos = size_str.find(|c: char| !c.is_ascii_digit()).unwrap_or(size_str.len());
- return size_str[..end_pos].parse::<u16>().ok();
- }
- }
- }
- None
-}
-
-fn write_fuzzel_size(size: u16) {
- let path = &fuzzel_ini_path() ;
- let ini = fs::read_to_string(path).unwrap_or_default();
- let mut new_lines = Vec::new();
- for line in ini.lines() {
- let trimmed = line.trim();
- if trimmed.starts_with("font") {
- if let Some(pos) = line.find("size=") {
- let mut new_line = line[..pos + 5].to_string();
- new_line.push_str(&size.to_string());
- let size_str = &line[pos + 5..];
- let skip = size_str.find(|c: char| !c.is_ascii_digit()).unwrap_or(size_str.len());
- new_line.push_str(&size_str[skip..]);
- new_lines.push(new_line);
- } else {
- new_lines.push(line.to_string());
- }
- } else {
- new_lines.push(line.to_string());
- }
- }
- let _ = fs::write(path, new_lines.join("\n"));
-}
-
-fn read_terminal_size() -> Option<u16> {
- let ini = fs::read_to_string(&foot_ini_path() ).ok()?;
- for line in ini.lines() {
- let trimmed = line.trim();
- if trimmed.starts_with("font") {
- if let Some(pos) = trimmed.find("terminal:size=") {
- let size_str = &trimmed[pos + 14..];
- let end_pos = size_str.find(|c: char| !c.is_ascii_digit()).unwrap_or(size_str.len());
- return size_str[..end_pos].parse::<u16>().ok();
- }
- }
- }
- None
-}
-
-fn write_terminal_size(size: u16) {
- let path = &foot_ini_path() ;
- let ini = fs::read_to_string(path).unwrap_or_default();
- let mut new_lines = Vec::new();
- for line in ini.lines() {
- let trimmed = line.trim();
- if trimmed.starts_with("font") {
- if let Some(pos) = line.find("terminal:size=") {
- let mut new_line = line[..pos + 14].to_string();
- new_line.push_str(&size.to_string());
- let size_str = &line[pos + 14..];
- let skip = size_str.find(|c: char| !c.is_ascii_digit()).unwrap_or(size_str.len());
- new_line.push_str(&size_str[skip..]);
- new_lines.push(new_line);
- } else {
- new_lines.push(line.to_string());
- }
- } else {
- new_lines.push(line.to_string());
- }
- }
- let _ = fs::write(path, new_lines.join("\n"));
-}
-
-pub async fn fetch_typeface_state() -> FontsState {
- read_typeface_config()
-}
-
-fn determine_dropdown_index(val: &str, sans: &str, serif: &str, mono: &str) -> usize {
- if val == sans { 0 }
- else if val == serif { 1 }
- else if val == mono { 2 }
- else { 3 }
-}
-
-fn determine_font_from_index(idx: usize, state: &FontsState) -> String {
- match idx {
- 0 => state.sans_serif.clone(),
- 1 => state.serif.clone(),
- 2 => state.monospace.clone(),
- _ => "Noto Sans".to_string(),
- }
-}
-
-pub fn read_typeface_config() -> FontsState {
- let (sans, serif, mono, borders, status, fuzzel_font, term) = read_preferred_fonts();
-
- let font_dirs = vec![
- "/usr/share/fonts".to_string(),
- cce_ui::fonts_dir(),
- ];
- let mut all_fonts = Vec::new();
- let mut mono_fonts = Vec::new();
- for d in font_dirs {
- if let Ok(entries) = fs::read_dir(d) {
- for entry in entries.flatten() {
- if let Some(name) = entry.file_name().to_str() {
- if name.ends_with(".ttf") || name.ends_with(".otf") {
- let name_clean = name.trim_end_matches(".ttf").trim_end_matches(".otf").replace("-", " ");
- all_fonts.push(name_clean.clone());
- if name_clean.to_lowercase().contains("mono") {
- mono_fonts.push(name_clean);
- }
- }
- }
- }
- }
- }
- all_fonts.sort();
- all_fonts.dedup();
- mono_fonts.sort();
- mono_fonts.dedup();
-
- let borders_idx = determine_dropdown_index(&borders, &sans, &serif, &mono);
- let status_idx = determine_dropdown_index(&status, &sans, &serif, &mono);
- let fuzzel_idx = determine_dropdown_index(&fuzzel_font, &sans, &serif, &mono);
- let terminal_idx = determine_dropdown_index(&term, &sans, &serif, &mono);
- let menu_options = vec![
- "Sans-Serif".to_string(),
- "Serif".to_string(),
- "Monospace".to_string(),
- "Other".to_string(),
- ];
-
- let mut borders_box = TextBox::new(borders.clone()).with_label("Window Borders").with_config(&fonts_conf_path() , "window_borders");
- borders_box.disabled = borders_idx != 3;
-
- let mut status_box = TextBox::new(status.clone()).with_label("Status Interface").with_config(&fonts_conf_path() , "status_interface");
- status_box.disabled = status_idx != 3;
-
- let mut fuzzel_box = TextBox::new(fuzzel_font.clone()).with_label("Fuzzel").with_config(&fonts_conf_path() , "fuzzel");
- fuzzel_box.disabled = fuzzel_idx != 3;
-
- let mut terminal_box = TextBox::new(term.clone()).with_label("Terminal").with_config(&fonts_conf_path() , "terminal");
- terminal_box.disabled = terminal_idx != 3;
-
- let fuzzel_size = read_fuzzel_size().unwrap_or(14);
- let terminal_size = read_terminal_size().unwrap_or(12);
-
- let mut state = FontsState::default();
- state.typeface_loaded = true;
- state.sans_serif = sans.clone();
- state.serif = serif.clone();
- state.monospace = mono.clone();
- state.window_borders = borders;
- state.status_interface = status;
- state.fuzzel = fuzzel_font;
- state.terminal = term;
- state.all_fonts = all_fonts;
- state.mono_fonts = mono_fonts;
- state.sans_box = TextBox::new(sans).with_label("Sans-Serif").with_config(&fonts_conf_path() , "sans-serif");
- state.serif_box = TextBox::new(serif).with_label("Serif").with_config(&fonts_conf_path() , "serif");
- state.mono_box = TextBox::new(mono).with_label("Monospace").with_config(&fonts_conf_path() , "monospace");
- state.borders_box = borders_box;
- state.status_box = status_box;
- state.fuzzel_box = fuzzel_box;
- state.terminal_box = terminal_box;
- state.borders_menu = Dropdown::new(menu_options.clone(), borders_idx).with_config(&fonts_conf_path() , "window_borders");
- state.status_menu = Dropdown::new(menu_options.clone(), status_idx).with_config(&fonts_conf_path() , "status_interface");
- state.fuzzel_menu = Dropdown::new(menu_options.clone(), fuzzel_idx).with_config(&fonts_conf_path() , "fuzzel");
- state.terminal_menu = Dropdown::new(menu_options, terminal_idx).with_config(&fonts_conf_path() , "terminal");
- state.fuzzel_size_box = Spinbox::new(fuzzel_size as i32, 6, 72, 1).with_config(&fuzzel_ini_path() , "size");
- state.terminal_size_box = Spinbox::new(terminal_size as i32, 6, 72, 1).with_config(&foot_ini_path() , "size");
- state
-}
-
-pub fn update(state: &mut FontsState, msg: FontsMessage) {
- match msg {
- FontsMessage::TypefaceRefreshed(refreshed) | FontsMessage::Refreshed(refreshed) => {
- state.typeface_loaded = refreshed.typeface_loaded;
- state.sans_serif = refreshed.sans_serif;
- state.serif = refreshed.serif;
- state.monospace = refreshed.monospace;
- state.window_borders = refreshed.window_borders;
- state.status_interface = refreshed.status_interface;
- state.fuzzel = refreshed.fuzzel;
- state.terminal = refreshed.terminal;
- state.all_fonts = refreshed.all_fonts;
- state.mono_fonts = refreshed.mono_fonts;
- }
- FontsMessage::SetSans(font) => {
- state.sans_serif = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetSerif(font) => {
- state.serif = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetMono(font) => {
- state.monospace = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetBorders(font) => {
- state.window_borders = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetStatus(font) => {
- state.status_interface = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetFuzzel(font) => {
- state.fuzzel = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetTerminal(font) => {
- state.terminal = font;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- FontsMessage::SetBordersIdx(idx) => {
- state.borders_menu.selected = idx;
- state.borders_box.disabled = idx != 3;
- if idx < 3 {
- state.window_borders = determine_font_from_index(idx, state);
- state.borders_box.set_value_string(&state.window_borders);
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- }
- FontsMessage::SetStatusIdx(idx) => {
- state.status_menu.selected = idx;
- state.status_box.disabled = idx != 3;
- if idx < 3 {
- state.status_interface = determine_font_from_index(idx, state);
- state.status_box.set_value_string(&state.status_interface);
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- }
- FontsMessage::SetFuzzelIdx(idx) => {
- state.fuzzel_menu.selected = idx;
- state.fuzzel_box.disabled = idx != 3;
- if idx < 3 {
- state.fuzzel = determine_font_from_index(idx, state);
- state.fuzzel_box.set_value_string(&state.fuzzel);
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- }
- FontsMessage::SetTerminalIdx(idx) => {
- state.terminal_menu.selected = idx;
- state.terminal_box.disabled = idx != 3;
- if idx < 3 {
- state.terminal = determine_font_from_index(idx, state);
- state.terminal_box.set_value_string(&state.terminal);
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace, &state.window_borders, &state.status_interface, &state.fuzzel, &state.terminal);
- }
- }
- FontsMessage::SetFuzzelSize(size) => {
- write_fuzzel_size(size);
- }
- FontsMessage::SetTerminalSize(size) => {
- write_terminal_size(size);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn section_widgets_gate_other_boxes() {
- let mut st = FontsState::default();
- // Presets selected: the four free-text boxes are not painted, so the
- // menu is the section's only reported root (plus the size spinbox where
- // the section has one).
- let g = st.section_widgets();
- assert_eq!(g[1].len(), 1, "borders: menu only");
- assert_eq!(g[2].len(), 1, "status: menu only");
- assert_eq!(g[3].len(), 2, "fuzzel: menu + size");
- assert_eq!(g[4].len(), 2, "terminal: menu + size");
-
- st.borders_menu.selected = FONT_OTHER;
- st.fuzzel_menu.selected = FONT_OTHER;
- let g = st.section_widgets();
- assert_eq!(g[1].len(), 2, "borders: menu + box");
- assert_eq!(g[2].len(), 1, "status untouched");
- assert_eq!(g[3].len(), 3, "fuzzel: menu + box + size");
- assert_eq!(g[4].len(), 2, "terminal untouched");
- }
-
- #[test]
- fn section_group_count_matches_the_view() {
- // The outer length picks each section's `sec_focused` index, so it has
- // to track the five `add_section` calls in the view.
- assert_eq!(FontsState::default().section_widgets().len(), 5);
- }
-}
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index d633337..d7bb0bd 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -8,7 +8,6 @@ pub mod system_info;
pub mod timers;
pub mod processes;
pub mod services;
-pub mod fonts;
pub mod accounts;
pub mod packages;
pub mod notifications;
@@ -29,18 +28,16 @@ pub enum Page {
Timers,
Processes,
Services,
- Fonts,
Packages,
}
impl Page {
- pub const ALL: [Page; 15] = [
+ pub const ALL: [Page; 14] = [
Page::Accounts,
Page::Audio,
Page::Bluetooth,
Page::Browser,
Page::DefaultApps,
- Page::Fonts,
Page::Network,
Page::Notifications,
Page::Packages,
@@ -67,7 +64,6 @@ impl Page {
Page::Timers => "Timers",
Page::Processes => "Processes",
Page::Services => "Services",
- Page::Fonts => "Fonts",
Page::Packages => "Packages",
}
}
diff --git a/src/watchers.rs b/src/watchers.rs
index 0299fc2..b62dc5a 100644
--- a/src/watchers.rs
+++ b/src/watchers.rs
@@ -1,7 +1,7 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
-use crate::pages::{Page, audio, bluetooth, browser, default_apps, network, fonts, processes, services, system_info, storage, packages, accounts, notifications, timers, power};
+use crate::pages::{Page, audio, bluetooth, browser, default_apps, network, processes, services, system_info, storage, packages, accounts, notifications, timers, power};
pub struct Watchers {
pub rx_audio: Receiver<audio::AudioState>,
@@ -16,7 +16,6 @@ pub struct Watchers {
pub rx_services: Receiver<Vec<services::ServiceInfo>>,
pub rx_default_apps: Receiver<default_apps::DefaultAppsInfo>,
pub rx_timers: Receiver<Vec<timers::TimerInfo>>,
- pub rx_fonts: Receiver<fonts::FontsState>,
pub rx_accounts: Receiver<Vec<accounts::AccountInfo>>,
pub rx_packages: Receiver<packages::PackagesState>,
}
@@ -125,7 +124,6 @@ pub fn spawn_all(
rx
};
- let rx_fonts = spawn_bg_active(current_page_shared.clone(), Page::Fonts.index() as u8, 30, || fonts::fetch_typeface_state());
let rx_services = spawn_bg_active(current_page_shared.clone(), Page::Services.index() as u8, 3, || services::fetch_services());
let rx_default_apps = spawn_bg_active(current_page_shared.clone(), Page::DefaultApps.index() as u8, 10, || default_apps::fetch_default_apps());
let rx_timers = spawn_bg_active(current_page_shared.clone(), Page::Timers.index() as u8, 5, || timers::fetch_timers());
@@ -149,7 +147,6 @@ pub fn spawn_all(
rx_services,
rx_default_apps,
rx_timers,
- rx_fonts,
rx_accounts,
rx_packages,
},