Compare commits

..

6 Commits

Author SHA1 Message Date
LGUG2Z
3776310d09 wip fallback 2025-03-26 15:33:50 -07:00
LGUG2Z
e702d93a8a feat(cli): add move/send-to-last-workspace cmds
This commit adds two new komorebic commands, move-to-last-workspace and
send-to-last-workspace, which move or send the focused window to the
last focused workspace on the focused monitor.
2025-03-23 16:07:43 -07:00
LGUG2Z
a8c687d3d5 chore(deps): cargo update 2025-03-22 20:22:44 -07:00
alex-ds13
30fbc1ae73 feat(client): reexport win32_display_data
This commit reexports the `win32_display_data` crate so that any 3rd
party app that needs it can get it through the `komorebi-client` without
having to keep manually synchronizing it with the version used by komorebi.
2025-03-22 19:35:23 -07:00
LGUG2Z
cb60e91842 feat(bar): show icons for uwp apps
This commit integrates the excellent investigation and work done by
@davor-skontra on the windows-icons repo to enable the retrieval of UWP
applications, including all those annoying Microsoft applications which
all share the ApplicationFrameHost.exe executable and the
ApplicationFrameWindow class.

Since these applications share the same executable, the icon cache in
komorei-bar has been updated to use the window hwnd as a key intead of
the window executable.

resolve #1226
2025-03-22 19:31:30 -07:00
LGUG2Z
64d29d606a refactor(wm): add dep injection to monitor reconiliator
This commit adds some dependency injection to the monitor reconciliator
module to make it easier to test the behaviour when different kinds of
data are returned from win32_display_data.
2025-03-22 13:16:15 -07:00
20 changed files with 186 additions and 32 deletions

25
Cargo.lock generated
View File

