Compare commits

..

8 Commits

Author SHA1 Message Date
LGUG2Z
261daedcf5 fix(borders): send notif on ignored hwnd events 2024-10-02 08:59:38 -07:00
LGUG2Z
bd24940173 fix(borders): update on floating win drag 2024-10-01 09:21:01 -07:00
LGUG2Z
9034122447 fix(borders): no redraws on floating win title change
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
105957b5fa refactor(wm): float_rules > ignore_rules w/ compat
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
bf5b675498 feat(wm): allow ws moves of floating apps
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
8c8d1175ba feat(wm): separate floating and ignored apps
WIP
2024-10-01 09:21:01 -07:00
LGUG2Z
bf2b73837b fix(windows): conditional compilation for 32-bit
This commit ensures that komorebi will compile when targeting 32-bit
architectures, namely `stable-i686-pc-windows-msvc`.

Thanks to @kennykerr for pointing out that Get/SetWindowLongPtrA/W calls
don't actually exist on 32-bit builds of Windows and are aliased instead
to Get/SetWindowLongA/W which take i32 args instead of isize args:
https://github.com/microsoft/windows-rs/issues/3304
2024-10-01 08:54:18 -07:00
LGUG2Z
04cde3f757 feat(wm): add all matching strats for ws rules
This commit ensures that the full range of matching strategies for both
Simple and Composite matching rules will be respected for both initial
and persistent workspace rules.

The generate-static-config command will no longer attempt to populate
workspace rules, and will likely slowly be deprecated as the
overwhelming majority have users have already migrated to the static
configuration file format.
2024-09-29 18:10:50 -07:00
22 changed files with 440 additions and 475 deletions

1
Cargo.lock generated
View File

