Skip to content

Commit

Permalink
feat(wm): add cmd to identify 'close to tray' apps
Browse files Browse the repository at this point in the history
Issue #6 highlighted a workflow that I don't personally use, but I am
sure is common among other Windows users, which is to use the Close
button to minimize an application to the tray.

Since this is largely a configurable option in those applications
(Discord etc.), I have implemented a command for the user to identify
those applications themselves when configuring the window manager,
instead of adding them to the previous Vec of known multi-window
applications that need to be identified by default.

Close/minimize to tray applications can be identified either by their
class or their executable name.

I figure it is pretty important to know the rules defined on the window
manager instance, so I have exposed these on a new window_manager::State
struct which is now what get returns from the 'komorebic.exe state'
command.

resolve #6
  • Loading branch information
LGUG2Z committed Aug 16, 2021
1 parent b6ff862 commit b2ab893
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 30 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ You can similarly stop the process by running `komorebic stop`.
If you have AutoHotKey installed and a `komorebi.ahk` file in your home directory (run `$Env:UserProfile` at a
PowerShell prompt to find your home directory), `komorebi` will automatically try to load it when starting.

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),
you have probably enabled a 'close/minimize to tray' option for that application. You can tell _komorebi_ to handle this
application appropriately by identifying it via the executable name or the window class:

```powershell
komorebic.exe identify-tray-application exe Discord.exe
komorebic.exe identify-tray-application exe Telegram.exe
```

## Configuration

As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
Expand Down Expand Up @@ -115,6 +125,7 @@ sample [komorebi.ahk](komorebi.sample.ahk) AHK script that you can use as a star
- [x] Floating rules based on exe name
- [x] Floating rules based on window title
- [x] Floating rules based on window class
- [x] Identify 'close/minimize to tray' applications
- [x] Toggle floating windows
- [x] Toggle monocle window
- [x] Toggle focus follows mouse
Expand All @@ -130,7 +141,7 @@ sample [komorebi.ahk](komorebi.sample.ahk) AHK script that you can use as a star
If you would like to contribute code to this repository, there are a few requests that I have to ensure a foundation of
code quality, consistency and commit hygiene:

- Flatten all `use` statements except in `bindings/build.rs`
- Flatten all `use` statements
- Run `cargo +nightly clippy` and ensure that all lints and suggestions have been addressed before committing
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
- Use `git cz` with
Expand Down
30 changes: 13 additions & 17 deletions bindings/build.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
fn main() {
windows::build!(
Windows::Win32::Foundation::{
POINT,
RECT,
BOOL,
PWSTR,
HWND,
LPARAM,
},
Windows::Win32::Foundation::RECT,
Windows::Win32::Foundation::POINT,
Windows::Win32::Foundation::BOOL,
Windows::Win32::Foundation::PWSTR,
Windows::Win32::Foundation::HWND,
Windows::Win32::Foundation::LPARAM,
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
Windows::Win32::Graphics::Dwm::*,
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
Windows::Win32::Graphics::Gdi::*,
Windows::Win32::System::Threading::{
PROCESS_ACCESS_RIGHTS,
PROCESS_NAME_FORMAT,
OpenProcess,
QueryFullProcessImageNameW,
GetCurrentThreadId,
AttachThreadInput,
GetCurrentProcessId
},
Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS,
Windows::Win32::System::Threading::PROCESS_NAME_FORMAT,
Windows::Win32::System::Threading::OpenProcess,
Windows::Win32::System::Threading::QueryFullProcessImageNameW,
Windows::Win32::System::Threading::GetCurrentThreadId,
Windows::Win32::System::Threading::AttachThreadInput,
Windows::Win32::System::Threading::GetCurrentProcessId,
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
Windows::Win32::UI::Accessibility::{SetWinEventHook, HWINEVENTHOOK},
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
Expand Down
8 changes: 8 additions & 0 deletions komorebi-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub enum SocketMessage {
FloatClass(String),
FloatExe(String),
FloatTitle(String),
IdentifyTrayApplication(ApplicationIdentifier, String),
State,
FocusFollowsMouse(bool),
}
Expand All @@ -79,6 +80,13 @@ impl FromStr for SocketMessage {
}
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum ApplicationIdentifier {
Exe,
Class,
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
#[strum(serialize_all = "snake_case")]
#[derive(Clap)]
Expand Down
4 changes: 4 additions & 0 deletions komorebi.sample.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Run, komorebic.exe float-exe wincompose.exe, , Hide
Run, komorebic.exe float-title Calculator, , Hide
Run, komorebic.exe float-exe 1Password.exe, , Hide

; Identify applications that close to the tray
Run, komorebic.exe identify-tray-application exe Discord.exe, , Hide
Run, komorebic.exe identify-tray-application exe Telegram.exe, , Hide

; Change the focused window, Alt + Vim direction keys
!h::
Run, komorebic.exe focus left, , Hide
Expand Down
4 changes: 3 additions & 1 deletion komorebi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ lazy_static! {
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec!["steam.exe".to_string()]));
static ref MULTI_WINDOW_EXES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
static ref TRAY_AND_MULTI_WINDOW_CLASSES: Arc<Mutex<Vec<String>>> =
Arc::new(Mutex::new(vec![]));
static ref TRAY_AND_MULTI_WINDOW_EXES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![
"explorer.exe".to_string(),
"firefox.exe".to_string(),
"chrome.exe".to_string(),
Expand Down
20 changes: 19 additions & 1 deletion komorebi/src/process_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
use uds_windows::UnixStream;

use komorebi_core::ApplicationIdentifier;
use komorebi_core::SocketMessage;

use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::FLOAT_CLASSES;
use crate::FLOAT_EXES;
use crate::FLOAT_TITLES;
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
use crate::TRAY_AND_MULTI_WINDOW_EXES;

#[tracing::instrument]
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
Expand Down Expand Up @@ -152,7 +156,7 @@ impl WindowManager {
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
}
SocketMessage::State => {
let state = serde_json::to_string_pretty(self)?;
let state = serde_json::to_string_pretty(&window_manager::State::from(self))?;
let mut socket = dirs::home_dir().context("there is no home directory")?;
socket.push("komorebic.sock");
let socket = socket.as_path();
Expand All @@ -176,6 +180,20 @@ impl WindowManager {
SocketMessage::WatchConfiguration(enable) => {
self.watch_configuration(enable)?;
}
SocketMessage::IdentifyTrayApplication(identifier, id) => match identifier {
ApplicationIdentifier::Exe => {
let mut exes = TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap();
if !exes.contains(&id) {
exes.push(id);
}
}
ApplicationIdentifier::Class => {
let mut classes = TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap();
if !classes.contains(&id) {
classes.push(id);
}
}
},
}

tracing::info!("processed");
Expand Down
19 changes: 14 additions & 5 deletions komorebi/src/process_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::HIDDEN_HWNDS;
use crate::MULTI_WINDOW_EXES;
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
use crate::TRAY_AND_MULTI_WINDOW_EXES;

#[tracing::instrument]
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
Expand Down Expand Up @@ -91,11 +92,19 @@ impl WindowManager {

WindowManagerEvent::Hide(_, window) => {
// Some major applications unfortunately send the HIDE signal when they are being
// minimized or destroyed. Will have to keep updating this list.
let common_multi_window_exes = MULTI_WINDOW_EXES.lock().unwrap();
// 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
// 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() || common_multi_window_exes.contains(&window.exe()?))
&& !programmatically_hidden_hwnds.contains(&window.hwnd)

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)
{
self.focused_workspace_mut()?.remove_window(window.hwnd)?;
self.update_focused_workspace(false)?;
Expand Down
41 changes: 36 additions & 5 deletions komorebi/src/window_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,50 @@ use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;

#[derive(Debug, Serialize)]
use crate::FLOAT_CLASSES;
use crate::FLOAT_EXES;
use crate::FLOAT_TITLES;
use crate::LAYERED_EXE_WHITELIST;
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
use crate::TRAY_AND_MULTI_WINDOW_EXES;

#[derive(Debug)]
pub struct WindowManager {
pub monitors: Ring<Monitor>,
#[serde(skip_serializing)]
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
#[serde(skip_serializing)]
pub command_listener: UnixListener,
pub is_paused: bool,
#[serde(skip_serializing)]
pub hotwatch: Hotwatch,
}

#[derive(Debug, Serialize)]
pub struct State {
pub monitors: Ring<Monitor>,
pub is_paused: bool,
pub float_classes: Vec<String>,
pub float_exes: Vec<String>,
pub float_titles: Vec<String>,
pub layered_exe_whitelist: Vec<String>,
pub tray_and_multi_window_exes: Vec<String>,
pub tray_and_multi_window_classes: Vec<String>,
}

#[allow(clippy::fallible_impl_from)]
impl From<&mut WindowManager> for State {
fn from(wm: &mut WindowManager) -> Self {
Self {
monitors: wm.monitors.clone(),
is_paused: wm.is_paused,
float_classes: FLOAT_CLASSES.lock().unwrap().clone(),
float_exes: FLOAT_EXES.lock().unwrap().clone(),
float_titles: FLOAT_TITLES.lock().unwrap().clone(),
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().unwrap().clone(),
tray_and_multi_window_exes: TRAY_AND_MULTI_WINDOW_EXES.lock().unwrap().clone(),
tray_and_multi_window_classes: TRAY_AND_MULTI_WINDOW_CLASSES.lock().unwrap().clone(),
}
}
}

impl_ring_elements!(WindowManager, Monitor);

#[tracing::instrument]
Expand Down
14 changes: 14 additions & 0 deletions komorebic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use komorebi_core::ApplicationIdentifier;
use komorebi_core::CycleDirection;
use komorebi_core::Layout;
use komorebi_core::LayoutFlip;
Expand Down Expand Up @@ -62,6 +63,7 @@ enum SubCommand {
FloatClass(FloatTarget),
FloatExe(FloatTarget),
FloatTitle(FloatTarget),
IdentifyTrayApplication(ApplicationTarget),
AdjustContainerPadding(SizingAdjustment),
AdjustWorkspacePadding(SizingAdjustment),
FlipLayout(LayoutFlip),
Expand Down Expand Up @@ -130,6 +132,12 @@ struct FloatTarget {
id: String,
}

#[derive(Clap)]
struct ApplicationTarget {
identifier: ApplicationIdentifier,
id: String,
}

#[derive(Clap)]
struct Resize {
edge: OperationDirection,
Expand Down Expand Up @@ -354,6 +362,12 @@ fn main() -> Result<()> {
};
send_message(&*SocketMessage::WatchConfiguration(enable).as_bytes()?)?;
}
SubCommand::IdentifyTrayApplication(target) => {
send_message(
&*SocketMessage::IdentifyTrayApplication(target.identifier, target.id)
.as_bytes()?,
)?;
}
}

Ok(())
Expand Down

0 comments on commit b2ab893

Please sign in to comment.