mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-11 22:12:53 +01:00
feat(wm): add native window maximization toggle
Windows that have been maximized do not retain their maximized state across workspaces as workspaces are built on top of sending SW_HIDE and SW_SHOW events which at various points of the event loop end up overriding SW_SHOWMAXIMIZED and SW_SHOWMAXIMIZE. To handle this use case, I have added a new 'komorebic toggle-maximize' command which sends SW_MAXIMIZE for a window and keeps a record of the window in the focused workspace in the same way that monocle windows are tracked. In this way, komorebi can know when switching to a workspace if it has to restore a window to a native maximized state. Some additional edge cases are caught in this commit in showing and hiding workspaces, to also account for floating windows and monocle containers. resolve #12
This commit is contained in:
42
README.md
42
README.md
@@ -133,6 +133,47 @@ You can run `komorebic.exe` to get a full list of the commands that you can use
|
||||
keybindings with. You can run `komorebic.exe <COMMAND> --help` to get a full explanation of the arguments required for
|
||||
each command.
|
||||
|
||||
```
|
||||
start Start komorebi.exe as a background process
|
||||
stop Stop the komorebi.exe process and restore all hidden windows
|
||||
state Show a JSON representation of the current window manager state
|
||||
log Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
||||
focus Change focus to the window in the specified direction
|
||||
move Move the focused window in the specified direction
|
||||
stack Stack the focused window in the specified direction
|
||||
resize Resize the focused window in the specified direction
|
||||
unstack Unstack the focused window
|
||||
cycle-stack Cycle the focused stack in the specified cycle direction
|
||||
move-to-monitor Move the focused window to the specified monitor
|
||||
move-to-workspace Move the focused window to the specified workspace
|
||||
focus-monitor Focus the specified monitor
|
||||
focus-workspace Focus the specified 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-workspace-padding Adjust workspace padding on the focused workspace
|
||||
flip-layout Flip the layout on the focused workspace (BSP only)
|
||||
promote Promote the focused window to the top of the tree
|
||||
retile Force the retiling of all managed windows
|
||||
ensure-workspaces Create at least this many workspaces for the specified monitor
|
||||
container-padding Set the container padding for the specified workspace
|
||||
workspace-padding Set the workspace padding for the specified workspace
|
||||
workspace-layout Set the layout for the specified workspace
|
||||
workspace-tiling Enable or disable window tiling for the specified workspace
|
||||
workspace-name Set the workspace name for the specified workspace
|
||||
toggle-pause Toggle the window manager on and off across all monitors
|
||||
toggle-tiling Toggle window tiling on the focused workspace
|
||||
toggle-float Toggle floating mode for the focused window
|
||||
toggle-monocle Toggle monocle mode for the focused container
|
||||
toggle-maximize Toggle native window fullscreen for the focused window
|
||||
restore-windows Restore all hidden windows (debugging command)
|
||||
reload-configuration Reload ~/komorebi.ahk (if it exists)
|
||||
watch-configuration Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
|
||||
float-rule Add a rule to always float the specified application
|
||||
identify-tray-application Identify an application that closes to the system tray
|
||||
focus-follows-mouse Enable or disable focus follows mouse for the operating system
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Multi-monitor
|
||||
@@ -158,6 +199,7 @@ each command.
|
||||
- [x] Identify 'close/minimize to tray' applications
|
||||
- [x] Toggle floating windows
|
||||
- [x] Toggle monocle window
|
||||
- [x] Toggle native maximization
|
||||
- [x] Toggle focus follows mouse
|
||||
- [x] Toggle automatic tiling
|
||||
- [x] Pause all window management
|
||||
|
||||
@@ -32,6 +32,7 @@ pub enum SocketMessage {
|
||||
Promote,
|
||||
ToggleFloat,
|
||||
ToggleMonocle,
|
||||
ToggleMaximize,
|
||||
// Current Workspace Commands
|
||||
AdjustContainerPadding(Sizing, i32),
|
||||
AdjustWorkspacePadding(Sizing, i32),
|
||||
|
||||
@@ -129,6 +129,11 @@ return
|
||||
Run, komorebic.exe toggle-monocle, , Hide
|
||||
return
|
||||
|
||||
; Toggle native maximize for the focused window, Alt + Shift + =
|
||||
!+=::
|
||||
Run, komorebic.exe toggle-maximize, , Hide
|
||||
return
|
||||
|
||||
; Flip horizontally, Alt + X
|
||||
!x::
|
||||
Run, komorebic.exe flip-layout horizontal, , Hide
|
||||
|
||||
@@ -70,14 +70,23 @@ impl Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn move_container_to_workspace(
|
||||
&mut self,
|
||||
target_workspace_idx: usize,
|
||||
follow: bool,
|
||||
) -> Result<()> {
|
||||
let container = self
|
||||
let workspace = self
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.context("there is no workspace")?;
|
||||
|
||||
if workspace.maximized_window().is_some() {
|
||||
return Err(eyre::anyhow!(
|
||||
"cannot move native maximized window to another monitor or workspace"
|
||||
));
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ impl WindowManager {
|
||||
}
|
||||
SocketMessage::ToggleFloat => self.toggle_float()?,
|
||||
SocketMessage::ToggleMonocle => self.toggle_monocle()?,
|
||||
SocketMessage::ToggleMaximize => self.toggle_maximize()?,
|
||||
SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {
|
||||
self.set_container_padding(monitor_idx, workspace_idx, size)?;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ impl WindowManager {
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
if workspace.containers().is_empty() || !workspace.contains_window(window.hwnd) {
|
||||
if !workspace.contains_window(window.hwnd) {
|
||||
workspace.new_container_for_window(*window);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,18 @@ impl Window {
|
||||
WindowsApi::restore_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn maximize(self) {
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock().unwrap();
|
||||
if let Some(idx) = programmatically_hidden_hwnds
|
||||
.iter()
|
||||
.position(|&hwnd| hwnd == self.hwnd)
|
||||
{
|
||||
programmatically_hidden_hwnds.remove(idx);
|
||||
}
|
||||
|
||||
WindowsApi::maximize_window(self.hwnd());
|
||||
}
|
||||
|
||||
pub fn focus(self) -> Result<()> {
|
||||
// Attach komorebi thread to Window thread
|
||||
let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd());
|
||||
|
||||
@@ -196,7 +196,9 @@ impl WindowManager {
|
||||
.update_focused_workspace()?;
|
||||
|
||||
if mouse_follows_focus {
|
||||
if let Ok(window) = self.focused_window_mut() {
|
||||
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
||||
window.focus()?;
|
||||
} else if let Ok(window) = self.focused_window_mut() {
|
||||
window.focus()?;
|
||||
} else {
|
||||
let desktop_window = Window {
|
||||
@@ -310,9 +312,17 @@ impl WindowManager {
|
||||
tracing::info!("moving container");
|
||||
|
||||
let monitor = self.focused_monitor_mut().context("there is no monitor")?;
|
||||
let container = monitor
|
||||
let workspace = monitor
|
||||
.focused_workspace_mut()
|
||||
.context("there is no workspace")?
|
||||
.context("there is no workspace")?;
|
||||
|
||||
if workspace.maximized_window().is_some() {
|
||||
return Err(eyre::anyhow!(
|
||||
"cannot move native maximized window to another monitor or workspace"
|
||||
));
|
||||
}
|
||||
|
||||
let container = workspace
|
||||
.remove_focused_container()
|
||||
.context("there is no container")?;
|
||||
|
||||
@@ -541,6 +551,34 @@ impl WindowManager {
|
||||
workspace.reintegrate_monocle_container()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn toggle_maximize(&mut self) -> Result<()> {
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
|
||||
match workspace.maximized_window() {
|
||||
None => self.maximize_window()?,
|
||||
Some(_) => self.unmaximize_window()?,
|
||||
}
|
||||
|
||||
self.update_focused_workspace(false)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn maximize_window(&mut self) -> Result<()> {
|
||||
tracing::info!("maximizing windowj");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.new_maximized_window()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn unmaximize_window(&mut self) -> Result<()> {
|
||||
tracing::info!("unmaximizing window");
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
workspace.reintegrate_maximized_window()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn flip_layout(&mut self, layout_flip: LayoutFlip) -> Result<()> {
|
||||
tracing::info!("flipping layout");
|
||||
|
||||
@@ -68,6 +68,7 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
|
||||
use bindings::Windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
|
||||
@@ -259,6 +260,10 @@ impl WindowsApi {
|
||||
Self::show_window(hwnd, SW_RESTORE);
|
||||
}
|
||||
|
||||
pub fn maximize_window(hwnd: HWND) {
|
||||
Self::show_window(hwnd, SW_MAXIMIZE);
|
||||
}
|
||||
|
||||
pub fn set_foreground_window(hwnd: HWND) -> Result<()> {
|
||||
match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) {
|
||||
WindowsResult::Ok(_) => Ok(()),
|
||||
|
||||
@@ -28,7 +28,12 @@ pub struct Workspace {
|
||||
monocle_container: Option<Container>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
monocle_restore_idx: Option<usize>,
|
||||
monocle_container_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||
maximized_window: Option<Window>,
|
||||
#[serde(skip_serializing)]
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
maximized_window_restore_idx: Option<usize>,
|
||||
#[getset(get = "pub", get_mut = "pub")]
|
||||
floating_windows: Vec<Window>,
|
||||
#[getset(get_copy = "pub", set = "pub")]
|
||||
@@ -57,7 +62,9 @@ impl Default for Workspace {
|
||||
name: None,
|
||||
containers: Ring::default(),
|
||||
monocle_container: None,
|
||||
monocle_restore_idx: None,
|
||||
maximized_window: None,
|
||||
maximized_window_restore_idx: None,
|
||||
monocle_container_restore_idx: None,
|
||||
floating_windows: Vec::default(),
|
||||
layout: Layout::BSP,
|
||||
layout_flip: None,
|
||||
@@ -77,6 +84,20 @@ impl Workspace {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.hide();
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
for window in container.windows_mut() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(&mut self) -> Result<()> {
|
||||
@@ -87,14 +108,31 @@ impl Workspace {
|
||||
window.restore();
|
||||
|
||||
if idx == i {
|
||||
to_focus = Option::from(window);
|
||||
to_focus = Option::from(*window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
for window in container.windows_mut() {
|
||||
window.restore();
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
window.restore();
|
||||
}
|
||||
|
||||
// Do this here to make sure that an error doesn't stop the restoration of other windows
|
||||
// Maximised windows should always be drawn at the top of the Z order
|
||||
if let Some(window) = to_focus {
|
||||
window.focus()?;
|
||||
if self.maximized_window().is_none() {
|
||||
window.focus()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -110,7 +148,9 @@ impl Workspace {
|
||||
if let Some(container) = self.monocle_container_mut() {
|
||||
if let Some(window) = container.focused_window_mut() {
|
||||
window.set_position(&adjusted_work_area, true)?;
|
||||
}
|
||||
};
|
||||
} else if let Some(window) = self.maximized_window_mut() {
|
||||
window.maximize();
|
||||
} else if !self.containers().is_empty() {
|
||||
let layouts = self.layout().calculate(
|
||||
&adjusted_work_area,
|
||||
@@ -223,8 +263,26 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn contains_window(&self, hwnd: isize) -> bool {
|
||||
for x in self.containers() {
|
||||
if x.contains_window(hwnd) {
|
||||
for container in self.containers() {
|
||||
if container.contains_window(hwnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
if hwnd == window.hwnd {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(container) = self.monocle_container() {
|
||||
if container.contains_window(hwnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for window in self.floating_windows() {
|
||||
if hwnd == window.hwnd {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -497,7 +555,7 @@ impl Workspace {
|
||||
// it had before
|
||||
|
||||
self.set_monocle_container(Option::from(container));
|
||||
self.set_monocle_restore_idx(Option::from(focused_idx));
|
||||
self.set_monocle_container_restore_idx(Option::from(focused_idx));
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
@@ -513,7 +571,7 @@ impl Workspace {
|
||||
|
||||
pub fn reintegrate_monocle_container(&mut self) -> Result<()> {
|
||||
let restore_idx = self
|
||||
.monocle_restore_idx()
|
||||
.monocle_container_restore_idx()
|
||||
.context("there is no monocle restore index")?;
|
||||
|
||||
let container = self
|
||||
@@ -534,6 +592,71 @@ impl Workspace {
|
||||
.load_focused_window();
|
||||
|
||||
self.set_monocle_container(None);
|
||||
self.set_monocle_container_restore_idx(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_maximized_window(&mut self) -> Result<()> {
|
||||
let focused_idx = self.focused_container_idx();
|
||||
|
||||
let container = self
|
||||
.focused_container_mut()
|
||||
.context("there is no container")?;
|
||||
|
||||
let window = container
|
||||
.remove_focused_window()
|
||||
.context("there is no window")?;
|
||||
|
||||
if container.windows().is_empty() {
|
||||
self.containers_mut().remove(focused_idx);
|
||||
self.resize_dimensions_mut().remove(focused_idx);
|
||||
} else {
|
||||
container.load_focused_window();
|
||||
}
|
||||
|
||||
self.set_maximized_window(Option::from(window));
|
||||
self.set_maximized_window_restore_idx(Option::from(focused_idx));
|
||||
|
||||
if let Some(window) = self.maximized_window() {
|
||||
window.maximize();
|
||||
}
|
||||
|
||||
if focused_idx != 0 {
|
||||
self.focus_container(focused_idx - 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reintegrate_maximized_window(&mut self) -> Result<()> {
|
||||
let restore_idx = self
|
||||
.maximized_window_restore_idx()
|
||||
.context("there is no monocle restore index")?;
|
||||
|
||||
let window = self
|
||||
.maximized_window()
|
||||
.as_ref()
|
||||
.context("there is no monocle container")?;
|
||||
|
||||
let window = *window;
|
||||
if !self.containers().is_empty() && restore_idx > self.containers().len() - 1 {
|
||||
self.containers_mut()
|
||||
.resize(restore_idx, Container::default());
|
||||
}
|
||||
|
||||
let mut container = Container::default();
|
||||
container.windows_mut().push_back(window);
|
||||
self.containers_mut().insert(restore_idx, container);
|
||||
|
||||
self.focus_container(restore_idx);
|
||||
|
||||
self.focused_container_mut()
|
||||
.context("there is no container")?
|
||||
.load_focused_window();
|
||||
|
||||
self.set_maximized_window(None);
|
||||
self.set_maximized_window_restore_idx(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -250,6 +250,8 @@ enum SubCommand {
|
||||
ToggleFloat,
|
||||
/// Toggle monocle mode for the focused container
|
||||
ToggleMonocle,
|
||||
/// Toggle native maximization for the focused window
|
||||
ToggleMaximize,
|
||||
/// Restore all hidden windows (debugging command)
|
||||
RestoreWindows,
|
||||
/// Reload ~/komorebi.ahk (if it exists)
|
||||
@@ -341,6 +343,9 @@ fn main() -> Result<()> {
|
||||
SubCommand::ToggleMonocle => {
|
||||
send_message(&*SocketMessage::ToggleMonocle.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::ToggleMaximize => {
|
||||
send_message(&*SocketMessage::ToggleMaximize.as_bytes()?)?;
|
||||
}
|
||||
SubCommand::WorkspaceLayout(arg) => {
|
||||
send_message(
|
||||
&*SocketMessage::WorkspaceLayout(arg.monitor, arg.workspace, arg.value)
|
||||
|
||||
Reference in New Issue
Block a user