@@ -2722,7 +2722,6 @@ dependencies = [
"num",
"num-derive",
"num-traits",
"random_word",
"schemars",
"serde",
"serde_json_lenient",

View File

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

View File

@@ -82,7 +82,7 @@ A [detailed installation and quickstart
guide](https://lgug2z.github.io/komorebi/installation.html) is available which shows how to get started
using `scoop`, `winget` or building from source.
[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/MMZUAtHbTYY/hqdefault.jpg)](https://www.youtube.com/watch?v=MMZUAtHbTYY)
[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/H9-_c1egQ4g/hqdefault.jpg)](https://www.youtube.com/watch?v=H9-_c1egQ4g)
# Comparison With Fancy Zones

View File

@@ -24,7 +24,6 @@ netdev = "0.31"
num = "0.4.3"
num-derive = "0.4.2"
num-traits = "0.2.19"
random_word = { version = "0.4.3", features = ["en"] }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@@ -1,17 +1,10 @@
use crate::config::KomobarConfig;
use crate::config::KomobarTheme;
use crate::config::Position;
use crate::config::PositionConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::BAR_HEIGHT;
use crate::MAX_LABEL_WIDTH;
use crate::MONITOR_LEFT;
use crate::MONITOR_RIGHT;
use crate::MONITOR_TOP;
use crossbeam_channel::Receiver;
use eframe::egui::Align;
use eframe::egui::CentralPanel;
@@ -26,6 +19,8 @@ use eframe::egui::Layout;
use eframe::egui::Margin;
use eframe::egui::Style;
use eframe::egui::TextStyle;
use eframe::egui::Vec2;
use eframe::egui::ViewportCommand;
use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::KomorebiTheme;
@@ -149,43 +144,14 @@ impl Komobar {
Self::add_custom_font(ctx, font_family);
}
let position = config.position.clone().unwrap_or(PositionConfig {
start: Some(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
}),
end: Some(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
}),
});
if let Some(hwnd) = process_hwnd() {
let start = position.start.unwrap_or(Position {
x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,
y: MONITOR_TOP.load(Ordering::SeqCst) as f32,
});
let end = position.end.unwrap_or(Position {
x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,
y: BAR_HEIGHT,
});
let rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
right: end.x as i32,
bottom: end.y as i32,
};
let window = komorebi_client::Window::from(hwnd);
match window.set_position(&rect, false) {
Ok(_) => {
tracing::info!("updated bar position");
}
Err(error) => {
tracing::error!("{}", error.to_string())
if let Some(viewport) = &config.viewport {
if let Some(inner_size) = viewport.inner_size {
let mut vec2 = Vec2::new(inner_size.x, inner_size.y * 2.0);
if self.scale_factor != 1.0 {
vec2 = Vec2::new(inner_size.x / self.scale_factor, inner_size.y * 2.0);
}
ctx.send_viewport_cmd(ViewportCommand::InnerSize(vec2));
}
}
@@ -324,8 +290,6 @@ impl Komobar {
scale_factor: cc.egui_ctx.native_pixels_per_point().unwrap_or(1.0),
};
komobar.apply_config(&cc.egui_ctx, &config, None);
// needs a double apply the first time for some reason
komobar.apply_config(&cc.egui_ctx, &config, None);
komobar

View File

@@ -7,15 +7,13 @@ use komorebi_client::Rect;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
/// The `komorebi.bar.json` configuration file reference for `v0.1.30`
pub struct KomobarConfig {
/// Bar positioning options
#[serde(alias = "viewport")]
pub position: Option<PositionConfig>,
/// Viewport options (see: https://docs.rs/egui/latest/egui/viewport/struct.ViewportBuilder.html)
pub viewport: Option<ViewportConfig>,
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/struct.Frame.html)
pub frame: Option<FrameConfig>,
/// Monitor options
@@ -34,43 +32,12 @@ pub struct KomobarConfig {
pub right_widgets: Vec<WidgetConfig>,
}
impl KomobarConfig {
pub fn aliases(raw: &str) {
let mut map = HashMap::new();
map.insert("position", ["viewport"]);
map.insert("end", ["inner_frame"]);
let mut display = false;
for aliases in map.values() {
for a in aliases {
if raw.contains(a) {
display = true;
}
}
}
if display {
println!("\nYour bar configuration file contains some options that have been renamed or deprecated:\n");
for (canonical, aliases) in map {
for alias in aliases {
if raw.contains(alias) {
println!(r#""{alias}" is now "{canonical}""#);
}
}
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct PositionConfig {
pub struct ViewportConfig {
/// The desired starting position of the bar (0,0 = top left of the screen)
#[serde(alias = "position")]
pub start: Option<Position>,
pub position: Option<Position>,
/// The desired size of the bar from the starting position (usually monitor width x desired height)
#[serde(alias = "inner_size")]
pub end: Option<Position>,
pub inner_size: Option<Position>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]

View File

@@ -14,7 +14,6 @@ mod widget;
use crate::bar::Komobar;
use crate::config::KomobarConfig;
use crate::config::Position;
use crate::config::PositionConfig;
use clap::Parser;
use eframe::egui::ViewportBuilder;
use font_loader::system_fonts;
@@ -26,27 +25,12 @@ use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;
use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
pub static WIDGET_SPACING: f32 = 10.0;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
#[derive(Parser)]
#[clap(author, about, version)]
@@ -63,46 +47,9 @@ struct Opts {
/// Write an example komorebi.bar.json to disk
#[clap(long)]
quickstart: bool,
/// Print a list of aliases that can be renamed to canonical variants
#[clap(long)]
#[clap(hide = true)]
aliases: bool,
}
extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
unsafe {
let mut process_id = 0;
GetWindowThreadProcessId(hwnd, Some(&mut process_id));
if process_id == GetCurrentProcessId() {
*(lparam.0 as *mut HWND) = hwnd;
BOOL::from(false) // Stop enumeration
} else {
BOOL::from(true) // Continue enumeration
}
}
}
fn process_hwnd() -> Option<isize> {
unsafe {
let mut hwnd = HWND::default();
let _ = EnumThreadWindows(
GetCurrentThreadId(),
Some(enum_window),
LPARAM(&mut hwnd as *mut HWND as isize),
);
if hwnd.0 as isize == 0 {
None
} else {
Some(hwnd.0 as isize)
}
}
}
fn main() -> color_eyre::Result<()> {
unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }?;
let opts: Opts = Opts::parse();
if opts.schema {
@@ -181,7 +128,7 @@ fn main() -> color_eyre::Result<()> {
Option::from,
);
let mut config = match config_path {
let config = match config_path {
None => {
let komorebi_bar_json =
include_str!("../../docs/komorebi.bar.example.json").to_string();
@@ -195,12 +142,10 @@ fn main() -> color_eyre::Result<()> {
KomobarConfig::read(&default_config_path)?
}
Some(ref config) => {
if !opts.aliases {
tracing::info!(
"found configuration file: {}",
config.as_path().to_string_lossy()
);
}
tracing::info!(
"found configuration file: {}",
config.as_path().to_string_lossy()
);
KomobarConfig::read(config)?
}
@@ -208,64 +153,33 @@ fn main() -> color_eyre::Result<()> {
let config_path = config_path.unwrap_or(default_config_path);
if opts.aliases {
KomobarConfig::aliases(&std::fs::read_to_string(&config_path)?);
std::process::exit(0);
}
let state = serde_json::from_str::<komorebi_client::State>(&komorebi_client::send_query(
&SocketMessage::State,
)?)?;
MONITOR_RIGHT.store(
state.monitors.elements()[config.monitor.index].size().right,
Ordering::SeqCst,
);
MONITOR_TOP.store(
state.monitors.elements()[config.monitor.index].size().top,
Ordering::SeqCst,
);
MONITOR_TOP.store(
state.monitors.elements()[config.monitor.index].size().left,
Ordering::SeqCst,
);
match config.position {
None => {
config.position = Some(PositionConfig {
start: Some(Position {
x: state.monitors.elements()[config.monitor.index].size().left as f32,
y: state.monitors.elements()[config.monitor.index].size().top as f32,
}),
end: Some(Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 50.0,
}),
})
}
Some(ref mut position) => {
if position.start.is_none() {
position.start = Some(Position {
x: state.monitors.elements()[config.monitor.index].size().left as f32,
y: state.monitors.elements()[config.monitor.index].size().top as f32,
});
}
if position.end.is_none() {
position.end = Some(Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 50.0,
})
}
}
}
let viewport_builder = ViewportBuilder::default()
let mut viewport_builder = ViewportBuilder::default()
.with_decorations(false)
// .with_transparent(config.transparent)
.with_taskbar(false);
.with_taskbar(false)
.with_position(Position { x: 0.0, y: 0.0 })
.with_inner_size({
Position {
x: state.monitors.elements()[config.monitor.index].size().right as f32,
y: 20.0,
}
});
if let Some(viewport) = &config.viewport {
if let Some(position) = &viewport.position {
let b = viewport_builder.clone();
viewport_builder = b.with_position(*position);
}
if let Some(inner_size) = &viewport.inner_size {
let b = viewport_builder.clone();
viewport_builder = b.with_inner_size(*inner_size);
}
}
let native_options = eframe::NativeOptions {
viewport: viewport_builder,
@@ -325,12 +239,10 @@ fn main() -> color_eyre::Result<()> {
let ctx_komorebi = cc.egui_ctx.clone();
std::thread::spawn(move || {
let subscriber_name = format!("komorebi-bar-{}", random_word::gen(random_word::Lang::En));
let listener = komorebi_client::subscribe(&subscriber_name)
let listener = komorebi_client::subscribe("komorebi-bar")
.expect("could not subscribe to komorebi notifications");
tracing::info!("subscribed to komorebi notifications: \"{}\"", subscriber_name);
tracing::info!("subscribed to komorebi notifications: \"komorebi-bar\"");
for client in listener.incoming() {
match client {
@@ -344,7 +256,9 @@ fn main() -> color_eyre::Result<()> {
// keep trying to reconnect to komorebi
while komorebi_client::send_message(
&SocketMessage::AddSubscriberSocket(subscriber_name.clone()),
&SocketMessage::AddSubscriberSocket(String::from(
"komorebi-bar",
)),
)
.is_err()
{

View File

@@ -49,6 +49,8 @@ lazy_static! {
pub static ref MONOCLE: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153))));
pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66))));
pub static ref FLOATING: AtomicU32 =
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(245, 245, 165))));
}
lazy_static! {
@@ -57,7 +59,7 @@ lazy_static! {
static ref FOCUS_STATE: Mutex<HashMap<isize, WindowKind>> = Mutex::new(HashMap::new());
}
pub struct Notification;
pub struct Notification(pub Option<isize>);
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
@@ -73,8 +75,8 @@ fn event_rx() -> Receiver<Notification> {
channel().1.clone()
}
pub fn send_notification() {
if event_tx().try_send(Notification).is_err() {
pub fn send_notification(hwnd: Option<isize>) {
if event_tx().try_send(Notification(hwnd)).is_err() {
tracing::warn!("channel is full; dropping notification")
}
}
@@ -118,6 +120,7 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 {
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
WindowKind::Stack => STACK.load(Ordering::SeqCst),
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
WindowKind::Floating => FLOATING.load(Ordering::SeqCst),
}
}
@@ -139,19 +142,29 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
let receiver = event_rx();
event_tx().send(Notification)?;
event_tx().send(Notification(None))?;
let mut previous_snapshot = Ring::default();
let mut previous_pending_move_op = None;
let mut previous_is_paused = false;
let mut previous_notification: Option<Notification> = None;
'receiver: for _ in receiver {
'receiver: for notification in receiver {
// Check the wm state every time we receive a notification
let state = wm.lock();
let is_paused = state.is_paused;
let focused_monitor_idx = state.focused_monitor_idx();
let focused_workspace_idx =
state.monitors.elements()[focused_monitor_idx].focused_workspace_idx();
let monitors = state.monitors.clone();
let pending_move_op = state.pending_move_op;
let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces()
[focused_workspace_idx]
.floating_windows()
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
drop(state);
match IMPLEMENTATION.load() {
@@ -220,6 +233,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
should_process_notification = true;
}
// when we switch focus to a floating window
if !should_process_notification
&& floating_window_hwnds.contains(&notification.0.unwrap_or_default())
{
should_process_notification = true;
}
if !should_process_notification {
if let Some(ref previous) = previous_notification {
if previous.0.unwrap_or_default() != notification.0.unwrap_or_default() {
should_process_notification = true;
}
}
}
if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification");
continue 'receiver;
@@ -345,16 +373,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
// Destroy any borders not associated with the focused workspace
let container_ids = ws
let mut container_and_floating_window_ids = ws
.containers()
.iter()
.map(|c| c.id().clone())
.collect::<Vec<_>>();
for w in ws.floating_windows() {
container_and_floating_window_ids.push(w.hwnd.to_string());
}
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& !container_ids.contains(id)
&& !container_and_floating_window_ids.contains(id)
{
border.destroy()?;
to_remove.push(id.clone());
@@ -366,8 +398,14 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
for (idx, c) in ws.containers().iter().enumerate() {
let hwnd = c.focused_window().copied().unwrap_or_default().hwnd;
let notification_hwnd = notification.0.unwrap_or_default();
// Update border when moving or resizing with mouse
if pending_move_op.is_some() && idx == ws.focused_container_idx() {
if pending_move_op.is_some()
&& idx == ws.focused_container_idx()
&& hwnd == notification_hwnd
{
let restore_z_order = Z_ORDER.load();
Z_ORDER.store(ZOrder::TopMost);
@@ -446,6 +484,101 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.update(&rect, should_invalidate)?;
}
{
let restore_z_order = Z_ORDER.load();
Z_ORDER.store(ZOrder::TopMost);
'windows: for window in ws.floating_windows() {
let hwnd = window.hwnd;
let notification_hwnd = notification.0.unwrap_or_default();
if pending_move_op.is_some() && hwnd == notification_hwnd {
let mut rect = WindowsApi::window_rect(hwnd)?;
while WindowsApi::lbutton_is_pressed() {
let border = match borders.entry(hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(&hwnd.to_string())
{
entry.insert(border)
} else {
continue 'monitors;
}
}
};
let new_rect = WindowsApi::window_rect(hwnd)?;
if rect != new_rect {
rect = new_rect;
border.update(&rect, true)?;
}
}
Z_ORDER.store(restore_z_order);
continue 'monitors;
}
let border = match borders.entry(window.hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(&window.hwnd.to_string())
{
entry.insert(border)
} else {
continue 'monitors;
}
}
};
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
let mut should_destroy = false;
if let Some(notification_hwnd) = notification.0 {
if notification_hwnd != window.hwnd {
should_destroy = true;
}
}
if WindowsApi::foreground_window().unwrap_or_default()
!= window.hwnd
{
should_destroy = true;
}
if should_destroy {
border.destroy()?;
borders.remove(&window.hwnd.to_string());
borders_monitors.remove(&window.hwnd.to_string());
continue 'windows;
}
#[allow(unused_assignments)]
let mut last_focus_state = None;
let new_focus_state = WindowKind::Floating;
{
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 should_invalidate = match last_focus_state {
None => true,
Some(last_focus_state) => last_focus_state != new_focus_state,
};
border.update(&rect, should_invalidate)?;
}
Z_ORDER.store(restore_z_order);
}
}
}
}
@@ -454,6 +587,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
previous_snapshot = monitors;
previous_pending_move_op = pending_move_op;
previous_is_paused = is_paused;
previous_notification = Some(notification);
}
Ok(())

View File

@@ -116,7 +116,8 @@ pub struct ApplicationConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<ApplicationOptions>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub float_identifiers: Option<Vec<MatchingRule>>,
#[serde(alias = "float_identifiers")]
pub ignore_identifiers: Option<Vec<MatchingRule>>,
}
impl ApplicationConfiguration {
@@ -187,7 +188,7 @@ impl ApplicationConfigurationGenerator {
let mut lines = vec![String::from("# Generated by komorebic.exe"), String::new()];
let mut float_rules = vec![];
let mut ignore_rules = vec![];
for app in cfgen {
lines.push(format!("# {}", app.name));
@@ -201,15 +202,15 @@ impl ApplicationConfigurationGenerator {
}
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let Some(ignore_identifiers) = app.ignore_identifiers {
for matching_rule in ignore_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule =
format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
if !ignore_rules.contains(&float_rule) {
ignore_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("# {comment}"));
@@ -238,7 +239,7 @@ impl ApplicationConfigurationGenerator {
let mut lines = vec![String::from("; Generated by komorebic.exe"), String::new()];
let mut float_rules = vec![];
let mut ignore_rules = vec![];
for app in cfgen {
lines.push(format!("; {}", app.name));
@@ -252,8 +253,8 @@ impl ApplicationConfigurationGenerator {
}
}
if let Some(float_identifiers) = app.float_identifiers {
for matching_rule in float_identifiers {
if let Some(ignore_identifiers) = app.ignore_identifiers {
for matching_rule in ignore_identifiers {
if let MatchingRule::Simple(float) = matching_rule {
let float_rule = format!(
"RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")",
@@ -261,8 +262,8 @@ impl ApplicationConfigurationGenerator {
);
// Don't want to send duped signals especially as configs get larger
if !float_rules.contains(&float_rule) {
float_rules.push(float_rule.clone());
if !ignore_rules.contains(&float_rule) {
ignore_rules.push(float_rule.clone());
// if let Some(comment) = float.comment {
// lines.push(format!("; {comment}"));

View File

@@ -174,7 +174,8 @@ pub enum SocketMessage {
ClearWorkspaceRules(usize, usize),
ClearNamedWorkspaceRules(String),
ClearAllWorkspaceRules,
FloatRule(ApplicationIdentifier, String),
#[serde(alias = "FloatRule")]
IgnoreRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),
IdentifyObjectNameChangeApplication(ApplicationIdentifier, String),
IdentifyTrayApplication(ApplicationIdentifier, String),
@@ -294,6 +295,7 @@ pub enum WindowKind {
Stack,
Monocle,
Unfocused,
Floating,
}
#[derive(

View File

@@ -139,7 +139,7 @@ lazy_static! {
static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =
Arc::new(Mutex::new(HashMap::new()));
static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
static ref FLOAT_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
static ref IGNORE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
// mstsc.exe creates these on Windows 11 when a WSL process is launched
// https://github.com/LGUG2Z/komorebi/issues/74
MatchingRule::Simple(IdWithIdentifier {
@@ -158,6 +158,7 @@ lazy_static! {
matching_strategy: Option::from(MatchingStrategy::Equals),
})
]));
static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));
static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"Chrome_RenderWidgetHostHWND".to_string(),
]));
@@ -165,13 +166,6 @@ lazy_static! {
"X410.exe".to_string(),
"vcxsrv.exe".to_string(),
]));
static ref SLOW_APPLICATION_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![
MatchingRule::Simple(IdWithIdentifier {
kind: ApplicationIdentifier::Exe,
id: String::from("firefox.exe"),
matching_strategy: Option::from(MatchingStrategy::Equals),
}),
]));
static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =
Arc::new(Mutex::new(HashMap::new()));
pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =
@@ -224,7 +218,6 @@ lazy_static! {
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
Arc::new(Mutex::new(HashMap::new()));
}
pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);
@@ -238,8 +231,6 @@ pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_ENABLED: AtomicBool = AtomicBool::new(false);
pub static ANIMATION_DURATION: AtomicU64 = AtomicU64::new(250);
pub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);
#[must_use]
pub fn current_virtual_desktop() -> Option<Vec<u8>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);

View File

@@ -20,6 +20,7 @@ use crate::workspace::Workspace;
use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
use crate::WindowsApi;
#[derive(
Debug,
@@ -178,66 +179,90 @@ impl Monitor {
bail!("cannot move native maximized window to another monitor or workspace");
}
let container = workspace
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
let foreground_hwnd = WindowsApi::foreground_window()?;
let floating_window_index = workspace
.floating_windows()
.iter()
.position(|w| w.hwnd == foreground_hwnd);
let workspaces = self.workspaces_mut();
if let Some(idx) = floating_window_index {
let window = workspace.floating_windows_mut().remove(idx);
#[allow(clippy::option_if_let_else)]
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
None => {
workspaces.resize(target_workspace_idx + 1, Workspace::default());
workspaces.get_mut(target_workspace_idx).unwrap()
}
Some(workspace) => workspace,
};
match direction {
Some(OperationDirection::Left) => match target_workspace.layout() {
Layout::Default(layout) => match layout {
DefaultLayout::RightMainVerticalStack => {
target_workspace.add_container_to_front(container);
}
DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace.insert_container_at_idx(0, container);
} else {
target_workspace.add_container_to_back(container);
}
}
_ => {
target_workspace.add_container_to_back(container);
}
},
Layout::Custom(_) => {
target_workspace.add_container_to_back(container);
let workspaces = self.workspaces_mut();
#[allow(clippy::option_if_let_else)]
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
None => {
workspaces.resize(target_workspace_idx + 1, Workspace::default());
workspaces.get_mut(target_workspace_idx).unwrap()
}
},
Some(OperationDirection::Right) => match target_workspace.layout() {
Layout::Default(layout) => {
let target_index = layout.leftmost_index(target_workspace.containers().len());
Some(workspace) => workspace,
};
match layout {
DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack => {
target_workspace.floating_windows_mut().push(window);
} else {
let container = workspace
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
let workspaces = self.workspaces_mut();
#[allow(clippy::option_if_let_else)]
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
None => {
workspaces.resize(target_workspace_idx + 1, Workspace::default());
workspaces.get_mut(target_workspace_idx).unwrap()
}
Some(workspace) => workspace,
};
match direction {
Some(OperationDirection::Left) => match target_workspace.layout() {
Layout::Default(layout) => match layout {
DefaultLayout::RightMainVerticalStack => {
target_workspace.add_container_to_front(container);
}
DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace.add_container_to_back(container);
target_workspace.insert_container_at_idx(0, container);
} else {
target_workspace.insert_container_at_idx(target_index, container);
target_workspace.add_container_to_back(container);
}
}
_ => {
target_workspace.insert_container_at_idx(target_index, container);
target_workspace.add_container_to_back(container);
}
},
Layout::Custom(_) => {
target_workspace.add_container_to_back(container);
}
},
Some(OperationDirection::Right) => match target_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(target_workspace.containers().len());
match layout {
DefaultLayout::RightMainVerticalStack
| DefaultLayout::UltrawideVerticalStack => {
if target_workspace.containers().len() == 1 {
target_workspace.add_container_to_back(container);
} else {
target_workspace
.insert_container_at_idx(target_index, container);
}
}
_ => {
target_workspace.insert_container_at_idx(target_index, container);
}
}
}
Layout::Custom(_) => {
target_workspace.add_container_to_front(container);
}
},
_ => {
target_workspace.add_container_to_back(container);
}
Layout::Custom(_) => {
target_workspace.add_container_to_front(container);
}
},
_ => {
target_workspace.add_container_to_back(container);
}
}

View File

@@ -172,7 +172,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if should_update {
tracing::info!("updated work area for {}", monitor.device_id());
monitor.update_focused_workspace(offset)?;
border_manager::send_notification();
border_manager::send_notification(None);
} else {
tracing::debug!(
"work areas match, reconciliation not required for {}",
@@ -219,7 +219,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
);
monitor.update_focused_workspace(offset)?;
border_manager::send_notification();
border_manager::send_notification(None);
} else {
tracing::debug!(
"resolutions match, reconciliation not required for {}",
@@ -406,7 +406,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Second retile to fix DPI/resolution related jank
wm.retile_all(true)?;
// Border updates to fix DPI/resolution related jank
border_manager::send_notification();
border_manager::send_notification(None);
}
}
}

