Compare commits

..

4 Commits

Author SHA1 Message Date
LGUG2Z
70a12802db feat(wm): use monitor hardware ids where available
This commit pulls in changes to win32-display-data which provide the
monitor hardware serial number id taken from WmiMonitorID where
available.

No work has yet been done to integrate this with options such as
display_index_preferences.
2025-01-25 15:37:46 -08:00
LGUG2Z
15e443a46b feat(config): add object name change title ignore list
This commit adds a title regex-based ignore list for applications
identified in object_name_change_applications. When a title change on an
EVENT_OBJECT_NAMECHANGE matches one of these regexes, the event will
never be processed as a Show.

This is an edge case workaround specifically targeting the issue of web
apps in Gecko-based browsers which update their page titles at a fixed
regular interval, which was highlighted in #1235.

resolve #1235
2025-01-24 15:50:45 -08:00
LGUG2Z
367ae95cd4 fix(wm): populate ws rules on config reload
This commit fixes a bug where workspace rules would not be populated
properly on file reloads, leading to issues with the
ReplaceConfiguration message handler.
2025-01-24 14:37:43 -08:00
LGUG2Z
edcba65156 fix(bar): consider all window types when hiding empty ws
This commit ensures that floating windows, monocle containers and
maximized windows will be considered when the hide_empty_workspaces
option is enabled for the komorebi widget.

re #1131
2025-01-24 12:48:12 -08:00
83 changed files with 2134 additions and 4941 deletions

857
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,9 @@ chrono = "0.4"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
color-eyre = "0.6"
eframe = "0.31"
egui_extras = "0.31"
dirs = "6"
eframe = "0.30"
egui_extras = "0.30"
dirs = "5"
dunce = "1"
hotwatch = "0.5"
schemars = "0.8"
@@ -27,38 +27,33 @@ lazy_static = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { package = "serde_json_lenient", version = "0.2" }
serde_yaml = "0.9"
strum = { version = "0.27", features = ["derive"] }
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "55cebdebfbd68dbd14945a1ba90f6b05b7be2893" }
windows-numerics = { version = "0.1" }
windows-implement = { version = "0.59" }
windows-interface = { version = "0.59" }
windows-core = { version = "0.60" }
shadow-rs = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "75286e77c068a89d12adcd6404c9c4874a60acf5" }
windows-implement = { version = "0.58" }
windows-interface = { version = "0.58" }
windows-core = { version = "0.58" }
shadow-rs = "0.37"
which = "7"
[workspace.dependencies.windows]
version = "0.60"
version = "0.58"
features = [
"implement",
"Foundation_Numerics",
"Win32_Devices",
"Win32_Devices_Display",
"Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Dxgi_Common",
"Win32_System_LibraryLoader",
"Win32_System_Power",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
@@ -72,16 +67,3 @@ features = [
"Media",
"Media_Control"
]
[profile.dev-jeezy]
inherits = "dev"
debug = false
opt-level = 1
[profile.dev-jeezy.package."*"]
opt-level = 3
[profile.release-jeezy]
inherits = "release"
incremental = true
codegen-units = 256

View File

@@ -389,7 +389,7 @@ every `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust c
Below is a simple example of how to use `komorebi-client` in a basic Rust application.
```rust
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.34"}
// komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.33"}
use anyhow::Result;
use komorebi_client::Notification;

View File

@@ -1,7 +1,7 @@
# flip-layout
```
Flip the layout on the focused workspace
Flip the layout on the focused workspace (BSP only)
Usage: komorebic.exe flip-layout <AXIS>

View File

@@ -1,12 +0,0 @@
# focus-monitor-at-cursor
```
Focus the monitor at the current cursor location
Usage: komorebic.exe focus-monitor-at-cursor
Options:
-h, --help
Print help
```

View File

@@ -7,7 +7,7 @@ Usage: komorebic.exe query <STATE_QUERY>
Arguments:
<STATE_QUERY>
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name]
[possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index]
Options:
-h, --help

View File

@@ -1,12 +0,0 @@
# toggle-window-based-work-area-offset
```
Toggle application of the window-based work area offset for the focused workspace
Usage: komorebic.exe toggle-window-based-work-area-offset
Options:
-h, --help
Print help
```

View File

@@ -1,12 +0,0 @@
# toggle-workspace-layer
```
Toggle between the Tiling and Floating layers on the focused workspace
Usage: komorebic.exe toggle-workspace-layer
Options:
-h, --help
Print help
```

View File

@@ -75,7 +75,7 @@ solo developer.
If you choose to use the active window border, you can set different colours to
give you visual queues when you are focused on a single window, a stack of
windows, or a window that is in monocle mode.
windows, or a window that is in monocole mode.
The example colours given are blue single, green for stack and pink for
monocle.
@@ -254,5 +254,5 @@ stackbars as well as the status bar.
If set in `komorebi.bar.json`, the theme will only be applied to the status bar.
All [Catppuccin palette variants](https://catppuccin.com/)
and [most Base16 palette variants](https://tinted-theming.github.io/tinted-gallery/)
and [most Base16 palette variants](https://tinted-theming.github.io/base16-gallery/)
are available as themes.

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.bar.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.33/schema.bar.json",
"monitor": 0,
"font_family": "JetBrains Mono",
"theme": {

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.34/schema.json",
"$schema": "https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.33/schema.json",
"app_specific_configuration_path": "$Env:USERPROFILE/applications.json",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",

View File

@@ -24,27 +24,6 @@ install-target target:
install:
just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
build-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just build-target $_ }
build-target target:
cargo +stable build --package {{ target }} --locked --profile release-jeezy
build:
just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
copy-target target:
cp .\target\release-jeezy\{{ target }}.exe $Env:USERPROFILE\.cargo\bin
copy-targets *targets:
"{{ targets }}" -split ' ' | ForEach-Object { just copy-target $_ }
wpm target:
just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}
copy:
just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui
run target:
cargo +stable run --bin {{ target }} --locked

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-bar"
version = "0.1.35"
edition = "2024"
version = "0.1.34"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,7 +16,7 @@ crossbeam-channel = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true }
eframe = { workspace = true }
egui-phosphor = "0.9"
egui-phosphor = "0.8"
font-loader = "0.11"
hotwatch = { workspace = true }
image = "0.25"
@@ -34,5 +34,4 @@ sysinfo = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }

View File

@@ -1,16 +1,9 @@
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;
use crate::KomorebiEvent;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crate::config::get_individual_spacing;
use crate::config::KomobarConfig;
use crate::config::KomobarTheme;
use crate::config::MonitorConfigOrIndex;
use crate::config::Position;
use crate::config::PositionConfig;
use crate::config::get_individual_spacing;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
@@ -20,6 +13,13 @@ use crate::render::RenderConfig;
use crate::render::RenderExt;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::KomorebiEvent;
use crate::BAR_HEIGHT;
use crate::DEFAULT_PADDING;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crossbeam_channel::Receiver;
use crossbeam_channel::TryRecvError;
use eframe::egui::Align;
@@ -47,22 +47,21 @@ use komorebi_client::KomorebiTheme;
use komorebi_client::MonitorNotification;
use komorebi_client::NotificationEvent;
use komorebi_client::SocketMessage;
use komorebi_themes::catppuccin_egui;
use komorebi_themes::Base16Value;
use komorebi_themes::Catppuccin;
use komorebi_themes::CatppuccinValue;
use komorebi_themes::catppuccin_egui;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub struct Komobar {
pub hwnd: Option<isize>,
pub monitor_index: Option<usize>,
pub disabled: bool,
pub config: KomobarConfig,
pub monitor_index: usize,
pub config: Arc<KomobarConfig>,
pub render_config: Rc<RefCell<RenderConfig>>,
pub komorebi_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
pub left_widgets: Vec<Box<dyn BarWidget>>,
@@ -178,11 +177,11 @@ pub fn apply_theme(
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.corner_radius = rounding.into();
style.visuals.widgets.inactive.corner_radius = rounding.into();
style.visuals.widgets.hovered.corner_radius = rounding.into();
style.visuals.widgets.active.corner_radius = rounding.into();
style.visuals.widgets.open.corner_radius = rounding.into();
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
});
}
}
@@ -195,45 +194,46 @@ impl Komobar {
pub fn apply_config(
&mut self,
ctx: &Context,
config: &KomobarConfig,
previous_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
) {
MAX_LABEL_WIDTH.store(
self.config.max_label_width.unwrap_or(400.0) as i32,
config.max_label_width.unwrap_or(400.0) as i32,
Ordering::SeqCst,
);
if let Some(font_family) = &self.config.font_family {
if let Some(font_family) = &config.font_family {
tracing::info!("attempting to add custom font family: {font_family}");
Self::add_custom_font(ctx, font_family);
}
// Update the `size_rect` so that the bar position can be changed on the EGUI update
// function
self.update_size_rect();
self.update_size_rect(config);
self.try_apply_theme(ctx);
self.try_apply_theme(config, ctx);
if let Some(font_size) = &self.config.font_size {
if let Some(font_size) = &config.font_size {
tracing::info!("attempting to set custom font size: {font_size}");
Self::set_font_size(ctx, *font_size);
}
self.render_config.replace((&self.config).new_renderconfig(
self.render_config.replace(config.new_renderconfig(
ctx,
*self.bg_color.borrow(),
self.config.icon_scale,
config.icon_scale,
));
let mut komorebi_notification_state = previous_notification_state;
let mut komorebi_widgets = Vec::new();
for (idx, widget_config) in self.config.left_widgets.iter().enumerate() {
for (idx, widget_config) in config.left_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Left));
}
}
if let Some(center_widgets) = &self.config.center_widgets {
if let Some(center_widgets) = &config.center_widgets {
for (idx, widget_config) in center_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Center));
@@ -241,21 +241,20 @@ impl Komobar {
}
}
for (idx, widget_config) in self.config.right_widgets.iter().enumerate() {
for (idx, widget_config) in config.right_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Right));
}
}
let mut left_widgets = self
.config
let mut left_widgets = config
.left_widgets
.iter()
.filter(|config| config.enabled())
.map(|config| config.as_boxed_bar_widget())
.collect::<Vec<Box<dyn BarWidget>>>();
let mut center_widgets = match &self.config.center_widgets {
let mut center_widgets = match &config.center_widgets {
Some(center_widgets) => center_widgets
.iter()
.filter(|config| config.enabled())
@@ -264,8 +263,7 @@ impl Komobar {
None => vec![],
};
let mut right_widgets = self
.config
let mut right_widgets = config
.right_widgets
.iter()
.filter(|config| config.enabled())
@@ -307,91 +305,79 @@ impl Komobar {
self.center_widgets = center_widgets;
self.right_widgets = right_widgets;
let (usr_monitor_index, config_work_area_offset) = match &self.config.monitor {
let (monitor_index, config_work_area_offset) = match &config.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => {
(monitor_config.index, monitor_config.work_area_offset)
}
MonitorConfigOrIndex::Index(idx) => (*idx, None),
};
let monitor_index = self.komorebi_notification_state.as_ref().and_then(|state| {
state
.borrow()
.monitor_usr_idx_map
.get(&usr_monitor_index)
.copied()
});
self.monitor_index = monitor_index;
if let Some(monitor_index) = self.monitor_index {
if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset)
{
if new_rect != prev_rect {
self.work_area_offset = *new_rect;
if let Err(error) = komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(monitor_index, *new_rect),
) {
tracing::error!(
"error applying work area offset to monitor '{}': {}",
monitor_index,
error,
);
} else {
tracing::info!("work area offset applied to monitor: {}", monitor_index);
}
}
} else if let Some(height) = self.config.height.or(Some(BAR_HEIGHT)) {
// We only add the `bottom_margin` to the work_area_offset since the top margin is
// already considered on the `size_rect.top`
let bottom_margin = self
.config
.margin
.as_ref()
.map_or(0, |v| v.to_individual(0.0).bottom as i32);
let new_rect = komorebi_client::Rect {
left: 0,
top: (height as i32)
+ (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))
+ bottom_margin,
right: 0,
bottom: (height as i32)
+ (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))
+ bottom_margin,
};
if new_rect != self.work_area_offset {
self.work_area_offset = new_rect;
if let Err(error) = komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(monitor_index, new_rect),
) {
tracing::error!(
"error applying work area offset to monitor '{monitor_index}': {error}"
);
} else {
tracing::info!("work area offset applied to monitor: {monitor_index}",);
}
if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset) {
if new_rect != prev_rect {
self.work_area_offset = *new_rect;
if let Err(error) = komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(self.monitor_index, *new_rect),
) {
tracing::error!(
"error applying work area offset to monitor '{}': {}",
self.monitor_index,
error,
);
} else {
tracing::info!(
"work area offset applied to monitor: {}",
self.monitor_index
);
}
}
} else if let Some(height) = config.height.or(Some(BAR_HEIGHT)) {
// We only add the `bottom_margin` to the work_area_offset since the top margin is
// already considered on the `size_rect.top`
let bottom_margin = config
.margin
.as_ref()
.map_or(0, |v| v.to_individual(0.0).bottom as i32);
let new_rect = komorebi_client::Rect {
left: 0,
top: (height as i32)
+ (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))
+ bottom_margin,
right: 0,
bottom: (height as i32)
+ (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))
+ bottom_margin,
};
if new_rect != self.work_area_offset {
self.work_area_offset = new_rect;
if let Err(error) = komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(self.monitor_index, new_rect),
) {
tracing::error!(
"error applying work area offset to monitor '{}': {}",
self.monitor_index,
error,
);
} else {
tracing::info!(
"work area offset applied to monitor: {}",
self.monitor_index
);
}
}
} else if self.komorebi_notification_state.is_some() && !self.disabled {
tracing::warn!(
"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects..."
);
self.disabled = true;
} else {
tracing::warn!(
"couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi."
);
self.disabled = true;
}
tracing::info!("widget configuration options applied");
self.komorebi_notification_state = komorebi_notification_state;
self.config = config.clone().into();
}
/// Updates the `size_rect` field. Returns a bool indicating if the field was changed or not
fn update_size_rect(&mut self) {
let position = self.config.position.clone().unwrap_or(PositionConfig {
fn update_size_rect(&mut self, config: &KomobarConfig) {
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,
@@ -412,20 +398,18 @@ impl Komobar {
y: BAR_HEIGHT,
});
if let Some(height) = self.config.height {
if let Some(height) = config.height {
end.y = height;
}
let margin = get_individual_spacing(0.0, &self.config.margin);
let margin = get_individual_spacing(0.0, &config.margin);
start.y += margin.top;
start.x += margin.left;
end.x -= margin.left + margin.right;
if end.y == 0.0 {
tracing::warn!(
"position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default"
)
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
}
self.size_rect = komorebi_client::Rect {
@@ -436,16 +420,16 @@ impl Komobar {
};
}
fn try_apply_theme(&mut self, ctx: &Context) {
match self.config.theme {
fn try_apply_theme(&mut self, config: &KomobarConfig, ctx: &Context) {
match config.theme {
Some(theme) => {
apply_theme(
ctx,
theme,
self.bg_color.clone(),
self.bg_color_with_alpha.clone(),
self.config.transparency_alpha,
self.config.grouping,
config.transparency_alpha,
config.grouping,
self.render_config.clone(),
);
}
@@ -463,8 +447,8 @@ impl Komobar {
},
);
let bar_transparency_alpha = self.config.transparency_alpha;
let bar_grouping = self.config.grouping;
let bar_transparency_alpha = config.transparency_alpha;
let bar_grouping = config.grouping;
let config = home_dir.join("komorebi.json");
match komorebi_client::StaticConfig::read(&config) {
Ok(config) => {
@@ -508,12 +492,11 @@ impl Komobar {
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.corner_radius =
rounding.into();
style.visuals.widgets.inactive.corner_radius = rounding.into();
style.visuals.widgets.hovered.corner_radius = rounding.into();
style.visuals.widgets.active.corner_radius = rounding.into();
style.visuals.widgets.open.corner_radius = rounding.into();
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
});
}
}
@@ -527,13 +510,12 @@ impl Komobar {
cc: &eframe::CreationContext<'_>,
rx_gui: Receiver<KomorebiEvent>,
rx_config: Receiver<KomobarConfig>,
config: KomobarConfig,
config: Arc<KomobarConfig>,
) -> Self {
let mut komobar = Self {
hwnd: process_hwnd(),
monitor_index: None,
disabled: false,
config,
monitor_index: 0,
config: config.clone(),
render_config: Rc::new(RefCell::new(RenderConfig::new())),
komorebi_notification_state: None,
left_widgets: vec![],
@@ -549,9 +531,9 @@ impl Komobar {
applied_theme_on_first_frame: false,
};
komobar.apply_config(&cc.egui_ctx, None);
komobar.apply_config(&cc.egui_ctx, &config, None);
// needs a double apply the first time for some reason
komobar.apply_config(&cc.egui_ctx, None);
komobar.apply_config(&cc.egui_ctx, &config, None);
komobar
}
@@ -589,7 +571,6 @@ impl Komobar {
fallbacks.insert("Microsoft YaHei", "C:\\Windows\\Fonts\\msyh.ttc"); // chinese
fallbacks.insert("Malgun Gothic", "C:\\Windows\\Fonts\\malgun.ttf"); // korean
fallbacks.insert("Leelawadee UI", "C:\\Windows\\Fonts\\LeelawUI.ttf"); // thai
for (name, path) in fallbacks {
if let Ok(bytes) = std::fs::read(path) {
@@ -626,37 +607,6 @@ impl Komobar {
// Tell egui to use these fonts:
ctx.set_fonts(fonts);
}
pub fn position_bar(&self) {
if let Some(hwnd) = self.hwnd {
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&self.size_rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{error}")
}
}
}
}
fn update_monitor_coordinates(&mut self, monitor_size: &komorebi_client::Rect) {
// Store the new monitor coordinates
MONITOR_TOP.store(monitor_size.top, Ordering::SeqCst);
MONITOR_LEFT.store(monitor_size.left, Ordering::SeqCst);
MONITOR_RIGHT.store(monitor_size.right, Ordering::SeqCst);
// Since the `config.position` is changed on `main.rs` we need to update it here.
// If the user had set up some `start` position, that will be overriden here
// since we have no way to know what was that value since it might have been
// changed on `main.rs`. However if the users use the new configs this won't be
// a problem for them.
if let Some(start) = self.config.position.as_mut().and_then(|p| p.start.as_mut()) {
start.x = monitor_size.left as f32;
start.y = monitor_size.top as f32;
}
}
}
impl eframe::App for Komobar {
// Needed for transparency
@@ -671,12 +621,19 @@ impl eframe::App for Komobar {
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
self.scale_factor = ctx.native_pixels_per_point().unwrap_or(1.0);
self.apply_config(ctx, self.komorebi_notification_state.clone());
self.apply_config(
ctx,
&self.config.clone(),
self.komorebi_notification_state.clone(),
);
}
if let Ok(updated_config) = self.rx_config.try_recv() {
self.config = updated_config;
self.apply_config(ctx, self.komorebi_notification_state.clone());
self.apply_config(
ctx,
&updated_config,
self.komorebi_notification_state.clone(),
);
}
match self.rx_gui.try_recv() {
@@ -689,93 +646,32 @@ impl eframe::App for Komobar {
}
},
Ok(KomorebiEvent::Notification(notification)) => {
let state = &notification.state;
let usr_monitor_index = match &self.config.monitor {
MonitorConfigOrIndex::MonitorConfig(monitor_config) => monitor_config.index,
MonitorConfigOrIndex::Index(idx) => *idx,
};
let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();
self.monitor_index = monitor_index;
let mut should_apply_config = false;
if self.monitor_index.is_none()
|| self
.monitor_index
.is_some_and(|idx| idx >= state.monitors.elements().len())
{
if !self.disabled {
// Monitor for this bar got disconnected lets disable the bar until it
// reconnects
self.disabled = true;
tracing::warn!(
"This bar's monitor got disconnected. The bar will be disabled until it reconnects..."
);
}
return;
} else {
if self.disabled {
tracing::info!("Found this bar's monitor. The bar will be enabled!");
// Restore the bar in case it has been minimized when the monitor
// disconnected
if let Some(hwnd) = self.hwnd {
let window = komorebi_client::Window::from(hwnd);
if window.is_miminized() {
komorebi_client::WindowsApi::restore_window(hwnd);
}
}
// Reset the current `work_area_offset` so that it gets recalculated and
// properly applied again, since if the monitor has connected for the first
// time it won't have the work_area_offset applied but the bar thinks it
// does.
self.work_area_offset = komorebi_client::Rect::default();
should_apply_config = true;
}
self.disabled = false;
}
if matches!(
let should_apply_config = if matches!(
notification.event,
NotificationEvent::Monitor(MonitorNotification::DisplayConnectionChange)
) {
let monitor_index = self.monitor_index.expect("should have a monitor index");
let state = &notification.state;
let monitor_size = state.monitors.elements()[monitor_index].size();
// Store the monitor coordinates in case they've changed
MONITOR_RIGHT.store(
state.monitors.elements()[self.monitor_index].size().right,
Ordering::SeqCst,
);
self.update_monitor_coordinates(monitor_size);
MONITOR_TOP.store(
state.monitors.elements()[self.monitor_index].size().top,
Ordering::SeqCst,
);
should_apply_config = true;
}
MONITOR_LEFT.store(
state.monitors.elements()[self.monitor_index].size().left,
Ordering::SeqCst,
);
if self.disabled {
return;
}
// Check if monitor coordinates/size has changed
if let Some(monitor_index) = self.monitor_index {
let monitor_size = state.monitors.elements()[monitor_index].size();
let top = MONITOR_TOP.load(Ordering::SeqCst);
let left = MONITOR_LEFT.load(Ordering::SeqCst);
let right = MONITOR_RIGHT.load(Ordering::SeqCst);
let rect = komorebi_client::Rect {
top,
left,
bottom: monitor_size.bottom,
right,
};
if *monitor_size != rect {
tracing::info!(
"Monitor coordinates/size has changed, storing new coordinates: {:#?}",
monitor_size
);
self.update_monitor_coordinates(monitor_size);
should_apply_config = true;
}
}
true
} else {
false
};
if let Some(komorebi_notification_state) = &self.komorebi_notification_state {
komorebi_notification_state
@@ -794,37 +690,36 @@ impl eframe::App for Komobar {
}
if should_apply_config {
self.apply_config(ctx, self.komorebi_notification_state.clone());
// Reposition the Bar
self.position_bar();
self.apply_config(
ctx,
&self.config.clone(),
self.komorebi_notification_state.clone(),
);
}
}
Ok(KomorebiEvent::Reconnect) => {
if let Some(monitor_index) = self.monitor_index {
if let Err(error) = komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(monitor_index, self.work_area_offset),
) {
tracing::error!(
"error applying work area offset to monitor '{}': {}",
monitor_index,
error,
);
} else {
tracing::info!("work area offset applied to monitor: {}", monitor_index);
}
if let Err(error) =
komorebi_client::send_message(&SocketMessage::MonitorWorkAreaOffset(
self.monitor_index,
self.work_area_offset,
))
{
tracing::error!(
"error applying work area offset to monitor '{}': {}",
self.monitor_index,
error,
);
} else {
tracing::info!(
"work area offset applied to monitor: {}",
self.monitor_index
);
}
}
}
if self.disabled {
// The check for disabled is performed above, if we get here and the bar is still
// disabled then we should return without drawing anything.
return;
}
if !self.applied_theme_on_first_frame {
self.try_apply_theme(ctx);
self.try_apply_theme(&self.config.clone(), ctx);
self.applied_theme_on_first_frame = true;
}
@@ -839,33 +734,43 @@ impl eframe::App for Komobar {
};
if self.size_rect != current_rect {
self.position_bar();
if let Some(hwnd) = self.hwnd {
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&self.size_rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
}
}
}
}
}
let frame = match &self.config.padding {
None => {
if let Some(frame) = &self.config.frame {
Frame::NONE
Frame::none()
.inner_margin(Margin::symmetric(
frame.inner_margin.x as i8,
frame.inner_margin.y as i8,
frame.inner_margin.x,
frame.inner_margin.y,
))
.fill(*self.bg_color_with_alpha.borrow())
} else {
Frame::NONE
.inner_margin(Margin::same(0))
Frame::none()
.inner_margin(Margin::same(0.0))
.fill(*self.bg_color_with_alpha.borrow())
}
}
Some(padding) => {
let padding = padding.to_individual(DEFAULT_PADDING);
Frame::NONE
Frame::none()
.inner_margin(Margin {
top: padding.top as i8,
bottom: padding.bottom as i8,
left: padding.left as i8,
right: padding.right as i8,
top: padding.top,
bottom: padding.bottom,
left: padding.left,
right: padding.right,
})
.fill(*self.bg_color_with_alpha.borrow())
}
@@ -878,13 +783,13 @@ impl eframe::App for Komobar {
CentralPanel::default().frame(frame).show(ctx, |ui| {
// Apply grouping logic for the bar as a whole
let area_frame = if let Some(frame) = &self.config.frame {
Frame::NONE
.inner_margin(Margin::symmetric(0, frame.inner_margin.y as i8))
.outer_margin(Margin::same(0))
Frame::none()
.inner_margin(Margin::symmetric(0.0, frame.inner_margin.y))
.outer_margin(Margin::same(0.0))
} else {
Frame::NONE
.inner_margin(Margin::same(0))
.outer_margin(Margin::same(0))
Frame::none()
.inner_margin(Margin::same(0.0))
.outer_margin(Margin::same(0.0))
};
let available_height = ui.max_rect().max.y;
@@ -904,13 +809,13 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
left_area_frame.inner_margin.left = padding.left as i8;
left_area_frame.inner_margin.top = padding.top as i8;
left_area_frame.inner_margin.bottom = padding.bottom as i8;
left_area_frame.inner_margin.left = padding.left;
left_area_frame.inner_margin.top = padding.top;
left_area_frame.inner_margin.bottom = padding.bottom;
} else if let Some(frame) = &self.config.frame {
left_area_frame.inner_margin.left = frame.inner_margin.x as i8;
left_area_frame.inner_margin.top = frame.inner_margin.y as i8;
left_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
left_area_frame.inner_margin.left = frame.inner_margin.x;
left_area_frame.inner_margin.top = frame.inner_margin.y;
left_area_frame.inner_margin.bottom = frame.inner_margin.y;
}
left_area_frame.show(ui, |ui| {
@@ -940,13 +845,13 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
right_area_frame.inner_margin.right = padding.right as i8;
right_area_frame.inner_margin.top = padding.top as i8;
right_area_frame.inner_margin.bottom = padding.bottom as i8;
right_area_frame.inner_margin.right = padding.right;
right_area_frame.inner_margin.top = padding.top;
right_area_frame.inner_margin.bottom = padding.bottom;
} else if let Some(frame) = &self.config.frame {
right_area_frame.inner_margin.right = frame.inner_margin.x as i8;
right_area_frame.inner_margin.top = frame.inner_margin.y as i8;
right_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
right_area_frame.inner_margin.right = frame.inner_margin.x;
right_area_frame.inner_margin.top = frame.inner_margin.y;
right_area_frame.inner_margin.bottom = frame.inner_margin.y;
}
right_area_frame.show(ui, |ui| {
@@ -984,11 +889,11 @@ impl eframe::App for Komobar {
.as_ref()
.map(|s| s.to_individual(DEFAULT_PADDING))
{
center_area_frame.inner_margin.top = padding.top as i8;
center_area_frame.inner_margin.bottom = padding.bottom as i8;
center_area_frame.inner_margin.top = padding.top;
center_area_frame.inner_margin.bottom = padding.bottom;
} else if let Some(frame) = &self.config.frame {
center_area_frame.inner_margin.top = frame.inner_margin.y as i8;
center_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;
center_area_frame.inner_margin.top = frame.inner_margin.y;
center_area_frame.inner_margin.bottom = frame.inner_margin.y;
}
center_area_frame.show(ui, |ui| {

View File

@@ -2,18 +2,18 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use starship_battery::units::ratio::percent;
use starship_battery::Manager;
use starship_battery::State;
use starship_battery::units::ratio::percent;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;

View File

@@ -1,6 +1,6 @@
use crate::DEFAULT_PADDING;
use crate::render::Grouping;
use crate::widget::WidgetConfig;
use crate::DEFAULT_PADDING;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;
use eframe::egui::Vec2;
@@ -13,7 +13,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.bar.json` configuration file reference for `v0.1.35`
/// The `komorebi.bar.json` configuration file reference for `v0.1.34`
pub struct KomobarConfig {
/// Bar height (default: 50)
pub height: Option<f32>,
@@ -115,9 +115,7 @@ impl KomobarConfig {
}
if display {
println!(
"\nYour bar configuration file contains some options that have been renamed or deprecated:\n"
);
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) {
@@ -127,15 +125,6 @@ impl KomobarConfig {
}
}
}
pub fn show_all_icons_on_komorebi_workspace(widgets: &[WidgetConfig]) -> bool {
widgets
.iter()
.any(|w| matches!(w, WidgetConfig::Komorebi(config) if config.workspaces.is_some_and(|w| w.enable && w.display.is_some_and(|s| matches!(s,
WorkspacesDisplayFormat::AllIcons
| WorkspacesDisplayFormat::AllIconsAndText
| WorkspacesDisplayFormat::AllIconsAndTextOnSelected)))))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -377,7 +366,7 @@ pub enum KomobarTheme {
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)
name: komorebi_themes::Base16,
accent: Option<komorebi_themes::Base16Value>,
},
@@ -427,90 +416,3 @@ pub enum DisplayFormat {
/// Show an icon and text for the selected element, and icons on the rest
IconAndTextOnSelected,
}
macro_rules! extend_enum {
($existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq)]
pub enum $new_enum {
// Add new variants
$(
$(#[$meta])*
$variant,
)*
// Include a variant that wraps the existing enum and flatten it when deserializing
#[serde(untagged)]
Existing($existing_enum),
}
// Implement From for the existing enum
impl From<$existing_enum> for $new_enum {
fn from(value: $existing_enum) -> Self {
$new_enum::Existing(value)
}
}
};
}
extend_enum!(DisplayFormat, WorkspacesDisplayFormat, {
/// Show all icons only
AllIcons,
/// Show both all icons and text
AllIconsAndText,
/// Show all icons and text for the selected element, and all icons on the rest
AllIconsAndTextOnSelected,
});
#[cfg(test)]
mod tests {
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OriginalDisplayFormat {
/// Show None Of The Things
NoneOfTheThings,
}
extend_enum!(OriginalDisplayFormat, ExtendedDisplayFormat, {
/// Show Some Of The Things
SomeOfTheThings,
});
#[derive(serde::Deserialize)]
struct ExampleConfig {
#[allow(unused)]
format: ExtendedDisplayFormat,
}
#[test]
pub fn extend_new_variant() {
let raw = json!({
"format": "SomeOfTheThings",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
}
#[test]
pub fn extend_existing_variant() {
let raw = json!({
"format": "NoneOfTheThings",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
}
#[test]
pub fn extend_invalid_variant() {
let raw = json!({
"format": "ALLOFTHETHINGS",
})
.to_string();
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_err())
}
}

View File

@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -2,13 +2,13 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -1,177 +0,0 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::WidgetText;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::time::Duration;
use std::time::Instant;
use windows::Win32::Globalization::LCIDToLocaleName;
use windows::Win32::Globalization::LOCALE_ALLOW_NEUTRAL_NAMES;
use windows::Win32::System::SystemServices::LOCALE_NAME_MAX_LENGTH;
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
const DEFAULT_DATA_REFRESH_INTERVAL: u64 = 1;
const ERROR_TEXT: &str = "Error";
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KeyboardConfig {
/// Enable the Input widget
pub enable: bool,
/// Data refresh interval (default: 1 second)
pub data_refresh_interval: Option<u64>,
/// Display label prefix
pub label_prefix: Option<LabelPrefix>,
}
impl From<KeyboardConfig> for Keyboard {
fn from(value: KeyboardConfig) -> Self {
let data_refresh_interval = value
.data_refresh_interval
.unwrap_or(DEFAULT_DATA_REFRESH_INTERVAL);
Self {
enable: value.enable,
data_refresh_interval,
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
last_updated: Instant::now(),
lang_name: get_lang(),
}
}
}
pub struct Keyboard {
pub enable: bool,
data_refresh_interval: u64,
label_prefix: LabelPrefix,
last_updated: Instant,
lang_name: String,
}
/// Retrieves the name of the active keyboard layout for the current foreground window.
///
/// This function determines the active keyboard layout by querying the system for the
/// foreground window's thread ID and its associated keyboard layout. It then attempts
/// to retrieve the locale name corresponding to the keyboard layout.
///
/// # Failure Cases
///
/// This function can fail in two distinct scenarios:
///
/// 1. **Failure to Retrieve the Locale Name**:
/// If the system fails to retrieve the locale name (e.g., due to an invalid or unsupported
/// language identifier), the function will return `Err(())`.
///
/// 2. **Invalid UTF-16 Characters in the Locale Name**:
/// If the retrieved locale name contains invalid UTF-16 sequences, the conversion to a Rust
/// `String` will fail, and the function will return `Err(())`.
///
/// # Returns
///
/// - `Ok(String)`: The name of the active keyboard layout as a valid UTF-8 string.
/// - `Err(())`: Indicates that the function failed to retrieve the locale name or encountered
/// invalid UTF-16 characters during conversion.
fn get_active_keyboard_layout() -> Result<String, ()> {
let foreground_window_tid = unsafe { GetWindowThreadProcessId(GetForegroundWindow(), None) };
let lcid = unsafe { GetKeyboardLayout(foreground_window_tid) };
// Extract the low word (language identifier) from the keyboard layout handle.
let lang_id = (lcid.0 as u32) & 0xFFFF;
let mut locale_name_buffer = [0; LOCALE_NAME_MAX_LENGTH as usize];
let char_count = unsafe {
LCIDToLocaleName(
lang_id,
Some(&mut locale_name_buffer),
LOCALE_ALLOW_NEUTRAL_NAMES,
)
};
match char_count {
0 => Err(()),
_ => String::from_utf16(&locale_name_buffer[..char_count as usize]).map_err(|_| ()),
}
}
/// Retrieves the name of the active keyboard layout or a fallback error message.
///
/// # Behavior
///
/// - **Success Case**:
/// If [`get_active_keyboard_layout`] succeeds, this function returns the retrieved keyboard
/// layout name as a `String`.
///
/// - **Failure Case**:
/// If [`get_active_keyboard_layout`] fails, this function returns the value of `ERROR_TEXT`
/// as a fallback message. This ensures that the function always returns a valid `String`,
/// even in error scenarios.
///
/// # Returns
///
/// A `String` representing either:
/// - The name of the active keyboard layout, or
/// - The fallback error message (`ERROR_TEXT`) if the layout name cannot be retrieved.
fn get_lang() -> String {
get_active_keyboard_layout()
.map(|l| l.trim_end_matches('\0').to_string())
.unwrap_or_else(|_| ERROR_TEXT.to_string())
}
impl Keyboard {
fn output(&mut self) -> String {
let now = Instant::now();
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
self.last_updated = now;
self.lang_name = get_lang();
}
match self.label_prefix {
LabelPrefix::Text | LabelPrefix::IconAndText => format!("KB: {}", self.lang_name),
LabelPrefix::None | LabelPrefix::Icon => self.lang_name.clone(),
}
}
}
impl BarWidget for Keyboard {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
let mut layout_job = LayoutJob::simple(
match self.label_prefix {
LabelPrefix::Icon | LabelPrefix::IconAndText => {
egui_phosphor::regular::KEYBOARD.to_string()
}
LabelPrefix::None | LabelPrefix::Text => String::new(),
},
config.icon_font_id.clone(),
ctx.style().visuals.selection.stroke.color,
100.0,
);
layout_job.append(
&output,
10.0,
TextFormat {
font_id: config.text_font_id.clone(),
color: ctx.style().visuals.text_color(),
valign: Align::Center,
..Default::default()
},
);
config.apply_on_widget(true, ui, |ui| {
ui.add(Label::new(WidgetText::LayoutJob(layout_job.clone())).selectable(false))
});
}
}
}
}

