mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-22 16:48:33 +02:00
This interactively rebased commit is comprised of the subsequent individual commits listed further below. At a high level: - work_area_offset is now automatically calculated by default - monitor can now take an index in addition to the previous object - position can largely be replaced by margin and padding for bars that are positioned at the top of the screen - frame can now largely be replaced by margin and padding for bars that are positioned at the top of the screen - height is now a more intuitive configuration option for setting the height of the bar Detailed explainations and examples are included in the body of PR #1224 on GitHub: https://github.com/LGUG2Z/komorebi/pull/1224 fix(bar): add simplified config for bar This commit creates a few new config options for the bar that should make it a lot simpler for new users to configure the bar. - Remove the need for `position`: if a position is given the bar will still use it with priority over the new config. Instead of position you can now use the following: - `height`: defines the height of the bar (50 by default) - `horizontal_margin`: defines the left and right offset of the bar, it is the same as setting a `position.start.x` and then remove the same amount on `position.end.x`. - `vertical_margin`: defines the top and bottom offset of the bar, it is the same as setting a `position.start.y` and then add a correct amount on the `work_area_offset`. - Remove the need for `frame`: some new configs were added that take priority over the old `frame`. These are: - `horizontal_padding`: defines the left and right padding of the bar. Similar to `frame.inner_margin.x`. - `vertical_padding`: defines the top and bottom padding of the bar. Similar to `frame.inner_margin.y`. - Remove the need for `work_area_offset`: if a `work_area_offset` is given then it will take priority, if not, then it will calculate the necessary `work_area_offset` using the bar height, position and horizontal and vertical margins. feat(bar): set margin/padding as one or two values This commit changes the `horizontal_margin`, `vertical_margin`, `horizontal_padding` and `vertical_padding` to now take a `SpacingAxisConfig` which can take a single value or two values. For example, you can set the vertical margin of the bar to add some spacing above and below like this: ```json "vertical_margin": 10 ``` Which will add a spacing of 10 above and below the bar. Or you can set it like this: ```json "vertical_margin": [10, 0] ``` Which will add a spacing of 10 above the bar but no spacing below. You can even set something like this: ```json "vertical_margin": [0, -10] ``` To make no spacing above and a negative spacing below to make it so the tiled windows show right next to the bar. This will basically be removing the workspace and container padding between the tiled windows and the bar. fix(bar): use a right_to_left layout on right side This commit changes the right area with the right widgets to have a different layout that is still right_to_left as previously but behaves much better in regards to its height. fix(bar): use default bar height When there is no `work_area_offset` and no `height` on the config it was using the `BAR_HEIGHT` as default, however the automatica work_area_offset calculation wasn't being done properly. Now it is! feat(bar): monitor can be `MonitorConfig` or index This commit allows the `"monitor":` config to take a `MonitorConfig` object like it used to or simply a number (index). docs(schema): update all json schemas fix(bar): update example bar config fix(bar): correct work_area_offset on secondary monitors feat(bar): add multiple options for margin/padding This commit removes the previous `horizontal_margin`, `vertical_margin`, `horizontal_padding` and `vertical_padding`, replacing them all with just `margin` and `padding`. These new options can be set either with a single value that sets that spacing on all sides, with an object specifying each individual side or with an object specifying some "vertical" and/or "horizontal" spacing which can have a single value, resulting on a symmetric spacing for that specific axis or two values to define each side of the axis individually.
439 lines
15 KiB
Rust
439 lines
15 KiB
Rust
mod bar;
|
|
mod battery;
|
|
mod config;
|
|
mod cpu;
|
|
mod date;
|
|
mod komorebi;
|
|
mod komorebi_layout;
|
|
mod media;
|
|
mod memory;
|
|
mod network;
|
|
mod render;
|
|
mod selected_frame;
|
|
mod storage;
|
|
mod time;
|
|
mod ui;
|
|
mod update;
|
|
mod widget;
|
|
|
|
use crate::bar::Komobar;
|
|
use crate::config::KomobarConfig;
|
|
use crate::config::Position;
|
|
use crate::config::PositionConfig;
|
|
use clap::Parser;
|
|
use config::MonitorConfigOrIndex;
|
|
use eframe::egui::ViewportBuilder;
|
|
use font_loader::system_fonts;
|
|
use hotwatch::EventKind;
|
|
use hotwatch::Hotwatch;
|
|
use image::RgbaImage;
|
|
use komorebi_client::SocketMessage;
|
|
use komorebi_client::SubscribeOptions;
|
|
use schemars::gen::SchemaSettings;
|
|
use std::collections::HashMap;
|
|
use std::io::BufReader;
|
|
use std::io::Read;
|
|
use std::path::PathBuf;
|
|
use std::sync::atomic::AtomicI32;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
use std::sync::LazyLock;
|
|
use std::sync::Mutex;
|
|
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 MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
|
|
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 MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
|
|
pub static BAR_HEIGHT: f32 = 50.0;
|
|
pub static DEFAULT_PADDING: f32 = 10.0;
|
|
|
|
pub static ICON_CACHE: LazyLock<Mutex<HashMap<String, RgbaImage>>> =
|
|
LazyLock::new(|| Mutex::new(HashMap::new()));
|
|
|
|
#[derive(Parser)]
|
|
#[clap(author, about, version)]
|
|
struct Opts {
|
|
/// Print the JSON schema of the configuration file and exit
|
|
#[clap(long)]
|
|
schema: bool,
|
|
/// Print a list of fonts available on this system and exit
|
|
#[clap(long)]
|
|
fonts: bool,
|
|
/// Path to a JSON or YAML configuration file
|
|
#[clap(short, long)]
|
|
config: Option<PathBuf>,
|
|
/// 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,
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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<()> {
|
|
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }?;
|
|
|
|
let opts: Opts = Opts::parse();
|
|
|
|
if opts.schema {
|
|
let settings = SchemaSettings::default().with(|s| {
|
|
s.option_nullable = false;
|
|
s.option_add_null_type = false;
|
|
s.inline_subschemas = true;
|
|
});
|
|
|
|
let gen = settings.into_generator();
|
|
let socket_message = gen.into_root_schema_for::<KomobarConfig>();
|
|
let schema = serde_json::to_string_pretty(&socket_message)?;
|
|
|
|
println!("{schema}");
|
|
std::process::exit(0);
|
|
}
|
|
|
|
if opts.fonts {
|
|
for font in system_fonts::query_all() {
|
|
println!("{font}");
|
|
}
|
|
|
|
std::process::exit(0);
|
|
}
|
|
|
|
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
|
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
|
}
|
|
|
|
color_eyre::install()?;
|
|
|
|
if std::env::var("RUST_LOG").is_err() {
|
|
std::env::set_var("RUST_LOG", "info");
|
|
}
|
|
|
|
tracing::subscriber::set_global_default(
|
|
tracing_subscriber::fmt::Subscriber::builder()
|
|
.with_env_filter(EnvFilter::from_default_env())
|
|
.finish(),
|
|
)?;
|
|
|
|
let home_dir: PathBuf = std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|
|
|_| dirs::home_dir().expect("there is no home directory"),
|
|
|home_path| {
|
|
let home = PathBuf::from(&home_path);
|
|
|
|
if home.as_path().is_dir() {
|
|
home
|
|
} else {
|
|
panic!("$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory");
|
|
}
|
|
},
|
|
);
|
|
|
|
if opts.quickstart {
|
|
let komorebi_bar_json = include_str!("../../docs/komorebi.bar.example.json").to_string();
|
|
std::fs::write(home_dir.join("komorebi.bar.json"), komorebi_bar_json)?;
|
|
println!(
|
|
"Example komorebi.bar.json file written to {}",
|
|
home_dir.as_path().display()
|
|
);
|
|
|
|
std::process::exit(0);
|
|
}
|
|
|
|
let default_config_path = home_dir.join("komorebi.bar.json");
|
|
|
|
let config_path = opts.config.map_or_else(
|
|
|| {
|
|
if !default_config_path.is_file() {
|
|
None
|
|
} else {
|
|
Some(default_config_path.clone())
|
|
}
|
|
},
|
|
Option::from,
|
|
);
|
|
|
|
let mut config = match config_path {
|
|
None => {
|
|
let komorebi_bar_json =
|
|
include_str!("../../docs/komorebi.bar.example.json").to_string();
|
|
|
|
std::fs::write(&default_config_path, komorebi_bar_json)?;
|
|
tracing::info!(
|
|
"created example configuration file: {}",
|
|
default_config_path.as_path().display()
|
|
);
|
|
|
|
KomobarConfig::read(&default_config_path)?
|
|
}
|
|
Some(ref config) => {
|
|
if !opts.aliases {
|
|
tracing::info!(
|
|
"found configuration file: {}",
|
|
config.as_path().to_string_lossy()
|
|
);
|
|
}
|
|
|
|
KomobarConfig::read(config)?
|
|
}
|
|
};
|
|
|
|
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 (monitor_index, work_area_offset) = match &config.monitor {
|
|
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
|
|
(monitor_config.index, monitor_config.work_area_offset)
|
|
}
|
|
MonitorConfigOrIndex::Index(idx) => (*idx, None),
|
|
};
|
|
|
|
MONITOR_RIGHT.store(
|
|
state.monitors.elements()[monitor_index].size().right,
|
|
Ordering::SeqCst,
|
|
);
|
|
|
|
MONITOR_TOP.store(
|
|
state.monitors.elements()[monitor_index].size().top,
|
|
Ordering::SeqCst,
|
|
);
|
|
|
|
MONITOR_LEFT.store(
|
|
state.monitors.elements()[monitor_index].size().left,
|
|
Ordering::SeqCst,
|
|
);
|
|
|
|
MONITOR_INDEX.store(monitor_index, Ordering::SeqCst);
|
|
|
|
match config.position {
|
|
None => {
|
|
config.position = Some(PositionConfig {
|
|
start: Some(Position {
|
|
x: state.monitors.elements()[monitor_index].size().left as f32,
|
|
y: state.monitors.elements()[monitor_index].size().top as f32,
|
|
}),
|
|
end: Some(Position {
|
|
x: state.monitors.elements()[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()[monitor_index].size().left as f32,
|
|
y: state.monitors.elements()[monitor_index].size().top as f32,
|
|
});
|
|
}
|
|
|
|
if position.end.is_none() {
|
|
position.end = Some(Position {
|
|
x: state.monitors.elements()[monitor_index].size().right as f32,
|
|
y: 50.0,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
let viewport_builder = ViewportBuilder::default()
|
|
.with_decorations(false)
|
|
.with_transparent(true)
|
|
.with_taskbar(false);
|
|
|
|
let native_options = eframe::NativeOptions {
|
|
viewport: viewport_builder,
|
|
..Default::default()
|
|
};
|
|
|
|
if let Some(rect) = &work_area_offset {
|
|
komorebi_client::send_message(&SocketMessage::MonitorWorkAreaOffset(monitor_index, *rect))?;
|
|
tracing::info!("work area offset applied to monitor: {}", monitor_index);
|
|
}
|
|
|
|
let (tx_gui, rx_gui) = crossbeam_channel::unbounded();
|
|
let (tx_config, rx_config) = crossbeam_channel::unbounded();
|
|
|
|
let mut hotwatch = Hotwatch::new()?;
|
|
let config_path_cl = config_path.clone();
|
|
|
|
hotwatch.watch(config_path, move |event| match event.kind {
|
|
EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) {
|
|
Ok(updated) => {
|
|
tracing::info!(
|
|
"configuration file updated: {}",
|
|
config_path_cl.as_path().to_string_lossy()
|
|
);
|
|
|
|
if let Err(error) = tx_config.send(updated) {
|
|
tracing::error!("could not send configuration update to gui: {error}")
|
|
}
|
|
}
|
|
Err(error) => {
|
|
tracing::error!("{error}");
|
|
}
|
|
},
|
|
_ => {}
|
|
})?;
|
|
|
|
tracing::info!("watching configuration file for changes");
|
|
|
|
let config_arc = Arc::new(config);
|
|
eframe::run_native(
|
|
"komorebi-bar",
|
|
native_options,
|
|
Box::new(|cc| {
|
|
let config_cl = config_arc.clone();
|
|
|
|
let ctx_repainter = cc.egui_ctx.clone();
|
|
std::thread::spawn(move || loop {
|
|
std::thread::sleep(Duration::from_secs(1));
|
|
ctx_repainter.request_repaint();
|
|
});
|
|
|
|
let ctx_komorebi = cc.egui_ctx.clone();
|
|
std::thread::spawn(move || {
|
|
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
|
|
|
|
let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {
|
|
filter_state_changes: true,
|
|
})
|
|
.expect("could not subscribe to komorebi notifications");
|
|
|
|
tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name);
|
|
|
|
for client in listener.incoming() {
|
|
match client {
|
|
Ok(subscription) => {
|
|
match subscription.set_read_timeout(Some(Duration::from_secs(1))) {
|
|
Ok(()) => {}
|
|
Err(error) => tracing::error!("{}", error),
|
|
}
|
|
let mut buffer = Vec::new();
|
|
let mut reader = BufReader::new(subscription);
|
|
|
|
// this is when we know a shutdown has been sent
|
|
if matches!(reader.read_to_end(&mut buffer), Ok(0)) {
|
|
tracing::info!("disconnected from komorebi");
|
|
|
|
// keep trying to reconnect to komorebi
|
|
while komorebi_client::send_message(
|
|
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
|
|
)
|
|
.is_err()
|
|
{
|
|
std::thread::sleep(Duration::from_secs(1));
|
|
}
|
|
|
|
tracing::info!("reconnected to komorebi");
|
|
|
|
let (monitor_index, work_area_offset) = match &config_cl.monitor {
|
|
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
|
|
(monitor_config.index, monitor_config.work_area_offset)
|
|
}
|
|
MonitorConfigOrIndex::Index(idx) => (*idx, None),
|
|
};
|
|
|
|
if let Some(rect) = &work_area_offset {
|
|
while komorebi_client::send_message(
|
|
&SocketMessage::MonitorWorkAreaOffset(
|
|
monitor_index,
|
|
*rect,
|
|
),
|
|
)
|
|
.is_err()
|
|
{
|
|
std::thread::sleep(Duration::from_secs(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
match String::from_utf8(buffer) {
|
|
Ok(notification_string) => {
|
|
match serde_json::from_str::<komorebi_client::Notification>(
|
|
¬ification_string,
|
|
) {
|
|
Ok(notification) => {
|
|
tracing::debug!("received notification from komorebi");
|
|
|
|
if let Err(error) = tx_gui.send(notification) {
|
|
tracing::error!("could not send komorebi notification update to gui thread: {error}")
|
|
}
|
|
|
|
ctx_komorebi.request_repaint();
|
|
}
|
|
Err(error) => {
|
|
tracing::error!("could not deserialize komorebi notification: {error}");
|
|
}
|
|
}
|
|
}
|
|
Err(error) => {
|
|
tracing::error!(
|
|
"komorebi notification string was invalid utf8: {error}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
Err(error) => {
|
|
tracing::error!("{error}");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config_arc)))
|
|
}),
|
|
)
|
|
.map_err(|error| color_eyre::eyre::Error::msg(error.to_string()))
|
|
}
|