From 18bb060b711e339645af0b18a49c8cf913c8380e Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sun, 6 Oct 2024 13:02:04 -0700 Subject: [PATCH] refactor(bar): use native apis for positioning This commit replaces almost all uses of the egui Viewport API for bar window positioning with calls to SetWindowPos via komorebi_client's Window struct. This seems to play much more smoothly with multi-monitor setups where each monitor has a different scaling factor, opening the door for multiple instances of komorebi-bar.exe to run against multiple monitors. As a result of this change, the "viewport" configuration option has been renamed to "position" and doc strings have been changed to remove the reference to the egui crate docs. Similarly, "viewport.position" and "viewport.inner_size" have been renamed to "position.start" and "position.end" respectively. Backwards-compatibility aliases have been included for all renames. --- Cargo.lock | 7 -- komorebi-bar/Cargo.toml | 1 - komorebi-bar/src/bar.rs | 58 ++++++++++---- komorebi-bar/src/config.rs | 43 ++++++++-- komorebi-bar/src/main.rs | 156 +++++++++++++++++++++++-------------- komorebic/src/main.rs | 20 ++++- 6 files changed, 198 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66aee854..88356659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,12 +501,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomic_float" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" - [[package]] name = "atspi" version = "0.19.0" @@ -2711,7 +2705,6 @@ dependencies = [ name = "komorebi-bar" version = "0.1.30" dependencies = [ - "atomic_float", "chrono", "clap", "color-eyre", diff --git a/komorebi-bar/Cargo.toml b/komorebi-bar/Cargo.toml index 98a564fb..802e79fc 100644 --- a/komorebi-bar/Cargo.toml +++ b/komorebi-bar/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" komorebi-client = { path = "../komorebi-client" } komorebi-themes = { path = "../komorebi-themes" } -atomic_float = "1" chrono = { workspace = true } clap = { workspace = true } color-eyre = { workspace = true } diff --git a/komorebi-bar/src/bar.rs b/komorebi-bar/src/bar.rs index d9c0a967..698a94a8 100644 --- a/komorebi-bar/src/bar.rs +++ b/komorebi-bar/src/bar.rs @@ -1,11 +1,17 @@ use crate::config::KomobarConfig; use crate::config::KomobarTheme; +use crate::config::Position; +use crate::config::PositionConfig; use crate::komorebi::Komorebi; use crate::komorebi::KomorebiNotificationState; +use crate::process_hwnd; use crate::widget::BarWidget; use crate::widget::WidgetConfig; -use crate::DPI; +use crate::BAR_HEIGHT; use crate::MAX_LABEL_WIDTH; +use crate::MONITOR_LEFT; +use crate::MONITOR_RIGHT; +use crate::MONITOR_TOP; use crossbeam_channel::Receiver; use eframe::egui::Align; use eframe::egui::CentralPanel; @@ -18,11 +24,8 @@ use eframe::egui::FontId; use eframe::egui::Frame; use eframe::egui::Layout; use eframe::egui::Margin; -use eframe::egui::Pos2; use eframe::egui::Style; use eframe::egui::TextStyle; -use eframe::egui::Vec2; -use eframe::egui::ViewportCommand; use font_loader::system_fonts; use font_loader::system_fonts::FontPropertyBuilder; use komorebi_client::KomorebiTheme; @@ -146,16 +149,43 @@ impl Komobar { Self::add_custom_font(ctx, font_family); } - if let Some(viewport) = &config.viewport { - let dpi = DPI.load(Ordering::SeqCst); - if let Some(position) = viewport.position { - let pos2 = Pos2::new(position.x / dpi, position.y / dpi); - ctx.send_viewport_cmd(ViewportCommand::OuterPosition(pos2)); - } + let position = config.position.clone().unwrap_or(PositionConfig { + start: Some(Position { + x: MONITOR_LEFT.load(Ordering::SeqCst) as f32, + y: MONITOR_TOP.load(Ordering::SeqCst) as f32, + }), + end: Some(Position { + x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32, + y: BAR_HEIGHT, + }), + }); - if let Some(position) = viewport.inner_size { - let vec2 = Vec2::new(position.x / dpi, position.y / dpi); - ctx.send_viewport_cmd(ViewportCommand::InnerSize(vec2)); + if let Some(hwnd) = process_hwnd() { + let start = position.start.unwrap_or(Position { + x: MONITOR_LEFT.load(Ordering::SeqCst) as f32, + y: MONITOR_TOP.load(Ordering::SeqCst) as f32, + }); + + let end = position.end.unwrap_or(Position { + x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32, + y: BAR_HEIGHT, + }); + + let rect = komorebi_client::Rect { + left: start.x as i32, + top: start.y as i32, + right: end.x as i32, + bottom: end.y as i32, + }; + + let window = komorebi_client::Window::from(hwnd); + match window.set_position(&rect, false) { + Ok(_) => { + tracing::info!("updated bar position"); + } + Err(error) => { + tracing::error!("{}", error.to_string()) + } } } @@ -294,6 +324,8 @@ impl Komobar { scale_factor: cc.egui_ctx.native_pixels_per_point().unwrap_or(1.0), }; + komobar.apply_config(&cc.egui_ctx, &config, None); + // needs a double apply the first time for some reason komobar.apply_config(&cc.egui_ctx, &config, None); komobar diff --git a/komorebi-bar/src/config.rs b/komorebi-bar/src/config.rs index dae9fc9d..8d59f6fb 100644 --- a/komorebi-bar/src/config.rs +++ b/komorebi-bar/src/config.rs @@ -7,13 +7,15 @@ use komorebi_client::Rect; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; +use std::collections::HashMap; use std::path::PathBuf; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] /// The `komorebi.bar.json` configuration file reference for `v0.1.30` pub struct KomobarConfig { - /// Viewport options (see: https://docs.rs/egui/latest/egui/viewport/struct.ViewportBuilder.html) - pub viewport: Option, + /// Bar positioning options + #[serde(alias = "viewport")] + pub position: Option, /// Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html) pub frame: Option, /// Monitor options @@ -32,12 +34,43 @@ pub struct KomobarConfig { pub right_widgets: Vec, } +impl KomobarConfig { + pub fn aliases(raw: &str) { + let mut map = HashMap::new(); + map.insert("position", ["viewport"]); + map.insert("end", ["inner_frame"]); + + let mut display = false; + + for aliases in map.values() { + for a in aliases { + if raw.contains(a) { + display = true; + } + } + } + + if display { + println!("\nYour bar configuration file contains some options that have been renamed or deprecated:\n"); + for (canonical, aliases) in map { + for alias in aliases { + if raw.contains(alias) { + println!(r#""{alias}" is now "{canonical}""#); + } + } + } + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct ViewportConfig { +pub struct PositionConfig { /// The desired starting position of the bar (0,0 = top left of the screen) - pub position: Option, + #[serde(alias = "position")] + pub start: Option, /// The desired size of the bar from the starting position (usually monitor width x desired height) - pub inner_size: Option, + #[serde(alias = "inner_size")] + pub end: Option, } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] diff --git a/komorebi-bar/src/main.rs b/komorebi-bar/src/main.rs index 7474a651..c7f3aa26 100644 --- a/komorebi-bar/src/main.rs +++ b/komorebi-bar/src/main.rs @@ -14,9 +14,8 @@ mod widget; use crate::bar::Komobar; use crate::config::KomobarConfig; use crate::config::Position; -use atomic_float::AtomicF32; +use crate::config::PositionConfig; use clap::Parser; -use color_eyre::eyre::bail; use eframe::egui::ViewportBuilder; use font_loader::system_fonts; use hotwatch::EventKind; @@ -31,12 +30,23 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; use tracing_subscriber::EnvFilter; +use windows::Win32::Foundation::BOOL; +use windows::Win32::Foundation::HWND; +use windows::Win32::Foundation::LPARAM; +use windows::Win32::System::Threading::GetCurrentProcessId; +use windows::Win32::System::Threading::GetCurrentThreadId; use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext; use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; +use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows; +use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId; pub static WIDGET_SPACING: f32 = 10.0; + pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400); -pub static DPI: AtomicF32 = AtomicF32::new(1.0); +pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0); +pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0); +pub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0); +pub static BAR_HEIGHT: f32 = 50.0; #[derive(Parser)] #[clap(author, about, version)] @@ -53,36 +63,41 @@ struct Opts { /// Write an example komorebi.bar.json to disk #[clap(long)] quickstart: bool, + /// Print a list of aliases that can be renamed to canonical variants + #[clap(long)] + #[clap(hide = true)] + aliases: bool, } -macro_rules! as_ptr { - ($value:expr) => { - $value as *mut core::ffi::c_void - }; -} - -pub fn dpi_for_monitor(hmonitor: isize) -> color_eyre::Result { - use windows::Win32::Graphics::Gdi::HMONITOR; - use windows::Win32::UI::HiDpi::GetDpiForMonitor; - use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI; - - let mut dpi_x = u32::default(); - let mut dpi_y = u32::default(); - +extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL { unsafe { - match GetDpiForMonitor( - HMONITOR(as_ptr!(hmonitor)), - MDT_EFFECTIVE_DPI, - std::ptr::addr_of_mut!(dpi_x), - std::ptr::addr_of_mut!(dpi_y), - ) { - Ok(_) => {} - Err(error) => bail!(error), + let mut process_id = 0; + GetWindowThreadProcessId(hwnd, Some(&mut process_id)); + + if process_id == GetCurrentProcessId() { + *(lparam.0 as *mut HWND) = hwnd; + BOOL::from(false) // Stop enumeration + } else { + BOOL::from(true) // Continue enumeration } } +} - #[allow(clippy::cast_precision_loss)] - Ok(dpi_y as f32 / 96.0) +fn process_hwnd() -> Option { + unsafe { + let mut hwnd = HWND::default(); + let _ = EnumThreadWindows( + GetCurrentThreadId(), + Some(enum_window), + LPARAM(&mut hwnd as *mut HWND as isize), + ); + + if hwnd.0 as isize == 0 { + None + } else { + Some(hwnd.0 as isize) + } + } } fn main() -> color_eyre::Result<()> { @@ -166,7 +181,7 @@ fn main() -> color_eyre::Result<()> { Option::from, ); - let config = match config_path { + let mut config = match config_path { None => { let komorebi_bar_json = include_str!("../../docs/komorebi.bar.example.json").to_string(); @@ -180,10 +195,12 @@ fn main() -> color_eyre::Result<()> { KomobarConfig::read(&default_config_path)? } Some(ref config) => { - tracing::info!( - "found configuration file: {}", - config.as_path().to_string_lossy() - ); + if !opts.aliases { + tracing::info!( + "found configuration file: {}", + config.as_path().to_string_lossy() + ); + } KomobarConfig::read(config)? } @@ -191,46 +208,65 @@ fn main() -> color_eyre::Result<()> { let config_path = config_path.unwrap_or(default_config_path); + if opts.aliases { + KomobarConfig::aliases(&std::fs::read_to_string(&config_path)?); + std::process::exit(0); + } + let state = serde_json::from_str::(&komorebi_client::send_query( &SocketMessage::State, )?)?; - let dpi = dpi_for_monitor(state.monitors.elements()[config.monitor.index].id())?; - DPI.store(dpi, Ordering::SeqCst); + MONITOR_RIGHT.store( + state.monitors.elements()[config.monitor.index].size().right, + Ordering::SeqCst, + ); - let mut viewport_builder = ViewportBuilder::default() - .with_decorations(false) - // .with_transparent(config.transparent) - .with_taskbar(false) - .with_position(Position { - x: state.monitors.elements()[config.monitor.index].size().left as f32 / dpi, - y: state.monitors.elements()[config.monitor.index].size().top as f32 / dpi, - }) - .with_inner_size({ - Position { - x: state.monitors.elements()[config.monitor.index].size().right as f32 / dpi, - y: 50.0 / dpi, - } - }); + MONITOR_TOP.store( + state.monitors.elements()[config.monitor.index].size().top, + Ordering::SeqCst, + ); - if let Some(viewport) = &config.viewport { - if let Some(mut position) = &viewport.position { - position.x /= dpi; - position.y /= dpi; + MONITOR_TOP.store( + state.monitors.elements()[config.monitor.index].size().left, + Ordering::SeqCst, + ); - let b = viewport_builder.clone(); - viewport_builder = b.with_position(position); + match config.position { + None => { + config.position = Some(PositionConfig { + start: Some(Position { + x: state.monitors.elements()[config.monitor.index].size().left as f32, + y: state.monitors.elements()[config.monitor.index].size().top as f32, + }), + end: Some(Position { + x: state.monitors.elements()[config.monitor.index].size().right as f32, + y: 50.0, + }), + }) } + Some(ref mut position) => { + if position.start.is_none() { + position.start = Some(Position { + x: state.monitors.elements()[config.monitor.index].size().left as f32, + y: state.monitors.elements()[config.monitor.index].size().top as f32, + }); + } - if let Some(mut inner_size) = &viewport.inner_size { - inner_size.x /= dpi; - inner_size.y /= dpi; - - let b = viewport_builder.clone(); - viewport_builder = b.with_inner_size(inner_size); + if position.end.is_none() { + position.end = Some(Position { + x: state.monitors.elements()[config.monitor.index].size().right as f32, + y: 50.0, + }) + } } } + let viewport_builder = ViewportBuilder::default() + .with_decorations(false) + // .with_transparent(config.transparent) + .with_taskbar(false); + let native_options = eframe::NativeOptions { viewport: viewport_builder, ..Default::default() diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 12e188b4..b914944a 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -2050,7 +2050,7 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue)) println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops"); println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"); - let static_config = arg.config.map_or_else( + let static_config = arg.config.clone().map_or_else( || { let komorebi_json = HOME_DIR.join("komorebi.json"); if komorebi_json.is_file() { @@ -2062,12 +2062,30 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue)) Option::from, ); + let bar_config = arg.config.map_or_else( + || { + let bar_json = HOME_DIR.join("komorebi.bar.json"); + if bar_json.is_file() { + Option::from(bar_json) + } else { + None + } + }, + Option::from, + ); + if let Some(config) = static_config { let path = resolve_home_path(config)?; let raw = std::fs::read_to_string(path)?; StaticConfig::aliases(&raw); StaticConfig::deprecated(&raw); } + + if bar_config.is_some() { + let output = Command::new("komorebi-bar.exe").arg("--aliases").output()?; + let stdout = String::from_utf8(output.stdout)?; + println!("{stdout}"); + } } SubCommand::Stop(arg) => { if arg.whkd {