Skip to content

Commit

Permalink
feat(wm): separate floating and ignored apps
Browse files Browse the repository at this point in the history
This commit introduces a distinction between ignored applications
(previously identified with float_rules) and floating applications.

All instances of "float_" with the initial meaning of "ignored" have
been renamed with backwards compatibility aliases.

Floating applications will be managed under Workspace.floating_windows
if identified using a rule, and this allows them to now be moved across
workspaces.

A new border type has been added for floating applications, and the
colour can be configured via theme.floating_border.

This interactively rebased commit contains changes from the following
individual commits:

17ea1e6
feat(wm): separate floating and ignored apps

8b34449
feat(wm): allow ws moves of floating apps

7d8e2ad
refactor(wm): float_rules > ignore_rules w/ compat

d68346a
fix(borders): no redraws on floating win title change

a93e937
fix(borders): update on floating win drag

68e9365
fix(borders): send notif on ignored hwnd events
  • Loading branch information
LGUG2Z committed Oct 13, 2024
1 parent 07a1538 commit 6db317d
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 166 deletions.
150 changes: 142 additions & 8 deletions komorebi/src/border_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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();

Expand All @@ -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")
}
}
Expand Down Expand Up @@ -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),
}
}

Expand All @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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);

Expand Down Expand Up @@ -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);
}
}
}
}
Expand All @@ -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(())
Expand Down
23 changes: 12 additions & 11 deletions komorebi/src/core/config_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand All @@ -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}"));
Expand Down Expand Up @@ -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));
Expand All @@ -252,17 +253,17 @@ 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\")",
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}"));
Expand Down
4 changes: 3 additions & 1 deletion komorebi/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -294,6 +295,7 @@ pub enum WindowKind {
Stack,
Monocle,
Unfocused,
Floating,
}

#[derive(
Expand Down
4 changes: 2 additions & 2 deletions komorebi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(),
]));
Expand Down Expand Up @@ -224,7 +225,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);
Expand Down
Loading

0 comments on commit 6db317d

Please sign in to comment.