fix(wm): prevent hidden_hwnds deadlock

I used a parking_lot to detect what I suspected to be the deadlock
resulting in issue #13.

I was pleasantly surprised by the alternative to std::sync::Mutex
provided by parking_lot, especially not having to unlock it to use it,
and of course the excellent and intuitive (even if experimental)
deadlock detector.

I have decided to use parking_lot::Mutex as an almost-drop-in
replacement for std::sync::Mutex, as I expect that this isn't the last
time komorebi will have a deadlocking issue, and I have put the deadlock
detection code which runs in a separate thread behind a
"deadlock_detection" feature.

The actual deadlock itself was solved by scoping the first lock in the
handler for WindowManagerEvent::Hide and then executing any required
operations (some of which, like window.maximize(), may require another
lock on HIDDEN_HWNDS) in a separate scope once the previous lock has
been dropped.

In the future I should look at integrating globals like HIDDEN_HWNDS
into WindowManager in a way that won't lead to double-mutable-borrow
issues.

fix #13
This commit is contained in:
LGUG2Z
2021-08-18 19:01:59 -07:00
parent 98f731ba13
commit 209cd82892
10 changed files with 202 additions and 49 deletions
Generated
+82 -2
View File
@@ -270,10 +270,16 @@ checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall", "redox_syscall 0.2.10",
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]] [[package]]
name = "fs-tail" name = "fs-tail"
version = "0.1.4" version = "0.1.4"
@@ -423,6 +429,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "instant"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "iovec" name = "iovec"
version = "0.1.4" version = "0.1.4"
@@ -465,6 +480,7 @@ dependencies = [
"komorebi-core", "komorebi-core",
"lazy_static", "lazy_static",
"nanoid", "nanoid",
"parking_lot",
"paste", "paste",
"serde", "serde",
"serde_json", "serde_json",
@@ -524,6 +540,15 @@ version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
[[package]]
name = "lock_api"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@@ -726,12 +751,50 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"backtrace",
"cfg-if 1.0.0",
"instant",
"libc",
"petgraph",
"redox_syscall 0.2.10",
"smallvec",
"thread-id",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.5" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.7" version = "0.2.7"
@@ -894,6 +957,12 @@ dependencies = [
"rand_core 0.3.1", "rand_core 0.3.1",
] ]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.10" version = "0.2.10"
@@ -910,7 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"redox_syscall", "redox_syscall 0.2.10",
] ]
[[package]] [[package]]
@@ -1106,6 +1175,17 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "thread-id"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
dependencies = [
"libc",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.3" version = "1.1.3"
+28 -2
View File
@@ -59,6 +59,8 @@ This means that:
## Getting Started ## Getting Started
### GitHub Releases
Prebuilt binaries are available on the [releases page](https://github.com/LGUG2Z/komorebi/releases) in a `zip` archive. Prebuilt binaries are available on the [releases page](https://github.com/LGUG2Z/komorebi/releases) in a `zip` archive.
Once downloaded, you will need to move the `komorebi.exe` and `komorebic.exe` binaries to a directory in your `Path` ( Once downloaded, you will need to move the `komorebi.exe` and `komorebic.exe` binaries to a directory in your `Path` (
you can see these directories by running `$Env:Path.split(";")` at a PowerShell prompt). you can see these directories by running `$Env:Path.split(";")` at a PowerShell prompt).
@@ -68,6 +70,8 @@ using [`setx`](https://docs.microsoft.com/en-us/windows-server/administration/wi
Variables pop up in System Properties Advanced (which can be launched with `SystemPropertiesAdvanced.exe` at a Variables pop up in System Properties Advanced (which can be launched with `SystemPropertiesAdvanced.exe` at a
PowerShell prompt), and then move the binaries to that directory. PowerShell prompt), and then move the binaries to that directory.
### Scoop
If you use the [Scoop](https://scoop.sh/) command line installer, you can run the following commands to install the If you use the [Scoop](https://scoop.sh/) command line installer, you can run the following commands to install the
binaries from the latest GitHub Release: binaries from the latest GitHub Release:
@@ -79,6 +83,8 @@ scoop install komorebi
If you install _komorebi_ using Scoop, the binaries will automatically be added to your `Path` and a command will be If you install _komorebi_ using Scoop, the binaries will automatically be added to your `Path` and a command will be
shown for you to run in order to get started using the sample configuration file. shown for you to run in order to get started using the sample configuration file.
### Building from Source
If you prefer to compile _komorebi_ from source, you will need If you prefer to compile _komorebi_ from source, you will need
a [working Rust development environment on Windows 10](https://rustup.rs/). The `x86_64-pc-windows-msvc` toolchain is a [working Rust development environment on Windows 10](https://rustup.rs/). The `x86_64-pc-windows-msvc` toolchain is
required, so make sure you have also installed required, so make sure you have also installed
@@ -91,6 +97,8 @@ cargo install --path komorebi --locked
cargo install --path komorebic --locked cargo install --path komorebic --locked
``` ```
### Running
Once you have either the prebuilt binaries in your `Path`, or have compiled the binaries from source (these will already Once you have either the prebuilt binaries in your `Path`, or have compiled the binaries from source (these will already
be in your `Path` if you installed Rust with [rustup](https://rustup.rs), which you absolutely should), you can be in your `Path` if you installed Rust with [rustup](https://rustup.rs), which you absolutely should), you can
run `komorebic start` at a Powershell prompt, and you will see the following output: run `komorebic start` at a Powershell prompt, and you will see the following output:
@@ -102,6 +110,8 @@ Start-Process komorebi -WindowStyle hidden
This means that `komorebi` is now running in the background, tiling all your windows, and listening for commands sent to This means that `komorebi` is now running in the background, tiling all your windows, and listening for commands sent to
it by `komorebic`. You can similarly stop the process by running `komorebic stop`. it by `komorebic`. You can similarly stop the process by running `komorebic stop`.
### Configuring
Once `komorebi` is running, you can execute the `komorebi.sample.ahk` script to set up the default keybindings via AHK Once `komorebi` is running, you can execute the `komorebi.sample.ahk` script to set up the default keybindings via AHK
(the file includes comments to help you start building your own configuration). (the file includes comments to help you start building your own configuration).
@@ -113,6 +123,8 @@ the `AutoHotKey64.exe` executable for AutoHotKey v2 is in your `Path`. If both `
exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file exist in your home directory, only `komorebi.ahk` will be loaded. An example of an AutoHotKey v2 configuration file
for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd). for _komorebi_ can be found [here](https://gist.github.com/crosstyan/dafacc0778dabf693ce9236c57b201cd).
### Common First-Time Troubleshooting
If you are experiencing behaviour where If you are experiencing behaviour where
[closing a window leaves a blank tile, but minimizing the same window does not](https://github.com/LGUG2Z/komorebi/issues/6) [closing a window leaves a blank tile, but minimizing the same window does not](https://github.com/LGUG2Z/komorebi/issues/6)
, you have probably enabled a 'close/minimize to tray' option for that application. You can tell _komorebi_ to handle , you have probably enabled a 'close/minimize to tray' option for that application. You can tell _komorebi_ to handle
@@ -123,7 +135,7 @@ komorebic.exe identify-tray-application exe Discord.exe
komorebic.exe identify-tray-application exe Telegram.exe komorebic.exe identify-tray-application exe Telegram.exe
``` ```
## Configuration ## Configuration with `komorebic`
As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
personally use AutoHotKey to manage my window management shortcuts, and have provided a personally use AutoHotKey to manage my window management shortcuts, and have provided a
@@ -151,6 +163,7 @@ focus-workspace Focus the specified workspace on the focused monito
new-workspace Create and append a new workspace on the focused monitor new-workspace Create and append a new workspace on the focused monitor
adjust-container-padding Adjust container padding on the focused workspace adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
flip-layout Flip the layout on the focused workspace (BSP only) flip-layout Flip the layout on the focused workspace (BSP only)
promote Promote the focused window to the top of the tree promote Promote the focused window to the top of the tree
retile Force the retiling of all managed windows retile Force the retiling of all managed windows
@@ -164,7 +177,7 @@ toggle-pause Toggle the window manager on and off across all mon
toggle-tiling Toggle window tiling on the focused workspace toggle-tiling Toggle window tiling on the focused workspace
toggle-float Toggle floating mode for the focused window toggle-float Toggle floating mode for the focused window
toggle-monocle Toggle monocle mode for the focused container toggle-monocle Toggle monocle mode for the focused container
toggle-maximize Toggle native window fullscreen for the focused window toggle-maximize Toggle native maximization for the focused window
restore-windows Restore all hidden windows (debugging command) restore-windows Restore all hidden windows (debugging command)
reload-configuration Reload ~/komorebi.ahk (if it exists) reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Toggle the automatic reloading of ~/komorebi.ahk (if it exists) watch-configuration Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
@@ -242,9 +255,22 @@ ensures that all hidden windows are restored before termination.
If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known
to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`. to `komorebi` are stored and continuously updated in `~/komorebi.hwnd.json`.
### Restoring Windows
Running `komorebic restore-windows` will read the list of window handles and forcibly restore them, regardless of Running `komorebic restore-windows` will read the list of window handles and forcibly restore them, regardless of
whether the main `komorebi` process is running. whether the main `komorebi` process is running.
### Panics and Deadlocks
If `komorebi` ever stops responding, it is most likely either due to either a panic or a deadlock. In the case of a
panic, this will be reported in the log. In the case of a deadlock, there will not be any errors in the log, but the
process and the log will appear frozen.
If you believe you have encountered a deadlock, you can compile `komorebi` with `--feature deadlock_detection` and try
reproducing the deadlock again. This will check for deadlocks every 5 seconds in the background, and if a deadlock is
found, information about it will appear in the log which can be shared when opening an issu which can be shared when
opening an issue.
## Window Manager State and Integrations ## Window Manager State and Integrations
The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON The current state of the window manager can be queried using the `komorebic state` command, which returns a JSON
+5 -1
View File
@@ -20,6 +20,7 @@ getset = "0.1"
hotwatch = "0.4" hotwatch = "0.4"
lazy_static = "1" lazy_static = "1"
nanoid = "0.4" nanoid = "0.4"
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
paste = "1" paste = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
@@ -29,4 +30,7 @@ tracing = "0.1"
tracing-appender = "0.1" tracing-appender = "0.1"
tracing-subscriber = "0.2" tracing-subscriber = "0.2"
uds_windows = "1" uds_windows = "1"
which = "4" which = "4"
[features]
deadlock_detection = []
+36 -4
View File
@@ -3,13 +3,19 @@
use std::process::Command; use std::process::Command;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; #[cfg(feature = "deadlock_detection")]
use std::thread;
#[cfg(feature = "deadlock_detection")]
use std::time::Duration;
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use lazy_static::lazy_static; use lazy_static::lazy_static;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
use sysinfo::SystemExt; use sysinfo::SystemExt;
use tracing_appender::non_blocking::WorkerGuard; use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
@@ -54,7 +60,7 @@ lazy_static! {
"chrome.exe".to_string(), "chrome.exe".to_string(),
"idea64.exe".to_string(), "idea64.exe".to_string(),
"ApplicationFrameHost.exe".to_string(), "ApplicationFrameHost.exe".to_string(),
"steam.exe".to_string() "steam.exe".to_string(),
])); ]));
static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![ static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"firefox.exe".to_string(), "firefox.exe".to_string(),
@@ -161,6 +167,29 @@ pub fn load_configuration() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "deadlock_detection")]
#[tracing::instrument]
fn detect_deadlocks() {
// Create a background thread which checks for deadlocks every 10s
thread::spawn(move || loop {
tracing::info!("running deadlock detector");
thread::sleep(Duration::from_secs(5));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
tracing::error!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
tracing::error!("deadlock #{}", i);
for t in threads {
tracing::error!("thread id: {:#?}", t.thread_id());
tracing::error!("{:#?}", t.backtrace());
}
}
});
}
#[tracing::instrument] #[tracing::instrument]
fn main() -> Result<()> { fn main() -> Result<()> {
match std::env::args().count() { match std::env::args().count() {
@@ -176,6 +205,9 @@ fn main() -> Result<()> {
// File logging worker guard has to have an assignment in the main fn to work // File logging worker guard has to have an assignment in the main fn to work
let (_guard, _color_guard) = setup()?; let (_guard, _color_guard) = setup()?;
#[cfg(feature = "deadlock_detection")]
detect_deadlocks();
let process_id = WindowsApi::current_process_id(); let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?; WindowsApi::allow_set_foreground_window(process_id)?;
@@ -189,7 +221,7 @@ fn main() -> Result<()> {
incoming, incoming,
)))?)); )))?));
wm.lock().unwrap().init()?; wm.lock().init()?;
listen_for_commands(wm.clone()); listen_for_commands(wm.clone());
listen_for_events(wm.clone()); listen_for_events(wm.clone());
@@ -210,7 +242,7 @@ fn main() -> Result<()> {
"received ctrl-c, restoring all hidden windows and terminating process" "received ctrl-c, restoring all hidden windows and terminating process"
); );
wm.lock().unwrap().restore_all_windows(); wm.lock().restore_all_windows();
std::process::exit(130); std::process::exit(130);
} }
_ => Ok(()), _ => Ok(()),
+7 -8
View File
@@ -3,11 +3,11 @@ use std::io::BufReader;
use std::io::Write; use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::thread; use std::thread;
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
use color_eyre::Result; use color_eyre::Result;
use parking_lot::Mutex;
use uds_windows::UnixStream; use uds_windows::UnixStream;
use komorebi_core::ApplicationIdentifier; use komorebi_core::ApplicationIdentifier;
@@ -26,7 +26,6 @@ use crate::TRAY_AND_MULTI_WINDOW_EXES;
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) { pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
let listener = wm let listener = wm
.lock() .lock()
.unwrap()
.command_listener .command_listener
.try_clone() .try_clone()
.expect("could not clone unix listener"); .expect("could not clone unix listener");
@@ -35,7 +34,7 @@ pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
tracing::info!("listening"); tracing::info!("listening");
for client in listener.incoming() { for client in listener.incoming() {
match client { match client {
Ok(stream) => match wm.lock().unwrap().read_commands(stream) { Ok(stream) => match wm.lock().read_commands(stream) {
Ok(()) => {} Ok(()) => {}
Err(error) => tracing::error!("{}", error), Err(error) => tracing::error!("{}", error),
}, },
@@ -74,19 +73,19 @@ impl WindowManager {
self.set_workspace_padding(monitor_idx, workspace_idx, size)?; self.set_workspace_padding(monitor_idx, workspace_idx, size)?;
} }
SocketMessage::FloatClass(target) => { SocketMessage::FloatClass(target) => {
let mut float_classes = FLOAT_CLASSES.lock().unwrap(); let mut float_classes = FLOAT_CLASSES.lock();
if !float_classes.contains(&target) { if !float_classes.contains(&target) {
float_classes.push(target); float_classes.push(target);
} }
} }
SocketMessage::FloatExe(target) => { SocketMessage::FloatExe(target) => {
let mut float_exes = FLOAT_EXES.lock().unwrap(); let mut float_exes = FLOAT_EXES.lock();
if !float_exes.contains(&target) { if !float_exes.contains(&target) {
float_exes.push(target); float_exes.push(target);
} }
} }
SocketMessage::FloatTitle(target) => { SocketMessage::FloatTitle(target) => {
let mut float_titles = FLOAT_TITLES.lock().unwrap(); let mut float_titles = FLOAT_TITLES.lock();
if !float_titles.contains(&target) { if !float_titles.contains(&target) {
float_titles.push(target); float_titles.push(target);
} }
@@ -183,13 +182,13 @@ impl WindowManager {
} }
SocketMessage::IdentifyTrayApplication(identifier, id) => match identifier { SocketMessage::IdentifyTrayApplication(identifier, id) => match identifier {
ApplicationIdentifier::Exe => { ApplicationIdentifier::Exe => {
let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap(); let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
if !exes.contains(&id) { if !exes.contains(&id) {
exes.push(id); exes.push(id);
} }
} }
ApplicationIdentifier::Class => { ApplicationIdentifier::Class => {
let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap(); let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
if !classes.contains(&id) { if !classes.contains(&id) {
classes.push(id); classes.push(id);
} }
+26 -13
View File
@@ -1,11 +1,11 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::thread; use std::thread;
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_channel::select; use crossbeam_channel::select;
use parking_lot::Mutex;
use komorebi_core::OperationDirection; use komorebi_core::OperationDirection;
use komorebi_core::Rect; use komorebi_core::Rect;
@@ -20,7 +20,7 @@ use crate::TRAY_AND_MULTI_WINDOW_EXES;
#[tracing::instrument] #[tracing::instrument]
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) { pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
let receiver = wm.lock().unwrap().incoming_events.lock().unwrap().clone(); let receiver = wm.lock().incoming_events.lock().clone();
thread::spawn(move || { thread::spawn(move || {
tracing::info!("listening"); tracing::info!("listening");
@@ -28,7 +28,7 @@ pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
select! { select! {
recv(receiver) -> mut maybe_event => { recv(receiver) -> mut maybe_event => {
if let Ok(event) = maybe_event.as_mut() { if let Ok(event) = maybe_event.as_mut() {
match wm.lock().unwrap().process_event(event) { match wm.lock().process_event(event) {
Ok(()) => {}, Ok(()) => {},
Err(error) => tracing::error!("{}", error) Err(error) => tracing::error!("{}", error)
} }
@@ -91,21 +91,28 @@ impl WindowManager {
} }
WindowManagerEvent::Hide(_, window) => { WindowManagerEvent::Hide(_, window) => {
let mut hide = false;
// Some major applications unfortunately send the HIDE signal when they are being // Some major applications unfortunately send the HIDE signal when they are being
// minimized or destroyed. Applications that close to the tray also do the same, // minimized or destroyed. Applications that close to the tray also do the same,
// and will have is_window() return true, as the process is still running even if // and will have is_window() return true, as the process is still running even if
// the window is not visible. // the window is not visible.
let tray_and_multi_window_exes = TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap();
let tray_and_multi_window_classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap();
// We don't want to purge windows that have been deliberately hidden by us, eg. when
// they are not on the top of a container stack.
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap();
if (!window.is_window() || tray_and_multi_window_exes.contains(&window.exe()?))
|| tray_and_multi_window_classes.contains(&window.class()?)
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
{ {
let tray_and_multi_window_exes = TRAY_AND_MULTI_WINDOW_EXES.lock();
let tray_and_multi_window_classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock();
// We don't want to purge windows that have been deliberately hidden by us, eg. when
// they are not on the top of a container stack.
let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if (!window.is_window() || tray_and_multi_window_exes.contains(&window.exe()?))
|| tray_and_multi_window_classes.contains(&window.class()?)
&& !programmatically_hidden_hwnds.contains(&window.hwnd)
{
hide = true;
}
}
if hide {
self.focused_workspace_mut()?.remove_window(window.hwnd)?; self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?; self.update_focused_workspace(false)?;
} }
@@ -120,6 +127,12 @@ impl WindowManager {
return Ok(()); return Ok(());
} }
if let Some(w) = workspace.maximized_window() {
if w.hwnd == window.hwnd {
return Ok(());
}
}
self.focused_workspace_mut()? self.focused_workspace_mut()?
.focus_container_by_window(window.hwnd)?; .focus_container_by_window(window.hwnd)?;
} }
+7 -7
View File
@@ -110,7 +110,7 @@ impl Window {
} }
pub fn hide(self) { pub fn hide(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap(); let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if !programmatically_hidden_hwnds.contains(&self.hwnd) { if !programmatically_hidden_hwnds.contains(&self.hwnd) {
programmatically_hidden_hwnds.push(self.hwnd); programmatically_hidden_hwnds.push(self.hwnd);
} }
@@ -119,7 +119,7 @@ impl Window {
} }
pub fn restore(self) { pub fn restore(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap(); let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds if let Some(idx) = programmatically_hidden_hwnds
.iter() .iter()
.position(|&hwnd| hwnd == self.hwnd) .position(|&hwnd| hwnd == self.hwnd)
@@ -131,7 +131,7 @@ impl Window {
} }
pub fn maximize(self) { pub fn maximize(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap(); let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds if let Some(idx) = programmatically_hidden_hwnds
.iter() .iter()
.position(|&hwnd| hwnd == self.hwnd) .position(|&hwnd| hwnd == self.hwnd)
@@ -204,9 +204,9 @@ impl Window {
#[tracing::instrument(fields(exe, title))] #[tracing::instrument(fields(exe, title))]
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> { pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
let classes = FLOAT_CLASSES.lock().unwrap(); let classes = FLOAT_CLASSES.lock();
let exes = FLOAT_EXES.lock().unwrap(); let exes = FLOAT_EXES.lock();
let titles = FLOAT_TITLES.lock().unwrap(); let titles = FLOAT_TITLES.lock();
if self.title().is_err() { if self.title().is_err() {
return Ok(false); return Ok(false);
@@ -239,7 +239,7 @@ impl Window {
} }
} }
let allow_layered = LAYERED_EXE_WHITELIST.lock().unwrap().contains(&exe_name); let allow_layered = LAYERED_EXE_WHITELIST.lock().contains(&exe_name);
let style = self.style()?; let style = self.style()?;
let ex_style = self.ex_style()?; let ex_style = self.ex_style()?;
+7 -7
View File
@@ -3,7 +3,6 @@ use std::io::ErrorKind;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::thread; use std::thread;
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
@@ -11,6 +10,7 @@ use color_eyre::Result;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use hotwatch::notify::DebouncedEvent; use hotwatch::notify::DebouncedEvent;
use hotwatch::Hotwatch; use hotwatch::Hotwatch;
use parking_lot::Mutex;
use serde::Serialize; use serde::Serialize;
use uds_windows::UnixListener; use uds_windows::UnixListener;
@@ -63,12 +63,12 @@ impl From<&mut WindowManager> for State {
Self { Self {
monitors: wm.monitors.clone(), monitors: wm.monitors.clone(),
is_paused: wm.is_paused, is_paused: wm.is_paused,
float_classes: FLOAT_CLASSES.lock().unwrap().clone(), float_classes: FLOAT_CLASSES.lock().clone(),
float_exes: FLOAT_EXES.lock().unwrap().clone(), float_exes: FLOAT_EXES.lock().clone(),
float_titles: FLOAT_TITLES.lock().unwrap().clone(), float_titles: FLOAT_TITLES.lock().clone(),
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().unwrap().clone(), layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(),
tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap().clone(), tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().clone(),
tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap().clone(), tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().clone(),
} }
} }
} }
+1 -2
View File
@@ -80,7 +80,7 @@ pub extern "system" fn win_event_hook(
// //
// [yatta\src\windows_event.rs:110] event = 32780 ObjectNameChange // [yatta\src\windows_event.rs:110] event = 32780 ObjectNameChange
// [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange // [yatta\src\windows_event.rs:110] event = 32779 ObjectLocationChange
let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock().unwrap(); let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
if let Ok(exe) = window.exe() { if let Ok(exe) = window.exe() {
if winevent == WinEvent::ObjectNameChange { if winevent == WinEvent::ObjectNameChange {
@@ -101,7 +101,6 @@ pub extern "system" fn win_event_hook(
if should_manage { if should_manage {
WINEVENT_CALLBACK_CHANNEL WINEVENT_CALLBACK_CHANNEL
.lock() .lock()
.unwrap()
.0 .0
.send(event_type) .send(event_type)
.expect("could not send message on WINEVENT_CALLBACK_CHANNEL"); .expect("could not send message on WINEVENT_CALLBACK_CHANNEL");
+3 -3
View File
@@ -1,13 +1,13 @@
use std::sync::atomic::AtomicIsize; use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex;
use bindings::Windows::Win32::Foundation::HWND; use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::Accessibility::SetWinEventHook; use bindings::Windows::Win32::UI::Accessibility::SetWinEventHook;
@@ -43,7 +43,7 @@ pub fn new(outgoing: Arc<Mutex<Sender<WindowManagerEvent>>>) -> WinEventListener
impl WinEventListener { impl WinEventListener {
pub fn start(self) { pub fn start(self) {
let hook = self.hook.clone(); let hook = self.hook.clone();
let outgoing = self.outgoing_events.lock().unwrap().clone(); let outgoing = self.outgoing_events.lock().clone();
thread::spawn(move || unsafe { thread::spawn(move || unsafe {
let hook_ref = SetWinEventHook( let hook_ref = SetWinEventHook(
@@ -61,7 +61,7 @@ impl WinEventListener {
// The code in the callback doesn't work in its own loop, needs to be within // The code in the callback doesn't work in its own loop, needs to be within
// the MessageLoop callback for the winevent callback to even fire // the MessageLoop callback for the winevent callback to even fire
MessageLoop::start(10, |_msg| { MessageLoop::start(10, |_msg| {
if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().unwrap().1.try_recv() { if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().1.try_recv() {
match outgoing.send(event) { match outgoing.send(event) {
Ok(_) => {} Ok(_) => {}
Err(error) => { Err(error) => {