View File

@@ -1,33 +1,30 @@
use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
use crate::bar::apply_theme;
use crate::config::DisplayFormat;
use crate::config::KomobarTheme;
use crate::config::WorkspacesDisplayFormat;
use crate::komorebi_layout::KomorebiLayout;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::ICON_CACHE;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_INDEX;
use eframe::egui::vec2;
use eframe::egui::Color32;
use eframe::egui::ColorImage;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::Frame;
use eframe::egui::Image;
use eframe::egui::Label;
use eframe::egui::Margin;
use eframe::egui::RichText;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::TextureHandle;
use eframe::egui::TextureOptions;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::vec2;
use image::RgbaImage;
use komorebi_client::Container;
use komorebi_client::NotificationEvent;
@@ -36,13 +33,11 @@ use komorebi_client::Rect;
use komorebi_client::SocketMessage;
use komorebi_client::Window;
use komorebi_client::Workspace;
use komorebi_client::WorkspaceLayer;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::Ordering;
@@ -53,8 +48,6 @@ pub struct KomorebiConfig {
pub workspaces: Option<KomorebiWorkspacesConfig>,
/// Configure the Layout widget
pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Workspace Layer widget
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
/// Configure the Focused Window widget
pub focused_window: Option<KomorebiFocusedWindowConfig>,
/// Configure the Configuration Switcher widget
@@ -68,7 +61,7 @@ pub struct KomorebiWorkspacesConfig {
/// Hide workspaces without any windows
pub hide_empty_workspaces: bool,
/// Display format of the workspace
pub display: Option<WorkspacesDisplayFormat>,
pub display: Option<DisplayFormat>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@@ -81,12 +74,6 @@ pub struct KomorebiLayoutConfig {
pub display: Option<DisplayFormat>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiWorkspaceLayerConfig {
/// Enable the Komorebi Workspace Layer widget
pub enable: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiFocusedWindowConfig {
/// Enable the Komorebi Focused Window widget
@@ -134,12 +121,10 @@ impl From<&KomorebiConfig> for Komorebi {
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
stack_accent: None,
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
monitor_usr_idx_map: HashMap::new(),
})),
workspaces: value.workspaces,
layout: value.layout.clone(),
focused_window: value.focused_window,
workspace_layer: value.workspace_layer,
configuration_switcher,
}
}
@@ -151,7 +136,6 @@ pub struct Komorebi {
pub workspaces: Option<KomorebiWorkspacesConfig>,
pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>,
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
@@ -159,92 +143,84 @@ impl BarWidget for Komorebi {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
let icon_size = Vec2::splat(config.icon_font_id.size);
let text_size = Vec2::splat(config.text_font_id.size);
if let Some(workspaces) = self.workspaces {
if workspaces.enable {
let mut update = None;
if !komorebi_notification_state.workspaces.is_empty() {
let format = workspaces.display.unwrap_or(DisplayFormat::Text.into());
let format = workspaces.display.unwrap_or(DisplayFormat::Text);
config.apply_on_widget(false, ui, |ui| {
for (i, (ws, containers, _)) in
for (i, (ws, container_information)) in
komorebi_notification_state.workspaces.iter().enumerate()
{
let is_selected = komorebi_notification_state.selected_workspace.eq(ws);
if SelectableFrame::new(
is_selected,
komorebi_notification_state.selected_workspace.eq(ws),
)
.show(ui, |ui| {
let mut has_icon = false;
if format == WorkspacesDisplayFormat::AllIcons
|| format == WorkspacesDisplayFormat::AllIconsAndText
|| format == WorkspacesDisplayFormat::AllIconsAndTextOnSelected
|| format == DisplayFormat::Icon.into()
|| format == DisplayFormat::IconAndText.into()
|| format == DisplayFormat::IconAndTextOnSelected.into()
|| (format == DisplayFormat::TextAndIconOnSelected.into() && is_selected)
if format == DisplayFormat::Icon
|| format == DisplayFormat::IconAndText
|| format == DisplayFormat::IconAndTextOnSelected
|| (format == DisplayFormat::TextAndIconOnSelected
&& komorebi_notification_state.selected_workspace.eq(ws))
{
has_icon = containers.iter().any(|(_, container_info)| {
container_info.icons.iter().any(|icon| icon.is_some())
});
let icons: Vec<_> =
container_information.icons.iter().flatten().collect();
if has_icon {
Frame::NONE
if !icons.is_empty() {
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y as i8,
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
for (is_focused, container) in containers {
for icon in container.icons.iter().flatten().collect::<Vec<_>>() {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(if *is_focused { icon_size } else { text_size }),
);
for icon in icons {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(icon_size),
);
if !has_icon {
has_icon = true;
}
}
});
}
}
// draw a custom icon when there is no app icon or text
if !has_icon && (matches!(format, WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon))
|| (!is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))) {
// draw a custom icon when there is no app icon
if match format {
DisplayFormat::Icon => !has_icon,
_ => false,
} {
let (response, painter) =
ui.allocate_painter(icon_size, Sense::hover());
let stroke = Stroke::new(
1.0,
if is_selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() },
ctx.style().visuals.selection.stroke.color,
);
let mut rect = response.rect;
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
painter.rect_stroke(rect, rounding, stroke);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
response.on_hover_text(ws.to_string())
// add hover text when there are only icons
} else if match format {
WorkspacesDisplayFormat::AllIcons | WorkspacesDisplayFormat::Existing(DisplayFormat::Icon) => has_icon,
DisplayFormat::Icon => has_icon,
_ => false,
} {
ui.response().on_hover_text(ws.to_string())
// add label only
} else if (format != WorkspacesDisplayFormat::AllIconsAndTextOnSelected && format != DisplayFormat::IconAndTextOnSelected.into())
|| (is_selected && matches!(format, WorkspacesDisplayFormat::AllIconsAndTextOnSelected | WorkspacesDisplayFormat::Existing(DisplayFormat::IconAndTextOnSelected)))
} else if format != DisplayFormat::IconAndTextOnSelected
|| (format == DisplayFormat::IconAndTextOnSelected
&& komorebi_notification_state.selected_workspace.eq(ws))
{
if is_selected {
ui.add(Label::new(RichText::new(ws.to_string()).color(ctx.style().visuals.selection.stroke.color)).selectable(false))
}
else {
ui.add(Label::new(ws.to_string()).selectable(false))
}
ui.add(Label::new(ws.to_string()).selectable(false))
} else {
ui.response()
}
@@ -303,42 +279,6 @@ impl BarWidget for Komorebi {
}
}
if let Some(layer_config) = &self.workspace_layer {
if layer_config.enable {
let layer = komorebi_notification_state
.workspaces
.iter()
.find(|o| komorebi_notification_state.selected_workspace.eq(&o.0))
.map(|(_, _, layer)| layer);
if let Some(layer) = layer {
let name = layer.to_string();
config.apply_on_widget(false, ui, |ui| {
if SelectableFrame::new(false)
.show(ui, |ui| ui.add(Label::new(name).selectable(false)))
.clicked()
&& komorebi_client::send_batch([
SocketMessage::MouseFollowsFocus(false),
SocketMessage::ToggleWorkspaceLayer,
SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
),
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n\
MouseFollowsFocus(false),
ToggleWorkspaceLayer,
MouseFollowsFocus({})",
komorebi_notification_state.mouse_follows_focus,
);
}
});
}
}
}
if let Some(layout_config) = &self.layout {
if layout_config.enable {
let workspace_idx: Option<usize> = komorebi_notification_state
@@ -434,7 +374,6 @@ impl BarWidget for Komorebi {
for (i, (title, icon)) in iter.enumerate() {
let selected = i == focused_window_idx && len != 1;
let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() };
if SelectableFrame::new(selected)
.show(ui, |ui| {
@@ -454,9 +393,9 @@ impl BarWidget for Komorebi {
&& i == focused_window_idx)
{
if let Some(img) = icon {
Frame::NONE
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y as i8,
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
let response = ui.add(
@@ -486,7 +425,7 @@ impl BarWidget for Komorebi {
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
available_height,
),
Label::new(RichText::new( title).color(text_color)).selectable(false).truncate(),
Label::new(title).selectable(false).truncate(),
);
}
})
@@ -533,14 +472,9 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
ctx.load_texture("icon", color_image, TextureOptions::default())
}
#[allow(clippy::type_complexity)]
#[derive(Clone, Debug)]
pub struct KomorebiNotificationState {
pub workspaces: Vec<(
String,
Vec<(bool, KomorebiNotificationStateContainerInformation)>,
WorkspaceLayer,
)>,
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
pub selected_workspace: String,
pub focused_container_information: KomorebiNotificationStateContainerInformation,
pub layout: KomorebiLayout,
@@ -549,7 +483,6 @@ pub struct KomorebiNotificationState {
pub work_area_offset: Option<Rect>,
pub stack_accent: Option<Color32>,
pub monitor_index: usize,
pub monitor_usr_idx_map: HashMap<usize, usize>,
}
impl KomorebiNotificationState {
@@ -561,7 +494,7 @@ impl KomorebiNotificationState {
pub fn handle_notification(
&mut self,
ctx: &Context,
monitor_index: Option<usize>,
monitor_index: usize,
notification: komorebi_client::Notification,
bg_color: Rc<RefCell<Color32>>,
bg_color_with_alpha: Rc<RefCell<Color32>>,
@@ -570,8 +503,6 @@ impl KomorebiNotificationState {
default_theme: Option<KomobarTheme>,
render_config: Rc<RefCell<RenderConfig>>,
) {
let show_all_icons = render_config.borrow().show_all_icons;
match notification.event {
NotificationEvent::WindowManager(_) => {}
NotificationEvent::Monitor(_) => {}
@@ -599,13 +530,9 @@ impl KomorebiNotificationState {
grouping,
render_config,
);
tracing::info!(
"removed theme from updated komorebi.json and applied default theme"
);
tracing::info!("removed theme from updated komorebi.json and applied default theme");
} else {
tracing::warn!(
"theme was removed from updated komorebi.json but there was no default theme to apply"
);
tracing::warn!("theme was removed from updated komorebi.json but there was no default theme to apply");
}
}
}
@@ -625,16 +552,6 @@ impl KomorebiNotificationState {
},
}
self.monitor_usr_idx_map = notification.state.monitor_usr_idx_map.clone();
if monitor_index.is_none()
|| monitor_index.is_some_and(|idx| idx >= notification.state.monitors.elements().len())
{
// The bar's monitor is diconnected, so the bar is disabled no need to check anything
// any further otherwise we'll get `OutOfBounds` panics.
return;
}
let monitor_index = monitor_index.expect("should have a monitor index");
self.monitor_index = monitor_index;
self.mouse_follows_focus = notification.state.mouse_follows_focus;
@@ -646,7 +563,6 @@ impl KomorebiNotificationState {
let focused_workspace_idx = monitor.focused_workspace_idx();
let mut workspaces = vec![];
self.selected_workspace = monitor.workspaces()[focused_workspace_idx]
.name()
.to_owned()
@@ -654,7 +570,11 @@ impl KomorebiNotificationState {
for (i, ws) in monitor.workspaces().iter().enumerate() {
let should_show = if self.hide_empty_workspaces {
focused_workspace_idx == i || !ws.is_empty()
focused_workspace_idx == i
|| !ws.containers().is_empty()
|| !ws.floating_windows().is_empty()
|| ws.monocle_container().is_some()
|| ws.maximized_window().is_some()
} else {
true
};
@@ -662,37 +582,7 @@ impl KomorebiNotificationState {
if should_show {
workspaces.push((
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
if show_all_icons {
let mut containers = vec![];
let mut has_monocle = false;
// add monocle container
if let Some(container) = ws.monocle_container() {
containers.push((true, container.into()));
has_monocle = true;
}
// add all tiled windows
for (i, container) in ws.containers().iter().enumerate() {
containers.push((
!has_monocle && i == ws.focused_container_idx(),
container.into(),
));
}
// add all floating windows
for floating_window in ws.floating_windows() {
containers.push((
!has_monocle && floating_window.is_focused(),
floating_window.into(),
));
}
containers
} else {
vec![(true, ws.into())]
},
ws.layer().to_owned(),
ws.into(),
));
}
}

View File

@@ -2,23 +2,22 @@ use crate::config::DisplayFormat;
use crate::komorebi::KomorebiLayoutConfig;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use eframe::egui::vec2;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Label;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::StrokeKind;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::vec2;
use komorebi_client::SocketMessage;
use schemars::JsonSchema;
use serde::de::Error;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::de::Error;
use serde_json::from_str;
use std::fmt::Display;
use std::fmt::Formatter;
@@ -123,22 +122,18 @@ impl KomorebiLayout {
}
}
fn show_icon(&mut self, is_selected: bool, font_id: FontId, ctx: &Context, ui: &mut Ui) {
fn show_icon(&mut self, font_id: FontId, ctx: &Context, ui: &mut Ui) {
// paint custom icons for the layout
let size = Vec2::splat(font_id.size);
let (response, painter) = ui.allocate_painter(size, Sense::hover());
let color = if is_selected {
ctx.style().visuals.selection.stroke.color
} else {
ui.style().visuals.text_color()
};
let color = ctx.style().visuals.selection.stroke.color;
let stroke = Stroke::new(1.0, color);
let mut rect = response.rect;
let rounding = CornerRadius::same((rect.width() * 0.1) as u8);
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);
painter.rect_stroke(rect, rounding, stroke);
match self {
KomorebiLayout::Default(layout) => match layout {
@@ -194,7 +189,7 @@ impl KomorebiLayout {
rect.width() * 0.35 + stroke.width,
));
painter.rect_filled(rect_left, rounding, color);
painter.rect_stroke(rect_right, rounding, stroke, StrokeKind::Outside);
painter.rect_stroke(rect_right, rounding, stroke);
}
KomorebiLayout::Paused => {
let mut rect_left = response.rect;
@@ -241,7 +236,7 @@ impl KomorebiLayout {
let layout_frame = SelectableFrame::new(false)
.show(ui, |ui| {
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
self.show_icon(false, font_id.clone(), ctx, ui);
self.show_icon(font_id.clone(), ctx, ui);
}
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
@@ -256,7 +251,7 @@ impl KomorebiLayout {
if show_options {
if let Some(workspace_idx) = workspace_idx {
Frame::NONE.show(ui, |ui| {
Frame::none().show(ui, |ui| {
ui.add(
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
.selectable(false),
@@ -284,12 +279,8 @@ impl KomorebiLayout {
]);
for layout_option in &mut layout_options {
let is_selected = self == layout_option;
if SelectableFrame::new(is_selected)
.show(ui, |ui| {
layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)
})
if SelectableFrame::new(self == layout_option)
.show(ui, |ui| layout_option.show_icon(font_id.clone(), ctx, ui))
.on_hover_text(match layout_option {
KomorebiLayout::Default(layout) => layout.to_string(),
KomorebiLayout::Monocle => "Toggle monocle".to_string(),

View File

@@ -3,7 +3,6 @@ mod battery;
mod config;
mod cpu;
mod date;
mod keyboard;
mod komorebi;
mod komorebi_layout;
mod media;
@@ -30,27 +29,28 @@ use hotwatch::Hotwatch;
use image::RgbaImage;
use komorebi_client::SocketMessage;
use komorebi_client::SubscribeOptions;
use schemars::r#gen::SchemaSettings;
use schemars::gen::SchemaSettings;
use std::collections::HashMap;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::LazyLock;
use std::sync::Mutex;
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::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
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;
use windows_core::BOOL;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
@@ -132,8 +132,8 @@ fn main() -> color_eyre::Result<()> {
s.inline_subschemas = true;
});
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<KomobarConfig>();
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}");
@@ -149,15 +149,13 @@ fn main() -> color_eyre::Result<()> {
}
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
std::env::set_var("RUST_LIB_BACKTRACE", "1");
}
color_eyre::install()?;
if std::env::var("RUST_LOG").is_err() {
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LOG", "info") };
std::env::set_var("RUST_LOG", "info");
}
tracing::subscriber::set_global_default(
@@ -239,16 +237,12 @@ fn main() -> color_eyre::Result<()> {
&SocketMessage::State,
)?)?;
let (usr_monitor_index, work_area_offset) = match &config.monitor {
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),
};
let monitor_index = state
.monitor_usr_idx_map
.get(&usr_monitor_index)
.map_or(usr_monitor_index, |i| *i);
MONITOR_RIGHT.store(
state.monitors.elements()[monitor_index].size().right,
@@ -339,6 +333,7 @@ fn main() -> color_eyre::Result<()> {
tracing::info!("watching configuration file for changes");
let config_arc = Arc::new(config);
eframe::run_native(
"komorebi-bar",
native_options,
@@ -351,7 +346,7 @@ fn main() -> color_eyre::Result<()> {
let ctx_komorebi = cc.egui_ctx.clone();
std::thread::spawn(move || {
let subscriber_name = format!("komorebi-bar-{}", random_word::r#gen(random_word::Lang::En));
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,
@@ -426,7 +421,7 @@ fn main() -> color_eyre::Result<()> {
}
});
Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config)))
Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config_arc)))
}),
)
.map_err(|error| color_eyre::eyre::Error::msg(error.to_string()))

View File

@@ -1,15 +1,15 @@
use crate::MAX_LABEL_WIDTH;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use num_derive::FromPrimitive;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -148,7 +148,7 @@ impl Network {
LabelPrefix::None | LabelPrefix::Icon => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"{: >width$}/s ",
"{: >width$}/s | ",
reading.received_text,
width = self.network_activity_fill_characters
),
@@ -159,14 +159,14 @@ impl Network {
),
),
NetworkReadingFormat::Total => (
format!("{} ", reading.received_text),
format!("{} | ", reading.received_text),
reading.transmitted_text,
),
},
LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {
NetworkReadingFormat::Speed => (
format!(
"DOWN: {: >width$}/s ",
"DOWN: {: >width$}/s | ",
reading.received_text,
width = self.network_activity_fill_characters
),
@@ -177,7 +177,7 @@ impl Network {
),
),
NetworkReadingFormat::Total => (
format!("\u{2211}DOWN: {}/s ", reading.received_text),
format!("\u{2211}DOWN: {}/s | ", reading.received_text),
format!("\u{2211}UP: {}/s", reading.transmitted_text),
),
},

View File

@@ -3,20 +3,21 @@ use crate::config::KomobarConfig;
use crate::config::MonitorConfigOrIndex;
use eframe::egui::Color32;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::InnerResponse;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::Shadow;
use eframe::egui::TextStyle;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
@@ -53,8 +54,6 @@ pub struct RenderConfig {
pub text_font_id: FontId,
/// FontId for icon (based on scaling the text font id)
pub icon_font_id: FontId,
/// Show all icons on the workspace section of the Komorebi widget
pub show_all_icons: bool,
}
pub trait RenderExt {
@@ -88,15 +87,6 @@ impl RenderExt for &KomobarConfig {
MonitorConfigOrIndex::Index(idx) => *idx,
};
// check if any of the alignments have a komorebi widget with the workspace set to show all icons
let show_all_icons =
KomobarConfig::show_all_icons_on_komorebi_workspace(&self.left_widgets)
|| self
.center_widgets
.as_ref()
.is_some_and(|list| KomobarConfig::show_all_icons_on_komorebi_workspace(list))
|| KomobarConfig::show_all_icons_on_komorebi_workspace(&self.right_widgets);
RenderConfig {
monitor_idx,
spacing: self.widget_spacing.unwrap_or(10.0),
@@ -107,7 +97,6 @@ impl RenderExt for &KomobarConfig {
applied_on_widget: false,
text_font_id,
icon_font_id,
show_all_icons,
}
}
}
@@ -132,7 +121,6 @@ impl RenderConfig {
applied_on_widget: false,
text_font_id: FontId::default(),
icon_font_id: FontId::default(),
show_all_icons: false,
}
}
@@ -147,10 +135,10 @@ impl RenderConfig {
return self.define_group_frame(
//TODO: this outer margin can be a config
Some(Margin {
left: 10,
right: 10,
top: 6,
bottom: 6,
left: 10.0,
right: 10.0,
top: 6.0,
bottom: 6.0,
}),
config,
ui_style,
@@ -203,11 +191,11 @@ impl RenderConfig {
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::NONE
Frame::none()
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(5, 0),
false => Margin::same(0),
true => Margin::symmetric(5.0, 0.0),
false => Margin::same(0.0),
})
.show(ui, add_contents)
}
@@ -232,13 +220,13 @@ impl RenderConfig {
Frame::group(ui_style)
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(6, 1),
false => Margin::symmetric(1, 1),
true => Margin::symmetric(6.0, 1.0),
false => Margin::symmetric(1.0, 1.0),
})
.stroke(ui_style.visuals.widgets.noninteractive.bg_stroke)
.corner_radius(match config.rounding {
.rounding(match config.rounding {
Some(rounding) => rounding.into(),
None => ui_style.visuals.widgets.noninteractive.corner_radius,
None => ui_style.visuals.widgets.noninteractive.rounding,
})
.fill(
self.background_color
@@ -249,27 +237,27 @@ impl RenderConfig {
// new styles can be added if needed here
GroupingStyle::Default => Shadow::NONE,
GroupingStyle::DefaultWithShadowB4O1S3 => Shadow {
blur: 4,
offset: [1, 1],
spread: 3,
blur: 4.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithShadowB4O0S3 => Shadow {
blur: 4,
offset: [0, 0],
spread: 3,
blur: 4.0,
offset: Vec2::new(0.0, 0.0),
spread: 3.0,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithShadowB0O1S3 => Shadow {
blur: 0,
offset: [1, 1],
spread: 3,
blur: 0.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB3O1S2 => Shadow {
blur: 3,
offset: [1, 1],
spread: 2,
blur: 3.0,
offset: Vec2::new(1.0, 1.0),
spread: 2.0,
color: ui_style
.visuals
.selection
@@ -278,9 +266,9 @@ impl RenderConfig {
.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB3O0S2 => Shadow {
blur: 3,
offset: [0, 0],
spread: 2,
blur: 3.0,
offset: Vec2::new(0.0, 0.0),
spread: 2.0,
color: ui_style
.visuals
.selection
@@ -289,9 +277,9 @@ impl RenderConfig {
.try_apply_alpha(config.transparency_alpha),
},
GroupingStyle::DefaultWithGlowB0O1S2 => Shadow {
blur: 0,
offset: [1, 1],
spread: 2,
blur: 0.0,
offset: Vec2::new(1.0, 1.0),
spread: 2.0,
color: ui_style
.visuals
.selection
@@ -307,9 +295,9 @@ impl RenderConfig {
fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {
let spacing = if self.applied_on_widget {
// Remove the default item spacing from the margin
(self.spacing - ui.spacing().item_spacing.x) as i8
self.spacing - ui.spacing().item_spacing.x
} else {
0
0.0
};
if !self.applied_on_widget {
@@ -321,20 +309,20 @@ impl RenderConfig {
Some(align) => match align {
Alignment::Left => spacing,
Alignment::Center => spacing,
Alignment::Right => 0,
Alignment::Right => 0.0,
},
None => 0,
None => 0.0,
},
right: match self.alignment {
Some(align) => match align {
Alignment::Left => 0,
Alignment::Center => 0,
Alignment::Left => 0.0,
Alignment::Center => 0.0,
Alignment::Right => spacing,
},
None => 0,
None => 0.0,
},
top: 0,
bottom: 0,
top: 0.0,
bottom: 0.0,
}
}
}
@@ -378,19 +366,16 @@ pub enum RoundingConfig {
Individual([f32; 4]),
}
impl From<RoundingConfig> for CornerRadius {
impl From<RoundingConfig> for Rounding {
fn from(value: RoundingConfig) -> Self {
match value {
RoundingConfig::Same(value) => Self::same(value as u8),
RoundingConfig::Individual(values) => {
let values = values.map(|f| f as u8);
Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
}
}
RoundingConfig::Same(value) => Rounding::same(value),
RoundingConfig::Individual(values) => Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
},
}
}
}

View File

@@ -18,14 +18,14 @@ impl SelectableFrame {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
let Self { selected } = self;
Frame::NONE
Frame::none()
.show(ui, |ui| {
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
if ui.is_rect_visible(response.rect) {
let inner_margin = Margin::symmetric(
ui.style().spacing.button_padding.x as i8,
ui.style().spacing.button_padding.y as i8,
ui.style().spacing.button_padding.x,
ui.style().spacing.button_padding.y,
);
if selected
@@ -35,14 +35,14 @@ impl SelectableFrame {
{
let visuals = ui.style().interact_selectable(&response, selected);
Frame::NONE
Frame::none()
.stroke(visuals.bg_stroke)
.corner_radius(visuals.corner_radius)
.rounding(visuals.rounding)
.fill(visuals.bg_fill)
.inner_margin(inner_margin)
.show(ui, add_contents);
} else {
Frame::NONE
Frame::none()
.inner_margin(inner_margin)
.show(ui, add_contents);
}

View File

@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -3,17 +3,16 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::CornerRadius;
use eframe::egui::Label;
use eframe::egui::Rounding;
use eframe::egui::Sense;
use eframe::egui::Stroke;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use eframe::egui::text::LayoutJob;
use eframe::epaint::StrokeKind;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -159,14 +158,14 @@ impl Time {
let color = ctx.style().visuals.text_color();
let stroke = Stroke::new(1.0, color);
let round_all = CornerRadius::same((response.rect.width() * 0.1) as u8);
let round_top = CornerRadius {
let round_all = Rounding::same(response.rect.width() * 0.1);
let round_top = Rounding {
nw: round_all.nw,
ne: round_all.ne,
..Default::default()
};
let round_none = CornerRadius::ZERO;
let round_bottom = CornerRadius {
let round_none = Rounding::ZERO;
let round_bottom = Rounding {
sw: round_all.nw,
se: round_all.ne,
..Default::default()
@@ -176,19 +175,14 @@ impl Time {
let mut rect = response.rect.shrink(stroke.width);
rect.set_height(rect.height() - height * 2.0);
rect = rect.translate(Vec2::new(0.0, height * 2.0));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
painter.rect_stroke(rect, round_all, stroke);
} else if max_power == 3 {
let mut rect = response.rect.shrink(stroke.width);
rect.set_height(rect.height() - height);
rect = rect.translate(Vec2::new(0.0, height));
painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);
painter.rect_stroke(rect, round_all, stroke);
} else {
painter.rect_stroke(
response.rect.shrink(stroke.width),
round_all,
stroke,
StrokeKind::Outside,
);
painter.rect_stroke(response.rect.shrink(stroke.width), round_all, stroke);
}
let mut rect_bin = response.rect;

View File

@@ -2,12 +2,12 @@ use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::selected_frame::SelectableFrame;
use crate::widget::BarWidget;
use eframe::egui::text::LayoutJob;
use eframe::egui::Align;
use eframe::egui::Context;
use eframe::egui::Label;
use eframe::egui::TextFormat;
use eframe::egui::Ui;
use eframe::egui::text::LayoutJob;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -4,8 +4,6 @@ use crate::cpu::Cpu;
use crate::cpu::CpuConfig;
use crate::date::Date;
use crate::date::DateConfig;
use crate::keyboard::Keyboard;
use crate::keyboard::KeyboardConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiConfig;
use crate::media::Media;
@@ -36,7 +34,6 @@ pub enum WidgetConfig {
Battery(BatteryConfig),
Cpu(CpuConfig),
Date(DateConfig),
Keyboard(KeyboardConfig),
Komorebi(KomorebiConfig),
Media(MediaConfig),
Memory(MemoryConfig),
@@ -52,7 +49,6 @@ impl WidgetConfig {
WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),
WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),
WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),
WidgetConfig::Keyboard(config) => Box::new(Keyboard::from(*config)),
WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),
WidgetConfig::Media(config) => Box::new(Media::from(*config)),
WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),
@@ -68,7 +64,6 @@ impl WidgetConfig {
WidgetConfig::Battery(config) => config.enable,
WidgetConfig::Cpu(config) => config.enable,
WidgetConfig::Date(config) => config.enable,
WidgetConfig::Keyboard(config) => config.enable,
WidgetConfig::Komorebi(config) => {
config.workspaces.as_ref().is_some_and(|w| w.enable)
|| config.layout.as_ref().is_some_and(|w| w.enable)

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-client"
version = "0.1.35"
edition = "2024"
version = "0.1.34"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,46 +1,20 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc)]
pub use komorebi::AnimationsConfig;
pub use komorebi::AspectRatio;
pub use komorebi::BorderColours;
pub use komorebi::CrossBoundaryBehaviour;
pub use komorebi::GlobalState;
pub use komorebi::KomorebiTheme;
pub use komorebi::MonitorConfig;
pub use komorebi::Notification;
pub use komorebi::NotificationEvent;
pub use komorebi::PredefinedAspectRatio;
pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
pub use komorebi::WindowContainerBehaviour;
pub use komorebi::WindowsApi;
pub use komorebi::WorkspaceConfig;
pub use komorebi::animation::PerAnimationPrefixConfig;
pub use komorebi::animation::prefix::AnimationPrefix;
pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration;
pub use komorebi::config_generation::IdWithIdentifier;
pub use komorebi::config_generation::IdWithIdentifierAndComment;
pub use komorebi::config_generation::MatchingRule;
pub use komorebi::config_generation::MatchingStrategy;
pub use komorebi::container::Container;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
pub use komorebi::core::resolve_home_path;
pub use komorebi::core::AnimationStyle;
pub use komorebi::core::ApplicationIdentifier;
pub use komorebi::core::Arrangement;
pub use komorebi::core::Axis;
pub use komorebi::core::BorderImplementation;
pub use komorebi::core::BorderStyle;
pub use komorebi::core::Column;
pub use komorebi::core::ColumnSplit;
pub use komorebi::core::ColumnSplitWithCapacity;
pub use komorebi::core::ColumnWidth;
pub use komorebi::core::CustomLayout;
pub use komorebi::core::CycleDirection;
pub use komorebi::core::DefaultLayout;
@@ -59,16 +33,23 @@ pub use komorebi::core::StackbarLabel;
pub use komorebi::core::StackbarMode;
pub use komorebi::core::StateQuery;
pub use komorebi::core::WindowKind;
pub use komorebi::core::config_generation::ApplicationConfigurationGenerator;
pub use komorebi::core::resolve_home_path;
pub use komorebi::monitor::Monitor;
pub use komorebi::monitor_reconciliator::MonitorNotification;
pub use komorebi::ring::Ring;
pub use komorebi::window::Window;
pub use komorebi::window_manager_event::WindowManagerEvent;
pub use komorebi::workspace::Workspace;
pub use komorebi::workspace::WorkspaceGlobals;
pub use komorebi::workspace::WorkspaceLayer;
pub use komorebi::BorderColours;
pub use komorebi::GlobalState;
pub use komorebi::KomorebiTheme;
pub use komorebi::Notification;
pub use komorebi::NotificationEvent;
pub use komorebi::RuleDebug;
pub use komorebi::StackbarConfig;
pub use komorebi::State;
pub use komorebi::StaticConfig;
pub use komorebi::SubscribeOptions;
pub use komorebi::TabsConfig;
use komorebi::DATA_DIR;

View File

@@ -1,7 +1,7 @@
[package]
name = "komorebi-gui"
version = "0.1.35"
edition = "2024"
version = "0.1.34"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -10,7 +10,6 @@ komorebi-client = { path = "../komorebi-client" }
eframe = { workspace = true }
egui_extras = { workspace = true }
random_word = { version = "0.4", features = ["en"] }
random_word = { version = "0.4.3", features = ["en"] }
serde_json = { workspace = true }
windows-core = { workspace = true }
windows = { workspace = true }

View File

@@ -1,9 +1,9 @@
#![warn(clippy::all)]
use eframe::egui;
use eframe::egui::color_picker::Alpha;
use eframe::egui::Color32;
use eframe::egui::ViewportBuilder;
use eframe::egui::color_picker::Alpha;
use komorebi_client::BorderStyle;
use komorebi_client::Colour;
use komorebi_client::DefaultLayout;
@@ -101,7 +101,7 @@ impl From<&komorebi_client::Workspace> for WorkspaceConfig {
let name = value
.name()
.to_owned()
.unwrap_or_else(|| random_word::r#gen(random_word::Lang::En).to_string());
.unwrap_or_else(|| random_word::gen(random_word::Lang::En).to_string());
Self {
layout,
@@ -215,7 +215,7 @@ impl KomorebiGui {
extern "system" fn enum_window(
hwnd: windows::Win32::Foundation::HWND,
lparam: windows::Win32::Foundation::LPARAM,
) -> windows_core::BOOL {
) -> windows::Win32::Foundation::BOOL {
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
let window = Window::from(hwnd.0 as isize);
@@ -248,11 +248,10 @@ impl eframe::App for KomorebiGui {
ui.collapsing("Window Rules", |ui| {
let window = Window::from(self.debug_hwnd);
let label = match (window.title(), window.exe()) {
(Ok(title), Ok(exe)) => {
format!("{title} ({exe})")
}
_ => String::from("Select a Window"),
let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {
format!("{title} ({exe})")
} else {
String::from("Select a Window")
};
if ui.button("Refresh Windows").clicked() {

View File

@@ -1,14 +1,14 @@
[package]
name = "komorebi-themes"
version = "0.1.35"
edition = "2024"
version = "0.1.34"
edition = "2021"
[dependencies]
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "96f26c88d83781f234d42222293ec73d23a39ad8" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "bdaff30959512c4f7ee7304117076a48633d777f", default-features = false, features = ["egui31"] }
base16-egui-themes = { git = "https://github.com/LGUG2Z/base16-egui-themes", rev = "24362c4" }
catppuccin-egui = { git = "https://github.com/LGUG2Z/catppuccin-egui", rev = "f85cc3c", default-features = false, features = ["egui30"] }
#catppuccin-egui = { version = "5", default-features = false, features = ["egui30"] }
eframe = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_variant = "0.1"
strum = { workspace = true }
strum = "0.26"

View File

@@ -4,7 +4,6 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::IntoEnumIterator;
pub use base16_egui_themes::Base16;
@@ -12,7 +11,7 @@ pub use catppuccin_egui;
pub use eframe::egui::Color32;
use serde_variant::to_variant_name;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type")]
pub enum Theme {
/// A theme from catppuccin-egui
@@ -49,7 +48,7 @@ impl Theme {
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub enum Base16Value {
Base00,
Base01,
@@ -93,7 +92,7 @@ impl Base16Value {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum Catppuccin {
Frappe,
Latte,
@@ -118,7 +117,7 @@ impl From<Catppuccin> for catppuccin_egui::Theme {
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub enum CatppuccinValue {
Rosewater,
Flamingo,

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebi"
version = "0.1.35"
version = "0.1.34"
description = "A tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -25,7 +25,7 @@ lazy_static = { workspace = true }
miow = "0.6"
nanoid = "0.4"
net2 = "0.2"
os_info = "3.10"
os_info = "3.8"
parking_lot = "0.12"
paste = { workspace = true }
regex = "1"
@@ -34,7 +34,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
shadow-rs = { workspace = true }
strum = { workspace = true }
strum = { version = "0.26", features = ["derive"] }
sysinfo = { workspace = true }
tracing = { workspace = true }
tracing-appender = { workspace = true }
@@ -44,7 +44,6 @@ which = { workspace = true }
win32-display-data = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }
windows-numerics = { workspace = true }
windows-implement = { workspace = true }
windows-interface = { workspace = true }
winput = "0.2"

View File

@@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use super::prefix::AnimationPrefix;

View File

@@ -8,10 +8,10 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use super::RenderDispatcher;
use super::ANIMATION_DURATION_GLOBAL;
use super::ANIMATION_FPS;
use super::ANIMATION_MANAGER;
use super::RenderDispatcher;
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct AnimationEngine;

View File

@@ -1,5 +1,5 @@
use crate::AnimationStyle;
use crate::core::Rect;
use crate::AnimationStyle;
use super::style::apply_ease_func;

View File

@@ -4,9 +4,9 @@ use crate::core::animation::AnimationStyle;
use lazy_static::lazy_static;
use prefix::AnimationPrefix;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use parking_lot::Mutex;
@@ -22,7 +22,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum PerAnimationPrefixConfig<T> {
Prefix(HashMap<AnimationPrefix, T>),

View File

@@ -1,34 +1,39 @@
use crate::WINDOWS_11;
use crate::WindowsApi;
use crate::border_manager::window_kind_colour;
use crate::border_manager::WindowKind;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUS_STATE;
use crate::border_manager::RENDER_TARGETS;
use crate::border_manager::RenderTarget;
use crate::border_manager::STYLE;
use crate::border_manager::WindowKind;
use crate::border_manager::window_kind_colour;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::windows_api;
use crate::WindowsApi;
use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::LazyLock;
use std::sync::OnceLock;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Foundation::Numerics::Matrix3x2;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::TRUE;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
@@ -37,58 +42,39 @@ use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;
use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;
use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
use windows::Win32::Graphics::Gdi::CreateRectRgn;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;
use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;
use windows::Win32::UI::WindowsAndMessaging::WM_CREATE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::BOOL;
use windows_core::PCWSTR;
use windows_numerics::Matrix3x2;
pub struct RenderFactory(ID2D1Factory);
unsafe impl Sync for RenderFactory {}
unsafe impl Send for RenderFactory {}
impl Deref for RenderFactory {
type Target = ID2D1Factory;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(clippy::expect_used)]
static RENDER_FACTORY: LazyLock<RenderFactory> = unsafe {
static RENDER_FACTORY: LazyLock<ID2D1Factory> = unsafe {
LazyLock::new(|| {
RenderFactory(
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
.expect("creating RENDER_FACTORY failed"),
)
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
.expect("creating RENDER_FACTORY failed")
})
};
@@ -114,7 +100,7 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
#[derive(Debug, Clone)]
pub struct Border {
pub hwnd: isize,
pub render_target: OnceLock<RenderTarget>,
pub render_target: OnceLock<ID2D1HwndRenderTarget>,
pub tracking_hwnd: isize,
pub window_rect: Rect,
pub window_kind: WindowKind,
@@ -194,7 +180,7 @@ impl Border {
loop {
unsafe {
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("border window event processing thread shutdown");
break;
};
@@ -275,11 +261,7 @@ impl Border {
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
if border
.render_target
.set(RenderTarget(render_target.clone()))
.is_err()
{
if border.render_target.set(render_target.clone()).is_err() {
return Err(anyhow!("could not store border render target"));
}
@@ -293,7 +275,7 @@ impl Border {
};
let mut render_targets = RENDER_TARGETS.lock();
render_targets.insert(border.hwnd, RenderTarget(render_target));
render_targets.insert(border.hwnd, render_target);
Ok(border.clone())
},
Err(error) => Err(error.into()),
@@ -318,7 +300,7 @@ impl Border {
// this triggers WM_PAINT in the callback below
pub fn invalidate(&self) {
let _ = unsafe { InvalidateRect(Option::from(self.hwnd()), None, false) };
let _ = unsafe { InvalidateRect(self.hwnd(), None, false) };
}
pub extern "system" fn callback(
@@ -475,11 +457,13 @@ impl Border {
});
// Get window kind and color
(*border_pointer).window_kind = FOCUS_STATE
.lock()
.get(&(window.0 as isize))
.copied()
.unwrap_or(WindowKind::Unfocused);
let window_kind = (*border_pointer).window_kind;
if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {
render_target.BeginDraw();
@@ -524,7 +508,7 @@ impl Border {
}
}
}
let _ = ValidateRect(Option::from(window), None);
let _ = ValidateRect(window, None);
LRESULT(0)
}
WM_DESTROY => {

View File

@@ -1,18 +1,17 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod border;
use crate::Colour;
use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
use crate::core::BorderImplementation;
use crate::core::BorderStyle;
use crate::core::WindowKind;
use crate::ring::Ring;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator::ALT_TAB_HWND;
pub use border::Border;
use crate::Colour;
use crate::Rgb;
use crate::WindowManager;
use crate::WindowsApi;
use border::border_hwnds;
pub use border::Border;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
@@ -22,16 +21,14 @@ use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::ops::Deref;
use std::sync::Arc;
use std::sync::OnceLock;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use strum::Display;
use std::sync::Arc;
use std::sync::OnceLock;
use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;
pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
@@ -59,19 +56,8 @@ lazy_static! {
static ref BORDER_STATE: Mutex<HashMap<String, Border>> = Mutex::new(HashMap::new());
static ref WINDOWS_BORDERS: Mutex<HashMap<isize, Border>> = Mutex::new(HashMap::new());
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
static ref RENDER_TARGETS: Mutex<HashMap<isize, RenderTarget>> = Mutex::new(HashMap::new());
}
#[derive(Debug, Clone)]
pub struct RenderTarget(pub ID2D1HwndRenderTarget);
unsafe impl Send for RenderTarget {}
impl Deref for RenderTarget {
type Target = ID2D1HwndRenderTarget;
fn deref(&self) -> &Self::Target {
&self.0
}
static ref RENDER_TARGETS: Mutex<HashMap<isize, ID2D1HwndRenderTarget>> =
Mutex::new(HashMap::new());
}
pub struct Notification(pub Option<isize>);
@@ -113,7 +99,6 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
borders.clear();
BORDERS_MONITORS.lock().clear();
WINDOWS_BORDERS.lock().clear();
FOCUS_STATE.lock().clear();
RENDER_TARGETS.lock().clear();
@@ -146,15 +131,13 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
@@ -170,7 +153,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let mut previous_pending_move_op = None;
let mut previous_is_paused = false;
let mut previous_notification: Option<Notification> = None;
let mut previous_layer = WorkspaceLayer::default();
'receiver: for notification in receiver {
// Check the wm state every time we receive a notification
@@ -187,9 +169,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
let workspace_layer = *state.monitors.elements()[focused_monitor_idx].workspaces()
[focused_workspace_idx]
.layer();
let foreground_window = WindowsApi::foreground_window().unwrap_or_default();
drop(state);
@@ -232,16 +211,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
.unwrap_or_default()
.set_accent(window_kind_colour(window_kind))?;
}
for window in ws.floating_windows() {
let mut window_kind = WindowKind::Unfocused;
if foreground_window == window.hwnd {
window_kind = WindowKind::Floating;
}
window.set_accent(window_kind_colour(window_kind))?;
}
}
}
}
@@ -282,7 +251,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
.map(|b| b.window_kind == WindowKind::Floating)
.unwrap_or_default())
});
if !should_process_notification && switch_focus_to_from_floating_window {
should_process_notification = true;
}
@@ -303,7 +271,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
let mut windows_borders = WINDOWS_BORDERS.lock();
let mut focus_state = FOCUS_STATE.lock();
// If borders are disabled
if !BORDER_ENABLED.load_consume()
@@ -318,9 +285,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
borders.clear();
borders_monitors.clear();
windows_borders.clear();
focus_state.clear();
previous_is_paused = is_paused;
continue 'receiver;
@@ -331,15 +296,19 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if let Some(ws) = m.focused_workspace() {
// Workspaces with tiling disabled don't have borders
if !ws.tile() {
// Remove all borders on this monitor
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, _| true,
)?;
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
continue 'monitors;
}
@@ -350,17 +319,14 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let border = match borders.entry(monocle.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
match Border::create(
if let Ok(border) = Border::create(
monocle.id(),
monocle.focused_window().copied().unwrap_or_default().hwnd,
) {
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
}
}
};
@@ -371,7 +337,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
WindowKind::Monocle
};
border.window_kind = new_focus_state;
focus_state.insert(border.hwnd, new_focus_state);
{
let mut focus_state = FOCUS_STATE.lock();
focus_state.insert(border.hwnd, new_focus_state);
}
let reference_hwnd =
monocle.focused_window().copied().unwrap_or_default().hwnd;
@@ -391,15 +360,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
let border_hwnd = border.hwnd;
// Remove all borders on this monitor except monocle
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, b| border_hwnd != b.hwnd,
)?;
let mut to_remove = vec![];
for (id, b) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
&& border_hwnd != b.hwnd
{
b.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
continue 'monitors;
}
@@ -411,20 +385,24 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
&& WindowsApi::is_zoomed(foreground_hwnd);
if is_maximized {
// Remove all borders on this monitor
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, _| true,
)?;
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default()
== monitor_idx
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
continue 'monitors;
}
// Collect focused workspace container and floating windows ID's
// Destroy any borders not associated with the focused workspace
let mut container_and_floating_window_ids = ws
.containers()
.iter()
@@ -435,67 +413,34 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
container_and_floating_window_ids.push(w.hwnd.to_string());
}
// Remove any borders not associated with the focused workspace
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|id, _| !container_and_floating_window_ids.contains(id),
)?;
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& !container_and_floating_window_ids.contains(id)
{
border.destroy()?;
to_remove.push(id.clone());
}
}
for id in &to_remove {
borders.remove(id);
}
'containers: for (idx, c) in ws.containers().iter().enumerate() {
// In case this container is a stack we need to check it's
// unfocused windows to remove any attached border
let is_stack = c.windows().len() > 1;
if is_stack {
let focused_window_idx = c.focused_window_idx();
let potential_stacked_border_handles = c
.windows()
.iter()
.enumerate()
.flat_map(|(i, w)| {
if i != focused_window_idx {
windows_borders.get(&w.hwnd).map(|b| b.hwnd)
} else {
None
}
})
.collect::<Vec<_>>();
if !potential_stacked_border_handles.is_empty() {
tracing::debug!(
"purging stacked borders: {:?}",
potential_stacked_border_handles
);
remove_borders(
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
monitor_idx,
|_, b| potential_stacked_border_handles.contains(&b.hwnd),
)?;
}
}
let focused_window_hwnd =
c.focused_window().map(|w| w.hwnd).unwrap_or_default();
// Get the border entry for this container from the map or create one
let mut new_border = false;
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
match Border::create(c.id(), focused_window_hwnd) {
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
if let Ok(border) = Border::create(
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
}
}
};
@@ -505,7 +450,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let new_focus_state = if idx != ws.focused_container_idx()
|| monitor_idx != focused_monitor_idx
|| focused_window_hwnd != foreground_window
|| c.focused_window()
.map(|w| w.hwnd != foreground_window)
.unwrap_or_default()
{
WindowKind::Unfocused
} else if c.windows().len() > 1 {
@@ -515,72 +462,34 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
};
border.window_kind = new_focus_state;
last_focus_state = focus_state.get(&border.hwnd).copied();
// If this container's border was previously tracking a different
// window, then we need to destroy that border and create a new one
// tracking the correct window.
if border.tracking_hwnd != focused_window_hwnd {
// Create new border
match Border::create(
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
Ok(b) => {
// Destroy previously stacked border window and remove its hwnd
// and tracking_hwnd.
border.destroy()?;
focus_state.remove(&border.hwnd);
if let Some(previous) =
windows_borders.get(&border.tracking_hwnd)
{
// Only remove the border from `windows_borders` if it
// still is the same border, if it isn't then it means it
// was already updated by another border for that window
// and in that case we don't want to remove it.
if previous.hwnd == border.hwnd {
windows_borders.remove(&border.tracking_hwnd);
}
}
// Replace with new border
new_border = true;
*border = b;
}
_ => {
continue 'monitors;
}
}
// Update the focused state for all containers on this workspace
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
}
let reference_hwnd =
c.focused_window().copied().unwrap_or_default().hwnd;
// avoid getting into a thread restart loop if we try to look up
// rect info for a window that has been destroyed by the time
// we get here
let rect = match WindowsApi::window_rect(focused_window_hwnd) {
let rect = match WindowsApi::window_rect(reference_hwnd) {
Ok(rect) => rect,
Err(_) => {
remove_border(
c.id(),
&mut borders,
&mut windows_borders,
&mut focus_state,
&mut borders_monitors,
)?;
let _ = border.destroy();
borders.remove(c.id());
continue 'containers;
}
};
let layer_changed = previous_layer != workspace_layer;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => {
(last_focus_state != new_focus_state) || layer_changed
}
Some(last_focus_state) => last_focus_state != new_focus_state,
};
if new_border || should_invalidate {
border.set_position(&rect, focused_window_hwnd)?;
if new_border {
border.set_position(&rect, reference_hwnd)?;
}
if should_invalidate {
@@ -592,7 +501,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
c.focused_window().cloned().unwrap_or_default().hwnd,
border.clone(),
);
focus_state.insert(border.hwnd, new_focus_state);
}
{
@@ -601,15 +509,13 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let border = match borders.entry(window.hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
match Border::create(&window.hwnd.to_string(), window.hwnd)
if let Ok(border) =
Border::create(&window.hwnd.to_string(), window.hwnd)
{
Ok(border) => {
new_border = true;
entry.insert(border)
}
_ => {
continue 'monitors;
}
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
}
}
};
@@ -623,17 +529,17 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
border.window_kind = new_focus_state;
last_focus_state = focus_state.get(&border.hwnd).copied();
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state =
focus_state.insert(border.hwnd, new_focus_state);
}
let rect = WindowsApi::window_rect(window.hwnd)?;
let layer_changed = previous_layer != workspace_layer;
let should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => {
last_focus_state != new_focus_state || layer_changed
}
Some(last_focus_state) => last_focus_state != new_focus_state,
};
if new_border {
@@ -646,7 +552,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
windows_borders.insert(window.hwnd, border.clone());
focus_state.insert(border.hwnd, new_focus_state);
}
}
}
@@ -658,59 +563,12 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
previous_pending_move_op = pending_move_op;
previous_is_paused = is_paused;
previous_notification = Some(notification);
previous_layer = workspace_layer;
}
Ok(())
}
/// Removes all borders from monitor with index `monitor_idx` filtered by
/// `condition`. This condition is a function that will take a reference to
/// the container id and the border and returns a bool, if true that border
/// will be removed.
fn remove_borders(
borders: &mut HashMap<String, Border>,
windows_borders: &mut HashMap<isize, Border>,
focus_state: &mut HashMap<isize, WindowKind>,
borders_monitors: &mut HashMap<String, usize>,
monitor_idx: usize,
condition: impl Fn(&String, &Border) -> bool,
) -> color_eyre::Result<()> {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& condition(id, border)
{
to_remove.push(id.clone());
}
}
for id in &to_remove {
remove_border(id, borders, windows_borders, focus_state, borders_monitors)?;
}
Ok(())
}
/// Removes the border with `id` and all its related info from all maps
fn remove_border(
id: &str,
borders: &mut HashMap<String, Border>,
windows_borders: &mut HashMap<isize, Border>,
focus_state: &mut HashMap<isize, WindowKind>,
borders_monitors: &mut HashMap<String, usize>,
) -> color_eyre::Result<()> {
if let Some(removed_border) = borders.remove(id) {
removed_border.destroy()?;
windows_borders.remove(&removed_border.tracking_hwnd);
focus_state.remove(&removed_border.hwnd);
}
borders_monitors.remove(id);
Ok(())
}
#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
pub enum ZOrder {
Top,
NoTopMost,

View File

@@ -1,14 +1,14 @@
use hex_color::HexColor;
use komorebi_themes::Color32;
use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::gen::SchemaGenerator;
use schemars::schema::InstanceType;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum Colour {
/// Colour represented as RGB
@@ -51,7 +51,7 @@ impl From<Colour> for Color32 {
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Hex(HexColor);
impl JsonSchema for Hex {
@@ -78,7 +78,7 @@ impl From<Colour> for u32 {
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Rgb {
/// Red
pub r: u32,
@@ -120,8 +120,8 @@ impl From<u32> for Rgb {
fn from(value: u32) -> Self {
Self {
r: value & 0xff,
g: (value >> 8) & 0xff,
b: (value >> 16) & 0xff,
g: value >> 8 & 0xff,
b: value >> 16 & 0xff,
}
}
}

View File

@@ -6,18 +6,18 @@
use std::ffi::c_void;
use std::ops::Deref;
use windows::core::IUnknown;
use windows::core::IUnknown_Vtbl;
use windows::core::GUID;
use windows::core::HRESULT;
use windows::core::HSTRING;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::SIZE;
use windows::Win32::UI::Shell::Common::IObjectArray;
use windows::core::GUID;
use windows::core::HRESULT;
use windows::core::HSTRING;
use windows::core::IUnknown;
use windows::core::IUnknown_Vtbl;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows_core::BOOL;
type DesktopID = GUID;
@@ -129,7 +129,7 @@ pub unsafe trait IApplicationView: IUnknown {
pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17
pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;
pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)
-> HRESULT;
-> HRESULT;
/*** IApplicationView methods ***/
pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20

View File

@@ -11,11 +11,11 @@ use interfaces::IServiceProvider;
use std::ffi::c_void;
use windows::Win32::Foundation::HWND;
use windows::Win32::System::Com::CLSCTX_ALL;
use windows::Win32::System::Com::COINIT_MULTITHREADED;
use windows::Win32::System::Com::CoCreateInstance;
use windows::Win32::System::Com::CoInitializeEx;
use windows::Win32::System::Com::CoUninitialize;
use windows::Win32::System::Com::CLSCTX_ALL;
use windows::Win32::System::Com::COINIT_MULTITHREADED;
use windows_core::Interface;
struct ComInit();
@@ -64,7 +64,7 @@ fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplication
})
}
#[unsafe(no_mangle)]
#[no_mangle]
pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {
COM_INIT.with(|_| {
let provider = get_iservice_provider();

View File

@@ -6,16 +6,7 @@ use strum::Display;
use strum::EnumString;
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum AnimationStyle {
Linear,

View File

@@ -7,12 +7,12 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::CustomLayout;
use super::DefaultLayout;
use super::Rect;
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::CustomLayout;
use super::DefaultLayout;
use super::Rect;
pub trait Arrangement {
fn calculate(

View File

@@ -75,7 +75,7 @@ pub struct IdWithIdentifier {
pub matching_strategy: Option<MatchingStrategy>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, JsonSchema)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum MatchingStrategy {
Legacy,
Equals,

View File

@@ -5,9 +5,9 @@ use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Path;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -1,9 +1,9 @@
use super::DefaultLayout;
use super::OperationDirection;
use super::custom_layout::Column;
use super::custom_layout::ColumnSplit;
use super::custom_layout::ColumnSplitWithCapacity;
use super::custom_layout::CustomLayout;
use super::DefaultLayout;
use super::OperationDirection;
pub trait Direction {
fn index_in_direction(

View File

@@ -6,23 +6,19 @@ use std::path::PathBuf;
use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum::Display;
use strum::EnumString;
use crate::KomorebiTheme;
use crate::animation::prefix::AnimationPrefix;
use crate::KomorebiTheme;
pub use animation::AnimationStyle;
pub use arrangement::Arrangement;
pub use arrangement::Axis;
pub use custom_layout::Column;
pub use custom_layout::ColumnSplit;
pub use custom_layout::ColumnSplitWithCapacity;
pub use custom_layout::ColumnWidth;
pub use custom_layout::CustomLayout;
pub use cycle_direction::CycleDirection;
pub use default_layout::DefaultLayout;
@@ -123,7 +119,6 @@ pub enum SocketMessage {
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusMonitorAtCursor,
FocusLastWorkspace,
CloseWorkspace,
FocusWorkspaceNumber(usize),
@@ -149,7 +144,6 @@ pub enum SocketMessage {
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String),
ToggleWorkspaceLayer,
// Configuration
ReloadConfiguration,
ReplaceConfiguration(PathBuf),
@@ -186,7 +180,6 @@ pub enum SocketMessage {
StackbarFontFamily(Option<String>),
WorkAreaOffset(Rect),
MonitorWorkAreaOffset(usize, Rect),
ToggleWindowBasedWorkAreaOffset,
ResizeDelta(i32),
InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),
InitialNamedWorkspaceRule(ApplicationIdentifier, String, String),
@@ -438,16 +431,7 @@ pub enum MoveBehaviour {
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary
@@ -457,16 +441,7 @@ pub enum CrossBoundaryBehaviour {
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
Display,
EnumString,
ValueEnum,
JsonSchema,
PartialEq,
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum HidingBehaviour {
/// Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)

View File

@@ -7,8 +7,8 @@ use serde::Serialize;
use strum::Display;
use strum::EnumString;
use super::Axis;
use super::direction::Direction;
use super::Axis;
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,

View File

@@ -44,15 +44,13 @@ pub fn send_notification(hwnd: isize) {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});

View File

@@ -40,12 +40,12 @@ use std::io::Write;
use std::net::TcpStream;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub use colour::*;
pub use core::*;
@@ -71,8 +71,8 @@ use serde::Deserialize;
use serde::Serialize;
use uds_windows::UnixStream;
use which::which;
use winreg::RegKey;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
@@ -182,7 +182,7 @@ lazy_static! {
static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =
Arc::new(Mutex::new(HidingBehaviour::Cloak));
Arc::new(Mutex::new(HidingBehaviour::Minimize));
pub static ref 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);

View File

@@ -10,32 +10,27 @@
use std::env::temp_dir;
use std::net::Shutdown;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use clap::Parser;
use color_eyre::Result;
use crossbeam_utils::Backoff;
use komorebi::animation::AnimationEngine;
use komorebi::animation::ANIMATION_ENABLED_GLOBAL;
use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;
use komorebi::animation::AnimationEngine;
use parking_lot::Mutex;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use sysinfo::Process;
use sysinfo::ProcessesToUpdate;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use uds_windows::UnixStream;
use komorebi::CUSTOM_FFM;
use komorebi::DATA_DIR;
use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID;
use komorebi::border_manager;
use komorebi::focus_manager;
use komorebi::load_configuration;
@@ -54,20 +49,23 @@ use komorebi::window_manager::WindowManager;
use komorebi::windows_api::WindowsApi;
use komorebi::winevent_listener;
use komorebi::workspace_reconciliator;
use komorebi::CUSTOM_FFM;
use komorebi::DATA_DIR;
use komorebi::HOME_DIR;
use komorebi::INITIAL_CONFIGURATION_LOADED;
use komorebi::SESSION_ID;
shadow_rs::shadow!(build);
fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
std::env::set_var("RUST_LIB_BACKTRACE", "1");
}
color_eyre::install()?;
if std::env::var("RUST_LOG").is_err() {
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RUST_LOG", "info") };
std::env::set_var("RUST_LOG", "info");
}
let appender = tracing_appender::rolling::daily(std::env::temp_dir(), "komorebi_plaintext.log");
@@ -126,22 +124,20 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
#[tracing::instrument]
fn detect_deadlocks() {
// Create a background thread which checks for deadlocks every 10s
std::thread::spawn(move || {
loop {
tracing::info!("running deadlock detector");
std::thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
std::thread::spawn(move || loop {
tracing::info!("running deadlock detector");
std::thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
tracing::error!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
tracing::error!("deadlock #{}", i);
for t in threads {
tracing::error!("thread id: {:#?}", t.thread_id());
tracing::error!("{:#?}", t.backtrace());
}
tracing::error!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
tracing::error!("deadlock #{}", i);
for t in threads {
tracing::error!("thread id: {:#?}", t.thread_id());
tracing::error!("{:#?}", t.backtrace());
}
}
});
@@ -180,7 +176,7 @@ fn main() -> Result<()> {
let session_id = WindowsApi::process_id_to_session_id()?;
SESSION_ID.store(session_id, Ordering::SeqCst);
let mut system = sysinfo::System::new();
let mut system = sysinfo::System::new_all();
system.refresh_processes(ProcessesToUpdate::All, true);
let matched_procs: Vec<&Process> = system.processes_by_name("komorebi.exe".as_ref()).collect();
@@ -196,9 +192,7 @@ fn main() -> Result<()> {
}
if len > 1 {
tracing::error!(
"komorebi.exe is already running, please exit the existing process before starting a new one"
);
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
}
}
@@ -250,10 +244,16 @@ fn main() -> Result<()> {
StaticConfig::postload(config, &wm)?;
}
listen_for_commands(wm.clone());
if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
};
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
if static_config.is_none() {
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
@@ -268,39 +268,27 @@ fn main() -> Result<()> {
let dumped_state = temp_dir().join("komorebi.state.json");
if !opts.clean_state && dumped_state.is_file() {
if let Ok(state) = serde_json::from_str(&std::fs::read_to_string(&dumped_state)?) {
wm.lock().apply_state(state);
} else {
tracing::warn!(
"cannot apply state from {}; state struct is not up to date",
dumped_state.display()
);
}
let state: State = serde_json::from_str(&std::fs::read_to_string(&dumped_state)?)?;
wm.lock().apply_state(state);
}
wm.lock().retile_all(false)?;
border_manager::listen_for_notifications(wm.clone());
stackbar_manager::listen_for_notifications(wm.clone());
transparency_manager::listen_for_notifications(wm.clone());
workspace_reconciliator::listen_for_notifications(wm.clone());
monitor_reconciliator::listen_for_notifications(wm.clone())?;
reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());
focus_manager::listen_for_notifications(wm.clone());
theme_manager::listen_for_notifications();
listen_for_commands(wm.clone());
if let Some(port) = opts.tcp_port {
listen_for_commands_tcp(wm.clone(), port);
}
listen_for_events(wm.clone());
if CUSTOM_FFM.load(Ordering::SeqCst) {
listen_for_movements(wm.clone());
}
border_manager::listen_for_notifications(wm.clone());
stackbar_manager::listen_for_notifications(wm.clone());
transparency_manager::listen_for_notifications(wm.clone());
workspace_reconciliator::listen_for_notifications(wm.clone());
monitor_reconciliator::listen_for_notifications(wm.clone())?;
reaper::watch_for_orphans(wm.clone());
focus_manager::listen_for_notifications(wm.clone());
theme_manager::listen_for_notifications();
let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);
ctrlc::set_handler(move || {
ctrlc_sender

View File

@@ -1,10 +1,9 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
@@ -15,15 +14,13 @@ use serde::Serialize;
use crate::core::Rect;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
use crate::WindowsApi;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(
Debug,
@@ -39,35 +36,31 @@ use crate::workspace::Workspace;
)]
pub struct Monitor {
#[getset(get_copy = "pub", set = "pub")]
pub id: isize,
id: isize,
#[getset(get = "pub", set = "pub")]
pub name: String,
name: String,
#[getset(get = "pub", set = "pub")]
pub device: String,
device: String,
#[getset(get = "pub", set = "pub")]
pub device_id: String,
device_id: String,
#[getset(get = "pub", set = "pub")]
pub serial_number_id: Option<String>,
serial_number_id: Option<String>,
#[getset(get = "pub", set = "pub")]
pub size: Rect,
size: Rect,
#[getset(get = "pub", set = "pub")]
pub work_area_size: Rect,
work_area_size: Rect,
#[getset(get_copy = "pub", set = "pub")]
pub work_area_offset: Option<Rect>,
work_area_offset: Option<Rect>,
#[getset(get_copy = "pub", set = "pub")]
pub window_based_work_area_offset: Option<Rect>,
window_based_work_area_offset: Option<Rect>,
#[getset(get_copy = "pub", set = "pub")]
pub window_based_work_area_offset_limit: isize,
pub workspaces: Ring<Workspace>,
window_based_work_area_offset_limit: isize,
workspaces: Ring<Workspace>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
pub last_focused_workspace: Option<usize>,
last_focused_workspace: Option<usize>,
#[getset(get_mut = "pub")]
pub workspace_names: HashMap<usize, String>,
#[getset(get_copy = "pub", set = "pub")]
pub container_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>,
workspace_names: HashMap<usize, String>,
}
impl_ring_elements!(Monitor, Workspace);
@@ -121,32 +114,10 @@ pub fn new(
workspaces,
last_focused_workspace: None,
workspace_names: HashMap::default(),
container_padding: None,
workspace_padding: None,
}
}
impl Monitor {
pub fn new(
id: isize,
size: Rect,
work_area_size: Rect,
name: String,
device: String,
device_id: String,
serial_number_id: Option<String>,
) -> Self {
new(
id,
size,
work_area_size,
name,
device,
device_id,
serial_number_id,
)
}
pub fn placeholder() -> Self {
Self {
id: 0,
@@ -162,8 +133,6 @@ impl Monitor {
workspaces: Default::default(),
last_focused_workspace: None,
workspace_names: Default::default(),
container_padding: None,
workspace_padding: None,
}
}
@@ -186,52 +155,6 @@ impl Monitor {
Ok(())
}
/// Updates the `globals` field of all workspaces
pub fn update_workspaces_globals(&mut self, offset: Option<Rect>) {
let container_padding = self
.container_padding()
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
for workspace in self.workspaces_mut() {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
}
}
/// Updates the `globals` field of workspace with index `workspace_idx`
pub fn update_workspace_globals(&mut self, workspace_idx: usize, offset: Option<Rect>) {
let container_padding = self
.container_padding()
.or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
let workspace_padding = self
.workspace_padding()
.or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
let work_area = *self.work_area_size();
let offset = self.work_area_offset.or(offset);
let window_based_work_area_offset = self.window_based_work_area_offset();
let limit = self.window_based_work_area_offset_limit();
if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {
workspace.globals_mut().container_padding = container_padding;
workspace.globals_mut().workspace_padding = workspace_padding;
workspace.globals_mut().work_area = work_area;
workspace.globals_mut().work_area_offset = offset;
workspace.globals_mut().window_based_work_area_offset = window_based_work_area_offset;
workspace.globals_mut().window_based_work_area_offset_limit = limit;
}
}
pub fn add_container(
&mut self,
container: Container,
@@ -458,17 +381,21 @@ impl Monitor {
}
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
let work_area = *self.work_area_size();
let window_based_work_area_offset = (
self.window_based_work_area_offset_limit(),
self.window_based_work_area_offset(),
);
let offset = if self.work_area_offset().is_some() {
self.work_area_offset()
} else {
offset
};
let focused_workspace_idx = self.focused_workspace_idx();
self.update_workspace_globals(focused_workspace_idx, offset);
self.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?
.update()?;
.update(&work_area, offset, window_based_work_area_offset)?;
Ok(())
}

View File

@@ -1,34 +1,23 @@
use std::sync::mpsc;
use std::time::Duration;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_DISPLAY_ADAPTER;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_MONITOR;
use windows::Win32::Devices::Display::GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL;
use windows::core::PCWSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::System::Power::POWERBROADCAST_SETTING;
use windows::Win32::System::SystemServices::GUID_LIDSWITCH_STATE_CHANGE;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::DBT_CONFIGCHANGED;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEARRIVAL;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEREMOVECOMPLETE;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVTYP_DEVICEINTERFACE;
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;
use windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;
use windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;
use windows::Win32::UI::WindowsAndMessaging::PBT_POWERSETTINGCHANGE;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;
use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;
use windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;
@@ -37,11 +26,10 @@ use windows::Win32::UI::WindowsAndMessaging::WM_WTSSESSION_CHANGE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_LOCK;
use windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_UNLOCK;
use windows::core::PCWSTR;
use crate::WindowsApi;
use crate::monitor_reconciliator;
use crate::windows_api;
use crate::WindowsApi;
// This is a hidden window specifically spawned to listen to system-wide events related to monitors
#[derive(Debug, Clone, Copy)]
@@ -87,7 +75,7 @@ impl Hidden {
loop {
unsafe {
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("hidden window event processing thread shutdown");
break;
};
@@ -104,57 +92,8 @@ impl Hidden {
let hwnd = hwnd_receiver.recv()?;
// Register Session Lock/Unlock events
WindowsApi::wts_register_session_notification(hwnd)?;
// Register Laptop lid open/close events
WindowsApi::register_power_setting_notification(
hwnd,
&GUID_LIDSWITCH_STATE_CHANGE,
REGISTER_NOTIFICATION_FLAGS(0),
)?;
// Register device interface events for multiple display related devices. Some of this
// device interfaces might not be needed but it doesn't hurt to have them in case some user
// uses some output device as monitor that falls into one of these device interface class
// GUID.
let monitor_filter = DEV_BROADCAST_DEVICEINTERFACE_W {
dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,
dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,
dbcc_reserved: 0,
dbcc_classguid: GUID_DEVINTERFACE_MONITOR,
dbcc_name: [0; 1],
};
let display_adapter_filter = DEV_BROADCAST_DEVICEINTERFACE_W {
dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,
dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,
dbcc_reserved: 0,
dbcc_classguid: GUID_DEVINTERFACE_DISPLAY_ADAPTER,
dbcc_name: [0; 1],
};
let video_output_filter = DEV_BROADCAST_DEVICEINTERFACE_W {
dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,
dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,
dbcc_reserved: 0,
dbcc_classguid: GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL,
dbcc_name: [0; 1],
};
WindowsApi::register_device_notification(
hwnd,
monitor_filter,
REGISTER_NOTIFICATION_FLAGS(0),
)?;
WindowsApi::register_device_notification(
hwnd,
display_adapter_filter,
REGISTER_NOTIFICATION_FLAGS(0),
)?;
WindowsApi::register_device_notification(
hwnd,
video_output_filter,
REGISTER_NOTIFICATION_FLAGS(0),
)?;
Ok(Self { hwnd })
}
@@ -189,53 +128,20 @@ impl Hidden {
);
LRESULT(0)
}
// Monitor change power status
PBT_POWERSETTINGCHANGE => {
if let POWERBROADCAST_SETTING {
PowerSetting: GUID_LIDSWITCH_STATE_CHANGE,
DataLength: _,
Data: [0],
} = *(lparam.0 as *const POWERBROADCAST_SETTING)
{
tracing::debug!(
"WM_POWERBROADCAST event received - laptop lid closed"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::DisplayConnectionChange,
);
} else if let POWERBROADCAST_SETTING {
PowerSetting: GUID_LIDSWITCH_STATE_CHANGE,
DataLength: _,
Data: [1],
} = *(lparam.0 as *const POWERBROADCAST_SETTING)
{
tracing::debug!(
"WM_POWERBROADCAST event received - laptop lid opened"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::DisplayConnectionChange,
);
}
LRESULT(0)
}
_ => LRESULT(0),
}
}
WM_WTSSESSION_CHANGE => {
match wparam.0 as u32 {
WTS_SESSION_LOCK => {
tracing::debug!(
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked"
);
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked");
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::SessionLocked,
);
}
WTS_SESSION_UNLOCK => {
tracing::debug!(
"WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked"
);
tracing::debug!("WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked");
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::SessionUnlocked,
@@ -255,8 +161,7 @@ impl Hidden {
// and resolution changes here
WM_DISPLAYCHANGE => {
tracing::debug!(
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed",
wparam.0
"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed", wparam.0
);
monitor_reconciliator::send_notification(
@@ -270,8 +175,8 @@ impl Hidden {
#[allow(clippy::cast_possible_truncation)]
if wparam.0 as u32 == SPI_SETWORKAREA.0 {
tracing::debug!(
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
);
"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::WorkAreaChanged,
@@ -283,15 +188,10 @@ impl Hidden {
// Original idea from https://stackoverflow.com/a/33762334
WM_DEVICECHANGE => {
#[allow(clippy::cast_possible_truncation)]
let event = wparam.0 as u32;
if event == DBT_DEVNODES_CHANGED
|| event == DBT_CONFIGCHANGED
|| event == DBT_DEVICEARRIVAL
|| event == DBT_DEVICEREMOVECOMPLETE
{
if wparam.0 as u32 == DBT_DEVNODES_CHANGED {
tracing::debug!(
"WM_DEVICECHANGE event received with one of [DBT_DEVNODES_CHANGED, DBT_CONFIGCHANGED, DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE] - display added or removed"
);
"WM_DEVICECHANGE event received with DBT_DEVNODES_CHANGED - display added or removed"
);
monitor_reconciliator::send_notification(
monitor_reconciliator::MonitorNotification::DisplayConnectionChange,
);

View File

@@ -1,18 +1,17 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::WORKSPACE_MATCHING_RULES;
use crate::WindowManager;
use crate::WindowsApi;
use crate::border_manager;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::Rect;
use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator::hidden::Hidden;
use crate::notify_subscribers;
use crate::MonitorConfig;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::WindowManager;
use crate::WindowsApi;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
@@ -21,10 +20,10 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
pub mod hidden;
@@ -45,10 +44,10 @@ static ACTIVE: AtomicBool = AtomicBool::new(true);
static CHANNEL: OnceLock<(Sender<MonitorNotification>, Receiver<MonitorNotification>)> =
OnceLock::new();
static MONITOR_CACHE: OnceLock<Mutex<HashMap<String, Monitor>>> = OnceLock::new();
static MONITOR_CACHE: OnceLock<Mutex<HashMap<String, MonitorConfig>>> = OnceLock::new();
pub fn channel() -> &'static (Sender<MonitorNotification>, Receiver<MonitorNotification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
CHANNEL.get_or_init(|| crossbeam_channel::bounded(1))
}
fn event_tx() -> Sender<MonitorNotification> {
@@ -65,12 +64,12 @@ pub fn send_notification(notification: MonitorNotification) {
}
}
pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {
pub fn insert_in_monitor_cache(device_id: &str, config: MonitorConfig) {
let mut monitor_cache = MONITOR_CACHE
.get_or_init(|| Mutex::new(HashMap::new()))
.lock();
monitor_cache.insert(serial_or_device_id.to_string(), monitor);
monitor_cache.insert(device_id.to_string(), config);
}
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
@@ -112,18 +111,16 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
tracing::info!("created hidden window to listen for monitor-related events");
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
}
}
@@ -138,19 +135,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let receiver = event_rx();
'receiver: for notification in receiver {
if !ACTIVE.load_consume()
&& matches!(
if !ACTIVE.load_consume() {
if matches!(
notification,
MonitorNotification::ResumingFromSuspendedState
| MonitorNotification::SessionUnlocked
)
{
tracing::debug!(
"reactivating reconciliator - system has resumed from suspended state or session has been unlocked"
);
) {
tracing::debug!(
"reactivating reconciliator - system has resumed from suspended state or session has been unlocked"
);
ACTIVE.store(true, Ordering::SeqCst);
border_manager::send_notification(None);
ACTIVE.store(true, Ordering::SeqCst);
border_manager::send_notification(None);
}
continue 'receiver;
}
let mut wm = wm.lock();
@@ -164,6 +163,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
ACTIVE.store(false, Ordering::SeqCst);
}
MonitorNotification::ResumingFromSuspendedState
| MonitorNotification::SessionUnlocked => {
// this is only handled above if the reconciliator is paused
}
MonitorNotification::WorkAreaChanged => {
tracing::debug!("handling work area changed notification");
let offset = wm.work_area_offset;
@@ -243,12 +246,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
}
}
// this is handled above if the reconciliator is paused but we should still check if
// there were any changes to the connected monitors while the system was
// suspended/locked.
MonitorNotification::ResumingFromSuspendedState
| MonitorNotification::SessionUnlocked
| MonitorNotification::DisplayConnectionChange => {
MonitorNotification::DisplayConnectionChange => {
tracing::debug!("handling display connection change notification");
let mut monitor_cache = MONITOR_CACHE
.get_or_init(|| Mutex::new(HashMap::new()))
@@ -262,13 +260,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Make sure that in our state any attached displays have the latest Win32 data
for monitor in wm.monitors_mut() {
for attached in &attached_devices {
if attached.serial_number_id().eq(monitor.serial_number_id())
|| attached.device_id().eq(monitor.device_id())
{
if attached.device_id().eq(monitor.device_id()) {
monitor.set_id(attached.id());
monitor.set_device(attached.device().clone());
monitor.set_device_id(attached.device_id().clone());
monitor.set_serial_number_id(attached.serial_number_id().clone());
monitor.set_name(attached.name().clone());
monitor.set_size(*attached.size());
monitor.set_work_area_size(*attached.work_area_size());
@@ -278,7 +271,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if initial_monitor_count == attached_devices.len() {
tracing::debug!("monitor counts match, reconciliation not required");
drop(wm);
continue 'receiver;
}
@@ -286,7 +278,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::debug!(
"no devices found, skipping reconciliation to avoid breaking state"
);
drop(wm);
continue 'receiver;
}
@@ -296,108 +287,41 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
attached_devices.len()
);
// Windows to remove from `known_hwnds`
let mut windows_to_remove = Vec::new();
// Gather all the containers that will be orphaned from disconnected and invalid displays
let mut orphaned_containers = vec![];
// Collect the ids in our state which aren't in the current attached display ids
// These are monitors that have been removed
let mut newly_removed_displays = vec![];
for (m_idx, m) in wm.monitors().iter().enumerate() {
if !attached_devices.iter().any(|attached| {
attached.serial_number_id().eq(m.serial_number_id())
|| attached.device_id().eq(m.device_id())
}) {
let id = m
.serial_number_id()
.as_ref()
.map_or(m.device_id().clone(), |sn| sn.clone());
newly_removed_displays.push(id.clone());
let focused_workspace_idx = m.focused_workspace_idx();
for (idx, workspace) in m.workspaces().iter().enumerate() {
let is_focused_workspace = idx == focused_workspace_idx;
let focused_container_idx = workspace.focused_container_idx();
for (c_idx, container) in workspace.containers().iter().enumerate()
{
let focused_window_idx = container.focused_window_idx();
for (w_idx, window) in container.windows().iter().enumerate() {
windows_to_remove.push(window.hwnd);
if is_focused_workspace
&& c_idx == focused_container_idx
&& w_idx == focused_window_idx
{
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
}
}
}
for m in wm.monitors().iter() {
if !attached_devices
.iter()
.any(|attached| attached.device_id().eq(m.device_id()))
{
newly_removed_displays.push(m.device_id().clone());
for workspace in m.workspaces() {
for container in workspace.containers() {
// Save the orphaned containers from the removed monitor
orphaned_containers.push(container.clone());
}
if let Some(maximized) = workspace.maximized_window() {
windows_to_remove.push(maximized.hwnd);
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if maximized.is_focused() {
maximized.minimize();
}
}
if let Some(container) = workspace.monocle_container() {
for window in container.windows() {
windows_to_remove.push(window.hwnd);
}
if let Some(window) = container.focused_window() {
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
}
}
}
for window in workspace.floating_windows() {
windows_to_remove.push(window.hwnd);
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
}
}
}
// Remove any workspace_rules for this specific monitor
let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();
let mut rules_to_remove = Vec::new();
for (i, rule) in workspace_rules.iter().enumerate().rev() {
if rule.monitor_index == m_idx {
rules_to_remove.push(i);
}
}
for i in rules_to_remove {
workspace_rules.remove(i);
}
// Let's add their state to the cache for later
monitor_cache.insert(id, m.clone());
monitor_cache.insert(m.device_id().clone(), m.into());
}
}
// Update known_hwnds
wm.known_hwnds.retain(|i, _| !windows_to_remove.contains(i));
if !orphaned_containers.is_empty() {
tracing::info!(
"removed orphaned containers from: {newly_removed_displays:?}"
);
}
if !newly_removed_displays.is_empty() {
// After we have cached them, remove them from our state
wm.monitors_mut().retain(|m| {
!newly_removed_displays.iter().any(|id| {
m.serial_number_id().as_ref().is_some_and(|sn| sn == id)
|| m.device_id() == id
})
});
wm.monitors_mut()
.retain(|m| !newly_removed_displays.contains(m.device_id()));
}
let post_removal_monitor_count = wm.monitors().len();
@@ -406,6 +330,24 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
wm.focus_monitor(0)?;
}
if !orphaned_containers.is_empty() {
if let Some(primary) = wm.monitors_mut().front_mut() {
if let Some(focused_ws) = primary.focused_workspace_mut() {
let focused_container_idx = focused_ws.focused_container_idx();
// Put the orphaned containers somewhere visible
for container in orphaned_containers {
focused_ws.add_container_to_back(container);
}
// Gotta reset the focus or the movement will feel "off"
if initial_monitor_count != post_removal_monitor_count {
focused_ws.focus_container(focused_container_idx);
}
}
}
}
let offset = wm.work_area_offset;
for monitor in wm.monitors_mut() {
@@ -418,11 +360,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let post_removal_monitor_count = wm.monitors().len();
// This is the list of device ids after we have removed detached displays. We can
// keep this with just the device_ids without the serial numbers since this is used
// only to check which one is the newly added monitor below if there is a new
// monitor. Everything done after with said new monitor will again consider both
// serial number and device ids.
// This is the list of device ids after we have removed detached displays
let post_removal_device_ids = wm
.monitors()
.iter()
@@ -432,7 +370,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Check for and add any new monitors that may have been plugged in
// Monitor and display index preferences get applied in this function
WindowsApi::load_monitor_information(&mut wm)?;
WindowsApi::load_monitor_information(&mut wm.monitors)?;
let post_addition_monitor_count = wm.monitors().len();
@@ -441,205 +379,42 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
"monitor count mismatch ({post_removal_monitor_count} vs {post_addition_monitor_count}), adding connected monitors",
);
let known_hwnds = wm.known_hwnds.clone();
let offset = wm.work_area_offset;
let mouse_follows_focus = wm.mouse_follows_focus;
let focused_monitor_idx = wm.focused_monitor_idx();
let focused_workspace_idx = wm.focused_workspace_idx()?;
// Look in the updated state for new monitors
for (i, m) in wm.monitors_mut().iter_mut().enumerate() {
let device_id = m.device_id();
for m in wm.monitors_mut() {
let device_id = m.device_id().clone();
// We identify a new monitor when we encounter a new device id
if !post_removal_device_ids.contains(device_id) {
if !post_removal_device_ids.contains(&device_id) {
let mut cache_hit = false;
let mut cached_id = String::new();
// Check if that device id exists in the cache for this session
if let Some((id, cached)) = monitor_cache.get_key_value(device_id).or(m
.serial_number_id()
.as_ref()
.and_then(|sn| monitor_cache.get_key_value(sn)))
{
if let Some(cached) = monitor_cache.get(&device_id) {
cache_hit = true;
cached_id = id.clone();
tracing::info!(
"found monitor and workspace configuration for {id} in the monitor cache, applying"
tracing::info!("found monitor and workspace configuration for {device_id} in the monitor cache, applying");
// If it does, load all the monitor settings from the cache entry
m.ensure_workspace_count(cached.workspaces.len());
m.set_work_area_offset(cached.work_area_offset);
m.set_window_based_work_area_offset(
cached.window_based_work_area_offset,
);
m.set_window_based_work_area_offset_limit(
cached.window_based_work_area_offset_limit.unwrap_or(1),
);
// If it does, update the cached monitor info with the new one and
// load the cached monitor removing any window that has since been
// closed or moved to another workspace
*m = Monitor {
// Data that should be the one just read from `win32-display-data`
id: m.id,
name: m.name.clone(),
device: m.device.clone(),
device_id: m.device_id.clone(),
serial_number_id: m.serial_number_id.clone(),
size: m.size,
work_area_size: m.work_area_size,
// The rest should come from the cached monitor
work_area_offset: cached.work_area_offset,
window_based_work_area_offset: cached
.window_based_work_area_offset,
window_based_work_area_offset_limit: cached
.window_based_work_area_offset_limit,
workspaces: cached.workspaces.clone(),
last_focused_workspace: cached.last_focused_workspace,
workspace_names: cached.workspace_names.clone(),
container_padding: cached.container_padding,
workspace_padding: cached.workspace_padding,
};
let focused_workspace_idx = m.focused_workspace_idx();
for (j, workspace) in m.workspaces_mut().iter_mut().enumerate() {
// If this is the focused workspace we need to show (restore) all
// windows that were visible since they were probably minimized by
// Windows.
let is_focused_workspace = j == focused_workspace_idx;
let focused_container_idx = workspace.focused_container_idx();
let mut empty_containers = Vec::new();
for (idx, container) in
workspace.containers_mut().iter_mut().enumerate()
{
container.windows_mut().retain(|window| {
window.exe().is_ok()
&& !known_hwnds.contains_key(&window.hwnd)
});
if container.windows().is_empty() {
empty_containers.push(idx);
}
if is_focused_workspace {
if let Some(window) = container.focused_window() {
tracing::debug!(
"restoring window: {}",
window.hwnd
);
WindowsApi::restore_window(window.hwnd);
} else {
// If the focused window was moved or removed by
// the user after the disconnect then focus the
// first window and show that one
container.focus_window(0);
if let Some(window) = container.focused_window() {
WindowsApi::restore_window(window.hwnd);
}
}
}
}
// Remove empty containers
for empty_idx in empty_containers {
if empty_idx == focused_container_idx {
workspace.remove_container(empty_idx);
} else {
workspace.remove_container_by_idx(empty_idx);
}
}
if let Some(window) = workspace.maximized_window() {
if window.exe().is_err()
|| known_hwnds.contains_key(&window.hwnd)
{
workspace.set_maximized_window(None);
} else if is_focused_workspace {
WindowsApi::restore_window(window.hwnd);
}
}
if let Some(container) = workspace.monocle_container_mut() {
container.windows_mut().retain(|window| {
window.exe().is_ok()
&& !known_hwnds.contains_key(&window.hwnd)
});
if container.windows().is_empty() {
workspace.set_monocle_container(None);
} else if is_focused_workspace {
if let Some(window) = container.focused_window() {
WindowsApi::restore_window(window.hwnd);
} else {
// If the focused window was moved or removed by
// the user after the disconnect then focus the
// first window and show that one
container.focus_window(0);
if let Some(window) = container.focused_window() {
WindowsApi::restore_window(window.hwnd);
}
}
}
}
workspace.floating_windows_mut().retain(|window| {
window.exe().is_ok()
&& !known_hwnds.contains_key(&window.hwnd)
});
if is_focused_workspace {
for window in workspace.floating_windows() {
WindowsApi::restore_window(window.hwnd);
}
}
// Apply workspace rules
let mut workspace_matching_rules =
WORKSPACE_MATCHING_RULES.lock();
if let Some(rules) = workspace
.workspace_config()
.as_ref()
.and_then(|c| c.workspace_rules.as_ref())
{
for r in rules {
workspace_matching_rules.push(WorkspaceMatchingRule {
monitor_index: i,
workspace_index: j,
matching_rule: r.clone(),
initial_only: false,
});
}
}
if let Some(rules) = workspace
.workspace_config()
.as_ref()
.and_then(|c| c.initial_workspace_rules.as_ref())
{
for r in rules {
workspace_matching_rules.push(WorkspaceMatchingRule {
monitor_index: i,
workspace_index: j,
matching_rule: r.clone(),
initial_only: true,
});
}
for (w_idx, workspace) in m.workspaces_mut().iter_mut().enumerate()
{
if let Some(cached_workspace) = cached.workspaces.get(w_idx) {
workspace.load_static_config(cached_workspace)?;
}
}
// Restore windows from new monitor and update the focused
// workspace
m.load_focused_workspace(mouse_follows_focus)?;
m.update_focused_workspace(offset)?;
}
// Entries in the cache should only be used once; remove the entry there was a cache hit
if cache_hit && !cached_id.is_empty() {
monitor_cache.remove(&cached_id);
if cache_hit {
monitor_cache.remove(&device_id);
}
}
}
// Refocus the previously focused monitor since the code above might
// steal the focus away.
wm.focus_monitor(focused_monitor_idx)?;
wm.focus_workspace(focused_workspace_idx)?;
}
let final_count = wm.monitors().len();

View File

@@ -8,22 +8,26 @@ use std::net::TcpListener;
use std::net::TcpStream;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use miow::pipe::connect;
use net2::TcpStreamExt;
use parking_lot::Mutex;
use schemars::r#gen::SchemaSettings;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use uds_windows::UnixStream;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::ApplicationIdentifier;
use crate::core::Axis;
use crate::core::BorderImplementation;
@@ -37,34 +41,7 @@ use crate::core::SocketMessage;
use crate::core::StateQuery;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowKind;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::GlobalState;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::Notification;
use crate::NotificationEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
use crate::SUBSCRIPTION_SOCKETS;
use crate::State;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
use crate::animation::ANIMATION_FPS;
@@ -89,8 +66,30 @@ use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::winevent_listener;
use crate::workspace::WorkspaceLayer;
use crate::workspace::WorkspaceWindowLocation;
use crate::GlobalState;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REMOVE_TITLEBARS;
use crate::SUBSCRIPTION_PIPES;
use crate::SUBSCRIPTION_SOCKETS;
use crate::SUBSCRIPTION_SOCKET_OPTIONS;
use crate::TCP_CONNECTIONS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use stackbar_manager::STACKBAR_LABEL;
use stackbar_manager::STACKBAR_MODE;
@@ -101,44 +100,42 @@ use stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
#[tracing::instrument]
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
let wm = wm.clone();
std::thread::spawn(move || loop {
let wm = wm.clone();
let _ = std::thread::spawn(move || {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
let _ = std::thread::spawn(move || {
let listener = wm
.lock()
.command_listener
.try_clone()
.expect("could not clone unix listener");
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => {
let wm_clone = wm.clone();
std::thread::spawn(move || {
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
match read_commands_uds(&wm_clone, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
});
}
Err(error) => {
tracing::error!("{}", error);
break;
}
tracing::info!("listening on komorebi.sock");
for client in listener.incoming() {
match client {
Ok(stream) => {
let wm_clone = wm.clone();
std::thread::spawn(move || {
match stream.set_read_timeout(Some(Duration::from_secs(1))) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
match read_commands_uds(&wm_clone, stream) {
Ok(()) => {}
Err(error) => tracing::error!("{}", error),
}
});
}
Err(error) => {
tracing::error!("{}", error);
break;
}
}
})
.join();
}
})
.join();
tracing::error!("restarting failed thread");
}
tracing::error!("restarting failed thread");
});
}
@@ -294,37 +291,13 @@ impl WindowManager {
}
}
SocketMessage::FocusWindow(direction) => {
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.focus_container_in_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.focus_floating_window_in_direction(direction)?;
}
}
self.focus_container_in_direction(direction)?;
}
SocketMessage::MoveWindow(direction) => {
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.move_container_in_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.move_floating_window_in_direction(direction)?;
}
}
self.move_container_in_direction(direction)?;
}
SocketMessage::CycleFocusWindow(direction) => {
let focused_workspace = self.focused_workspace()?;
match focused_workspace.layer() {
WorkspaceLayer::Tiling => {
self.focus_container_in_cycle_direction(direction)?;
}
WorkspaceLayer::Floating => {
self.focus_floating_window_in_cycle_direction(direction)?;
}
}
self.focus_container_in_cycle_direction(direction)?;
}
SocketMessage::CycleMoveWindow(direction) => {
self.move_container_in_cycle_direction(direction)?;
@@ -762,11 +735,6 @@ impl WindowManager {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(self.mouse_follows_focus, true)?;
}
SocketMessage::FocusMonitorAtCursor => {
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
self.focus_monitor(monitor_idx)?;
}
}
SocketMessage::Retile => {
border_manager::destroy_all_borders()?;
self.retile_all(false)?
@@ -880,15 +848,7 @@ impl WindowManager {
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
self.focus_monitor(monitor_idx)?;
}
let focused_monitor = self
@@ -911,15 +871,7 @@ impl WindowManager {
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
self.focus_monitor(monitor_idx)?;
}
let mut can_close = false;
@@ -955,15 +907,7 @@ impl WindowManager {
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
self.focus_monitor(monitor_idx)?;
}
let idx = self
@@ -986,15 +930,7 @@ impl WindowManager {
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
self.focus_monitor(monitor_idx)?;
}
if self.focused_workspace_idx().unwrap_or_default() != workspace_idx {
@@ -1006,15 +942,7 @@ impl WindowManager {
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
self.focus_monitor(monitor_idx)?;
}
let focused_monitor_idx = self.focused_monitor_idx();
@@ -1047,50 +975,6 @@ impl WindowManager {
self.focus_workspace(workspace_idx)?;
}
}
SocketMessage::ToggleWorkspaceLayer => {
let mouse_follows_focus = self.mouse_follows_focus;
let workspace = self.focused_workspace_mut()?;
let mut to_focus = None;
match workspace.layer() {
WorkspaceLayer::Tiling => {
workspace.set_layer(WorkspaceLayer::Floating);
for (i, window) in workspace.floating_windows().iter().enumerate() {
if i == 0 {
to_focus = Some(*window);
}
window.raise()?;
}
for container in workspace.containers() {
if let Some(window) = container.focused_window() {
window.lower()?;
}
}
}
WorkspaceLayer::Floating => {
workspace.set_layer(WorkspaceLayer::Tiling);
let focused_container_idx = workspace.focused_container_idx();
for (i, container) in workspace.containers_mut().iter_mut().enumerate() {
if let Some(window) = container.focused_window() {
if i == focused_container_idx {
to_focus = Some(*window);
}
window.raise()?;
}
}
for window in workspace.floating_windows() {
window.lower()?;
}
}
};
if let Some(window) = to_focus {
window.focus(mouse_follows_focus)?;
}
}
SocketMessage::Stop => {
self.stop(false)?;
}
@@ -1216,7 +1100,7 @@ impl WindowManager {
let container_len = workspace.containers().len();
let no_layout_rules = workspace.layout_rules().is_empty();
if let Layout::Custom(custom) = workspace.layout_mut() {
if let Layout::Custom(ref mut custom) = workspace.layout_mut() {
if matches!(axis, Axis::Horizontal) {
#[allow(clippy::cast_precision_loss)]
let percentage = custom
@@ -1378,9 +1262,7 @@ impl WindowManager {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Windows) => {
tracing::warn!(
"ignoring command that could mix different focus follows mouse implementations"
);
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
}
}
}
@@ -1404,9 +1286,7 @@ impl WindowManager {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Komorebi) => {
tracing::warn!(
"ignoring command that could mix different focus follows mouse implementations"
);
tracing::warn!("ignoring command that could mix different focus follows mouse implementations");
}
}
}
@@ -1533,14 +1413,6 @@ impl WindowManager {
self.retile_all(false)?;
}
}
SocketMessage::ToggleWindowBasedWorkAreaOffset => {
let workspace = self.focused_workspace_mut()?;
workspace.set_apply_window_based_work_area_offset(
!workspace.apply_window_based_work_area_offset(),
);
self.retile_all(false)?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
let resize = workspace.resize_dimensions();
@@ -1648,41 +1520,33 @@ impl WindowManager {
}
SocketMessage::ToggleWorkspaceWindowContainerBehaviour => {
let current_global_behaviour = self.window_management_behaviour.current_behaviour;
match self
if let Some(behaviour) = self
.focused_workspace_mut()?
.window_container_behaviour_mut()
{
Some(behaviour) => match behaviour {
match behaviour {
WindowContainerBehaviour::Create => {
*behaviour = WindowContainerBehaviour::Append
}
WindowContainerBehaviour::Append => {
*behaviour = WindowContainerBehaviour::Create
}
},
_ => {
self.focused_workspace_mut()?
.set_window_container_behaviour(Some(match current_global_behaviour {
WindowContainerBehaviour::Create => {
WindowContainerBehaviour::Append
}
WindowContainerBehaviour::Append => {
WindowContainerBehaviour::Create
}
}));
}
} else {
self.focused_workspace_mut()?
.set_window_container_behaviour(Some(match current_global_behaviour {
WindowContainerBehaviour::Create => WindowContainerBehaviour::Append,
WindowContainerBehaviour::Append => WindowContainerBehaviour::Create,
}));
};
}
SocketMessage::ToggleWorkspaceFloatOverride => {
let current_global_override = self.window_management_behaviour.float_override;
match self.focused_workspace_mut()?.float_override_mut() {
Some(float_override) => {
*float_override = !*float_override;
}
_ => {
self.focused_workspace_mut()?
.set_float_override(Some(!current_global_override));
}
if let Some(float_override) = self.focused_workspace_mut()?.float_override_mut() {
*float_override = !*float_override;
} else {
self.focused_workspace_mut()?
.set_float_override(Some(!current_global_override));
};
}
SocketMessage::WindowHidingBehaviour(behaviour) => {
@@ -1866,8 +1730,8 @@ impl WindowManager {
s.inline_subschemas = true;
});
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
reply.write_all(schema.as_bytes())?;
@@ -1918,9 +1782,6 @@ impl WindowManager {
| SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}
};
// Update list of known_hwnds and their monitor/workspace index pair
self.update_known_hwnds();
notify_subscribers(
Notification {
event: NotificationEvent::Socket(message.clone()),

View File

@@ -1,10 +1,11 @@
use std::sync::Arc;
use std::fs::OpenOptions;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
@@ -13,13 +14,6 @@ use crate::core::Rect;
use crate::core::Sizing;
use crate::core::WindowContainerBehaviour;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::Notification;
use crate::NotificationEvent;
use crate::REGEX_IDENTIFIERS;
use crate::State;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::border_manager;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
@@ -27,16 +21,23 @@ use crate::current_virtual_desktop;
use crate::notify_subscribers;
use crate::stackbar_manager;
use crate::transparency_manager;
use crate::window::RuleDebug;
use crate::window::should_act;
use crate::window::RuleDebug;
use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::workspace::WorkspaceLayer;
use crate::workspace_reconciliator;
use crate::workspace_reconciliator::ALT_TAB_HWND;
use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
use crate::Notification;
use crate::NotificationEvent;
use crate::State;
use crate::DATA_DIR;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
#[tracing::instrument]
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
@@ -253,12 +254,7 @@ impl WindowManager {
already_moved_window_handles.remove(&window.hwnd);
}
WindowManagerEvent::FocusChange(_, window) => {
// don't want to trigger the full workspace updates when there are no managed
// containers - this makes floating windows on empty workspaces go into very
// annoying focus change loops which prevents users from interacting with them
if !self.focused_workspace()?.containers().is_empty() {
self.update_focused_workspace(self.mouse_follows_focus, false)?;
}
self.update_focused_workspace(self.mouse_follows_focus, false)?;
let workspace = self.focused_workspace_mut()?;
let floating_window_idx = workspace
@@ -281,12 +277,10 @@ impl WindowManager {
} else {
workspace.focus_container_by_window(window.hwnd)?;
}
workspace.set_layer(WorkspaceLayer::Tiling);
}
Some(idx) => {
if let Some(_window) = workspace.floating_windows().get(idx) {
workspace.set_layer(WorkspaceLayer::Floating);
if let Some(window) = workspace.floating_windows().get(idx) {
window.focus(false)?;
}
}
}
@@ -308,28 +302,30 @@ impl WindowManager {
let mut needs_reconciliation = false;
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
if focused_pair != (*m_idx, *w_idx) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
for (i, monitors) in self.monitors().iter().enumerate() {
for (j, workspace) in monitors.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd) && focused_pair != (i, j) {
// At this point we know we are going to send a notification to the workspace reconciliator
// So we get the topmost window returned by EnumWindows, which is almost always the window
// that has been selected by alt-tab
if let Ok(alt_tab_windows) = WindowsApi::alt_tab_windows() {
if let Some(first) =
alt_tab_windows.iter().find(|w| w.title().is_ok())
{
// If our record of this HWND hasn't been updated in over a minute
let mut instant = ALT_TAB_HWND_INSTANT.lock();
if instant.elapsed().gt(&Duration::from_secs(1)) {
// Update our record with the HWND we just found
ALT_TAB_HWND.store(Some(first.hwnd));
// Update the timestamp of our record
*instant = Instant::now();
}
}
}
}
workspace_reconciliator::send_notification(*m_idx, *w_idx);
needs_reconciliation = true;
workspace_reconciliator::send_notification(i, j);
needs_reconciliation = true;
}
}
}
@@ -340,14 +336,11 @@ impl WindowManager {
// duplicates across multiple workspaces, as it results in ghost layout tiles.
let mut proceed = true;
if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd) {
if let Some(focused_workspace_idx) = self
.monitors()
.get(*m_idx)
.map(|m| m.focused_workspace_idx())
{
if *m_idx != self.focused_monitor_idx()
&& *w_idx != focused_workspace_idx
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
if workspace.contains_window(window.hwnd)
&& i != self.focused_monitor_idx()
&& j != monitor.focused_workspace_idx()
{
tracing::debug!(
"ignoring show event for window already associated with another workspace"
@@ -396,13 +389,11 @@ impl WindowManager {
if behaviour.float_override {
workspace.floating_windows_mut().push(window);
workspace.set_layer(WorkspaceLayer::Floating);
self.update_focused_workspace(false, false)?;
} else {
match behaviour.current_behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
workspace.set_layer(WorkspaceLayer::Tiling);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
@@ -412,7 +403,6 @@ impl WindowManager {
anyhow!("there is no focused container")
})?
.add_window(window);
workspace.set_layer(WorkspaceLayer::Tiling);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
}
@@ -509,9 +499,15 @@ impl WindowManager {
// This will be true if we have moved to another monitor
let mut moved_across_monitors = false;
if let Some((m_idx, _)) = self.known_hwnds.get(&window.hwnd) {
if *m_idx != target_monitor_idx {
moved_across_monitors = true;
for (i, monitors) in self.monitors().iter().enumerate() {
for workspace in monitors.workspaces() {
if workspace.contains_window(window.hwnd) && i != target_monitor_idx {
moved_across_monitors = true;
break;
}
}
if moved_across_monitors {
break;
}
}
@@ -661,7 +657,7 @@ impl WindowManager {
let mut ops = vec![];
macro_rules! resize_op {
($coordinate:expr_2021, $comparator:tt, $direction:expr_2021) => {{
($coordinate:expr, $comparator:tt, $direction:expr) => {{
let adjusted = $coordinate * 2;
let sizing = if adjusted $comparator 0 {
Sizing::Decrease
@@ -715,8 +711,40 @@ impl WindowManager {
window.center(&self.focused_monitor_work_area()?)?;
}
// Update list of known_hwnds and their monitor/workspace index pair
self.update_known_hwnds();
tracing::trace!("updating list of known hwnds");
let mut known_hwnds = vec![];
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
for container in workspace.containers() {
for window in container.windows() {
known_hwnds.push(window.hwnd);
}
}
for window in workspace.floating_windows() {
known_hwnds.push(window.hwnd);
}
if let Some(window) = workspace.maximized_window() {
known_hwnds.push(window.hwnd);
}
if let Some(container) = workspace.monocle_container() {
for window in container.windows() {
known_hwnds.push(window.hwnd);
}
}
}
}
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(hwnd_json)?;
serde_json::to_writer_pretty(&file, &known_hwnds)?;
notify_subscribers(
Notification {

View File

@@ -1,9 +1,9 @@
use std::sync::Arc;
use parking_lot::Mutex;
use winput::Action;
use winput::message_loop;
use winput::message_loop::Event;
use winput::Action;
use crate::core::FocusFollowsMouseImplementation;

View File

@@ -1,204 +1,72 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::DATA_DIR;
use crate::NotificationEvent;
use crate::Window;
use crate::WindowManager;
use crate::WindowManagerEvent;
use crate::border_manager;
use crate::notify_subscribers;
use crate::winevent::WinEvent;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use lazy_static::lazy_static;
use crate::WindowManager;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
lazy_static! {
pub static ref HWNDS_CACHE: Arc<Mutex<HashMap<isize, (usize, usize)>>> =
Arc::new(Mutex::new(HashMap::new()));
}
pub struct ReaperNotification(pub HashMap<isize, (usize, usize)>);
static CHANNEL: OnceLock<(Sender<ReaperNotification>, Receiver<ReaperNotification>)> =
OnceLock::new();
pub fn channel() -> &'static (Sender<ReaperNotification>, Receiver<ReaperNotification>) {
CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
}
fn event_tx() -> Sender<ReaperNotification> {
channel().0.clone()
}
fn event_rx() -> Receiver<ReaperNotification> {
channel().1.clone()
}
pub fn send_notification(hwnds: HashMap<isize, (usize, usize)>) {
if event_tx().try_send(ReaperNotification(hwnds)).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
pub fn listen_for_notifications(
wm: Arc<Mutex<WindowManager>>,
known_hwnds: HashMap<isize, (usize, usize)>,
) {
watch_for_orphans(known_hwnds);
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match find_orphans(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
}
}
});
}
fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("watching");
let receiver = event_rx();
let arc = wm.clone();
for notification in receiver {
let orphan_hwnds = notification.0;
let mut wm = wm.lock();
loop {
std::thread::sleep(Duration::from_secs(1));
let mut wm = arc.lock();
let offset = wm.work_area_offset;
let mut update_borders = false;
for (hwnd, (m_idx, w_idx)) in orphan_hwnds.iter() {
if let Some(monitor) = wm.monitors_mut().get_mut(*m_idx) {
let focused_workspace_idx = monitor.focused_workspace_idx();
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
let window_based_work_area_offset = (
monitor.window_based_work_area_offset_limit(),
monitor.window_based_work_area_offset(),
);
if let Some(workspace) = monitor.workspaces_mut().get_mut(*w_idx) {
// Remove orphan window
if let Err(error) = workspace.remove_window(*hwnd) {
tracing::warn!(
"error reaping orphan window ({}) on monitor: {}, workspace: {}. Error: {}",
hwnd,
m_idx,
w_idx,
error,
);
}
let offset = if monitor.work_area_offset().is_some() {
monitor.work_area_offset()
} else {
offset
};
if focused_workspace_idx == *w_idx {
// If this is not a focused workspace there is no need to update the
// workspace or the borders. That will already be done when the user
// changes to this workspace.
workspace.update()?;
update_borders = true;
}
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
let reaped_orphans = workspace.reap_orphans()?;
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
workspace.update(&work_area, offset, window_based_work_area_offset)?;
update_borders = true;
tracing::info!(
"reaped orphan window ({}) on monitor: {}, workspace: {}",
hwnd,
m_idx,
w_idx,
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,
reaped_orphans.1,
i,
j
);
}
}
wm.known_hwnds.remove(hwnd);
let window = Window::from(*hwnd);
notify_subscribers(
crate::Notification {
event: NotificationEvent::WindowManager(WindowManagerEvent::Destroy(
WinEvent::ObjectDestroy,
window,
)),
state: wm.as_ref().into(),
},
true,
)?;
}
if update_borders {
border_manager::send_notification(None);
}
// Save to file
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(hwnd_json)?;
serde_json::to_writer_pretty(&file, &wm.known_hwnds.keys().collect::<Vec<_>>())?;
}
Ok(())
}
fn watch_for_orphans(known_hwnds: HashMap<isize, (usize, usize)>) {
// Cache current hwnds
{
let mut cache = HWNDS_CACHE.lock();
*cache = known_hwnds;
}
std::thread::spawn(move || {
loop {
match find_orphans() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
}
}
}
});
}
fn find_orphans() -> color_eyre::Result<()> {
tracing::info!("watching");
loop {
std::thread::sleep(Duration::from_millis(20));
let mut cache = HWNDS_CACHE.lock();
let mut orphan_hwnds = HashMap::new();
for (hwnd, (m_idx, w_idx)) in cache.iter() {
let window = Window::from(*hwnd);
if !window.is_window()
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
// docs is closed
//
// I hate every single person who worked on Microsoft Office 365, especially Word
|| !window.is_visible()
{
orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));
}
}
if !orphan_hwnds.is_empty() {
// Update reaper cache
cache.retain(|h, _| !orphan_hwnds.contains_key(h));
// Send handles to remove
event_tx().send(ReaperNotification(orphan_hwnds))?;
}
}
}

View File

@@ -1,26 +1,26 @@
mod stackbar;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WindowManager;
use crate::WindowsApi;
use crate::container::Container;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::stackbar_manager::stackbar::Stackbar;
use crate::WindowManager;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use crossbeam_utils::atomic::AtomicConsume;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use std::sync::OnceLock;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::OnceLock;
pub static STACKBAR_FONT_SIZE: AtomicI32 = AtomicI32::new(0); // 0 will produce the system default
pub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white
@@ -71,15 +71,13 @@ pub fn should_have_stackbar(window_count: usize) -> bool {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
@@ -195,12 +193,13 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Get the stackbar entry for this container from the map or create one
let stackbar = match stackbars.entry(container.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => match Stackbar::create(container.id()) {
Ok(stackbar) => entry.insert(stackbar),
_ => {
Entry::Vacant(entry) => {
if let Ok(stackbar) = Stackbar::create(container.id()) {
entry.insert(stackbar)
} else {
continue 'receiver;
}
},
}
};
stackbars_monitors.insert(container.id().clone(), monitor_idx);

View File

@@ -1,6 +1,3 @@
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WINDOWS_11;
use crate::WindowsApi;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::STYLE;
@@ -8,6 +5,7 @@ use crate::container::Container;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::core::StackbarLabel;
use crate::stackbar_manager::STACKBARS_CONTAINERS;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
@@ -16,13 +14,16 @@ use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBARS_CONTAINERS;
use crate::windows_api;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::WINDOWS_11;
use crossbeam_utils::atomic::AtomicConsume;
use std::os::windows::ffi::OsStrExt;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Duration;
use windows::core::PCWSTR;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HWND;
@@ -32,38 +33,38 @@ use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::CreateFontIndirectW;
use windows::Win32::Graphics::Gdi::CreatePen;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::DT_CENTER;
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
use windows::Win32::Graphics::Gdi::DT_VCENTER;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::DrawTextW;
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
use windows::Win32::Graphics::Gdi::FW_BOLD;
use windows::Win32::Graphics::Gdi::GetDC;
use windows::Win32::Graphics::Gdi::GetDeviceCaps;
use windows::Win32::Graphics::Gdi::LOGFONTW;
use windows::Win32::Graphics::Gdi::LOGPIXELSY;
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::ReleaseDC;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::SelectObject;
use windows::Win32::Graphics::Gdi::SetBkColor;
use windows::Win32::Graphics::Gdi::SetTextColor;
use windows::Win32::Graphics::Gdi::DT_CENTER;
use windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;
use windows::Win32::Graphics::Gdi::DT_SINGLELINE;
use windows::Win32::Graphics::Gdi::DT_VCENTER;
use windows::Win32::Graphics::Gdi::FONT_QUALITY;
use windows::Win32::Graphics::Gdi::FW_BOLD;
use windows::Win32::Graphics::Gdi::LOGFONTW;
use windows::Win32::Graphics::Gdi::LOGPIXELSY;
use windows::Win32::Graphics::Gdi::PROOF_QUALITY;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::System::WindowsProgramming::MulDiv;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
@@ -71,7 +72,6 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
use windows::core::PCWSTR;
#[derive(Debug)]
pub struct Stackbar {
@@ -123,7 +123,7 @@ impl Stackbar {
0,
None,
None,
Option::from(HINSTANCE(windows_api::as_ptr!(instance))),
HINSTANCE(windows_api::as_ptr!(instance)),
None,
)?;
@@ -133,7 +133,7 @@ impl Stackbar {
let mut msg: MSG = MSG::default();
loop {
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
tracing::debug!("stackbar window event processing thread shutdown");
break;
};
@@ -183,13 +183,13 @@ impl Stackbar {
WindowsApi::position_window(self.hwnd, &layout, false)?;
unsafe {
let hdc = GetDC(Option::from(self.hwnd()));
let hdc = GetDC(self.hwnd());
let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));
let hbrush = CreateSolidBrush(COLORREF(background));
SelectObject(hdc, hpen.into());
SelectObject(hdc, hbrush.into());
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
SetBkColor(hdc, COLORREF(background));
let mut logfont = LOGFONTW {
@@ -209,14 +209,14 @@ impl Stackbar {
let logical_height = -MulDiv(
STACKBAR_FONT_SIZE.load(Ordering::SeqCst),
72,
GetDeviceCaps(Option::from(hdc), LOGPIXELSY),
GetDeviceCaps(hdc, LOGPIXELSY),
);
logfont.lfHeight = logical_height;
let hfont = CreateFontIndirectW(&logfont);
SelectObject(hdc, hfont.into());
SelectObject(hdc, hfont);
for (i, window) in container.windows().iter().enumerate() {
if window.hwnd == container.focused_window().copied().unwrap_or_default().hwnd {
@@ -283,13 +283,13 @@ impl Stackbar {
);
}
ReleaseDC(Option::from(self.hwnd()), hdc);
ReleaseDC(self.hwnd(), hdc);
// TODO: error handling
let _ = DeleteObject(hpen.into());
let _ = DeleteObject(hpen);
// TODO: error handling
let _ = DeleteObject(hbrush.into());
let _ = DeleteObject(hbrush);
// TODO: error handling
let _ = DeleteObject(hfont.into());
let _ = DeleteObject(hfont);
}
Ok(())
@@ -342,10 +342,10 @@ impl Stackbar {
window.set_position(&focused_window_rect, false)
{
tracing::error!(
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
*window,
err
);
"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})",
*window,
err
);
}
}

View File

@@ -1,28 +1,4 @@
use crate::AspectRatio;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::PredefinedAspectRatio;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::animation::PerAnimationPrefixConfig;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
@@ -31,17 +7,15 @@ use crate::animation::ANIMATION_FPS;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::animation::DEFAULT_ANIMATION_FPS;
use crate::animation::PerAnimationPrefixConfig;
use crate::border_manager;
use crate::border_manager::ZOrder;
use crate::border_manager::IMPLEMENTATION;
use crate::border_manager::STYLE;
use crate::border_manager::ZOrder;
use crate::colour::Colour;
use crate::core::BorderImplementation;
use crate::core::StackbarLabel;
use crate::core::StackbarMode;
use crate::current_virtual_desktop;
use crate::monitor;
use crate::monitor::Monitor;
use crate::monitor_reconciliator;
use crate::ring::Ring;
@@ -61,10 +35,41 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;
use crate::AspectRatio;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::PredefinedAspectRatio;
use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOATING_APPLICATIONS;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;
use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use crate::core::AnimationStyle;
use crate::core::BorderStyle;
use crate::core::DefaultLayout;
@@ -77,12 +82,6 @@ use crate::core::Rect;
use crate::core::SocketMessage;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
use crate::core::config_generation::ApplicationOptions;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use crate::core::resolve_home_path;
use color_eyre::Result;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
@@ -97,31 +96,26 @@ use std::collections::HashSet;
use std::io::ErrorKind;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct BorderColours {
/// Border colour when the container contains a single window
#[serde(skip_serializing_if = "Option::is_none")]
pub single: Option<Colour>,
/// Border colour when the container contains multiple windows
#[serde(skip_serializing_if = "Option::is_none")]
pub stack: Option<Colour>,
/// Border colour when the container is in monocle mode
#[serde(skip_serializing_if = "Option::is_none")]
pub monocle: Option<Colour>,
/// Border colour when the container is in floating mode
#[serde(skip_serializing_if = "Option::is_none")]
pub floating: Option<Colour>,
/// Border colour when the container is unfocused
#[serde(skip_serializing_if = "Option::is_none")]
pub unfocused: Option<Colour>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceConfig {
/// Name
pub name: String,
@@ -131,7 +125,7 @@ pub struct WorkspaceConfig {
/// END OF LIFE FEATURE: Custom Layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_layout: Option<PathBuf>,
/// Layout rules in the format of threshold => layout (default: None)
/// Layout rules (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
/// END OF LIFE FEATURE: Custom layout rules (default: None)
@@ -140,7 +134,7 @@ pub struct WorkspaceConfig {
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub container_padding: Option<i32>,
/// Workspace padding (default: global)
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
/// Initial workspace application rules
@@ -155,10 +149,8 @@ pub struct WorkspaceConfig {
/// Determine what happens when a new window is opened (default: Create)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
/// Window container behaviour rules in the format of threshold => behaviour (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_container_behaviour_rules: Option<HashMap<usize, WindowContainerBehaviour>>,
/// Enable or disable float override, which makes it so every new window opens in floating mode (default: false)
/// Enable or disable float override, which makes it so every new window opens in floating mode
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>,
/// Specify an axis on which to flip the selected layout (default: None)
@@ -177,12 +169,6 @@ impl From<&Workspace> for WorkspaceConfig {
Layout::Custom(_) => {}
}
}
let layout_rules = (!layout_rules.is_empty()).then_some(layout_rules);
let mut window_container_behaviour_rules = HashMap::new();
for (threshold, behaviour) in value.window_container_behaviour_rules().iter().flatten() {
window_container_behaviour_rules.insert(*threshold, *behaviour);
}
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
@@ -208,42 +194,28 @@ impl From<&Workspace> for WorkspaceConfig {
.name()
.clone()
.unwrap_or_else(|| String::from("unnamed")),
layout: value
.tile()
.then_some(match value.layout() {
Layout::Default(layout) => Option::from(*layout),
Layout::Custom(_) => None,
})
.flatten(),
custom_layout: value
.workspace_config()
.as_ref()
.and_then(|c| c.custom_layout.clone()),
layout_rules,
custom_layout_rules: value
.workspace_config()
.as_ref()
.and_then(|c| c.custom_layout_rules.clone()),
layout: match value.layout() {
Layout::Default(layout) => Option::from(*layout),
// TODO: figure out how we might resolve file references in the future
Layout::Custom(_) => None,
},
custom_layout: None,
layout_rules: Option::from(layout_rules),
// TODO: figure out how we might resolve file references in the future
custom_layout_rules: None,
container_padding,
workspace_padding,
initial_workspace_rules: value
.workspace_config()
.as_ref()
.and_then(|c| c.initial_workspace_rules.clone()),
workspace_rules: value
.workspace_config()
.as_ref()
.and_then(|c| c.workspace_rules.clone()),
initial_workspace_rules: None,
workspace_rules: None,
apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()),
window_container_behaviour: *value.window_container_behaviour(),
window_container_behaviour_rules: Option::from(window_container_behaviour_rules),
float_override: *value.float_override(),
layout_flip: value.layout_flip(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MonitorConfig {
/// Workspace configurations
pub workspaces: Vec<WorkspaceConfig>,
@@ -256,12 +228,6 @@ pub struct MonitorConfig {
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
#[serde(skip_serializing_if = "Option::is_none")]
pub window_based_work_area_offset_limit: Option<isize>,
/// Container padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub container_padding: Option<i32>,
/// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
}
impl From<&Monitor> for MonitorConfig {
@@ -271,38 +237,17 @@ impl From<&Monitor> for MonitorConfig {
workspaces.push(WorkspaceConfig::from(w));
}
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
let container_padding = value.container_padding().and_then(|container_padding| {
if container_padding == default_container_padding {
None
} else {
Option::from(container_padding)
}
});
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
if workspace_padding == default_workspace_padding {
None
} else {
Option::from(workspace_padding)
}
});
Self {
workspaces,
work_area_offset: value.work_area_offset(),
window_based_work_area_offset: value.window_based_work_area_offset(),
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
container_padding,
workspace_padding,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
/// The `komorebi.json` static configuration file reference for `v0.1.35`
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.json` static configuration file reference for `v0.1.34`
pub struct StaticConfig {
/// DEPRECATED from v0.1.22: no longer required
#[serde(skip_serializing_if = "Option::is_none")]
@@ -438,8 +383,8 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_compensation_time: Option<u64>,
/// Komorebi status bar configuration files for multiple instances on different monitors
// this option is a little special because it is only consumed by komorebic
#[serde(skip_serializing_if = "Option::is_none")]
// this option is a little special because it is only consumed by komorebic
pub bar_configurations: Option<Vec<PathBuf>>,
/// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars
#[serde(skip_serializing_if = "Option::is_none")]
@@ -449,22 +394,18 @@ pub struct StaticConfig {
pub floating_window_aspect_ratio: Option<AspectRatio>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct AnimationsConfig {
/// Enable or disable animations (default: false)
pub enabled: PerAnimationPrefixConfig<bool>,
enabled: PerAnimationPrefixConfig<bool>,
/// Set the animation duration in ms (default: 250)
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<PerAnimationPrefixConfig<u64>>,
duration: Option<PerAnimationPrefixConfig<u64>>,
/// Set the animation style (default: Linear)
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<PerAnimationPrefixConfig<AnimationStyle>>,
style: Option<PerAnimationPrefixConfig<AnimationStyle>>,
/// Set the animation FPS (default: 60)
#[serde(skip_serializing_if = "Option::is_none")]
pub fps: Option<u64>,
fps: Option<u64>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "palette")]
pub enum KomorebiTheme {
/// A theme from catppuccin-egui
@@ -472,63 +413,45 @@ pub enum KomorebiTheme {
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
name: komorebi_themes::Catppuccin,
/// Border colour when the container contains a single window (default: Blue)
#[serde(skip_serializing_if = "Option::is_none")]
single_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container contains multiple windows (default: Green)
#[serde(skip_serializing_if = "Option::is_none")]
stack_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is in monocle mode (default: Pink)
#[serde(skip_serializing_if = "Option::is_none")]
monocle_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the window is floating (default: Yellow)
#[serde(skip_serializing_if = "Option::is_none")]
floating_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is unfocused (default: Base)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar focused tab text colour (default: Green)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar unfocused tab text colour (default: Text)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_unfocused_text: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar tab background colour (default: Base)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_background: Option<komorebi_themes::CatppuccinValue>,
/// Komorebi status bar accent (default: Blue)
#[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::CatppuccinValue>,
},
/// A theme from base16-egui-themes
Base16 {
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)
name: komorebi_themes::Base16,
/// Border colour when the container contains a single window (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
single_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container contains multiple windows (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stack_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is in monocle mode (default: Base0F)
#[serde(skip_serializing_if = "Option::is_none")]
monocle_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the window is floating (default: Base09)
#[serde(skip_serializing_if = "Option::is_none")]
floating_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
unfocused_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar unfocused tab text colour (default: Base05)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
/// Stackbar tab background colour (default: Base01)
#[serde(skip_serializing_if = "Option::is_none")]
stackbar_background: Option<komorebi_themes::Base16Value>,
/// Komorebi status bar accent (default: Base0D)
#[serde(skip_serializing_if = "Option::is_none")]
bar_accent: Option<komorebi_themes::Base16Value>,
},
}
@@ -555,9 +478,7 @@ impl StaticConfig {
}
if display {
println!(
"\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n"
)
println!("\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n")
}
}
@@ -582,9 +503,7 @@ impl StaticConfig {
}
if display {
println!(
"\nYour configuration file contains some options that have been renamed or deprecated:\n"
);
println!("\nYour 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) {
@@ -618,41 +537,31 @@ impl StaticConfig {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct TabsConfig {
/// Width of a stackbar tab
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<i32>,
width: Option<i32>,
/// Focused tab text colour
#[serde(skip_serializing_if = "Option::is_none")]
pub focused_text: Option<Colour>,
focused_text: Option<Colour>,
/// Unfocused tab text colour
#[serde(skip_serializing_if = "Option::is_none")]
pub unfocused_text: Option<Colour>,
unfocused_text: Option<Colour>,
/// Tab background colour
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<Colour>,
background: Option<Colour>,
/// Font family
#[serde(skip_serializing_if = "Option::is_none")]
pub font_family: Option<String>,
font_family: Option<String>,
/// Font size
#[serde(skip_serializing_if = "Option::is_none")]
pub font_size: Option<i32>,
font_size: Option<i32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct StackbarConfig {
/// Stackbar height
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<i32>,
/// Stackbar label
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<StackbarLabel>,
/// Stackbar mode
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<StackbarMode>,
/// Stackbar tab configuration options
#[serde(skip_serializing_if = "Option::is_none")]
pub tabs: Option<TabsConfig>,
}
@@ -1210,7 +1119,6 @@ impl StaticConfig {
let mut wm = WindowManager {
monitors: Ring::default(),
monitor_usr_idx_map: HashMap::new(),
incoming_events: incoming,
command_listener: listener,
is_paused: false,
@@ -1239,7 +1147,6 @@ impl StaticConfig {
pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0,
known_hwnds: HashMap::new(),
};
match value.focus_follows_mouse {
@@ -1273,82 +1180,32 @@ impl StaticConfig {
let value = Self::read(path)?;
let mut wm = wm.lock();
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
workspace_matching_rules.clear();
drop(workspace_matching_rules);
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
.as_ref()
.is_some_and(|sn| sn == id)
|| monitor.device_id() == id)
.then_some(*c_idx)
});
c_idx
};
let idx = preferred_config_idx.or({
// Monitor without preferred config idx.
// Get index of first config that is not a preferred config of some other monitor
// and that has not been used yet. This might return `None` as well, in that case
// this monitor won't have a config tied to it and will use the default values.
let m_config_count = value
.monitors
.as_ref()
.map(|ms| ms.len())
.unwrap_or_default();
(0..m_config_count)
.find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))
});
if let Some(monitor_config) = value
.monitors
.as_ref()
.and_then(|ms| idx.and_then(|i| ms.get(i)))
{
if let Some(used_config_idx) = idx {
configs_used.push(used_config_idx);
}
monitor.ensure_workspace_count(monitor_config.workspaces.len());
monitor.set_work_area_offset(monitor_config.work_area_offset);
monitor.set_window_based_work_area_offset(
monitor_config.window_based_work_area_offset,
);
monitor.set_window_based_work_area_offset_limit(
monitor_config
.window_based_work_area_offset_limit
.unwrap_or(1),
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
if let Some(monitors) = value.monitors {
for (i, monitor) in monitors.iter().enumerate() {
{
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
if let Some(device_id) = display_index_preferences.get(&i) {
monitor_reconciliator::insert_in_monitor_cache(device_id, monitor.clone());
}
}
// Check if this monitor config is the preferred config for this monitor and store
// a copy of the monitor itself on the monitor cache if it is.
if idx == preferred_config_idx {
let id = monitor
.serial_number_id()
.as_ref()
.map_or(monitor.device_id(), |sn| sn);
monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());
if let Some(m) = wm.monitors_mut().get_mut(i) {
m.ensure_workspace_count(monitor.workspaces.len());
m.set_work_area_offset(monitor.work_area_offset);
m.set_window_based_work_area_offset(monitor.window_based_work_area_offset);
m.set_window_based_work_area_offset_limit(
monitor.window_based_work_area_offset_limit.unwrap_or(1),
);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
}
}
}
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
for (j, ws) in monitor_config.workspaces.iter().enumerate() {
for (j, ws) in monitor.workspaces.iter().enumerate() {
if let Some(rules) = &ws.workspace_rules {
for r in rules {
workspace_matching_rules.push(WorkspaceMatchingRule {
@@ -1374,60 +1231,6 @@ impl StaticConfig {
}
}
// Check for configs that should be tied to a specific display that isn't loaded right now
// and cache a monitor with those configs with the specific `serial_number_id` so that when
// those devices are connected later we can use the correct config from the cache.
if configs_with_preference.len() > configs_used.len() {
for i in configs_with_preference
.iter()
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
(id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))
{
// The name, device, device_id and serial_number_id can be empty here since
// once the monitor with this preferred index actually connects the
// `load_monitor_information` function will update these fields.
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"".into(),
"".into(),
"".into(),
None,
);
m.ensure_workspace_count(monitor_config.workspaces.len());
m.set_work_area_offset(monitor_config.work_area_offset);
m.set_window_based_work_area_offset(
monitor_config.window_based_work_area_offset,
);
m.set_window_based_work_area_offset_limit(
monitor_config
.window_based_work_area_offset_limit
.unwrap_or(1),
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.update_workspaces_globals(offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
}
}
monitor_reconciliator::insert_in_monitor_cache(&id, m);
}
}
}
wm.enforce_workspace_rules()?;
if value.border == Some(true) {
@@ -1442,85 +1245,29 @@ impl StaticConfig {
value.apply_globals()?;
let configs_with_preference: Vec<_> =
DISPLAY_INDEX_PREFERENCES.lock().keys().copied().collect();
let mut configs_used = Vec::new();
if let Some(monitors) = value.monitors {
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
workspace_matching_rules.clear();
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
workspace_matching_rules.clear();
drop(workspace_matching_rules);
for (i, monitor) in monitors.iter().enumerate() {
if let Some(m) = wm.monitors_mut().get_mut(i) {
m.ensure_workspace_count(monitor.workspaces.len());
if m.work_area_offset().is_none() {
m.set_work_area_offset(monitor.work_area_offset);
}
m.set_window_based_work_area_offset(monitor.window_based_work_area_offset);
m.set_window_based_work_area_offset_limit(
monitor.window_based_work_area_offset_limit.unwrap_or(1),
);
let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
(monitor
.serial_number_id()
.as_ref()
.is_some_and(|sn| sn == id)
|| monitor.device_id() == id)
.then_some(*c_idx)
});
c_idx
};
let idx = preferred_config_idx.or({
// Monitor without preferred config idx.
// Get index of first config that is not a preferred config of some other monitor
// and that has not been used yet. This might return `None` as well, in that case
// this monitor won't have a config tied to it and will use the default values.
let m_config_count = value
.monitors
.as_ref()
.map(|ms| ms.len())
.unwrap_or_default();
(0..m_config_count)
.find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))
});
if let Some(monitor_config) = value
.monitors
.as_ref()
.and_then(|ms| idx.and_then(|i| ms.get(i)))
{
if let Some(used_config_idx) = idx {
configs_used.push(used_config_idx);
}
monitor.ensure_workspace_count(monitor_config.workspaces.len());
if monitor.work_area_offset().is_none() {
monitor.set_work_area_offset(monitor_config.work_area_offset);
}
monitor.set_window_based_work_area_offset(
monitor_config.window_based_work_area_offset,
);
monitor.set_window_based_work_area_offset_limit(
monitor_config
.window_based_work_area_offset_limit
.unwrap_or(1),
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
}
}
}
// Check if this monitor config is the preferred config for this monitor and store
// a copy of the monitor itself on the monitor cache if it is.
if idx == preferred_config_idx {
let id = monitor
.serial_number_id()
.as_ref()
.map_or(monitor.device_id(), |sn| sn);
monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());
}
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
for (j, ws) in monitor_config.workspaces.iter().enumerate() {
for (j, ws) in monitor.workspaces.iter().enumerate() {
if let Some(rules) = &ws.workspace_rules {
for r in rules {
workspace_matching_rules.push(WorkspaceMatchingRule {
@@ -1546,60 +1293,6 @@ impl StaticConfig {
}
}
// Check for configs that should be tied to a specific display that isn't loaded right now
// and cache a monitor with those configs with the specific `serial_number_id` so that when
// those devices are connected later we can use the correct config from the cache.
if configs_with_preference.len() > configs_used.len() {
for i in configs_with_preference
.iter()
.filter(|i| !configs_used.contains(i))
{
let id = {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
display_index_preferences.get(i).cloned()
};
if let (Some(id), Some(monitor_config)) =
(id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))
{
// The name, device, device_id and serial_number_id can be empty here since
// once the monitor with this preferred index actually connects the
// `load_monitor_information` function will update these fields.
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"".into(),
"".into(),
"".into(),
None,
);
m.ensure_workspace_count(monitor_config.workspaces.len());
m.set_work_area_offset(monitor_config.work_area_offset);
m.set_window_based_work_area_offset(
monitor_config.window_based_work_area_offset,
);
m.set_window_based_work_area_offset_limit(
monitor_config
.window_based_work_area_offset_limit
.unwrap_or(1),
);
m.set_container_padding(monitor_config.container_padding);
m.set_workspace_padding(monitor_config.workspace_padding);
m.update_workspaces_globals(offset);
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
ws.load_static_config(workspace_config)?;
}
}
monitor_reconciliator::insert_in_monitor_cache(&id, m);
}
}
}
wm.enforce_workspace_rules()?;
if let Some(enabled) = value.border {

View File

@@ -1,18 +1,18 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::Colour;
use crate::KomorebiTheme;
use crate::border_manager;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
use crate::Colour;
use crate::KomorebiTheme;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
use std::ops::Deref;
use std::sync::OnceLock;
use std::sync::atomic::Ordering;
use std::sync::OnceLock;
pub struct Notification(KomorebiTheme);
@@ -50,15 +50,13 @@ pub fn send_notification(theme: KomorebiTheme) {
}
pub fn listen_for_notifications() {
std::thread::spawn(move || {
loop {
match handle_notifications() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
std::thread::spawn(move || loop {
match handle_notifications() {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});

View File

@@ -4,17 +4,17 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use parking_lot::Mutex;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU8;
use std::sync::Arc;
use std::sync::OnceLock;
use crate::REGEX_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
use crate::should_act;
use crate::Window;
use crate::WindowManager;
use crate::WindowsApi;
use crate::should_act;
use crate::REGEX_IDENTIFIERS;
use crate::TRANSPARENCY_BLACKLIST;
pub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);
pub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);
@@ -49,15 +49,13 @@ pub fn send_notification() {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
@@ -177,9 +175,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
match window.transparent() {
Err(error) => {
let hwnd = foreground_hwnd;
tracing::error!(
"failed to make unfocused window {hwnd} transparent: {error}"
)
tracing::error!("failed to make unfocused window {hwnd} transparent: {error}" )
}
Ok(..) => {
known_hwnds.lock().push(window.hwnd);

View File

@@ -1,7 +1,8 @@
use crate::AnimationStyle;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use crate::animation::lerp::Lerp;
use crate::animation::prefix::new_animation_key;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::AnimationEngine;
use crate::animation::RenderDispatcher;
use crate::animation::ANIMATION_DURATION_GLOBAL;
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
@@ -9,15 +10,14 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::ANIMATION_MANAGER;
use crate::animation::ANIMATION_STYLE_GLOBAL;
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
use crate::animation::AnimationEngine;
use crate::animation::RenderDispatcher;
use crate::animation::lerp::Lerp;
use crate::animation::prefix::AnimationPrefix;
use crate::animation::prefix::new_animation_key;
use crate::com::SetCloak;
use crate::focus_manager;
use crate::stackbar_manager;
use crate::windows_api;
use crate::AnimationStyle;
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
@@ -31,23 +31,26 @@ use std::time::Duration;
use crate::core::config_generation::IdWithIdentifier;
use crate::core::config_generation::MatchingRule;
use crate::core::config_generation::MatchingStrategy;
use color_eyre::Result;
use color_eyre::eyre;
use color_eyre::Result;
use crossbeam_utils::atomic::AtomicConsume;
use regex::Regex;
use schemars::JsonSchema;
use serde::ser::SerializeStruct;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeStruct;
use strum::Display;
use strum::EnumString;
use windows::Win32::Foundation::HWND;
use crate::core::ApplicationIdentifier;
use crate::core::HidingBehaviour;
use crate::core::Rect;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
@@ -58,11 +61,6 @@ use crate::NO_TITLEBAR;
use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
use crate::styles::ExtendedWindowStyle;
use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
pub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);
pub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);
@@ -299,9 +297,7 @@ impl RenderDispatcher for TransparencyRenderDispatcher {
}
}
#[derive(
Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum AspectRatio {
/// A predefined aspect ratio
@@ -310,22 +306,13 @@ pub enum AspectRatio {
Custom(i32, i32),
}
impl Default for AspectRatio {
fn default() -> Self {
AspectRatio::Predefined(PredefinedAspectRatio::default())
}
}
#[derive(
Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, JsonSchema, PartialEq,
)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum PredefinedAspectRatio {
/// 21:9
Ultrawide,
/// 16:9
Widescreen,
/// 4:3
#[default]
Standard,
}
@@ -736,30 +723,6 @@ impl Window {
self.update_style(&style)
}
/// Raise the window to the top of the Z order, but do not activate or focus
/// it. Use raise_and_focus_window to activate and focus a window.
/// It also checks if there is a border attached to this window and if it is
/// it raises it as well.
pub fn raise(self) -> Result<()> {
WindowsApi::raise_window(self.hwnd)?;
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::raise_window(border.hwnd)?;
}
Ok(())
}
/// Lower the window to the bottom of the Z order, but do not activate or focus
/// it.
/// It also checks if there is a border attached to this window and if it is
/// it lowers it as well.
pub fn lower(self) -> Result<()> {
WindowsApi::lower_window(self.hwnd)?;
if let Some(border) = crate::border_manager::window_border(self.hwnd) {
WindowsApi::lower_window(border.hwnd)?;
}
Ok(())
}
#[tracing::instrument(fields(exe, title), skip(debug))]
pub fn should_manage(
self,
@@ -886,7 +849,7 @@ fn window_is_eligible(
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let should_ignore = match should_act(
let should_ignore = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -894,15 +857,14 @@ fn window_is_eligible(
&ignore_identifiers,
&regex_identifiers,
) {
Some(rule) => {
debug.matches_ignore_identifier = Some(rule);
true
}
_ => false,
debug.matches_ignore_identifier = Some(rule);
true
} else {
false
};
let manage_identifiers = MANAGE_IDENTIFIERS.lock();
let managed_override = match should_act(
let managed_override = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -910,11 +872,10 @@ fn window_is_eligible(
&manage_identifiers,
&regex_identifiers,
) {
Some(rule) => {
debug.matches_managed_override = Some(rule);
true
}
_ => false,
debug.matches_managed_override = Some(rule);
true
} else {
false
};
let floating_identifiers = FLOATING_APPLICATIONS.lock();
@@ -934,7 +895,7 @@ fn window_is_eligible(
}
let layered_whitelist = LAYERED_WHITELIST.lock();
let mut allow_layered = match should_act(
let mut allow_layered = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -942,11 +903,10 @@ fn window_is_eligible(
&layered_whitelist,
&regex_identifiers,
) {
Some(rule) => {
debug.matches_layered_whitelist = Some(rule);
true
}
_ => false,
debug.matches_layered_whitelist = Some(rule);
true
} else {
false
};
let known_layered_hwnds = transparency_manager::known_hwnds();
@@ -973,7 +933,7 @@ fn window_is_eligible(
};
let titlebars_removed = NO_TITLEBAR.lock();
let allow_titlebar_removed = match should_act(
let allow_titlebar_removed = if let Some(rule) = should_act(
title,
exe_name,
class,
@@ -981,11 +941,10 @@ fn window_is_eligible(
&titlebars_removed,
&regex_identifiers,
) {
Some(rule) => {
debug.matches_no_titlebar = Some(rule);
true
}
_ => false,
debug.matches_no_titlebar = Some(rule);
true
} else {
false
};
{

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,12 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use crate::window::should_act;
use crate::window::Window;
use crate::winevent::WinEvent;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
use crate::REGEX_IDENTIFIERS;
use crate::window::Window;
use crate::window::should_act;
use crate::winevent::WinEvent;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "content")]

View File

@@ -1,15 +1,18 @@
use core::ffi::c_void;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::mem::size_of;
use color_eyre::Result;
use color_eyre::eyre::Error;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use windows::Win32::Foundation::COLORREF;
use color_eyre::eyre::Error;
use color_eyre::Result;
use windows::core::Result as WindowsCrateResult;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HINSTANCE;
use windows::Win32::Foundation::HMODULE;
@@ -18,9 +21,8 @@ use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::POINT;
use windows::Win32::Foundation::RECT;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR;
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
use windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE;
@@ -28,56 +30,50 @@ use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
use windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;
use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::EnumDisplayMonitors;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MonitorFromPoint;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::HBRUSH;
use windows::Win32::Graphics::Gdi::HDC;
use windows::Win32::Graphics::Gdi::HMONITOR;
use windows::Win32::Graphics::Gdi::MONITORENUMPROC;
use windows::Win32::Graphics::Gdi::MONITORINFOEXW;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::Power::HPOWERNOTIFY;
use windows::Win32::System::Power::RegisterPowerSettingNotification;
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
use windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::OpenProcess;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
use windows::Win32::System::Threading::PROCESS_NAME_WIN32;
use windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;
use windows::Win32::System::Threading::QueryFullProcessImageNameW;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::HiDpi::GetDpiForMonitor;
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;
use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;
use windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;
use windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;
use windows::Win32::UI::Input::KeyboardAndMouse::SendInput;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;
use windows::Win32::UI::WindowsAndMessaging::EnumWindows;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;
@@ -88,37 +84,14 @@ use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
use windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;
use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::IsIconic;
use windows::Win32::UI::WindowsAndMessaging::IsWindow;
use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;
use windows::Win32::UI::WindowsAndMessaging::IsZoomed;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::MoveWindow;
use windows::Win32::UI::WindowsAndMessaging::PostMessageW;
use windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterClassW;
use windows::Win32::UI::WindowsAndMessaging::RegisterDeviceNotificationW;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SetCursorPos;
use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;
use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;
@@ -126,6 +99,30 @@ use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::SetWindowPos;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;
use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
use windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;
use windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
@@ -136,27 +133,21 @@ use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;
use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;
use windows::core::PCWSTR;
use windows::core::PWSTR;
use windows::core::Result as WindowsCrateResult;
use windows_core::BOOL;
use crate::core::Rect;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::Window;
use crate::WindowManager;
use crate::container::Container;
use crate::monitor;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks;
use crate::Window;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::MONITOR_INDEX_PREFERENCES;
macro_rules! as_ptr {
($value:expr_2021) => {
($value:expr) => {
$value as *mut core::ffi::c_void
};
}
@@ -237,9 +228,16 @@ impl WindowsApi {
callback: MONITORENUMPROC,
callback_data_address: isize,
) -> Result<()> {
unsafe { EnumDisplayMonitors(None, None, callback, LPARAM(callback_data_address)) }
.ok()
.process()
unsafe {
EnumDisplayMonitors(
HDC(std::ptr::null_mut()),
None,
callback,
LPARAM(callback_data_address),
)
}
.ok()
.process()
}
pub fn valid_hmonitors() -> Result<Vec<(String, isize)>> {
@@ -254,10 +252,7 @@ impl WindowsApi {
.collect::<Vec<_>>())
}
pub fn load_monitor_information(wm: &mut WindowManager) -> Result<()> {
let monitors = &mut wm.monitors;
let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map;
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
'read: for display in win32_display_data::connected_displays_all().flatten() {
let path = display.device_path.clone();
@@ -301,8 +296,7 @@ impl WindowsApi {
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock();
for (index, id) in &*display_index_preferences {
if m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || id.eq(m.device_id())
{
if id.eq(m.device_id()) {
index_preference = Option::from(index);
}
}
@@ -331,40 +325,6 @@ impl WindowsApi {
.elements_mut()
.retain(|m| m.name().ne("PLACEHOLDER"));
// Rebuild monitor index map
*monitor_usr_idx_map = HashMap::new();
let mut added_monitor_idxs = Vec::new();
for (index, id) in &*DISPLAY_INDEX_PREFERENCES.lock() {
if let Some(m_idx) = monitors.elements().iter().position(|m| {
m.serial_number_id().as_ref().is_some_and(|sn| sn == id) || m.device_id() == id
}) {
monitor_usr_idx_map.insert(*index, m_idx);
added_monitor_idxs.push(m_idx);
}
}
let max_usr_idx = monitors
.elements()
.len()
.max(monitor_usr_idx_map.keys().max().map_or(0, |v| *v));
let mut available_usr_idxs = (0..max_usr_idx)
.filter(|i| !monitor_usr_idx_map.contains_key(i))
.collect::<Vec<_>>();
let not_added_monitor_idxs = (0..monitors.elements().len())
.filter(|i| !added_monitor_idxs.contains(i))
.collect::<Vec<_>>();
for i in not_added_monitor_idxs {
if let Some(next_usr_idx) = available_usr_idxs.first() {
monitor_usr_idx_map.insert(*next_usr_idx, i);
available_usr_idxs.remove(0);
} else if let Some(idx) = monitor_usr_idx_map.keys().max() {
monitor_usr_idx_map.insert(*idx, i);
}
}
Ok(())
}
@@ -478,13 +438,10 @@ impl WindowsApi {
unsafe { BringWindowToTop(HWND(as_ptr!(hwnd))) }.process()
}
/// Raise the window to the top of the Z order, but do not activate or focus
/// it. Use raise_and_focus_window to activate and focus a window.
// Raise the window to the top of the Z order, but do not activate or focus
// it. Use raise_and_focus_window to activate and focus a window.
pub fn raise_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW;
let flags = SetWindowPosition::NO_MOVE | SetWindowPosition::NO_ACTIVATE;
let position = HWND_TOP;
Self::set_window_pos(
@@ -495,23 +452,6 @@ impl WindowsApi {
)
}
/// Lower the window to the bottom of the Z order, but do not activate or focus
/// it.
pub fn lower_window(hwnd: isize) -> Result<()> {
let flags = SetWindowPosition::NO_MOVE
| SetWindowPosition::NO_SIZE
| SetWindowPosition::NO_ACTIVATE
| SetWindowPosition::SHOW_WINDOW;
let position = HWND_BOTTOM;
Self::set_window_pos(
HWND(as_ptr!(hwnd)),
&Rect::default(),
position,
flags.bits(),
)
}
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> {
let flags = {
SetWindowPosition::NO_SEND_CHANGING
@@ -533,7 +473,7 @@ impl WindowsApi {
unsafe {
SetWindowPos(
hwnd,
Option::from(position),
position,
layout.left,
layout.top,
layout.right,
@@ -571,7 +511,7 @@ impl WindowsApi {
}
fn post_message(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> Result<()> {
unsafe { PostMessageW(Option::from(hwnd), message, wparam, lparam) }.process()
unsafe { PostMessageW(hwnd, message, wparam, lparam) }.process()
}
pub fn close_window(hwnd: isize) -> Result<()> {
@@ -614,7 +554,7 @@ impl WindowsApi {
// Error ignored, as the operation is not always necessary.
let _ = SetWindowPos(
HWND(as_ptr!(hwnd)),
Option::from(HWND_TOP),
HWND_TOP,
0,
0,
0,
@@ -630,7 +570,7 @@ impl WindowsApi {
#[allow(dead_code)]
pub fn top_window() -> Result<isize> {
unsafe { GetTopWindow(None)? }.process()
unsafe { GetTopWindow(HWND::default())? }.process()
}
pub fn desktop_window() -> Result<isize> {
@@ -946,7 +886,7 @@ impl WindowsApi {
}
pub fn is_window(hwnd: isize) -> bool {
unsafe { IsWindow(Option::from(HWND(as_ptr!(hwnd)))) }.into()
unsafe { IsWindow(HWND(as_ptr!(hwnd))) }.into()
}
pub fn is_window_visible(hwnd: isize) -> bool {
@@ -1037,9 +977,7 @@ impl WindowsApi {
tracing::info!("current value of ForegroundLockTimeout is {value}");
if value != 0 {
tracing::info!(
"updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating"
);
tracing::info!("updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating");
Self::system_parameters_info_w(
SPI_SETFOREGROUNDLOCKTIMEOUT,
@@ -1176,7 +1114,7 @@ impl WindowsApi {
CW_USEDEFAULT,
None,
None,
Option::from(HINSTANCE(as_ptr!(instance))),
HINSTANCE(as_ptr!(instance)),
Some(border as _),
)?
}
@@ -1225,35 +1163,16 @@ impl WindowsApi {
CW_USEDEFAULT,
None,
None,
Option::from(HINSTANCE(as_ptr!(instance))),
HINSTANCE(as_ptr!(instance)),
None,
)?
}
.process()
}
pub fn register_power_setting_notification(
hwnd: isize,
guid: &windows_core::GUID,
flags: REGISTER_NOTIFICATION_FLAGS,
) -> WindowsCrateResult<HPOWERNOTIFY> {
unsafe { RegisterPowerSettingNotification(HANDLE::from(HWND(as_ptr!(hwnd))), guid, flags) }
}
pub fn register_device_notification(
hwnd: isize,
mut filter: DEV_BROADCAST_DEVICEINTERFACE_W,
flags: REGISTER_NOTIFICATION_FLAGS,
) -> WindowsCrateResult<HDEVNOTIFY> {
unsafe {
let state_ptr: *const c_void = &mut filter as *mut _ as *const c_void;
RegisterDeviceNotificationW(HANDLE::from(HWND(as_ptr!(hwnd))), state_ptr, flags)
}
}
pub fn invalidate_rect(hwnd: isize, rect: Option<&Rect>, erase: bool) -> bool {
let rect = rect.map(|rect| &rect.rect() as *const RECT);
unsafe { InvalidateRect(Option::from(HWND(as_ptr!(hwnd))), rect, erase) }.as_bool()
unsafe { InvalidateRect(HWND(as_ptr!(hwnd)), rect, erase) }.as_bool()
}
pub fn alt_is_pressed() -> bool {

View File

@@ -8,19 +8,19 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent;
use crate::winevent_listener;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::WPARAM;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;
use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;
use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;
use windows::Win32::UI::WindowsAndMessaging::WS_CHILD;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;
use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;
use windows_core::BOOL;
pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };
@@ -115,16 +115,6 @@ pub extern "system" fn win_event_hook(
}
}
// sometimes the border focus state and colors don't get updated because this event comes too
// slow for the value of GetForegroundWindow to be up to date by the time it is inspected in
// the border manager to determine if a window show have its border show as "focused"
//
// so here we can just fire another event at the border manager when the system has finally
// registered the new foreground window and this time the correct border colors will be applied
if matches!(winevent, WinEvent::SystemForeground) && !has_filtered_style(hwnd) {
border_manager::send_notification(Some(hwnd.0 as isize));
}
let event_type = match WindowManagerEvent::from_win_event(winevent, window) {
None => {
tracing::trace!(

View File

@@ -3,13 +3,14 @@ use std::time::Duration;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::Accessibility::SetWinEventHook;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;
use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::TranslateMessage;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT;
use windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS;
@@ -40,7 +41,7 @@ pub fn start() {
loop {
unsafe {
if !GetMessageW(&mut msg, None, 0, 0).as_bool() {
if !GetMessageW(&mut msg, HWND(std::ptr::null_mut()), 0, 0).as_bool() {
tracing::debug!("windows event processing thread shutdown");
break;
};

View File

@@ -1,11 +1,9 @@
use std::collections::VecDeque;
use std::fmt::Display;
use std::fmt::Formatter;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
@@ -22,13 +20,6 @@ use crate::core::Layout;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
use crate::WindowContainerBehaviour;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
@@ -40,6 +31,13 @@ use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::window::WindowDetails;
use crate::windows_api::WindowsApi;
use crate::WindowContainerBehaviour;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
#[derive(
@@ -56,67 +54,42 @@ use crate::windows_api::WindowsApi;
)]
pub struct Workspace {
#[getset(get = "pub", set = "pub")]
pub name: Option<String>,
pub containers: Ring<Container>,
name: Option<String>,
containers: Ring<Container>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub monocle_container: Option<Container>,
monocle_container: Option<Container>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
pub monocle_container_restore_idx: Option<usize>,
monocle_container_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub maximized_window: Option<Window>,
maximized_window: Option<Window>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
pub maximized_window_restore_idx: Option<usize>,
maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
pub floating_windows: Vec<Window>,
floating_windows: Vec<Window>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layout: Layout,
layout: Layout,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layout_rules: Vec<(usize, Layout)>,
layout_rules: Vec<(usize, Layout)>,
#[getset(get_copy = "pub", set = "pub")]
pub layout_flip: Option<Axis>,
layout_flip: Option<Axis>,
#[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>,
workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
pub container_padding: Option<i32>,
container_padding: Option<i32>,
#[getset(get = "pub", set = "pub")]
pub latest_layout: Vec<Rect>,
latest_layout: Vec<Rect>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub resize_dimensions: Vec<Option<Rect>>,
resize_dimensions: Vec<Option<Rect>>,
#[getset(get = "pub", set = "pub")]
pub tile: bool,
tile: bool,
#[getset(get_copy = "pub", set = "pub")]
pub apply_window_based_work_area_offset: bool,
apply_window_based_work_area_offset: bool,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub window_container_behaviour: Option<WindowContainerBehaviour>,
window_container_behaviour: Option<WindowContainerBehaviour>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub float_override: Option<bool>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub globals: WorkspaceGlobals,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
pub workspace_config: Option<WorkspaceConfig>,
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum WorkspaceLayer {
#[default]
Tiling,
Floating,
}
impl Display for WorkspaceLayer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WorkspaceLayer::Tiling => write!(f, "Tiling"),
WorkspaceLayer::Floating => write!(f, "Floating"),
}
}
float_override: Option<bool>,
}
impl_ring_elements!(Workspace, Container);
@@ -141,11 +114,7 @@ impl Default for Workspace {
tile: true,
apply_window_based_work_area_offset: true,
window_container_behaviour: None,
window_container_behaviour_rules: None,
float_override: None,
layer: Default::default(),
globals: Default::default(),
workspace_config: None,
}
}
}
@@ -158,37 +127,21 @@ pub enum WorkspaceWindowLocation {
Floating(usize), // idx in floating_windows
}
#[derive(
Debug,
Default,
Copy,
Clone,
Serialize,
Deserialize,
Getters,
CopyGetters,
MutGetters,
Setters,
JsonSchema,
PartialEq,
)]
/// Settings setup either by the parent monitor or by the `WindowManager`
pub struct WorkspaceGlobals {
pub container_padding: Option<i32>,
pub workspace_padding: Option<i32>,
pub work_area: Rect,
pub work_area_offset: Option<Rect>,
pub window_based_work_area_offset: Option<Rect>,
pub window_based_work_area_offset_limit: isize,
}
impl Workspace {
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
self.name = Option::from(config.name.clone());
self.set_container_padding(config.container_padding);
if config.container_padding.is_some() {
self.set_container_padding(config.container_padding);
} else {
self.set_container_padding(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));
}
self.set_workspace_padding(config.workspace_padding);
if config.workspace_padding.is_some() {
self.set_workspace_padding(config.workspace_padding);
} else {
self.set_container_padding(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));
}
if let Some(layout) = &config.layout {
self.layout = Layout::Default(*layout);
@@ -205,55 +158,42 @@ impl Workspace {
self.tile = false;
}
let mut all_layout_rules = vec![];
if let Some(layout_rules) = &config.layout_rules {
let mut all_rules = vec![];
for (count, rule) in layout_rules {
all_layout_rules.push((*count, Layout::Default(*rule)));
all_rules.push((*count, Layout::Default(*rule)));
}
all_layout_rules.sort_by_key(|(i, _)| *i);
self.set_layout_rules(all_rules);
self.tile = true;
}
self.set_layout_rules(all_layout_rules.clone());
if let Some(layout_rules) = &config.custom_layout_rules {
let rules = self.layout_rules_mut();
for (count, pathbuf) in layout_rules {
let rule = CustomLayout::from_path(pathbuf)?;
all_layout_rules.push((*count, Layout::Custom(rule)));
rules.push((*count, Layout::Custom(rule)));
}
all_layout_rules.sort_by_key(|(i, _)| *i);
self.tile = true;
self.set_layout_rules(all_layout_rules);
}
self.set_apply_window_based_work_area_offset(
config.apply_window_based_work_area_offset.unwrap_or(true),
);
self.set_window_container_behaviour(config.window_container_behaviour);
if let Some(window_container_behaviour_rules) = &config.window_container_behaviour_rules {
if window_container_behaviour_rules.is_empty() {
self.set_window_container_behaviour_rules(None);
} else {
let mut all_rules = vec![];
for (count, behaviour) in window_container_behaviour_rules {
all_rules.push((*count, *behaviour));
}
all_rules.sort_by_key(|(i, _)| *i);
self.set_window_container_behaviour_rules(Some(all_rules));
}
} else {
self.set_window_container_behaviour_rules(None);
if config.window_container_behaviour.is_some() {
self.set_window_container_behaviour(config.window_container_behaviour);
}
self.set_float_override(config.float_override);
self.set_layout_flip(config.layout_flip);
if config.float_override.is_some() {
self.set_float_override(config.float_override);
}
self.set_workspace_config(Some(config.clone()));
if config.layout_flip.is_some() {
self.set_layout_flip(config.layout_flip);
}
Ok(())
}
@@ -322,7 +262,7 @@ impl Workspace {
// Maximised windows and floating windows should always be drawn at the top of the Z order
// when switching to a workspace
if let Some(window) = to_focus {
if self.maximized_window().is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.focus(mouse_follows_focus)?;
@@ -334,29 +274,24 @@ impl Workspace {
Ok(())
}
pub fn update(&mut self) -> Result<()> {
pub fn update(
&mut self,
work_area: &Rect,
work_area_offset: Option<Rect>,
window_based_work_area_offset: (isize, Option<Rect>),
) -> Result<()> {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
return Ok(());
}
let container_padding = self
.container_padding()
.or(self.globals().container_padding)
.unwrap_or_default();
let workspace_padding = self
.workspace_padding()
.or(self.globals().workspace_padding)
.unwrap_or_default();
let work_area = self.globals().work_area;
let work_area_offset = self.globals().work_area_offset;
let window_based_work_area_offset = self.globals().window_based_work_area_offset;
let window_based_work_area_offset_limit =
self.globals().window_based_work_area_offset_limit;
let (window_based_work_area_offset_limit, window_based_work_area_offset) =
window_based_work_area_offset;
let container_padding = self.container_padding();
let mut adjusted_work_area = work_area_offset.map_or_else(
|| work_area,
|| *work_area,
|offset| {
let mut with_offset = work_area;
let mut with_offset = *work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
@@ -384,16 +319,16 @@ impl Workspace {
);
}
adjusted_work_area.add_padding(workspace_padding);
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
self.enforce_resize_constraints();
if !self.layout_rules().is_empty() {
let mut updated_layout = None;
for (threshold, layout) in self.layout_rules() {
if self.containers().len() >= *threshold {
updated_layout = Option::from(layout.clone());
for rule in self.layout_rules() {
if self.containers().len() >= rule.0 {
updated_layout = Option::from(rule.1.clone());
}
}
@@ -402,23 +337,12 @@ impl Workspace {
}
}
if let Some(window_container_behaviour_rules) = self.window_container_behaviour_rules() {
let mut updated_behaviour = None;
for (threshold, behaviour) in window_container_behaviour_rules {
if self.containers().len() >= *threshold {
updated_behaviour = Option::from(*behaviour);
}
}
self.set_window_container_behaviour(updated_behaviour);
}
let managed_maximized_window = self.maximized_window().is_some();
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding);
adjusted_work_area.add_padding(container_padding.unwrap_or_default());
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
adjusted_work_area.add_padding(border_offset);
@@ -437,7 +361,7 @@ impl Workspace {
"there must be at least one container to calculate a workspace layout"
)
})?,
Some(container_padding),
self.container_padding(),
self.layout_flip(),
self.resize_dimensions(),
);
@@ -446,6 +370,7 @@ impl Workspace {
let no_titlebar = NO_TITLEBAR.lock().clone();
let regex_identifiers = REGEX_IDENTIFIERS.lock().clone();
let container_padding = self.container_padding().unwrap_or(0);
let containers = self.containers_mut();
for (i, container) in containers.iter_mut().enumerate() {
@@ -508,16 +433,7 @@ impl Workspace {
// number of layouts / containers. This should never actually truncate as the remove_window
// function takes care of cleaning up resize dimensions when destroying empty containers
let container_count = self.containers().len();
// since monocle is a toggle, we never want to truncate the resize dimensions since it will
// almost always be toggled off and the container will be reintegrated into layout
//
// without this check, if there are exactly two containers, when one is toggled to monocle
// the resize dimensions will be truncated to len == 1, and when it is reintegrated, if it
// had a resize adjustment before, that will have been lost
if self.monocle_container().is_none() {
self.resize_dimensions_mut().resize(container_count, None);
}
self.resize_dimensions_mut().resize(container_count, None);
Ok(())
}
@@ -549,15 +465,7 @@ impl Workspace {
}
for window in self.visible_windows().into_iter().flatten() {
if !window.is_window()
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
// docs is closed
//
// I hate every single person who worked on Microsoft Office 365, especially Word
|| !window.is_visible()
{
if !window.is_window() {
hwnds.push(window.hwnd);
}
}

View File

@@ -1,7 +1,7 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::WindowManager;
use crate::border_manager;
use crate::WindowManager;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicCell;
@@ -51,18 +51,16 @@ pub fn send_notification(monitor_idx: usize, workspace_idx: usize) {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || {
loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
if cfg!(debug_assertions) {
tracing::error!("restarting failed thread: {:?}", error)
} else {
tracing::error!("restarting failed thread: {}", error)
}
}
}

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebic-no-console"
version = "0.1.35"
version = "0.1.34"
description = "The command-line interface (without a console) for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,9 +1,9 @@
[package]
name = "komorebic"
version = "0.1.35"
version = "0.1.34"
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
repository = "https://github.com/LGUG2Z/komorebi"
edition = "2024"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -24,10 +24,13 @@ reqwest = { version = "0.12", features = ["blocking"] }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = "0.9"
shadow-rs = { workspace = true }
sysinfo = { workspace = true }
thiserror = "2"
uds_windows = { workspace = true }
which = { workspace = true }
win32-display-data = { workspace = true }
windows = { workspace = true }
[build-dependencies]

View File

@@ -17,31 +17,31 @@ use std::time::Duration;
use clap::CommandFactory;
use clap::Parser;
use clap::ValueEnum;
use color_eyre::Result;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use dirs::data_local_dir;
use fs_tail::TailedFile;
use komorebi_client::ApplicationSpecificConfiguration;
use komorebi_client::Notification;
use komorebi_client::resolve_home_path;
use komorebi_client::send_message;
use komorebi_client::send_query;
use komorebi_client::ApplicationSpecificConfiguration;
use komorebi_client::Notification;
use lazy_static::lazy_static;
use miette::NamedSource;
use miette::Report;
use miette::SourceOffset;
use miette::SourceSpan;
use paste::paste;
use schemars::r#gen::SchemaSettings;
use schemars::gen::SchemaSettings;
use schemars::schema_for;
use serde::Deserialize;
use sysinfo::ProcessesToUpdate;
use which::which;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use komorebi_client::ApplicationConfigurationGenerator;
use komorebi_client::ApplicationIdentifier;
@@ -1108,8 +1108,6 @@ enum SubCommand {
/// Focus the specified monitor
#[clap(arg_required_else_help = true)]
FocusMonitor(FocusMonitor),
/// Focus the monitor at the current cursor location
FocusMonitorAtCursor,
/// Focus the last focused workspace on the focused monitor
FocusLastWorkspace,
/// Focus the specified workspace on the focused monitor
@@ -1155,8 +1153,6 @@ enum SubCommand {
/// Set offsets for a monitor to exclude parts of the work area from tiling
#[clap(arg_required_else_help = true)]
MonitorWorkAreaOffset(MonitorWorkAreaOffset),
/// Toggle application of the window-based work area offset for the focused workspace
ToggleWindowBasedWorkAreaOffset,
/// Set container padding on the focused workspace
#[clap(arg_required_else_help = true)]
FocusedWorkspaceContainerPadding(FocusedWorkspaceContainerPadding),
@@ -1269,8 +1265,6 @@ enum SubCommand {
/// mode, for the currently focused workspace. If there was no override value set for the
/// workspace previously it takes the opposite of the global value.
ToggleWorkspaceFloatOverride,
/// Toggle between the Tiling and Floating layers on the focused workspace
ToggleWorkspaceLayer,
/// Toggle window tiling on the focused workspace
TogglePause,
/// Toggle window tiling on the focused workspace
@@ -1541,9 +1535,7 @@ fn main() -> Result<()> {
let whkdrc = include_str!("../../docs/whkdrc.sample");
std::fs::write(WHKD_CONFIG_DIR.join("whkdrc"), whkdrc)?;
println!(
"Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created"
);
println!("Example komorebi.json, komorebi.bar.json, whkdrc and latest applications.json files created");
println!("You can now run komorebic start --whkd --bar");
}
SubCommand::EnableAutostart(args) => {
@@ -1589,12 +1581,8 @@ fn main() -> Result<()> {
.env("TARGET_ARGS", arguments)
.output()?;
println!(
"NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,"
);
println!(
"you need to add this to System Properties > Environment Variables > User Variables"
);
println!("NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,");
println!("you need to add this to System Properties > Environment Variables > User Variables");
println!("in order for the autostart command to work properly");
}
SubCommand::DisableAutostart => {
@@ -1658,23 +1646,16 @@ fn main() -> Result<()> {
println!("{:?}", Report::new(diagnostic));
}
println!(
"Found komorebi.json; this file can be passed to the start command with the --config flag\n"
);
println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n");
if let Ok(config) = StaticConfig::read(&static_config) {
match config.app_specific_configuration_path {
None => {
println!(
"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"
);
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n");
}
Some(path) => {
if !Path::exists(Path::new(&path)) {
println!(
"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n",
path.display()
);
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
}
}
}
@@ -1692,14 +1673,9 @@ fn main() -> Result<()> {
StaticConfig::end_of_life(&raw);
if config_whkd.exists() {
println!(
"Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n",
config_whkd.to_string_lossy()
);
println!("Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n", config_whkd.to_string_lossy());
} else {
println!(
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
);
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
}
} else if config_pwsh.exists() {
println!("Found komorebi.ps1; this file will be autoloaded by komorebi\n");
@@ -1709,17 +1685,13 @@ fn main() -> Result<()> {
config_whkd.to_string_lossy()
);
} else {
println!(
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
);
println!("No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n");
}
} else if config_ahk.exists() {
println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n");
} else {
println!("No komorebi configuration found in {home_display}\n");
println!(
"If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n"
);
println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n");
}
let client = reqwest::blocking::Client::new();
@@ -1741,9 +1713,7 @@ fn main() -> Result<()> {
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!(
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
);
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
}
}
}
@@ -1906,9 +1876,6 @@ fn main() -> Result<()> {
bottom: arg.bottom,
}))?;
}
SubCommand::ToggleWindowBasedWorkAreaOffset => {
send_message(&SocketMessage::ToggleWindowBasedWorkAreaOffset)?;
}
SubCommand::ContainerPadding(arg) => {
send_message(&SocketMessage::ContainerPadding(
arg.monitor,
@@ -2058,45 +2025,38 @@ fn main() -> Result<()> {
}
if arg.whkd && which("whkd").is_err() {
bail!(
"could not find whkd, please make sure it is installed before using the --whkd flag"
);
bail!("could not find whkd, please make sure it is installed before using the --whkd flag");
}
if arg.masir && which("masir").is_err() {
bail!(
"could not find masir, please make sure it is installed before using the --masir flag"
);
bail!("could not find masir, please make sure it is installed before using the --masir flag");
}
if arg.ahk && which(&ahk).is_err() {
bail!(
"could not find autohotkey, please make sure it is installed before using the --ahk flag"
);
bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag");
}
let mut buf: PathBuf;
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop
let exec = match Command::new("where.exe").arg("komorebi.ps1").output() {
Ok(output) => {
let stdout = String::from_utf8(output.stdout)?;
match stdout.trim() {
"" => None,
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
stdout if !stdout.contains("scoop") => None,
stdout => {
buf = PathBuf::from(stdout);
buf.pop(); // %USERPROFILE%\scoop\shims
buf.pop(); // %USERPROFILE%\scoop
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
Some(buf.to_str().ok_or_else(|| {
anyhow!("cannot create a string from the scoop komorebi path")
})?)
}
let exec = if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
let stdout = String::from_utf8(output.stdout)?;
match stdout.trim() {
"" => None,
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
stdout if !stdout.contains("scoop") => None,
stdout => {
buf = PathBuf::from(stdout);
buf.pop(); // %USERPROFILE%\scoop\shims
buf.pop(); // %USERPROFILE%\scoop
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
Some(buf.to_str().ok_or_else(|| {
anyhow!("cannot create a string from the scoop komorebi path")
})?)
}
}
_ => None,
} else {
None
};
let mut flags = vec![];
@@ -2298,28 +2258,18 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
println!("\nThank you for using komorebi!\n");
println!("# Commercial Use License");
println!(
"* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work"
);
println!("* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work");
println!("\n# Personal Use Sponsorship");
println!(
"* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference"
);
println!("* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference");
println!("* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors");
println!("\n# Community");
println!(
"* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops"
);
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!(
"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews"
);
println!(
"* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem"
);
println!("* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem");
println!("\n# Documentation");
println!(
"* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"
);
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let bar_config = arg.config.map_or_else(
|| {
@@ -2366,9 +2316,7 @@ if (!(Get-Process masir -ErrorAction SilentlyContinue))
{
let trimmed = release.tag_name.trim_start_matches("v");
if trimmed > version {
println!(
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
);
println!("An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}");
}
}
}
@@ -2640,9 +2588,6 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::FocusMonitor(arg) => {
send_message(&SocketMessage::FocusMonitorNumber(arg.target))?;
}
SubCommand::FocusMonitorAtCursor => {
send_message(&SocketMessage::FocusMonitorAtCursor)?;
}
SubCommand::FocusLastWorkspace => {
send_message(&SocketMessage::FocusLastWorkspace)?;
}
@@ -2899,9 +2844,6 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::ToggleWorkspaceFloatOverride => {
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
}
SubCommand::ToggleWorkspaceLayer => {
send_message(&SocketMessage::ToggleWorkspaceLayer)?;
}
SubCommand::WindowHidingBehaviour(arg) => {
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
}
@@ -2993,9 +2935,7 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
file.write_all(formatted_content.as_bytes())?;
println!(
"File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration"
);
println!("File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration");
}
SubCommand::FetchAppSpecificConfiguration => {
let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json")?
@@ -3011,12 +2951,10 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
file.write_all(content.as_bytes())?;
println!("Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n");
println!(
"Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n"
);
println!(
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
output_file.display().to_string().replace("\\", "/")
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
output_file.display().to_string().replace("\\", "/")
);
}
SubCommand::ApplicationSpecificConfigurationSchema => {
@@ -3041,8 +2979,8 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
s.inline_subschemas = true;
});
let r#gen = settings.into_generator();
let socket_message = r#gen.into_root_schema_for::<StaticConfig>();
let gen = settings.into_generator();
let socket_message = gen.into_root_schema_for::<StaticConfig>();
let schema = serde_json::to_string_pretty(&socket_message)?;
println!("{schema}");
}

View File

@@ -127,7 +127,6 @@ nav:
- cli/send-to-monitor-workspace.md
- cli/move-to-monitor-workspace.md
- cli/focus-monitor.md
- cli/focus-monitor-at-cursor.md
- cli/focus-last-workspace.md
- cli/focus-workspace.md
- cli/focus-workspaces.md
@@ -144,7 +143,6 @@ nav:
- cli/invisible-borders.md
- cli/global-work-area-offset.md
- cli/monitor-work-area-offset.md
- cli/toggle-window-based-work-area-offset.md
- cli/focused-workspace-container-padding.md
- cli/focused-workspace-padding.md
- cli/adjust-container-padding.md
@@ -177,7 +175,6 @@ nav:
- cli/toggle-float-override.md
- cli/toggle-workspace-window-container-behaviour.md
- cli/toggle-workspace-float-override.md
- cli/toggle-workspace-layer.md
- cli/toggle-pause.md
- cli/toggle-tiling.md
- cli/toggle-float.md

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KomobarConfig",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.35`",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.34`",
"type": "object",
"required": [
"left_widgets",
@@ -271,66 +271,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Keyboard"
],
"properties": {
"Keyboard": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 1 second)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Input widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -504,19 +444,6 @@
}
}
},
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable"
],
"properties": {
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
@@ -529,73 +456,39 @@
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"description": "Show only icon",
"type": "string",
"enum": [
"AllIcons"
"Icon"
]
},
{
"description": "Show both all icons and text",
"description": "Show only text",
"type": "string",
"enum": [
"AllIconsAndText"
"Text"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
"TextAndIconOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
@@ -1625,66 +1518,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Keyboard"
],
"properties": {
"Keyboard": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 1 second)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Input widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -1858,19 +1691,6 @@
}
}
},
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable"
],
"properties": {
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
@@ -1883,73 +1703,39 @@
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"description": "Show only icon",
"type": "string",
"enum": [
"AllIcons"
"Icon"
]
},
{
"description": "Show both all icons and text",
"description": "Show only text",
"type": "string",
"enum": [
"AllIconsAndText"
"Text"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
"TextAndIconOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
@@ -2912,66 +2698,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"Keyboard"
],
"properties": {
"Keyboard": {
"type": "object",
"required": [
"enable"
],
"properties": {
"data_refresh_interval": {
"description": "Data refresh interval (default: 1 second)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"enable": {
"description": "Enable the Input widget",
"type": "boolean"
},
"label_prefix": {
"description": "Display label prefix",
"oneOf": [
{
"description": "Show no prefix",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Show an icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text",
"type": "string",
"enum": [
"IconAndText"
]
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -3145,19 +2871,6 @@
}
}
},
"workspace_layer": {
"description": "Configure the Workspace Layer widget",
"type": "object",
"required": [
"enable"
],
"properties": {
"enable": {
"description": "Enable the Komorebi Workspace Layer widget",
"type": "boolean"
}
}
},
"workspaces": {
"description": "Configure the Workspaces widget",
"type": "object",
@@ -3170,73 +2883,39 @@
"description": "Display format of the workspace",
"oneOf": [
{
"description": "Show all icons only",
"description": "Show only icon",
"type": "string",
"enum": [
"AllIcons"
"Icon"
]
},
{
"description": "Show both all icons and text",
"description": "Show only text",
"type": "string",
"enum": [
"AllIconsAndText"
"Text"
]
},
{
"description": "Show all icons and text for the selected element, and all icons on the rest",
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"AllIconsAndTextOnSelected"
"TextAndIconOnSelected"
]
},
{
"type": "object",
"required": [
"Existing"
],
"properties": {
"Existing": {
"oneOf": [
{
"description": "Show only icon",
"type": "string",
"enum": [
"Icon"
]
},
{
"description": "Show only text",
"type": "string",
"enum": [
"Text"
]
},
{
"description": "Show an icon and text for the selected element, and text on the rest",
"type": "string",
"enum": [
"TextAndIconOnSelected"
]
},
{
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
}
},
"additionalProperties": false
"description": "Show both icon and text",
"type": "string",
"enum": [
"IconAndText"
]
},
{
"description": "Show an icon and text for the selected element, and icons on the rest",
"type": "string",
"enum": [
"IconAndTextOnSelected"
]
}
]
},
@@ -3744,7 +3423,7 @@
]
},
"name": {
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)",
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)",
"type": "string",
"enum": [
"3024",

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.35`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.34`",
"type": "object",
"properties": {
"animation": {
@@ -1075,11 +1075,6 @@
"workspaces"
],
"properties": {
"container_padding": {
"description": "Container padding (default: global)",
"type": "integer",
"format": "int32"
},
"window_based_work_area_offset": {
"description": "Window based work area offset (default: None)",
"type": "object",
@@ -1149,11 +1144,6 @@
}
}
},
"workspace_padding": {
"description": "Workspace padding (default: global)",
"type": "integer",
"format": "int32"
},
"workspaces": {
"description": "Workspace configurations",
"type": "array",
@@ -1294,7 +1284,7 @@
]
},
"layout_rules": {
"description": "Layout rules in the format of threshold => layout (default: None)",
"description": "Layout rules (default: None)",
"type": "object",
"additionalProperties": {
"type": "string",
@@ -1333,30 +1323,8 @@
}
]
},
"window_container_behaviour_rules": {
"description": "Window container behaviour rules in the format of threshold => behaviour (default: None)",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"description": "Create a new container for each new window",
"type": "string",
"enum": [
"Create"
]
},
{
"description": "Append new windows to the focused window container",
"type": "string",
"enum": [
"Append"
]
}
]
}
},
"workspace_padding": {
"description": "Workspace padding (default: global)",
"description": "Container padding (default: global)",
"type": "integer",
"format": "int32"
},
@@ -2277,7 +2245,7 @@
]
},
"name": {
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)",
"description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/base16-gallery)",
"type": "string",
"enum": [
"3024",