Skip to content

Commit

Permalink
refactor(bar): use native apis for positioning
Browse files Browse the repository at this point in the history
This commit replaces almost all uses of the egui 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.
  • Loading branch information
LGUG2Z committed Oct 6, 2024
1 parent 861d415 commit 799e6d1
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 81 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion komorebi-bar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
48 changes: 35 additions & 13 deletions komorebi-bar/src/bar.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::config::KomobarConfig;
use crate::config::KomobarTheme;
use crate::config::Position;
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;
Expand All @@ -18,11 +23,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;
Expand Down Expand Up @@ -146,16 +148,34 @@ 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));
}
if let Some(position) = &config.position {
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,
});

if let Some(position) = viewport.inner_size {
let vec2 = Vec2::new(position.x / dpi, position.y / dpi);
ctx.send_viewport_cmd(ViewportCommand::InnerSize(vec2));
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())
}
}
}
}

Expand Down Expand Up @@ -294,6 +314,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
Expand Down
13 changes: 8 additions & 5 deletions komorebi-bar/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ 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<ViewportConfig>,
/// Bar positioning options
#[serde(alias = "viewport")]
pub position: Option<PositionConfig>,
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html)
pub frame: Option<FrameConfig>,
/// Monitor options
Expand All @@ -33,11 +34,13 @@ pub struct KomobarConfig {
}

#[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<Position>,
#[serde(alias = "position")]
pub start: Option<Position>,
/// The desired size of the bar from the starting position (usually monitor width x desired height)
pub inner_size: Option<Position>,
#[serde(alias = "inner_size")]
pub end: Option<Position>,
}

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
Expand Down
135 changes: 80 additions & 55 deletions komorebi-bar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand All @@ -55,34 +65,35 @@ struct Opts {
quickstart: bool,
}

macro_rules! as_ptr {
($value:expr) => {
$value as *mut core::ffi::c_void
};
extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
unsafe {
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
}
}
}

pub fn dpi_for_monitor(hmonitor: isize) -> color_eyre::Result<f32> {
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();

fn process_hwnd() -> Option<isize> {
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 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)
}
}

#[allow(clippy::cast_precision_loss)]
Ok(dpi_y as f32 / 96.0)
}

fn main() -> color_eyre::Result<()> {
Expand Down Expand Up @@ -166,7 +177,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();
Expand Down Expand Up @@ -195,42 +206,56 @@ fn main() -> color_eyre::Result<()> {
&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()
Expand Down

0 comments on commit 799e6d1

Please sign in to comment.