mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-23 00:58:37 +02:00
feat(wm): add cmd to identify 'close to tray' apps
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
This commit is contained in:
13
README.md
13
README.md
@@ -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
|
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.
|
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
|
## Configuration
|
||||||
|
|
||||||
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
|
||||||
@@ -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 exe name
|
||||||
- [x] Floating rules based on window title
|
- [x] Floating rules based on window title
|
||||||
- [x] Floating rules based on window class
|
- [x] Floating rules based on window class
|
||||||
|
- [x] Identify 'close/minimize to tray' applications
|
||||||
- [x] Toggle floating windows
|
- [x] Toggle floating windows
|
||||||
- [x] Toggle monocle window
|
- [x] Toggle monocle window
|
||||||
- [x] Toggle focus follows mouse
|
- [x] Toggle focus follows mouse
|
||||||
@@ -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
|
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:
|
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 clippy` and ensure that all lints and suggestions have been addressed before committing
|
||||||
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
|
- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing
|
||||||
- Use `git cz` with
|
- Use `git cz` with
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
windows::build!(
|
windows::build!(
|
||||||
Windows::Win32::Foundation::{
|
Windows::Win32::Foundation::RECT,
|
||||||
POINT,
|
Windows::Win32::Foundation::POINT,
|
||||||
RECT,
|
Windows::Win32::Foundation::BOOL,
|
||||||
BOOL,
|
Windows::Win32::Foundation::PWSTR,
|
||||||
PWSTR,
|
Windows::Win32::Foundation::HWND,
|
||||||
HWND,
|
Windows::Win32::Foundation::LPARAM,
|
||||||
LPARAM,
|
|
||||||
},
|
|
||||||
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
|
// error: `Windows.Win32.Graphics.Dwm.DWMWA_CLOAKED` not found in metadata
|
||||||
Windows::Win32::Graphics::Dwm::*,
|
Windows::Win32::Graphics::Dwm::*,
|
||||||
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
|
// error: `Windows.Win32.Graphics.Gdi.MONITOR_DEFAULTTONEAREST` not found in metadata
|
||||||
Windows::Win32::Graphics::Gdi::*,
|
Windows::Win32::Graphics::Gdi::*,
|
||||||
Windows::Win32::System::Threading::{
|
Windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS,
|
||||||
PROCESS_ACCESS_RIGHTS,
|
Windows::Win32::System::Threading::PROCESS_NAME_FORMAT,
|
||||||
PROCESS_NAME_FORMAT,
|
Windows::Win32::System::Threading::OpenProcess,
|
||||||
OpenProcess,
|
Windows::Win32::System::Threading::QueryFullProcessImageNameW,
|
||||||
QueryFullProcessImageNameW,
|
Windows::Win32::System::Threading::GetCurrentThreadId,
|
||||||
GetCurrentThreadId,
|
Windows::Win32::System::Threading::AttachThreadInput,
|
||||||
AttachThreadInput,
|
Windows::Win32::System::Threading::GetCurrentProcessId,
|
||||||
GetCurrentProcessId
|
|
||||||
},
|
|
||||||
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
|
Windows::Win32::UI::KeyboardAndMouseInput::SetFocus,
|
||||||
Windows::Win32::UI::Accessibility::{SetWinEventHook, HWINEVENTHOOK},
|
Windows::Win32::UI::Accessibility::{SetWinEventHook, HWINEVENTHOOK},
|
||||||
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
|
// error: `Windows.Win32.UI.WindowsAndMessaging.GWL_EXSTYLE` not found in metadata
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ pub enum SocketMessage {
|
|||||||
FloatClass(String),
|
FloatClass(String),
|
||||||
FloatExe(String),
|
FloatExe(String),
|
||||||
FloatTitle(String),
|
FloatTitle(String),
|
||||||
|
IdentifyTrayApplication(ApplicationIdentifier, String),
|
||||||
State,
|
State,
|
||||||
FocusFollowsMouse(bool),
|
FocusFollowsMouse(bool),
|
||||||
}
|
}
|
||||||
@@ -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)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ Run, komorebic.exe float-exe wincompose.exe, , Hide
|
|||||||
Run, komorebic.exe float-title Calculator, , Hide
|
Run, komorebic.exe float-title Calculator, , Hide
|
||||||
Run, komorebic.exe float-exe 1Password.exe, , 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
|
; Change the focused window, Alt + Vim direction keys
|
||||||
!h::
|
!h::
|
||||||
Run, komorebic.exe focus left, , Hide
|
Run, komorebic.exe focus left, , Hide
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ lazy_static! {
|
|||||||
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));
|
||||||
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
static ref LAYERED_EXE_WHITELIST: Arc<Mutex<Vec<String>>> =
|
||||||
Arc::new(Mutex::new(vec!["steam.exe".to_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(),
|
"explorer.exe".to_string(),
|
||||||
"firefox.exe".to_string(),
|
"firefox.exe".to_string(),
|
||||||
"chrome.exe".to_string(),
|
"chrome.exe".to_string(),
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ use color_eyre::eyre::ContextCompat;
|
|||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use uds_windows::UnixStream;
|
use uds_windows::UnixStream;
|
||||||
|
|
||||||
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::SocketMessage;
|
use komorebi_core::SocketMessage;
|
||||||
|
|
||||||
|
use crate::window_manager;
|
||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::FLOAT_CLASSES;
|
use crate::FLOAT_CLASSES;
|
||||||
use crate::FLOAT_EXES;
|
use crate::FLOAT_EXES;
|
||||||
use crate::FLOAT_TITLES;
|
use crate::FLOAT_TITLES;
|
||||||
|
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
|
||||||
|
use crate::TRAY_AND_MULTI_WINDOW_EXES;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {
|
||||||
@@ -152,7 +156,7 @@ impl WindowManager {
|
|||||||
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
|
self.set_workspace_name(monitor_idx, workspace_idx, name)?;
|
||||||
}
|
}
|
||||||
SocketMessage::State => {
|
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")?;
|
let mut socket = dirs::home_dir().context("there is no home directory")?;
|
||||||
socket.push("komorebic.sock");
|
socket.push("komorebic.sock");
|
||||||
let socket = socket.as_path();
|
let socket = socket.as_path();
|
||||||
@@ -176,6 +180,20 @@ impl WindowManager {
|
|||||||
SocketMessage::WatchConfiguration(enable) => {
|
SocketMessage::WatchConfiguration(enable) => {
|
||||||
self.watch_configuration(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");
|
tracing::info!("processed");
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ use crate::window_manager::WindowManager;
|
|||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::HIDDEN_HWNDS;
|
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]
|
#[tracing::instrument]
|
||||||
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
pub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {
|
||||||
@@ -91,11 +92,19 @@ impl WindowManager {
|
|||||||
|
|
||||||
WindowManagerEvent::Hide(_, window) => {
|
WindowManagerEvent::Hide(_, window) => {
|
||||||
// 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. Will have to keep updating this list.
|
// minimized or destroyed. Applications that close to the tray also do the same,
|
||||||
let common_multi_window_exes = MULTI_WINDOW_EXES.lock().unwrap();
|
// 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();
|
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.focused_workspace_mut()?.remove_window(window.hwnd)?;
|
||||||
self.update_focused_workspace(false)?;
|
self.update_focused_workspace(false)?;
|
||||||
|
|||||||
@@ -28,19 +28,50 @@ use crate::window::Window;
|
|||||||
use crate::window_manager_event::WindowManagerEvent;
|
use crate::window_manager_event::WindowManagerEvent;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
|
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, Serialize)]
|
#[derive(Debug)]
|
||||||
pub struct WindowManager {
|
pub struct WindowManager {
|
||||||
pub monitors: Ring<Monitor>,
|
pub monitors: Ring<Monitor>,
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub command_listener: UnixListener,
|
pub command_listener: UnixListener,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub hotwatch: Hotwatch,
|
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);
|
impl_ring_elements!(WindowManager, Monitor);
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use bindings::Windows::Win32::Foundation::HWND;
|
|||||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
||||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||||
|
use komorebi_core::ApplicationIdentifier;
|
||||||
use komorebi_core::CycleDirection;
|
use komorebi_core::CycleDirection;
|
||||||
use komorebi_core::Layout;
|
use komorebi_core::Layout;
|
||||||
use komorebi_core::LayoutFlip;
|
use komorebi_core::LayoutFlip;
|
||||||
@@ -62,6 +63,7 @@ enum SubCommand {
|
|||||||
FloatClass(FloatTarget),
|
FloatClass(FloatTarget),
|
||||||
FloatExe(FloatTarget),
|
FloatExe(FloatTarget),
|
||||||
FloatTitle(FloatTarget),
|
FloatTitle(FloatTarget),
|
||||||
|
IdentifyTrayApplication(ApplicationTarget),
|
||||||
AdjustContainerPadding(SizingAdjustment),
|
AdjustContainerPadding(SizingAdjustment),
|
||||||
AdjustWorkspacePadding(SizingAdjustment),
|
AdjustWorkspacePadding(SizingAdjustment),
|
||||||
FlipLayout(LayoutFlip),
|
FlipLayout(LayoutFlip),
|
||||||
@@ -130,6 +132,12 @@ struct FloatTarget {
|
|||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clap)]
|
||||||
|
struct ApplicationTarget {
|
||||||
|
identifier: ApplicationIdentifier,
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
struct Resize {
|
struct Resize {
|
||||||
edge: OperationDirection,
|
edge: OperationDirection,
|
||||||
@@ -354,6 +362,12 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
send_message(&*SocketMessage::WatchConfiguration(enable).as_bytes()?)?;
|
send_message(&*SocketMessage::WatchConfiguration(enable).as_bytes()?)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::IdentifyTrayApplication(target) => {
|
||||||
|
send_message(
|
||||||
|
&*SocketMessage::IdentifyTrayApplication(target.identifier, target.id)
|
||||||
|
.as_bytes()?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user