@@ -2745,7 +2745,8 @@ dependencies = [
"tracing-subscriber",
"windows 0.61.1",
"windows-core 0.61.0",
"windows-icons",
"windows-icons 0.1.0 (git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354)",
"windows-icons 0.1.0 (git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5)",
]
[[package]]
@@ -4472,6 +4473,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -6085,7 +6092,7 @@ dependencies = [
[[package]]
name = "win32-display-data"
version = "0.1.0"
source = "git+https://github.com/LGUG2Z/win32-display-data?rev=93949750b1f123fb79827ba4d66ffcab68055654#93949750b1f123fb79827ba4d66ffcab68055654"
source = "git+https://github.com/LGUG2Z/win32-display-data?rev=a28c6559a9de2f92c142a714947a9b081776caca#a28c6559a9de2f92c142a714947a9b081776caca"
dependencies = [
"itertools 0.14.0",
"serde",
@@ -6270,6 +6277,20 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-icons"
version = "0.1.0"
source = "git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354#0c9d7ee1b807347c507d3a9862dd007b4d3f4354"
dependencies = [
"base64",
"image",
"regex",
"roxmltree",
"sysinfo",
"winapi",
"windows 0.58.0",
]
[[package]]
name = "windows-icons"
version = "0.1.0"

View File

@@ -35,7 +35,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
paste = "1"
sysinfo = "0.33"
uds_windows = "1"
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "93949750b1f123fb79827ba4d66ffcab68055654" }
win32-display-data = { git = "https://github.com/LGUG2Z/win32-display-data", rev = "a28c6559a9de2f92c142a714947a9b081776caca" }
windows-numerics = { version = "0.2" }
windows-implement = { version = "0.60" }
windows-interface = { version = "0.59" }

View File

@@ -10,9 +10,9 @@ Options:
Desired ease function for animation
[default: linear]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart,
ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ,
ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
[possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart,
ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo, ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back,
ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]
-a, --animation-type <ANIMATION_TYPE>
Animation type to apply the style to. If not specified, sets global style

View File

@@ -18,7 +18,7 @@ Arguments:
Options:
-w, --window-kind <WINDOW_KIND>
[default: single]
[possible values: single, stack, monocle, unfocused, floating]
[possible values: single, stack, monocle, unfocused, unfocused-locked, floating]
-h, --help
Print help

View File

@@ -0,0 +1,12 @@
# move-to-last-workspace
```
Move the focused window to the last focused monitor workspace
Usage: komorebic.exe move-to-last-workspace
Options:
-h, --help
Print help
```

View File

@@ -0,0 +1,12 @@
# send-to-last-workspace
```
Send the focused window to the last focused monitor workspace
Usage: komorebic.exe send-to-last-workspace
Options:
-h, --help
Print help
```

12
docs/cli/toggle-lock.md Normal file
View File

@@ -0,0 +1,12 @@
# toggle-lock
```
Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
Usage: komorebic.exe toggle-lock
Options:
-h, --help
Print help
```

View File

@@ -1,8 +1,8 @@
# toggle-workspace-float-override
```
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace
previously it takes the opposite of the global value
Enable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes
the opposite of the global value
Usage: komorebic.exe toggle-workspace-float-override

View File

@@ -1,8 +1,7 @@
# toggle-workspace-window-container-behaviour
```
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the
global value
Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value
Usage: komorebic.exe toggle-workspace-window-container-behaviour

View File

@@ -37,7 +37,8 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
windows = { workspace = true }
windows-core = { workspace = true }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
windows-icons-fallback = { package = "windows-icons", git = "https://github.com/LGUG2Z/windows-icons", rev = "d67cc9920aa9b4883393e411fb4fa2ddd4c498b5" }
[features]
default = ["schemars"]

View File

@@ -47,7 +47,7 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
pub static DEFAULT_PADDING: f32 = 10.0;
pub static ICON_CACHE: LazyLock<Mutex<HashMap<String, RgbaImage>>> =
pub static ICON_CACHE: LazyLock<Mutex<HashMap<isize, RgbaImage>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
#[derive(Parser)]

View File

@@ -837,11 +837,16 @@ impl From<&Container> for KomorebiNotificationStateContainerInformation {
for window in windows {
let mut icon_cache = ICON_CACHE.lock().unwrap();
let mut update_cache = false;
let exe = window.exe().unwrap_or_default();
let hwnd = window.hwnd;
match icon_cache.get(&exe) {
match icon_cache.get(&hwnd) {
None => {
icons.push(windows_icons::get_icon_by_process_id(window.process_id()));
let icon = match windows_icons::get_icon_by_hwnd(window.hwnd) {
None => windows_icons_fallback::get_icon_by_process_id(window.process_id()),
Some(icon) => Some(icon),
};
icons.push(icon);
update_cache = true;
}
Some(icon) => {
@@ -851,7 +856,7 @@ impl From<&Container> for KomorebiNotificationStateContainerInformation {
if update_cache {
if let Some(Some(icon)) = icons.last() {
icon_cache.insert(exe, icon.clone());
icon_cache.insert(hwnd, icon.clone());
}
}
}
@@ -873,11 +878,16 @@ impl From<&Window> for KomorebiNotificationStateContainerInformation {
let mut icon_cache = ICON_CACHE.lock().unwrap();
let mut update_cache = false;
let mut icons = vec![];
let exe = value.exe().unwrap_or_default();
let hwnd = value.hwnd;
match icon_cache.get(&exe) {
match icon_cache.get(&hwnd) {
None => {
icons.push(windows_icons::get_icon_by_process_id(value.process_id()));
let icon = match windows_icons::get_icon_by_hwnd(hwnd) {
None => windows_icons_fallback::get_icon_by_process_id(value.process_id()),
Some(icon) => Some(icon),
};
icons.push(icon);
update_cache = true;
}
Some(icon) => {
@@ -887,7 +897,7 @@ impl From<&Window> for KomorebiNotificationStateContainerInformation {
if update_cache {
if let Some(Some(icon)) = icons.last() {
icon_cache.insert(exe, icon.clone());
icon_cache.insert(hwnd, icon.clone());
}
}

View File

@@ -46,6 +46,7 @@ pub use komorebi::core::WindowKind;
pub use komorebi::monitor::Monitor;
pub use komorebi::monitor_reconciliator::MonitorNotification;
pub use komorebi::ring::Ring;
pub use komorebi::win32_display_data;
pub use komorebi::window::Window;
pub use komorebi::window_manager_event::WindowManagerEvent;
pub use komorebi::workspace::Workspace;

View File

@@ -501,10 +501,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|| focused_window_hwnd != foreground_window
{
if ws.locked_containers().contains(&idx) {
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {

View File

@@ -62,6 +62,8 @@ pub enum SocketMessage {
UnstackAll,
ResizeWindowEdge(OperationDirection, Sizing),
ResizeWindowAxis(Axis, Sizing),
MoveContainerToLastWorkspace,
SendContainerToLastWorkspace,
MoveContainerToMonitorNumber(usize),
CycleMoveContainerToMonitor(CycleDirection),
MoveContainerToWorkspaceNumber(usize),

View File

@@ -53,6 +53,7 @@ pub use core::*;
pub use process_command::*;
pub use process_event::*;
pub use static_config::*;
pub use win32_display_data;
pub use window::*;
pub use window_manager::*;
pub use window_manager_event::*;

View File

@@ -84,10 +84,12 @@ pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {
monitor_cache.insert(preferred_id, monitor);
}
pub fn attached_display_devices() -> color_eyre::Result<Vec<Monitor>> {
let all_displays = win32_display_data::connected_displays_all()
.flatten()
.collect::<Vec<_>>();
pub fn attached_display_devices<F, I>(display_provider: F) -> color_eyre::Result<Vec<Monitor>>
where
F: Fn() -> I + Copy,
I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,
{
let all_displays = display_provider().flatten().collect::<Vec<_>>();
let mut serial_id_map = HashMap::new();
@@ -154,7 +156,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
tracing::info!("created hidden window to listen for monitor-related events");
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
match handle_notifications(wm.clone(), win32_display_data::connected_displays_all) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
@@ -171,7 +173,14 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Re
Ok(())
}
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
pub fn handle_notifications<F, I>(
wm: Arc<Mutex<WindowManager>>,
display_provider: F,
) -> color_eyre::Result<()>
where
F: Fn() -> I + Copy,
I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,
{
tracing::info!("listening");
let receiver = event_rx();
@@ -296,7 +305,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let initial_monitor_count = wm.monitors().len();
// Get the currently attached display devices
let attached_devices = attached_display_devices()?;
let attached_devices = attached_display_devices(display_provider)?;
// Make sure that in our state any attached displays have the latest Win32 data
for monitor in wm.monitors_mut() {

View File

@@ -640,6 +640,67 @@ impl WindowManager {
SocketMessage::AdjustWorkspacePadding(sizing, adjustment) => {
self.adjust_workspace_padding(sizing, adjustment)?;
}
SocketMessage::MoveContainerToLastWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
}
let idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx();
if let Some(monitor) = self.focused_monitor_mut() {
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
self.move_container_to_workspace(last_focused_workspace, true, None)?;
}
}
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.set_last_focused_workspace(Option::from(idx));
}
SocketMessage::SendContainerToLastWorkspace => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {
if monitor_idx != self.focused_monitor_idx() {
if let Some(monitor) = self.monitors().get(monitor_idx) {
if let Some(workspace) = monitor.focused_workspace() {
if workspace.is_empty() {
self.focus_monitor(monitor_idx)?;
}
}
}
}
}
let idx = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?
.focused_workspace_idx();
if let Some(monitor) = self.focused_monitor_mut() {
if let Some(last_focused_workspace) = monitor.last_focused_workspace() {
self.move_container_to_workspace(last_focused_workspace, false, None)?;
}
}
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.set_last_focused_workspace(Option::from(idx));
}
SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, true, None)?;
}

View File

@@ -1104,6 +1104,10 @@ enum SubCommand {
/// Move the focused window to the specified monitor workspace
#[clap(arg_required_else_help = true)]
MoveToMonitorWorkspace(MoveToMonitorWorkspace),
/// Send the focused window to the last focused monitor workspace
SendToLastWorkspace,
/// Move the focused window to the last focused monitor workspace
MoveToLastWorkspace,
/// Focus the specified monitor
#[clap(arg_required_else_help = true)]
FocusMonitor(FocusMonitor),
@@ -1857,6 +1861,12 @@ fn main() -> Result<()> {
arg.cycle_direction,
))?;
}
SubCommand::MoveToLastWorkspace => {
send_message(&SocketMessage::MoveContainerToLastWorkspace)?;
}
SubCommand::SendToLastWorkspace => {
send_message(&SocketMessage::SendContainerToLastWorkspace)?;
}
SubCommand::SwapWorkspacesWithMonitor(arg) => {
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(arg.target))?;
}

View File

@@ -133,6 +133,8 @@ nav:
- cli/cycle-send-to-workspace.md
- cli/send-to-monitor-workspace.md
- cli/move-to-monitor-workspace.md
- cli/send-to-last-workspace.md
- cli/move-to-last-workspace.md
- cli/focus-monitor.md
- cli/focus-monitor-at-cursor.md
- cli/focus-last-workspace.md
@@ -191,6 +193,7 @@ nav:
- cli/toggle-float.md
- cli/toggle-monocle.md
- cli/toggle-maximize.md
- cli/toggle-lock.md
- cli/restore-windows.md
- cli/manage.md
- cli/unmanage.md