mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-13 06:20:24 +01:00
Compare commits
7 Commits
feature/de
...
feature/al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93cab6fd0 | ||
|
|
08377cbffc | ||
|
|
9fd0c7bf39 | ||
|
|
f02703876a | ||
|
|
349508dd16 | ||
|
|
f0c8143503 | ||
|
|
e4bc74f7ea |
2
justfile
2
justfile
@@ -28,7 +28,6 @@ install:
|
||||
just install-target komorebi
|
||||
|
||||
run:
|
||||
just install-target komorebic
|
||||
cargo +stable run --bin komorebi --locked
|
||||
|
||||
warn $RUST_LOG="warn":
|
||||
@@ -44,7 +43,6 @@ trace $RUST_LOG="trace":
|
||||
just run
|
||||
|
||||
deadlock $RUST_LOG="trace":
|
||||
just install-komorebic
|
||||
cargo +stable run --bin komorebi --locked --features deadlock_detection
|
||||
|
||||
docgen:
|
||||
|
||||
@@ -26,7 +26,7 @@ pub trait Arrangement {
|
||||
}
|
||||
|
||||
impl Arrangement for DefaultLayout {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||
fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
@@ -58,12 +58,13 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) => match len {
|
||||
2.. => columns_reverse(&mut layouts),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 2.. = len {
|
||||
columns_reverse(&mut layouts);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
@@ -82,12 +83,13 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) => match len {
|
||||
2.. => rows_reverse(&mut layouts),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 2.. = len {
|
||||
rows_reverse(&mut layouts);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
@@ -135,28 +137,28 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) => match len {
|
||||
2.. => {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 2.. = len {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
|
||||
for rect in rest.iter_mut() {
|
||||
rect.left = primary.left;
|
||||
}
|
||||
primary.left = rest[0].left + rest[0].right;
|
||||
for rect in rest.iter_mut() {
|
||||
rect.left = primary.left;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
primary.left = rest[0].left + rest[0].right;
|
||||
}
|
||||
}
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) => match len {
|
||||
3.. => rows_reverse(&mut layouts[1..]),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 3.. = len {
|
||||
rows_reverse(&mut layouts[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
@@ -207,28 +209,28 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) => match len {
|
||||
2.. => {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 2.. = len {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
|
||||
primary.left = rest[0].left;
|
||||
for rect in rest.iter_mut() {
|
||||
rect.left = primary.left + primary.right;
|
||||
}
|
||||
primary.left = rest[0].left;
|
||||
for rect in rest.iter_mut() {
|
||||
rect.left = primary.left + primary.right;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) => match len {
|
||||
3.. => rows_reverse(&mut layouts[1..]),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 3.. = len {
|
||||
rows_reverse(&mut layouts[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
@@ -276,28 +278,28 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) => match len {
|
||||
2.. => {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 2.. = len {
|
||||
let (primary, rest) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
|
||||
for rect in rest.iter_mut() {
|
||||
rect.top = primary.top;
|
||||
}
|
||||
primary.top = rest[0].top + rest[0].bottom;
|
||||
for rect in rest.iter_mut() {
|
||||
rect.top = primary.top;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
primary.top = rest[0].top + rest[0].bottom;
|
||||
}
|
||||
}
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) => match len {
|
||||
3.. => columns_reverse(&mut layouts[1..]),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 3.. = len {
|
||||
columns_reverse(&mut layouts[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
@@ -374,8 +376,11 @@ impl Arrangement for DefaultLayout {
|
||||
layout.right += adjustment.right;
|
||||
});
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical) => match len {
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Horizontal | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
match len {
|
||||
2 => {
|
||||
let (primary, secondary) = layouts.split_at_mut(1);
|
||||
let primary = &mut primary[0];
|
||||
@@ -397,20 +402,20 @@ impl Arrangement for DefaultLayout {
|
||||
secondary.left = primary.left + primary.right;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match layout_flip {
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical) => match len {
|
||||
4.. => rows_reverse(&mut layouts[2..]),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
if matches!(
|
||||
layout_flip,
|
||||
Some(Axis::Vertical | Axis::HorizontalAndVertical)
|
||||
) {
|
||||
if let 4.. = len {
|
||||
rows_reverse(&mut layouts[2..]);
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
},
|
||||
}
|
||||
#[allow(
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_truncation,
|
||||
|
||||
@@ -283,6 +283,8 @@ pub enum MoveBehaviour {
|
||||
Swap,
|
||||
/// Insert the window container into the focused workspace on the adjacent monitor
|
||||
Insert,
|
||||
/// Do nothing if trying to move a window container in the direction of an adjacent monitor
|
||||
NoOp,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -19,6 +19,7 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Foundation::BOOL;
|
||||
use windows::Win32::Foundation::COLORREF;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
@@ -47,10 +48,29 @@ use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
|
||||
pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
||||
|
||||
if let Ok(class) = WindowsApi::real_window_class_w(hwnd) {
|
||||
if class.starts_with("komoborder") {
|
||||
hwnds.push(hwnd.0);
|
||||
}
|
||||
}
|
||||
|
||||
true.into()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Border {
|
||||
pub hwnd: isize,
|
||||
}
|
||||
|
||||
impl From<isize> for Border {
|
||||
fn from(value: isize) -> Self {
|
||||
Self { hwnd: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Border {
|
||||
pub const fn hwnd(&self) -> HWND {
|
||||
HWND(self.hwnd)
|
||||
@@ -97,7 +117,7 @@ impl Border {
|
||||
}
|
||||
|
||||
pub fn destroy(&self) -> color_eyre::Result<()> {
|
||||
WindowsApi::destroy_window(self.hwnd())
|
||||
WindowsApi::close_window(self.hwnd())
|
||||
}
|
||||
|
||||
pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
mod border;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
@@ -9,19 +11,24 @@ use parking_lot::Mutex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
use crate::workspace_reconciliator::ALT_TAB_HWND;
|
||||
use crate::Colour;
|
||||
use crate::Rect;
|
||||
use crate::Rgb;
|
||||
use crate::WindowManager;
|
||||
use crate::WindowsApi;
|
||||
use border::border_hwnds;
|
||||
use border::Border;
|
||||
use komorebi_core::WindowKind;
|
||||
|
||||
@@ -68,6 +75,11 @@ pub fn event_rx() -> Receiver<Notification> {
|
||||
|
||||
pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
tracing::info!(
|
||||
"purging known borders: {:?}",
|
||||
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
@@ -77,128 +89,87 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
BORDERS_MONITORS.lock().clear();
|
||||
FOCUS_STATE.lock().clear();
|
||||
|
||||
let mut remaining_hwnds = vec![];
|
||||
|
||||
WindowsApi::enum_windows(
|
||||
Some(border_hwnds),
|
||||
&mut remaining_hwnds as *mut Vec<isize> as isize,
|
||||
)?;
|
||||
|
||||
if !remaining_hwnds.is_empty() {
|
||||
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
|
||||
|
||||
for hwnd in remaining_hwnds {
|
||||
Border::from(hwnd).destroy()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
let receiver = event_rx();
|
||||
let mut instant: Option<Instant> = None;
|
||||
event_tx().send(Notification)?;
|
||||
|
||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||
'receiver: for _ in receiver {
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
|
||||
// Check the wm state every time we receive a notification
|
||||
let state = wm.lock();
|
||||
|
||||
if !BORDER_ENABLED.load_consume() || state.is_paused {
|
||||
if !borders.is_empty() {
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
}
|
||||
|
||||
'receiver: for _ in receiver {
|
||||
if let Some(instant) = instant {
|
||||
if instant.elapsed().lt(&Duration::from_millis(50)) {
|
||||
continue 'receiver;
|
||||
}
|
||||
}
|
||||
|
||||
let focused_monitor_idx = state.focused_monitor_idx();
|
||||
instant = Some(Instant::now());
|
||||
|
||||
for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Workspaces with tiling disabled don't have borders
|
||||
if !ws.tile() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
// Check the wm state every time we receive a notification
|
||||
let state = wm.lock();
|
||||
|
||||
continue 'receiver;
|
||||
}
|
||||
// If borders are disabled
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
// Or if the wm is paused
|
||||
|| state.is_paused
|
||||
// Or if we are handling an alt-tab across workspaces
|
||||
|| ALT_TAB_HWND.load().is_some()
|
||||
{
|
||||
// Destroy the borders we know about
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
borders.clear();
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
let border = borders.entry(monocle.id().clone()).or_insert_with(|| {
|
||||
Border::create(monocle.id()).expect("border creation failed")
|
||||
});
|
||||
|
||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
||||
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(border.hwnd, WindowKind::Monocle);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
monocle
|
||||
.focused_window()
|
||||
.expect("monocle container has no focused window")
|
||||
.hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||
WindowsApi::foreground_window().unwrap_or_default(),
|
||||
));
|
||||
|
||||
if is_maximized {
|
||||
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 'receiver;
|
||||
}
|
||||
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
let container_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|c| c.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
let focused_monitor_idx = state.focused_monitor_idx();
|
||||
|
||||
for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Workspaces with tiling disabled don't have borders
|
||||
if !ws.tile() {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
||||
if !container_ids.contains(id) {
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,80 +177,169 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// Update border when moving or resizing with mouse
|
||||
if state.pending_move_op.is_some() && idx == ws.focused_container_idx() {
|
||||
let restore_z_order = *Z_ORDER.lock();
|
||||
*Z_ORDER.lock() = ZOrder::TopMost;
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
let mut rect = WindowsApi::window_rect(
|
||||
c.focused_window()
|
||||
.expect("container has no focused window")
|
||||
.hwnd(),
|
||||
)?;
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
while WindowsApi::lbutton_is_pressed() {
|
||||
let border = borders.entry(c.id().clone()).or_insert_with(|| {
|
||||
Border::create(c.id()).expect("border creation failed")
|
||||
});
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
let new_rect = WindowsApi::window_rect(
|
||||
c.focused_window()
|
||||
.expect("container has no focused window")
|
||||
.hwnd(),
|
||||
)?;
|
||||
|
||||
if rect != new_rect {
|
||||
rect = new_rect;
|
||||
border.update(&rect)?;
|
||||
}
|
||||
let border = match borders.entry(monocle.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(monocle.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
*Z_ORDER.lock() = restore_z_order;
|
||||
|
||||
continue 'receiver;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let border = borders.entry(c.id().clone()).or_insert_with(|| {
|
||||
Border::create(c.id()).expect("border creation failed")
|
||||
});
|
||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
||||
|
||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(border.hwnd, WindowKind::Monocle);
|
||||
}
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(
|
||||
border.hwnd,
|
||||
if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else {
|
||||
if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
}
|
||||
},
|
||||
);
|
||||
let rect = WindowsApi::window_rect(
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||
WindowsApi::foreground_window().unwrap_or_default(),
|
||||
));
|
||||
|
||||
if is_maximized {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
c.focused_window()
|
||||
.expect("container has no focused window")
|
||||
.hwnd(),
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
let container_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|c| c.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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)
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// Update border when moving or resizing with mouse
|
||||
if state.pending_move_op.is_some() && idx == ws.focused_container_idx() {
|
||||
let restore_z_order = *Z_ORDER.lock();
|
||||
*Z_ORDER.lock() = ZOrder::TopMost;
|
||||
|
||||
let mut rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
while WindowsApi::lbutton_is_pressed() {
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(c.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'receiver;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let new_rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
if rect != new_rect {
|
||||
rect = new_rect;
|
||||
border.update(&rect)?;
|
||||
}
|
||||
}
|
||||
|
||||
*Z_ORDER.lock() = restore_z_order;
|
||||
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(c.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'receiver;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(
|
||||
border.hwnd,
|
||||
if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -290,9 +350,9 @@ pub enum ZOrder {
|
||||
TopMost,
|
||||
}
|
||||
|
||||
impl Into<isize> for ZOrder {
|
||||
fn into(self) -> isize {
|
||||
match self {
|
||||
impl From<ZOrder> for isize {
|
||||
fn from(val: ZOrder) -> Self {
|
||||
match val {
|
||||
ZOrder::Top => 0,
|
||||
ZOrder::NoTopMost => -2,
|
||||
ZOrder::Bottom => 1,
|
||||
|
||||
@@ -650,7 +650,9 @@ impl WindowManager {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
}
|
||||
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
if self.focused_workspace_idx().unwrap_or_default() != workspace_idx {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {
|
||||
// This is to ensure that even on an empty workspace on a secondary monitor, the
|
||||
@@ -672,8 +674,15 @@ impl WindowManager {
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
let focused_monitor_idx = self.focused_monitor_idx();
|
||||
let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default();
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
|
||||
if focused_pair != (monitor_idx, workspace_idx) {
|
||||
self.focus_monitor(monitor_idx)?;
|
||||
self.focus_workspace(workspace_idx)?;
|
||||
}
|
||||
}
|
||||
SocketMessage::FocusNamedWorkspace(ref name) => {
|
||||
if let Some((monitor_idx, workspace_idx)) =
|
||||
@@ -1187,6 +1196,7 @@ impl WindowManager {
|
||||
MoveBehaviour::Insert => {
|
||||
self.cross_monitor_move_behaviour = MoveBehaviour::Swap;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use color_eyre::eyre::anyhow;
|
||||
use color_eyre::Result;
|
||||
@@ -23,6 +25,8 @@ use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
use crate::winevent::WinEvent;
|
||||
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::DATA_DIR;
|
||||
@@ -270,17 +274,33 @@ impl WindowManager {
|
||||
|
||||
for (i, monitors) in self.monitors().iter().enumerate() {
|
||||
for (j, workspace) in monitors.workspaces().iter().enumerate() {
|
||||
if workspace.contains_window(window.hwnd) {
|
||||
if focused_pair != (i, j) {
|
||||
workspace_reconciliator::event_tx().send(
|
||||
workspace_reconciliator::Notification {
|
||||
monitor_idx: i,
|
||||
workspace_idx: j,
|
||||
},
|
||||
)?;
|
||||
|
||||
needs_reconciliation = true;
|
||||
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::event_tx().send(
|
||||
workspace_reconciliator::Notification {
|
||||
monitor_idx: i,
|
||||
workspace_idx: j,
|
||||
},
|
||||
)?;
|
||||
|
||||
needs_reconciliation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ use crate::PERMAIGNORE_CLASSES;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::WSL2_UI_PROCESSES;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema)]
|
||||
pub struct Window {
|
||||
pub hwnd: isize,
|
||||
}
|
||||
|
||||
@@ -1273,6 +1273,11 @@ impl WindowManager {
|
||||
|
||||
let workspace = self.focused_workspace()?;
|
||||
|
||||
// Do not proceed if we have a monocle container or maximized window
|
||||
if workspace.monocle_container().is_some() || workspace.maximized_window().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("focusing container");
|
||||
|
||||
let new_idx = workspace.new_idx_for_direction(direction);
|
||||
@@ -1333,6 +1338,11 @@ impl WindowManager {
|
||||
// If there is nowhere to move on the current workspace, try to move it onto the monitor
|
||||
// in that direction if there is one
|
||||
None => {
|
||||
// Don't do anything if the user has set the MoveBehaviour to NoOp
|
||||
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::NoOp) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let target_monitor_idx = self
|
||||
.monitor_idx_in_direction(direction)
|
||||
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
|
||||
@@ -2307,6 +2317,13 @@ impl WindowManager {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn focused_workspace_idx(&self) -> Result<usize> {
|
||||
Ok(self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
.focused_workspace_idx())
|
||||
}
|
||||
|
||||
pub fn focused_workspace(&self) -> Result<&Workspace> {
|
||||
self.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor"))?
|
||||
|
||||
@@ -121,7 +121,6 @@ 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::WM_DESTROY;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
|
||||
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
|
||||
@@ -140,6 +139,7 @@ use crate::monitor::Monitor;
|
||||
use crate::ring::Ring;
|
||||
use crate::set_window_position::SetWindowPosition;
|
||||
use crate::windows_callbacks;
|
||||
use crate::Window;
|
||||
|
||||
pub enum WindowsResult<T, E> {
|
||||
Err(E),
|
||||
@@ -433,13 +433,6 @@ impl WindowsApi {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window(hwnd: HWND) -> Result<()> {
|
||||
match Self::post_message(hwnd, WM_DESTROY, WPARAM(0), LPARAM(0)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(anyhow!("could not close window")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_HIDE);
|
||||
}
|
||||
@@ -501,6 +494,16 @@ impl WindowsApi {
|
||||
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.process()
|
||||
}
|
||||
|
||||
pub fn alt_tab_windows() -> Result<Vec<Window>> {
|
||||
let mut hwnds = vec![];
|
||||
Self::enum_windows(
|
||||
Some(windows_callbacks::alt_tab_windows),
|
||||
&mut hwnds as *mut Vec<Window> as isize,
|
||||
)?;
|
||||
|
||||
Ok(hwnds)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn top_visible_window() -> Result<isize> {
|
||||
let hwnd = Self::top_window()?;
|
||||
|
||||
@@ -152,6 +152,26 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };
|
||||
|
||||
let is_visible = WindowsApi::is_window_visible(hwnd);
|
||||
let is_window = WindowsApi::is_window(hwnd);
|
||||
let is_minimized = WindowsApi::is_iconic(hwnd);
|
||||
|
||||
if is_visible && is_window && !is_minimized {
|
||||
let window = Window { hwnd: hwnd.0 };
|
||||
|
||||
if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) {
|
||||
if should_manage {
|
||||
windows.push(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub extern "system" fn win_event_hook(
|
||||
_h_win_event_hook: HWINEVENTHOOK,
|
||||
event: u32,
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::WindowManager;
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Notification {
|
||||
@@ -11,6 +18,12 @@ pub struct Notification {
|
||||
pub workspace_idx: usize,
|
||||
}
|
||||
|
||||
pub static ALT_TAB_HWND: AtomicCell<Option<isize>> = AtomicCell::new(None);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ALT_TAB_HWND_INSTANT: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||
}
|
||||
|
||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||
@@ -26,31 +39,78 @@ pub fn event_rx() -> Receiver<Notification> {
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
tracing::info!("listening");
|
||||
let receiver = event_rx();
|
||||
|
||||
std::thread::spawn(move || -> color_eyre::Result<()> {
|
||||
for notification in receiver {
|
||||
tracing::info!("running reconciliation");
|
||||
let mut wm = wm.lock();
|
||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
let updated_pair = (notification.monitor_idx, notification.workspace_idx);
|
||||
|
||||
if focused_pair != updated_pair {
|
||||
wm.focus_monitor(notification.monitor_idx)?;
|
||||
let mouse_follows_focus = wm.mouse_follows_focus;
|
||||
|
||||
if let Some(monitor) = wm.focused_monitor_mut() {
|
||||
monitor.focus_workspace(notification.workspace_idx)?;
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
let receiver = event_rx();
|
||||
let arc = wm.clone();
|
||||
|
||||
for notification in receiver {
|
||||
tracing::info!("running reconciliation");
|
||||
|
||||
let mut wm = wm.lock();
|
||||
let focused_monitor_idx = wm.focused_monitor_idx();
|
||||
let focused_workspace_idx =
|
||||
wm.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;
|
||||
|
||||
let focused_pair = (focused_monitor_idx, focused_workspace_idx);
|
||||
let updated_pair = (notification.monitor_idx, notification.workspace_idx);
|
||||
|
||||
if focused_pair != updated_pair {
|
||||
wm.focus_monitor(notification.monitor_idx)?;
|
||||
let mouse_follows_focus = wm.mouse_follows_focus;
|
||||
|
||||
if let Some(monitor) = wm.focused_monitor_mut() {
|
||||
monitor.focus_workspace(notification.workspace_idx)?;
|
||||
monitor.load_focused_workspace(mouse_follows_focus)?;
|
||||
}
|
||||
|
||||
// Drop our lock on the window manager state here to not slow down updates
|
||||
drop(wm);
|
||||
|
||||
// Check if there was an alt-tab across workspaces in the last second
|
||||
if let Some(hwnd) = ALT_TAB_HWND.load() {
|
||||
if ALT_TAB_HWND_INSTANT
|
||||
.lock()
|
||||
.elapsed()
|
||||
.lt(&Duration::from_secs(1))
|
||||
{
|
||||
// Sleep for 100 millis to let other events pass
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
tracing::info!("focusing alt-tabbed window");
|
||||
|
||||
// Take a new lock on the wm and try to focus the container with
|
||||
// the recorded HWND from the alt-tab
|
||||
let mut wm = arc.lock();
|
||||
if let Ok(workspace) = wm.focused_workspace_mut() {
|
||||
// Regardless of if this fails, we need to get past this part
|
||||
// to unblock the border manager below
|
||||
let _ = workspace.focus_container_by_window(hwnd);
|
||||
}
|
||||
|
||||
// Unblock the border manager
|
||||
ALT_TAB_HWND.store(None);
|
||||
// Send a notification to the border manager to update the borders
|
||||
border_manager::event_tx().send(border_manager::Notification)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user