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.
This commit is contained in:
LGUG2Z committed 2026-10-31 10:51:02 -07:00
1 parent a57648f36d
commit 4831a6c3ab
7 files changed
+170 -171

No files matched your search

@@ -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<Item = &'a WidgetConfig>) {
for name in widgets.into_iter().filter_map(WidgetConfig::unsupported) {
tracing::warn!("the {name} widget is not supported on this platform and is skipped");
}
}
+1
View File
@@ -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;
+8
View File
@@ -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 {
+6 -91
View File
@@ -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<dyn BarWidget>;
}
impl WidgetConfig {
pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
impl WidgetConfigExt for WidgetConfig {
fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
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,
}
}
}
+1
View File
@@ -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;
@@ -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 {
@@ -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<dyn BarWidget> {
/// Turns a widget configuration into the widget that renders it.
pub trait WidgetConfigExt {
fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget>;
}
impl WidgetConfigExt for WidgetConfig {
fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {
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,
}
}
}