From 086833af96423bcd91e0ce9e57c998e75fa47bdf Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 30 Oct 2026 23:45:00 -0700 Subject: [PATCH] refactor(bar-core): add the BarPlatform hooks with the Windows and macOS implementations komorebi_bar_core::platform holds what the widgets need from the operating system and differs between the bars: BarPlatform { open_system_tool, latest_release_api_url, calendar_icon }, the SystemTool enum (task manager, power settings, network settings, a URL) and install()/platform(), a once-set global the shell fills first thing in main() (a widget calls platform() when it is clicked or built, never before main). Each bar gets an os.rs with its implementation (main.rs re-exports the core module as crate::platform, so shared files can say crate::platform::platform()): Windows spawns exactly the commands the widgets spawn today (cmd.exe /C taskmgr.exe, cmd.exe /C start ms-settings:batterysaver, cmd.exe /C ncpa, explorer.exe ); macOS maps TaskManager to open -a 'Activity Monitor' and network settings to open x-apple.systempreferences:... as memory.rs and network.rs do today, and keeps the commands the shared files spawn on macOS today for the rest (cmd.exe taskmgr.exe for cpu, cmd.exe start ms-settings:batterysaver for battery, explorer.exe for a URL), which are broken there: a one-line fix per tool in os.rs, not part of this change. Nothing calls the hooks yet: the next revisions switch the widgets over, one concern each. Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors (6 bar warnings, unchanged); native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; rustfmt --check clean on the new files and both main.rs. No widget calls the hooks yet, so no behaviour changes. --- crates/komorebi-bar-core/src/lib.rs | 2 + crates/komorebi-bar-core/src/platform.rs | 48 +++++++++++++++++++++++ komorebi-bar/src/main.rs | 4 ++ komorebi-bar/src/os.rs | 29 ++++++++++++++ komorebi-for-mac/komorebi-bar/src/main.rs | 4 ++ komorebi-for-mac/komorebi-bar/src/os.rs | 35 +++++++++++++++++ 6 files changed, 122 insertions(+) create mode 100644 crates/komorebi-bar-core/src/platform.rs create mode 100644 komorebi-bar/src/os.rs create mode 100644 komorebi-for-mac/komorebi-bar/src/os.rs diff --git a/crates/komorebi-bar-core/src/lib.rs b/crates/komorebi-bar-core/src/lib.rs index b65fd141..9088d849 100644 --- a/crates/komorebi-bar-core/src/lib.rs +++ b/crates/komorebi-bar-core/src/lib.rs @@ -16,3 +16,5 @@ pub mod config; pub mod render; pub mod widgets; + +pub mod platform; diff --git a/crates/komorebi-bar-core/src/platform.rs b/crates/komorebi-bar-core/src/platform.rs new file mode 100644 index 00000000..635e5dc3 --- /dev/null +++ b/crates/komorebi-bar-core/src/platform.rs @@ -0,0 +1,48 @@ +//! What the widgets need from the operating system and what differs between the bars. +//! +//! A shell implements [`BarPlatform`] and calls [`install`] first thing in `main`; the widgets +//! reach it through [`platform`] when they are built or clicked. + +use std::io; +use std::sync::OnceLock; + +/// A tool the bar opens when a widget is clicked. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SystemTool { + /// The task manager (Task Manager, Activity Monitor) + TaskManager, + /// The operating system's power and battery settings + PowerSettings, + /// The operating system's network settings + NetworkSettings, + /// A URL, in the default browser + Url(String), +} + +pub trait BarPlatform: Send + Sync { + /// Opens a tool; the result is whether it could be started. + fn open_system_tool(&self, tool: SystemTool) -> io::Result<()>; + + /// The GitHub API URL that says which release of this platform's komorebi is the latest. + fn latest_release_api_url(&self) -> &'static str; + + /// The glyph the date widget shows as its calendar icon. + fn calendar_icon(&self) -> &'static str; +} + +static PLATFORM: OnceLock<&'static dyn BarPlatform> = OnceLock::new(); + +/// Sets the platform of the process. The first call wins; a second one is ignored. +pub fn install(platform: &'static dyn BarPlatform) { + let _ = PLATFORM.set(platform); +} + +/// # Panics +/// +/// Panics if [`install`] has not been called: a shell must install its platform before it builds +/// a widget. +pub fn platform() -> &'static dyn BarPlatform { + *PLATFORM + .get() + .expect("komorebi_bar_core::platform::install must be called first thing in main") +} diff --git a/komorebi-bar/src/main.rs b/komorebi-bar/src/main.rs index 4ca843f5..89b25c77 100644 --- a/komorebi-bar/src/main.rs +++ b/komorebi-bar/src/main.rs @@ -1,5 +1,7 @@ mod bar; mod config; +mod os; +pub use komorebi_bar_core::platform; pub use komorebi_bar_core::render; pub use komorebi_bar_core::selected_frame; pub use komorebi_bar_core::ui; @@ -96,6 +98,8 @@ pub enum KomorebiEvent { } fn main() -> color_eyre::Result<()> { + komorebi_bar_core::platform::install(&os::WindowsBar); + unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }?; let opts: Opts = Opts::parse(); diff --git a/komorebi-bar/src/os.rs b/komorebi-bar/src/os.rs new file mode 100644 index 00000000..2fca94c4 --- /dev/null +++ b/komorebi-bar/src/os.rs @@ -0,0 +1,29 @@ +use egui_phosphor::regular::CALENDAR_DOTS; +use komorebi_bar_core::platform::BarPlatform; +use komorebi_bar_core::platform::SystemTool; +use std::io; +use std::process::Command; + +pub struct WindowsBar; + +impl BarPlatform for WindowsBar { + fn open_system_tool(&self, tool: SystemTool) -> io::Result<()> { + match tool { + SystemTool::TaskManager => Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn(), + SystemTool::PowerSettings => Command::new("cmd.exe") + .args(["/C", "start", "ms-settings:batterysaver"]) + .spawn(), + SystemTool::NetworkSettings => Command::new("cmd.exe").args(["/C", "ncpa"]).spawn(), + SystemTool::Url(url) => Command::new("explorer.exe").args([url]).spawn(), + } + .map(|_| ()) + } + + fn latest_release_api_url(&self) -> &'static str { + "https://api.github.com/repos/LGUG2Z/komorebi/releases/latest" + } + + fn calendar_icon(&self) -> &'static str { + CALENDAR_DOTS + } +} diff --git a/komorebi-for-mac/komorebi-bar/src/main.rs b/komorebi-for-mac/komorebi-bar/src/main.rs index 857092c2..c08f444c 100644 --- a/komorebi-for-mac/komorebi-bar/src/main.rs +++ b/komorebi-for-mac/komorebi-bar/src/main.rs @@ -1,5 +1,7 @@ mod bar; mod config; +mod os; +pub use komorebi_bar_core::platform; pub use komorebi_bar_core::render; pub use komorebi_bar_core::selected_frame; pub use komorebi_bar_core::ui; @@ -63,6 +65,8 @@ pub enum KomorebiEvent { } fn main() -> eyre::Result<()> { + komorebi_bar_core::platform::install(&os::MacBar); + let opts: Opts = Opts::parse(); #[cfg(feature = "schemars")] diff --git a/komorebi-for-mac/komorebi-bar/src/os.rs b/komorebi-for-mac/komorebi-bar/src/os.rs new file mode 100644 index 00000000..c3df076a --- /dev/null +++ b/komorebi-for-mac/komorebi-bar/src/os.rs @@ -0,0 +1,35 @@ +use egui_phosphor::regular::CALENDAR; +use komorebi_bar_core::platform::BarPlatform; +use komorebi_bar_core::platform::SystemTool; +use std::io; +use std::process::Command; + +pub struct MacBar; + +impl BarPlatform for MacBar { + fn open_system_tool(&self, tool: SystemTool) -> io::Result<()> { + match tool { + SystemTool::TaskManager => Command::new("open") + .args(["-a", "Activity Monitor"]) + .spawn(), + // cmd.exe does not exist on macOS: this is what the battery widget does today + SystemTool::PowerSettings => Command::new("cmd.exe") + .args(["/C", "start", "ms-settings:batterysaver"]) + .spawn(), + SystemTool::NetworkSettings => Command::new("open") + .args(["x-apple.systempreferences:com.apple.Network-Settings.extension"]) + .spawn(), + // explorer.exe does not exist on macOS: this is what the update widget does today + SystemTool::Url(url) => Command::new("explorer.exe").args([url]).spawn(), + } + .map(|_| ()) + } + + fn latest_release_api_url(&self) -> &'static str { + "https://api.github.com/repos/LGUG2Z/komorebi-for-mac/releases/latest" + } + + fn calendar_icon(&self) -> &'static str { + CALENDAR + } +}