Compare commits

...

2 Commits

Author SHA1 Message Date
LGUG2Z
665a45afed refactor(bar): use native apis for positioning
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.
2024-10-06 14:32:41 -07:00
LGUG2Z
861d415551 chore(cargo): enable lto for release builds 2024-10-05 18:38:40 -07:00
7 changed files with 201 additions and 87 deletions

7
Cargo.lock generated
View File

@@ -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",

View File

@@ -63,3 +63,6 @@ features = [
"Media",
"Media_Control"
]
[profile.release]
lto = true

View File

@@ -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 }

View File

@@ -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

View File

@@ -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<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
@@ -32,12 +34,43 @@ pub struct KomobarConfig {
pub right_widgets: Vec<WidgetConfig>,
}
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<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)]

View File

@@ -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<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();
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<isize> {
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::State>(&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()

View File

@@ -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 {