mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-07 03:13:31 +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
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,6 +57,7 @@ pub enum SocketMessage {
|
||||
FloatClass(String),
|
||||
FloatExe(String),
|
||||
FloatTitle(String),
|
||||
IdentifyTrayApplication(ApplicationIdentifier, String),
|
||||
State,
|
||||
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)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[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-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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>>) {
|
||||
@@ -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();
|
||||
@@ -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");
|
||||
|
||||
@@ -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>>) {
|
||||
@@ -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)?;
|
||||
|
||||
@@ -28,19 +28,50 @@ use crate::window::Window;
|
||||
use crate::window_manager_event::WindowManagerEvent;
|
||||
use crate::windows_api::WindowsApi;
|
||||
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 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]
|
||||
|
||||
@@ -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;
|
||||
@@ -62,6 +63,7 @@ enum SubCommand {
|
||||
FloatClass(FloatTarget),
|
||||
FloatExe(FloatTarget),
|
||||
FloatTitle(FloatTarget),
|
||||
IdentifyTrayApplication(ApplicationTarget),
|
||||
AdjustContainerPadding(SizingAdjustment),
|
||||
AdjustWorkspacePadding(SizingAdjustment),
|
||||
FlipLayout(LayoutFlip),
|
||||
@@ -130,6 +132,12 @@ struct FloatTarget {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct ApplicationTarget {
|
||||
identifier: ApplicationIdentifier,
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct Resize {
|
||||
edge: OperationDirection,
|
||||
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user