From 4831a6c3aba18cd006cbdff748faebb08107ad2c Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sat, 31 Oct 2026 10:48:17 -0700 Subject: [PATCH] refactor(bar-core): WidgetConfig with a variant for every widget lives in the core The widget configuration enum moves to widgets/widget.rs of the core with all thirteen variants (Keyboard, Media and Systray are no longer cfg-gated or commented out per tree), plus `supported()`/`unsupported()`/`enabled()` (enabled is now false for a widget the platform cannot run) and `warn_unsupported`, which both shells call when they read the config. The construction of the widget from its config (`as_boxed_bar_widget`, which needs the runtime types) becomes the extension trait `WidgetConfigExt` in each shell widget.rs; macOS builds a no-op `Unsupported` for the three Windows-only widgets, which `enabled()` never lets through. BEHAVIOUR (macOS), per D-coord-1: a macOS config with a `Keyboard`, `Media` or `Systray` widget is now accepted and skipped with a logged warning ("the Keyboard widget is not supported on this platform and is skipped"); before it failed to deserialize (unknown variant), so the whole bar config was rejected. Mac hand check: such a config starts the bar and logs the warning. The macOS schema view removes the three variants from the schema, so komorebi-for-mac/schema.bar.json and the nix options do not change. Verified: schema harness macOS and Windows byte-identical (3,774 / 9,091 lines); native bar-core tests 3+5; Windows clippy -D warnings --locked clean (re-run after touching the files: Checking komorebi-bar-core and komorebi-bar); macOS fake-CC check --locked 0 errors. Not run: the bars. --- .../komorebi-bar-core/src/widgets/widget.rs | 127 ++++++++++++++++++ komorebi-bar/src/bar.rs | 1 + komorebi-bar/src/config.rs | 8 ++ komorebi-bar/src/widgets/widget.rs | 97 +------------ komorebi-for-mac/komorebi-bar/src/bar.rs | 1 + komorebi-for-mac/komorebi-bar/src/config.rs | 8 ++ .../komorebi-bar/src/widgets/widget.rs | 99 +++----------- 7 files changed, 170 insertions(+), 171 deletions(-) diff --git a/crates/komorebi-bar-core/src/widgets/widget.rs b/crates/komorebi-bar-core/src/widgets/widget.rs index f992a972..5e2ef7d9 100644 --- a/crates/komorebi-bar-core/src/widgets/widget.rs +++ b/crates/komorebi-bar-core/src/widgets/widget.rs @@ -1,7 +1,134 @@ use crate::render::RenderConfig; +use crate::widgets::applications::ApplicationsConfig; +use crate::widgets::battery::BatteryConfig; +use crate::widgets::cpu::CpuConfig; +use crate::widgets::date::DateConfig; +use crate::widgets::komorebi_config::KomorebiConfig; +use crate::widgets::memory::MemoryConfig; +use crate::widgets::network_config::NetworkConfig; +use crate::widgets::platform_config::KeyboardConfig; +use crate::widgets::platform_config::MediaConfig; +use crate::widgets::platform_config::SystrayConfig; +use crate::widgets::storage::StorageConfig; +use crate::widgets::time::TimeConfig; +use crate::widgets::update::UpdateConfig; use eframe::egui::Context; use eframe::egui::Ui; +use serde::Deserialize; +use serde::Serialize; pub trait BarWidget { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig); } + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +/// Widget configuration +pub enum WidgetConfig { + /// Applications widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Applications"))] + Applications(ApplicationsConfig), + /// Battery widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Battery"))] + Battery(BatteryConfig), + /// CPU widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Cpu"))] + Cpu(CpuConfig), + /// Date widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Date"))] + Date(DateConfig), + /// Keyboard widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Keyboard"))] + Keyboard(KeyboardConfig), + /// Komorebi widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))] + Komorebi(KomorebiConfig), + /// Media widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Media"))] + Media(MediaConfig), + /// Memory widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Memory"))] + Memory(MemoryConfig), + /// Network widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Network"))] + Network(NetworkConfig), + /// Storage widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Storage"))] + Storage(StorageConfig), + /// System Tray widget configuration (Windows only) + #[cfg_attr(feature = "schemars", schemars(title = "Systray"))] + Systray(SystrayConfig), + /// Time widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Time"))] + Time(TimeConfig), + /// Update widget configuration + #[cfg_attr(feature = "schemars", schemars(title = "Update"))] + Update(UpdateConfig), +} + +impl WidgetConfig { + /// The widget a configuration key names, if the platform this bar runs on has it. The + /// keyboard, media and system tray widgets exist on Windows only: elsewhere the key is + /// accepted and skipped (see [`WidgetConfig::unsupported`]). + #[must_use] + pub const fn supported(&self) -> bool { + match self { + WidgetConfig::Keyboard(_) | WidgetConfig::Media(_) | WidgetConfig::Systray(_) => { + cfg!(target_os = "windows") + } + _ => true, + } + } + + /// The name of this widget when the platform cannot run it. + #[must_use] + pub const fn unsupported(&self) -> Option<&'static str> { + if self.supported() { + return None; + } + + match self { + WidgetConfig::Keyboard(_) => Some("Keyboard"), + WidgetConfig::Media(_) => Some("Media"), + WidgetConfig::Systray(_) => Some("Systray"), + _ => None, + } + } + + /// Whether the widget is enabled and can run on this platform. + #[must_use] + pub fn enabled(&self) -> bool { + self.supported() + && match self { + WidgetConfig::Applications(config) => config.enable, + WidgetConfig::Battery(config) => config.enable, + WidgetConfig::Cpu(config) => config.enable, + WidgetConfig::Date(config) => config.enable, + WidgetConfig::Keyboard(config) => config.enable, + WidgetConfig::Komorebi(config) => { + config.workspaces.as_ref().is_some_and(|w| w.enable) + || config.layout.as_ref().is_some_and(|w| w.enable) + || config.focused_container.as_ref().is_some_and(|w| w.enable) + || config + .configuration_switcher + .as_ref() + .is_some_and(|w| w.enable) + } + WidgetConfig::Media(config) => config.enable, + WidgetConfig::Memory(config) => config.enable, + WidgetConfig::Network(config) => config.enable, + WidgetConfig::Storage(config) => config.enable, + WidgetConfig::Systray(config) => config.enable, + WidgetConfig::Time(config) => config.enable, + WidgetConfig::Update(config) => config.enable, + } + } +} + +/// Logs a warning for every widget in `widgets` that this platform cannot run; they are accepted +/// in the configuration and skipped. +pub fn warn_unsupported<'a>(widgets: impl IntoIterator) { + for name in widgets.into_iter().filter_map(WidgetConfig::unsupported) { + tracing::warn!("the {name} widget is not supported on this platform and is skipped"); + } +} diff --git a/komorebi-bar/src/bar.rs b/komorebi-bar/src/bar.rs index f113e3e2..c54125db 100644 --- a/komorebi-bar/src/bar.rs +++ b/komorebi-bar/src/bar.rs @@ -23,6 +23,7 @@ use crate::widgets::komorebi::Komorebi; use crate::widgets::komorebi::MonitorInfo; use crate::widgets::widget::BarWidget; use crate::widgets::widget::WidgetConfig; +use crate::widgets::widget::WidgetConfigExt; use color_eyre::eyre; use crossbeam_channel::Receiver; use crossbeam_channel::TryRecvError; diff --git a/komorebi-bar/src/config.rs b/komorebi-bar/src/config.rs index 637f6292..ea8b3917 100644 --- a/komorebi-bar/src/config.rs +++ b/komorebi-bar/src/config.rs @@ -522,6 +522,14 @@ impl KomobarConfig { _ => panic!("unsupported format"), }; + komorebi_bar_core::widgets::widget::warn_unsupported( + value + .left_widgets + .iter() + .chain(value.center_widgets.iter().flatten()) + .chain(value.right_widgets.iter()), + ); + if value.frame.is_none() { value.frame = Some(FrameConfig { inner_margin: Position { diff --git a/komorebi-bar/src/widgets/widget.rs b/komorebi-bar/src/widgets/widget.rs index 0b9fa4c6..9f62748f 100644 --- a/komorebi-bar/src/widgets/widget.rs +++ b/komorebi-bar/src/widgets/widget.rs @@ -1,84 +1,27 @@ use crate::widgets::applications::Applications; -use crate::widgets::applications::ApplicationsConfig; use crate::widgets::battery::Battery; -use crate::widgets::battery::BatteryConfig; use crate::widgets::cpu::Cpu; -use crate::widgets::cpu::CpuConfig; use crate::widgets::date::Date; -use crate::widgets::date::DateConfig; use crate::widgets::keyboard::Keyboard; -use crate::widgets::keyboard::KeyboardConfig; use crate::widgets::komorebi::Komorebi; -use crate::widgets::komorebi::KomorebiConfig; use crate::widgets::media::Media; -use crate::widgets::media::MediaConfig; use crate::widgets::memory::Memory; -use crate::widgets::memory::MemoryConfig; use crate::widgets::network::Network; -use crate::widgets::network::NetworkConfig; use crate::widgets::storage::Storage; -use crate::widgets::storage::StorageConfig; -#[cfg(target_os = "windows")] use crate::widgets::systray::Systray; -#[cfg(target_os = "windows")] -use crate::widgets::systray::SystrayConfig; use crate::widgets::time::Time; -use crate::widgets::time::TimeConfig; use crate::widgets::update::Update; -use crate::widgets::update::UpdateConfig; -use serde::Deserialize; -use serde::Serialize; pub use komorebi_bar_core::widgets::widget::BarWidget; +pub use komorebi_bar_core::widgets::widget::WidgetConfig; -#[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -/// Widget configuration -pub enum WidgetConfig { - /// Applications widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Applications"))] - Applications(ApplicationsConfig), - /// Battery widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Battery"))] - Battery(BatteryConfig), - /// CPU widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Cpu"))] - Cpu(CpuConfig), - /// Date widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Date"))] - Date(DateConfig), - /// Keyboard widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Keyboard"))] - Keyboard(KeyboardConfig), - /// Komorebi widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))] - Komorebi(KomorebiConfig), - /// Media widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Media"))] - Media(MediaConfig), - /// Memory widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Memory"))] - Memory(MemoryConfig), - /// Network widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Network"))] - Network(NetworkConfig), - /// Storage widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Storage"))] - Storage(StorageConfig), - /// System Tray widget configuration (Windows only) - #[cfg(target_os = "windows")] - #[cfg_attr(feature = "schemars", schemars(title = "Systray"))] - Systray(SystrayConfig), - /// Time widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Time"))] - Time(TimeConfig), - /// Update widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Update"))] - Update(UpdateConfig), +/// Turns a widget configuration into the widget that renders it. +pub trait WidgetConfigExt { + fn as_boxed_bar_widget(&self) -> Box; } -impl WidgetConfig { - pub fn as_boxed_bar_widget(&self) -> Box { +impl WidgetConfigExt for WidgetConfig { + fn as_boxed_bar_widget(&self) -> Box { match self { WidgetConfig::Applications(config) => Box::new(Applications::from(config)), WidgetConfig::Battery(config) => Box::new(Battery::from(*config)), @@ -90,37 +33,9 @@ impl WidgetConfig { WidgetConfig::Memory(config) => Box::new(Memory::from(*config)), WidgetConfig::Network(config) => Box::new(Network::from(*config)), WidgetConfig::Storage(config) => Box::new(Storage::from(*config)), - #[cfg(target_os = "windows")] WidgetConfig::Systray(config) => Box::new(Systray::from(config)), WidgetConfig::Time(config) => Box::new(Time::from(config.clone())), WidgetConfig::Update(config) => Box::new(Update::from(*config)), } } - - pub fn enabled(&self) -> bool { - match self { - WidgetConfig::Applications(config) => config.enable, - WidgetConfig::Battery(config) => config.enable, - WidgetConfig::Cpu(config) => config.enable, - WidgetConfig::Date(config) => config.enable, - WidgetConfig::Keyboard(config) => config.enable, - WidgetConfig::Komorebi(config) => { - config.workspaces.as_ref().is_some_and(|w| w.enable) - || config.layout.as_ref().is_some_and(|w| w.enable) - || config.focused_container.as_ref().is_some_and(|w| w.enable) - || config - .configuration_switcher - .as_ref() - .is_some_and(|w| w.enable) - } - WidgetConfig::Media(config) => config.enable, - WidgetConfig::Memory(config) => config.enable, - WidgetConfig::Network(config) => config.enable, - WidgetConfig::Storage(config) => config.enable, - #[cfg(target_os = "windows")] - WidgetConfig::Systray(config) => config.enable, - WidgetConfig::Time(config) => config.enable, - WidgetConfig::Update(config) => config.enable, - } - } } diff --git a/komorebi-for-mac/komorebi-bar/src/bar.rs b/komorebi-for-mac/komorebi-bar/src/bar.rs index 533ac9af..2a47ef88 100644 --- a/komorebi-for-mac/komorebi-bar/src/bar.rs +++ b/komorebi-for-mac/komorebi-bar/src/bar.rs @@ -20,6 +20,7 @@ use crate::widgets::komorebi::Komorebi; use crate::widgets::komorebi::MonitorInfo; use crate::widgets::widget::BarWidget; use crate::widgets::widget::WidgetConfig; +use crate::widgets::widget::WidgetConfigExt; use crossbeam_channel::Receiver; use crossbeam_channel::TryRecvError; use eframe::egui::Align; diff --git a/komorebi-for-mac/komorebi-bar/src/config.rs b/komorebi-for-mac/komorebi-bar/src/config.rs index b913baa5..6dc90c76 100644 --- a/komorebi-for-mac/komorebi-bar/src/config.rs +++ b/komorebi-for-mac/komorebi-bar/src/config.rs @@ -515,6 +515,14 @@ impl KomobarConfig { _ => panic!("unsupported format"), }; + komorebi_bar_core::widgets::widget::warn_unsupported( + value + .left_widgets + .iter() + .chain(value.center_widgets.iter().flatten()) + .chain(value.right_widgets.iter()), + ); + if value.frame.is_none() { value.frame = Some(FrameConfig { inner_margin: Position { diff --git a/komorebi-for-mac/komorebi-bar/src/widgets/widget.rs b/komorebi-for-mac/komorebi-bar/src/widgets/widget.rs index 0f78b517..6880736b 100644 --- a/komorebi-for-mac/komorebi-bar/src/widgets/widget.rs +++ b/komorebi-for-mac/komorebi-bar/src/widgets/widget.rs @@ -1,110 +1,49 @@ use crate::widgets::applications::Applications; -use crate::widgets::applications::ApplicationsConfig; use crate::widgets::battery::Battery; -use crate::widgets::battery::BatteryConfig; use crate::widgets::cpu::Cpu; -use crate::widgets::cpu::CpuConfig; use crate::widgets::date::Date; -use crate::widgets::date::DateConfig; -// use crate::widgets::keyboard::Keyboard; -// use crate::widgets::keyboard::KeyboardConfig; use crate::widgets::komorebi::Komorebi; -use crate::widgets::komorebi::KomorebiConfig; -// use crate::widgets::media::Media; -// use crate::widgets::media::MediaConfig; use crate::widgets::memory::Memory; -use crate::widgets::memory::MemoryConfig; use crate::widgets::network::Network; -use crate::widgets::network::NetworkConfig; use crate::widgets::storage::Storage; -use crate::widgets::storage::StorageConfig; use crate::widgets::time::Time; -use crate::widgets::time::TimeConfig; use crate::widgets::update::Update; -use crate::widgets::update::UpdateConfig; -use serde::Deserialize; -use serde::Serialize; +use eframe::egui::Context; +use eframe::egui::Ui; pub use komorebi_bar_core::widgets::widget::BarWidget; +pub use komorebi_bar_core::widgets::widget::WidgetConfig; +use komorebi_bar_core::render::RenderConfig; -#[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -/// Widget configuration -pub enum WidgetConfig { - /// Applications widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Applications"))] - Applications(ApplicationsConfig), - /// Battery widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Battery"))] - Battery(BatteryConfig), - /// CPU widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Cpu"))] - Cpu(CpuConfig), - /// Date widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Date"))] - Date(DateConfig), - // Keyboard(KeyboardConfig), - /// Komorebi widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Komorebi"))] - Komorebi(KomorebiConfig), - // Media(MediaConfig), - /// Memory widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Memory"))] - Memory(MemoryConfig), - /// Network widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Network"))] - Network(NetworkConfig), - /// Storage widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Storage"))] - Storage(StorageConfig), - /// Time widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Time"))] - Time(TimeConfig), - /// Update widget configuration - #[cfg_attr(feature = "schemars", schemars(title = "Update"))] - Update(UpdateConfig), +/// Stands in for a widget that macOS cannot run; `WidgetConfig::enabled` is false for those, so +/// this is never built from a configuration that the bar loads. +struct Unsupported; + +impl BarWidget for Unsupported { + fn render(&mut self, _ctx: &Context, _ui: &mut Ui, _config: &mut RenderConfig) {} } -impl WidgetConfig { - pub fn as_boxed_bar_widget(&self) -> Box { +/// Turns a widget configuration into the widget that renders it. +pub trait WidgetConfigExt { + fn as_boxed_bar_widget(&self) -> Box; +} + +impl WidgetConfigExt for WidgetConfig { + fn as_boxed_bar_widget(&self) -> Box { match self { WidgetConfig::Applications(config) => Box::new(Applications::from(config)), WidgetConfig::Battery(config) => Box::new(Battery::from(*config)), WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)), WidgetConfig::Date(config) => Box::new(Date::from(config.clone())), - // WidgetConfig::Keyboard(config) => Box::new(Keyboard::from(*config)), WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)), - // WidgetConfig::Media(config) => Box::new(Media::from(*config)), WidgetConfig::Memory(config) => Box::new(Memory::from(*config)), WidgetConfig::Network(config) => Box::new(Network::from(*config)), WidgetConfig::Storage(config) => Box::new(Storage::from(*config)), WidgetConfig::Time(config) => Box::new(Time::from(config.clone())), WidgetConfig::Update(config) => Box::new(Update::from(*config)), - } - } - - pub fn enabled(&self) -> bool { - match self { - WidgetConfig::Applications(config) => config.enable, - WidgetConfig::Battery(config) => config.enable, - WidgetConfig::Cpu(config) => config.enable, - WidgetConfig::Date(config) => config.enable, - // WidgetConfig::Keyboard(config) => config.enable, - WidgetConfig::Komorebi(config) => { - config.workspaces.as_ref().is_some_and(|w| w.enable) - || config.layout.as_ref().is_some_and(|w| w.enable) - || config.focused_container.as_ref().is_some_and(|w| w.enable) - || config - .configuration_switcher - .as_ref() - .is_some_and(|w| w.enable) + WidgetConfig::Keyboard(_) | WidgetConfig::Media(_) | WidgetConfig::Systray(_) => { + Box::new(Unsupported) } - // WidgetConfig::Media(config) => config.enable, - WidgetConfig::Memory(config) => config.enable, - WidgetConfig::Network(config) => config.enable, - WidgetConfig::Storage(config) => config.enable, - WidgetConfig::Time(config) => config.enable, - WidgetConfig::Update(config) => config.enable, } } }