Skip to content

Commit 0629230

Browse files
authored
feat!: change move to a dialog and allow resizing (#5)
1 parent 7d75aa5 commit 0629230

File tree

8 files changed

+524
-263
lines changed

8 files changed

+524
-263
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## [Unreleased]
44

5+
### Changed
6+
7+
- The `Move` context menu item has been renamed to `Move & Resize`
8+
9+
### Added
10+
11+
- Added `Move & Resize` to the tray icon menu.
12+
- The new `Move & Resize` will open a dialog that allows fine editing of the position and size values for the switcher.
13+
514
## [0.5.0] - 2025-03-25
615

716
### Added

src/app.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
77
use winit::window::WindowId;
88

99
use crate::egui_glue::EguiWindow;
10+
use crate::window_registry_info::WindowRegistryInfo;
1011

12+
#[derive(Debug, Clone)]
1113
pub enum AppMessage {
1214
UpdateWorkspaces(Vec<crate::komorebi::Workspace>),
1315
MenuEvent(muda::MenuEvent),
1416
SystemSettingsChanged,
1517
DpiChanged,
18+
StartMoveResize,
19+
CreateResizeWindow {
20+
host: isize,
21+
info: WindowRegistryInfo,
22+
},
23+
CloseWindow(WindowId),
24+
NotifyWindowInfoChanges(WindowRegistryInfo),
1625
}
1726

1827
pub struct App {
@@ -24,21 +33,41 @@ pub struct App {
2433
}
2534

2635
impl App {
27-
pub fn new(taskbar_hwnd: HWND, proxy: EventLoopProxy<AppMessage>) -> Self {
36+
pub fn new(taskbar_hwnd: HWND, proxy: EventLoopProxy<AppMessage>) -> anyhow::Result<Self> {
2837
let wgpu_instance = egui_wgpu::wgpu::Instance::new(&wgpu::InstanceDescriptor {
2938
backends: wgpu::Backends::DX12,
3039
..Default::default()
3140
});
3241

33-
let tray_icon = crate::tray_icon::TrayIcon::new().ok();
42+
let tray_icon = crate::tray_icon::TrayIcon::new(proxy.clone()).ok();
3443

35-
Self {
44+
Ok(Self {
3645
wgpu_instance,
3746
taskbar_hwnd,
3847
windows: Default::default(),
3948
proxy,
4049
tray_icon,
50+
})
51+
}
52+
53+
fn handle_app_message(
54+
&mut self,
55+
event_loop: &ActiveEventLoop,
56+
event: &AppMessage,
57+
) -> anyhow::Result<()> {
58+
match event {
59+
AppMessage::CreateResizeWindow { host, info } => {
60+
self.create_resize_window(event_loop, HWND(*host as _), *info)?
61+
}
62+
63+
AppMessage::CloseWindow(window_id) => {
64+
self.windows.remove(window_id);
65+
}
66+
67+
_ => {}
4168
}
69+
70+
Ok(())
4271
}
4372
}
4473

@@ -55,14 +84,20 @@ impl ApplicationHandler<AppMessage> for App {
5584
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
5685

5786
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppMessage) {
87+
if let Err(e) = self.handle_app_message(event_loop, &event) {
88+
tracing::error!("Error while handling AppMessage: {e}")
89+
}
90+
5891
if let Some(tray) = &self.tray_icon {
59-
tray.handle_app_message(event_loop, &event);
92+
if let Err(e) = tray.handle_app_message(event_loop, &event) {
93+
tracing::error!("Error while handling AppMessage for tray: {e}")
94+
}
6095
}
6196

6297
for window in self.windows.values_mut() {
6398
let ctx = window.surface.egui_renderer.egui_ctx();
6499
if let Err(e) = window.view.handle_app_message(ctx, event_loop, &event) {
65-
tracing::error!("Error while handling AppMessage: {e}")
100+
tracing::error!("Error while handling AppMessage for window: {e}")
66101
}
67102

68103
window.request_redraw();
@@ -76,10 +111,13 @@ impl ApplicationHandler<AppMessage> for App {
76111
event: WindowEvent,
77112
) {
78113
if event == WindowEvent::CloseRequested {
79-
tracing::info!("Closing main window");
114+
tracing::info!("Closing window {window_id:?}");
80115
self.windows.remove(&window_id);
81-
tracing::info!("Exiting event loop");
82-
event_loop.exit();
116+
117+
if self.windows.is_empty() {
118+
tracing::info!("Exiting event loop");
119+
event_loop.exit();
120+
}
83121
}
84122

85123
let Some(window) = self.windows.get_mut(&window_id) else {

src/host.rs

Lines changed: 76 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,76 @@ use windows::core::*;
22
use windows::Win32::Foundation::*;
33
use windows::Win32::System::LibraryLoader::*;
44
use windows::Win32::UI::WindowsAndMessaging::*;
5-
use windows_registry::CURRENT_USER;
65
use winit::event_loop::EventLoopProxy;
76

87
use crate::app::AppMessage;
9-
10-
#[cfg(debug_assertions)]
11-
const APP_REG_KEY: &str = "SOFTWARE\\amrbashir\\komorebi-switcher-debug";
12-
#[cfg(not(debug_assertions))]
13-
const APP_REG_KEY: &str = "SOFTWARE\\amrbashir\\komorebi-switcher";
14-
15-
const WINDOW_POS_X_KEY: &str = "window-pos-x";
16-
const WINDOW_POS_Y_KEY: &str = "window-pos-y";
8+
use crate::main_window::MainWindowView;
9+
use crate::window_registry_info::WindowRegistryInfo;
1710

1811
#[cfg(debug_assertions)]
1912
const HOST_CLASSNAME: PCWSTR = w!("komorebi-switcher-debug::host");
2013
#[cfg(not(debug_assertions))]
2114
const HOST_CLASSNAME: PCWSTR = w!("komorebi-switcher::host");
2215

16+
pub unsafe fn create_host(
17+
taskbar_hwnd: HWND,
18+
proxy: EventLoopProxy<AppMessage>,
19+
window_info: &WindowRegistryInfo,
20+
) -> anyhow::Result<HWND> {
21+
let hinstance = unsafe { GetModuleHandleW(None) }?;
22+
23+
let mut rect = RECT::default();
24+
GetClientRect(taskbar_hwnd, &mut rect)?;
25+
26+
let wc = WNDCLASSW {
27+
hInstance: hinstance.into(),
28+
lpszClassName: HOST_CLASSNAME,
29+
style: CS_HREDRAW | CS_VREDRAW,
30+
lpfnWndProc: Some(wndproc_host),
31+
..Default::default()
32+
};
33+
34+
let atom = RegisterClassW(&wc);
35+
debug_assert!(atom != 0);
36+
37+
let userdata = WndProcUserData { proxy };
38+
39+
let height = if window_info.auto_height {
40+
rect.bottom - rect.top
41+
} else {
42+
window_info.height
43+
};
44+
45+
let hwnd = CreateWindowExW(
46+
WS_EX_NOACTIVATE | WS_EX_NOREDIRECTIONBITMAP,
47+
HOST_CLASSNAME,
48+
PCWSTR::null(),
49+
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
50+
window_info.x,
51+
window_info.y,
52+
window_info.width,
53+
height,
54+
Some(taskbar_hwnd),
55+
None,
56+
None,
57+
Some(Box::into_raw(Box::new(userdata)) as _),
58+
)?;
59+
60+
SetWindowPos(
61+
hwnd,
62+
Some(HWND_TOP),
63+
0,
64+
0,
65+
0,
66+
0,
67+
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
68+
)?;
69+
70+
Ok(hwnd)
71+
}
72+
2373
struct WndProcUserData {
2474
proxy: EventLoopProxy<AppMessage>,
25-
taskbar_hwnd: HWND,
2675
}
2776

2877
impl WndProcUserData {
@@ -31,23 +80,6 @@ impl WndProcUserData {
3180
}
3281
}
3382

34-
unsafe extern "system" fn enum_child_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
35-
let children = &mut *(lparam.0 as *mut Vec<HWND>);
36-
children.push(hwnd);
37-
true.into()
38-
}
39-
40-
fn enum_child_windows(hwnd: HWND) -> Vec<HWND> {
41-
let mut children = Vec::new();
42-
43-
let children_ptr = &mut children as *mut Vec<HWND>;
44-
let children_ptr = LPARAM(children_ptr as _);
45-
46-
let _ = unsafe { EnumChildWindows(Some(hwnd), Some(enum_child_proc), children_ptr) };
47-
48-
children
49-
}
50-
5183
unsafe extern "system" fn wndproc_host(
5284
hwnd: HWND,
5385
msg: u32,
@@ -70,43 +102,14 @@ unsafe extern "system" fn wndproc_host(
70102
}
71103
}
72104

73-
// Disable position changes in y direction
74-
// and clamp x direction to stay visible in taskbar
75-
WM_WINDOWPOSCHANGING => {
76-
let window_pos = &mut *(lparam.0 as *mut WINDOWPOS);
77-
window_pos.y = 0;
78-
79-
let userdata = WndProcUserData::from_hwnd(hwnd);
80-
81-
let mut rect = RECT::default();
82-
if GetClientRect(userdata.taskbar_hwnd, &mut rect).is_ok() {
83-
window_pos.x = window_pos.x.max(0).min(rect.right - window_pos.cx);
84-
}
85-
}
86-
87-
// Save host position to be loaded on startup
88-
WM_WINDOWPOSCHANGED => {
89-
let window_pos = &*(lparam.0 as *const WINDOWPOS);
90-
91-
let key = CURRENT_USER.create(APP_REG_KEY);
92-
if let Ok(key) = key {
93-
let x = window_pos.x;
94-
let y = window_pos.y;
95-
96-
tracing::debug!("Storing window position into registry {x},{y}");
97-
98-
if let Err(e) = key.set_string(WINDOW_POS_X_KEY, &x.to_string()) {
99-
tracing::error!("Failed to store window pos x into registry: {e}")
100-
}
101-
102-
if let Err(e) = key.set_string(WINDOW_POS_Y_KEY, &y.to_string()) {
103-
tracing::error!("Failed to store window pos y into registry: {e}")
104-
}
105-
}
106-
}
107-
108105
// Resize children when this host is resized
109106
WM_SIZE => {
107+
// Skip resizing children if this host is in resize mode
108+
let prop = GetPropW(hwnd, MainWindowView::IN_RESIZE_PROP);
109+
if prop.0 as isize != 0 {
110+
return DefWindowProcW(hwnd, msg, wparam, lparam);
111+
}
112+
110113
let mut rect = RECT::default();
111114
if GetClientRect(hwnd, &mut rect).is_ok() {
112115
let width = rect.right - rect.left;
@@ -154,62 +157,19 @@ unsafe extern "system" fn wndproc_host(
154157
DefWindowProcW(hwnd, msg, wparam, lparam)
155158
}
156159

157-
pub unsafe fn create_host(
158-
taskbar_hwnd: HWND,
159-
proxy: EventLoopProxy<AppMessage>,
160-
) -> anyhow::Result<HWND> {
161-
let hinstance = unsafe { GetModuleHandleW(None) }?;
162-
163-
let mut rect = RECT::default();
164-
GetClientRect(taskbar_hwnd, &mut rect)?;
165-
166-
let wc = WNDCLASSW {
167-
hInstance: hinstance.into(),
168-
lpszClassName: HOST_CLASSNAME,
169-
style: CS_HREDRAW | CS_VREDRAW,
170-
lpfnWndProc: Some(wndproc_host),
171-
..Default::default()
172-
};
173-
174-
let atom = RegisterClassW(&wc);
175-
debug_assert!(atom != 0);
176-
177-
tracing::debug!("Loading window position from registry");
178-
let key = CURRENT_USER.create(APP_REG_KEY)?;
179-
let window_pos_x = key.get_string(WINDOW_POS_X_KEY).ok();
180-
let window_pos_y = key.get_string(WINDOW_POS_Y_KEY).ok();
181-
let window_pos_x = window_pos_x.and_then(|s| s.parse().ok());
182-
let window_pos_y = window_pos_y.and_then(|s| s.parse().ok());
160+
unsafe extern "system" fn enum_child_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
161+
let children = &mut *(lparam.0 as *mut Vec<HWND>);
162+
children.push(hwnd);
163+
true.into()
164+
}
183165

184-
let userdata = WndProcUserData {
185-
proxy,
186-
taskbar_hwnd,
187-
};
166+
fn enum_child_windows(hwnd: HWND) -> Vec<HWND> {
167+
let mut children = Vec::new();
188168

189-
let hwnd = CreateWindowExW(
190-
WS_EX_NOACTIVATE | WS_EX_NOREDIRECTIONBITMAP,
191-
HOST_CLASSNAME,
192-
PCWSTR::null(),
193-
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
194-
window_pos_x.unwrap_or(16),
195-
window_pos_y.unwrap_or(0),
196-
200,
197-
rect.bottom - rect.top,
198-
Some(taskbar_hwnd),
199-
None,
200-
None,
201-
Some(Box::into_raw(Box::new(userdata)) as _),
202-
)?;
169+
let children_ptr = &mut children as *mut Vec<HWND>;
170+
let children_ptr = LPARAM(children_ptr as _);
203171

204-
SetWindowPos(
205-
hwnd,
206-
Some(HWND_TOP),
207-
0,
208-
0,
209-
0,
210-
0,
211-
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
212-
)?;
172+
let _ = unsafe { EnumChildWindows(Some(hwnd), Some(enum_child_proc), children_ptr) };
213173

214-
Ok(hwnd)
174+
children
215175
}

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ mod egui_glue;
1313
mod host;
1414
mod komorebi;
1515
mod main_window;
16+
mod resize_window;
1617
mod tray_icon;
1718
mod widgets;
19+
mod window_registry_info;
1820

1921
fn run() -> anyhow::Result<()> {
2022
let evl = EventLoop::<AppMessage>::with_user_event().build()?;
@@ -28,7 +30,7 @@ fn run() -> anyhow::Result<()> {
2830

2931
let taskbar_hwnd = unsafe { FindWindowW(w!("Shell_TrayWnd"), PCWSTR::null()) }?;
3032

31-
let mut app = App::new(taskbar_hwnd, evl.create_proxy());
33+
let mut app = App::new(taskbar_hwnd, evl.create_proxy())?;
3234
evl.run_app(&mut app)?;
3335

3436
Ok(())

0 commit comments

Comments
 (0)