View File

@@ -68,8 +68,8 @@ use crate::ANIMATION_STYLE;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
@@ -387,20 +387,20 @@ impl WindowManager {
}));
}
}
SocketMessage::FloatRule(identifier, ref id) => {
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
SocketMessage::IgnoreRule(identifier, ref id) => {
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let mut should_push = true;
for f in &*float_identifiers {
if let MatchingRule::Simple(f) = f {
if f.id.eq(id) {
for i in &*ignore_identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
float_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
ignore_identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
@@ -1388,7 +1388,7 @@ impl WindowManager {
}
}
border_manager::send_notification();
border_manager::send_notification(None);
}
}
SocketMessage::BorderColour(kind, r, g, b) => match kind {
@@ -1404,6 +1404,9 @@ impl WindowManager {
WindowKind::Unfocused => {
border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
WindowKind::Floating => {
border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
}
},
SocketMessage::BorderStyle(style) => {
STYLE.store(style);
@@ -1533,7 +1536,7 @@ impl WindowManager {
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification();
border_manager::send_notification(None);
transparency_manager::send_notification();
stackbar_manager::send_notification();

View File

@@ -33,6 +33,7 @@ use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT;
use crate::Notification;
use crate::NotificationEvent;
use crate::DATA_DIR;
use crate::FLOATING_APPLICATIONS;
use crate::HIDDEN_HWNDS;
use crate::REGEX_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
@@ -101,6 +102,10 @@ impl WindowManager {
}
if !transparency_override {
if rule_debug.matches_ignore_identifier.is_some() {
border_manager::send_notification(Option::from(event.hwnd()));
}
return Ok(());
}
}
@@ -149,14 +154,6 @@ impl WindowManager {
_ => {}
}
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
if let WindowManagerEvent::FocusChange(_, window) = event {
let _ = workspace.focus_changed(window.hwnd);
}
}
}
self.enforce_workspace_rules()?;
if matches!(event, WindowManagerEvent::MouseCapture(..)) {
@@ -246,24 +243,31 @@ impl WindowManager {
self.update_focused_workspace(self.mouse_follows_focus, false)?;
let workspace = self.focused_workspace_mut()?;
if !workspace
let floating_window_idx = workspace
.floating_windows()
.iter()
.any(|w| w.hwnd == window.hwnd)
{
if let Some(w) = workspace.maximized_window() {
if w.hwnd == window.hwnd {
return Ok(());
.position(|w| w.hwnd == window.hwnd);
match floating_window_idx {
None => {
if let Some(w) = workspace.maximized_window() {
if w.hwnd == window.hwnd {
return Ok(());
}
}
if let Some(monocle) = workspace.monocle_container() {
if let Some(window) = monocle.focused_window() {
window.focus(false)?;
}
} else {
workspace.focus_container_by_window(window.hwnd)?;
}
}
if let Some(monocle) = workspace.monocle_container() {
if let Some(window) = monocle.focused_window() {
Some(idx) => {
if let Some(window) = workspace.floating_windows().get(idx) {
window.focus(false)?;
}
} else {
self.focused_workspace_mut()?
.focus_container_by_window(window.hwnd)?;
}
}
}
@@ -336,19 +340,44 @@ impl WindowManager {
let monocle_container = workspace.monocle_container().clone();
if !workspace_contains_window && !needs_reconciliation {
match behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
workspace
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no focused container"))?
.add_window(window);
self.update_focused_workspace(true, false)?;
let floating_applications = FLOATING_APPLICATIONS.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut should_float = false;
stackbar_manager::send_notification();
if !floating_applications.is_empty() {
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) =
(window.title(), window.exe(), window.class(), window.path())
{
should_float = should_act(
&title,
&exe_name,
&class,
&path,
&floating_applications,
&regex_identifiers,
)
.is_some();
}
}
if should_float && !matches!(event, WindowManagerEvent::Manage(_)) {
workspace.floating_windows_mut().push(window);
self.update_focused_workspace(false, true)?;
} else {
match behaviour {
WindowContainerBehaviour::Create => {
workspace.new_container_for_window(window);
self.update_focused_workspace(false, false)?;
}
WindowContainerBehaviour::Append => {
workspace
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no focused container"))?
.add_window(window);
self.update_focused_workspace(true, false)?;
stackbar_manager::send_notification();
}
}
}
}
@@ -642,7 +671,7 @@ impl WindowManager {
};
notify_subscribers(&serde_json::to_string(&notification)?)?;
border_manager::send_notification();
border_manager::send_notification(Some(event.hwnd()));
transparency_manager::send_notification();
stackbar_manager::send_notification();

View File

@@ -51,7 +51,7 @@ pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
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)?;
border_manager::send_notification();
border_manager::send_notification(None);
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,

View File

@@ -35,15 +35,14 @@ use crate::DATA_DIR;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::FLOATING_APPLICATIONS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
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;
@@ -304,10 +303,14 @@ pub struct StaticConfig {
pub global_work_area_offset: Option<Rect>,
/// Individual window floating rules
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rules: Option<Vec<MatchingRule>>,
#[serde(alias = "float_rules")]
pub ignore_rules: Option<Vec<MatchingRule>>,
/// Individual window force-manage rules
#[serde(skip_serializing_if = "Option::is_none")]
pub manage_rules: Option<Vec<MatchingRule>>,
/// Identify applications which should be managed as floating windows
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_applications: Option<Vec<MatchingRule>>,
/// Identify border overflow applications
#[serde(skip_serializing_if = "Option::is_none")]
pub border_overflow_applications: Option<Vec<MatchingRule>>,
@@ -335,12 +338,6 @@ pub struct StaticConfig {
/// Theme configuration options
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<KomorebiTheme>,
/// Identify applications which are slow to send initial event notifications
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_identifiers: Option<Vec<MatchingRule>>,
/// How long to wait when compensating for slow applications, in milliseconds (default: 20)
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application_compensation_time: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -367,6 +364,8 @@ pub enum KomorebiTheme {
stack_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is in monocle mode (default: Pink)
monocle_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the window is floating (default: Yellow)
floating_border: Option<komorebi_themes::CatppuccinValue>,
/// Border colour when the container is unfocused (default: Base)
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
/// Stackbar focused tab text colour (default: Green)
@@ -388,6 +387,8 @@ pub enum KomorebiTheme {
stack_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is in monocle mode (default: Base0F)
monocle_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the window is floating (default: Base09)
floating_border: Option<komorebi_themes::Base16Value>,
/// Border colour when the container is unfocused (default: Base01)
unfocused_border: Option<komorebi_themes::Base16Value>,
/// Stackbar focused tab text colour (default: Base0B)
@@ -541,7 +542,8 @@ impl From<&WindowManager> for StaticConfig {
monitors: Option::from(monitors),
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
global_work_area_offset: value.work_area_offset,
float_rules: None,
ignore_rules: None,
floating_applications: None,
manage_rules: None,
border_overflow_applications: None,
tray_and_multi_window_applications: None,
@@ -552,10 +554,6 @@ impl From<&WindowManager> for StaticConfig {
stackbar: None,
animation: None,
theme: None,
slow_application_compensation_time: Option::from(
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
),
slow_application_identifiers: Option::from(SLOW_APPLICATION_IDENTIFIERS.lock().clone()),
}
}
}
@@ -649,7 +647,7 @@ impl StaticConfig {
}
}
border_manager::send_notification();
border_manager::send_notification(None);
}
transparency_manager::TRANSPARENCY_ENABLED
@@ -657,17 +655,21 @@ impl StaticConfig {
transparency_manager::TRANSPARENCY_ALPHA
.store(self.transparency_alpha.unwrap_or(200), Ordering::SeqCst);
let mut float_identifiers = FLOAT_IDENTIFIERS.lock();
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
let mut layered_identifiers = LAYERED_WHITELIST.lock();
let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();
let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
let mut floating_applications = FLOATING_APPLICATIONS.lock();
if let Some(rules) = &mut self.float_rules {
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
if let Some(rules) = &mut self.ignore_rules {
populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?;
}
if let Some(rules) = &mut self.floating_applications {
populate_rules(rules, &mut floating_applications, &mut regex_identifiers)?;
}
if let Some(rules) = &mut self.manage_rules {
@@ -698,14 +700,6 @@ impl StaticConfig {
populate_rules(rules, &mut transparency_blacklist, &mut regex_identifiers)?;
}
if let Some(rules) = &mut self.slow_application_identifiers {
populate_rules(
rules,
&mut slow_application_identifiers,
&mut regex_identifiers,
)?;
}
if let Some(stackbar) = &self.stackbar {
if let Some(height) = &stackbar.height {
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
@@ -747,6 +741,7 @@ impl StaticConfig {
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
@@ -757,6 +752,7 @@ impl StaticConfig {
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
@@ -775,6 +771,10 @@ impl StaticConfig {
.unwrap_or(komorebi_themes::CatppuccinValue::Pink)
.color32(name.as_theme());
let floating_border = floating_border
.unwrap_or(komorebi_themes::CatppuccinValue::Yellow)
.color32(name.as_theme());
let unfocused_border = unfocused_border
.unwrap_or(komorebi_themes::CatppuccinValue::Base)
.color32(name.as_theme());
@@ -795,6 +795,7 @@ impl StaticConfig {
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
@@ -806,6 +807,7 @@ impl StaticConfig {
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
@@ -828,6 +830,10 @@ impl StaticConfig {
.unwrap_or(komorebi_themes::Base16Value::Base01)
.color32(*name);
let floating_border = floating_border
.unwrap_or(komorebi_themes::Base16Value::Base09)
.color32(*name);
let stackbar_focused_text = stackbar_focused_text
.unwrap_or(komorebi_themes::Base16Value::Base0B)
.color32(*name);
@@ -844,6 +850,7 @@ impl StaticConfig {
single_border,
stack_border,
monocle_border,
floating_border,
unfocused_border,
stackbar_focused_text,
stackbar_unfocused_text,
@@ -856,6 +863,8 @@ impl StaticConfig {
border_manager::MONOCLE
.store(u32::from(Colour::from(monocle_border)), Ordering::SeqCst);
border_manager::STACK.store(u32::from(Colour::from(stack_border)), Ordering::SeqCst);
border_manager::FLOATING
.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);
border_manager::UNFOCUSED
.store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);
@@ -881,8 +890,8 @@ impl StaticConfig {
let asc = ApplicationConfigurationGenerator::load(&content)?;
for mut entry in asc {
if let Some(rules) = &mut entry.float_identifiers {
populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?;
if let Some(rules) = &mut entry.ignore_identifiers {
populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?;
}
if let Some(ref options) = entry.options {

View File

@@ -6,8 +6,6 @@ use crate::windows_api;
use crate::ANIMATIONS_IN_PROGRESS;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
use crate::SLOW_APPLICATION_IDENTIFIERS;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Display;
@@ -41,9 +39,9 @@ use crate::styles::WindowStyle;
use crate::transparency_manager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDDEN_HWNDS;
use crate::HIDING_BEHAVIOUR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::NO_TITLEBAR;
@@ -181,7 +179,7 @@ impl Window {
let mut animation = self.animation;
border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
border_manager::send_notification();
border_manager::send_notification(Some(self.hwnd));
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);
stackbar_manager::send_notification();
@@ -203,7 +201,7 @@ impl Window {
stackbar_manager::STACKBAR_TEMPORARILY_DISABLED
.store(false, Ordering::SeqCst);
border_manager::send_notification();
border_manager::send_notification(Some(hwnd));
stackbar_manager::send_notification();
transparency_manager::send_notification();
}
@@ -534,7 +532,7 @@ pub struct RuleDebug {
pub class: Option<String>,
pub path: Option<String>,
pub matches_permaignore_class: Option<String>,
pub matches_float_identifier: Option<MatchingRule>,
pub matches_ignore_identifier: Option<MatchingRule>,
pub matches_managed_override: Option<MatchingRule>,
pub matches_layered_whitelist: Option<MatchingRule>,
pub matches_wsl2_gui: Option<String>,
@@ -563,16 +561,16 @@ fn window_is_eligible(
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let float_identifiers = FLOAT_IDENTIFIERS.lock();
let ignore_identifiers = IGNORE_IDENTIFIERS.lock();
let should_float = if let Some(rule) = should_act(
title,
exe_name,
class,
path,
&float_identifiers,
&ignore_identifiers,
&regex_identifiers,
) {
debug.matches_float_identifier = Some(rule);
debug.matches_ignore_identifier = Some(rule);
true
} else {
false
@@ -636,23 +634,8 @@ fn window_is_eligible(
titlebars_removed.contains(exe_name)
};
{
let slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
let should_sleep = should_act(
title,
exe_name,
class,
path,
&slow_application_identifiers,
&regex_identifiers,
)
.is_some();
if should_sleep {
std::thread::sleep(Duration::from_millis(
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
));
}
if exe_name.contains("firefox") {
std::thread::sleep(Duration::from_millis(10));
}
if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))

View File

@@ -71,9 +71,9 @@ use crate::Rgb;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
use crate::DISPLAY_INDEX_PREFERENCES;
use crate::FLOAT_IDENTIFIERS;
use crate::HIDING_BEHAVIOUR;
use crate::HOME_DIR;
use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
@@ -136,7 +136,8 @@ pub struct GlobalState {
pub stackbar_tab_width: i32,
pub stackbar_height: i32,
pub remove_titlebars: bool,
pub float_identifiers: Vec<MatchingRule>,
#[serde(alias = "float_identifiers")]
pub ignore_identifiers: Vec<MatchingRule>,
pub manage_identifiers: Vec<MatchingRule>,
pub layered_whitelist: Vec<MatchingRule>,
pub tray_and_multi_window_identifiers: Vec<MatchingRule>,
@@ -185,7 +186,7 @@ impl Default for GlobalState {
stackbar_tab_width: STACKBAR_TAB_WIDTH.load(Ordering::SeqCst),
stackbar_height: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst),
remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst),
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
ignore_identifiers: IGNORE_IDENTIFIERS.lock().clone(),
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
layered_whitelist: LAYERED_WHITELIST.lock().clone(),
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
@@ -825,37 +826,28 @@ impl WindowManager {
}
}
}
} else {
if self.focused_workspace()?.containers().is_empty() {
let desktop_window = Window::from(WindowsApi::desktop_window()?);
}
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
// if we passed false for follow_focus and there is a container on the workspace
if !follow_focus && self.focused_container_mut().is_ok() {
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window().is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container().is_none()
{
if let Ok(window) = self.focused_window_mut() {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
}
}
}
// if we passed false for follow_focus and there is a container on the workspace
if self.focused_container_mut().is_ok() {
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window().is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container().is_none()
{
if let Ok(window) = self.focused_window_mut() {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
}
}
}
// This is to correctly restore and focus when switching to a workspace which
// contains a managed maximized window
// This is to correctly restore and focus when switching to a workspace which
// contains a managed maximized window
if !follow_focus {
if let Some(window) = self.focused_workspace()?.maximized_window() {
window.restore();
if trigger_focus {
@@ -968,14 +960,6 @@ impl WindowManager {
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
if let Some(monocle) = workspace.monocle_container() {
for window in monocle.windows() {
if matches!(border_implementation, BorderImplementation::Windows) {
window.remove_accent()?;
}
}
}
for containers in workspace.containers_mut() {
for window in containers.windows_mut() {
if no_titlebar.contains(&window.exe()?) {

View File

@@ -221,18 +221,19 @@ impl Workspace {
container.restore();
}
for window in self.floating_windows() {
window.restore();
}
if let Some(container) = self.focused_container_mut() {
container.focus_window(container.focused_window_idx());
}
for window in self.floating_windows() {
window.restore();
}
// Do this here to make sure that an error doesn't stop the restoration of other windows
// Maximised windows should always be drawn at the top of the Z order
// 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() {
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
window.focus(mouse_follows_focus)?;
}
}
@@ -393,26 +394,6 @@ impl Workspace {
Ok(())
}
// focus_changed performs updates in response to the fact that a focus
// change event has occurred. The focus change is assumed to be valid, and
// should not result in a new focus change - the intent here is to update
// focus-reactive elements, such as the stackbar.
pub fn focus_changed(&mut self, hwnd: isize) -> Result<()> {
if !self.tile() {
return Ok(());
}
let containers = self.containers_mut();
for container in containers.iter_mut() {
if let Some(idx) = container.idx_for_window(hwnd) {
container.focus_window(idx);
container.restore();
}
}
Ok(())
}
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
let mut hwnds = vec![];
let mut floating_hwnds = vec![];

View File

@@ -118,7 +118,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Unblock the border manager
ALT_TAB_HWND.store(None);
// Send a notification to the border manager to update the borders
border_manager::send_notification();
border_manager::send_notification(None);
}
}
}

View File

@@ -583,7 +583,7 @@ macro_rules! gen_application_target_subcommand_args {
}
gen_application_target_subcommand_args! {
FloatRule,
IgnoreRule,
ManageRule,
IdentifyTrayApplication,
IdentifyLayeredApplication,
@@ -1208,9 +1208,10 @@ enum SubCommand {
/// Set the operation behaviour when the focused window is not managed
#[clap(arg_required_else_help = true)]
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),
/// Add a rule to always float the specified application
/// Add a rule to ignore the specified application
#[clap(arg_required_else_help = true)]
FloatRule(FloatRule),
#[clap(alias = "float-rule")]
IgnoreRule(IgnoreRule),
/// Add a rule to always manage the specified application
#[clap(arg_required_else_help = true)]
ManageRule(ManageRule),
@@ -2050,7 +2051,7 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
println!("* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops");
println!("* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands");
let static_config = arg.config.clone().map_or_else(
let static_config = arg.config.map_or_else(
|| {
let komorebi_json = HOME_DIR.join("komorebi.json");
if komorebi_json.is_file() {
@@ -2062,30 +2063,12 @@ if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
Option::from,
);
let bar_config = arg.config.map_or_else(
|| {
let bar_json = HOME_DIR.join("komorebi.bar.json");
if bar_json.is_file() {
Option::from(bar_json)
} else {
None
}
},
Option::from,
);
if let Some(config) = static_config {
let path = resolve_home_path(config)?;
let raw = std::fs::read_to_string(path)?;
StaticConfig::aliases(&raw);
StaticConfig::deprecated(&raw);
}
if bar_config.is_some() {
let output = Command::new("komorebi-bar.exe").arg("--aliases").output()?;
let stdout = String::from_utf8(output.stdout)?;
println!("{stdout}");
}
}
SubCommand::Stop(arg) => {
if arg.whkd {
@@ -2146,8 +2129,8 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
}
}
}
SubCommand::FloatRule(arg) => {
send_message(&SocketMessage::FloatRule(arg.identifier, arg.id))?;
SubCommand::IgnoreRule(arg) => {
send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?;
}
SubCommand::ManageRule(arg) => {
send_message(&SocketMessage::ManageRule(arg.identifier, arg.id))?;