1#![deny(unsafe_op_in_unsafe_fn)]
2
3use std::{
4 any::Any,
5 cell::{Cell, RefCell},
6 ffi::c_void,
7 iter::once,
8 num::NonZeroIsize,
9 path::PathBuf,
10 rc::{Rc, Weak},
11 str::FromStr,
12 sync::{Arc, Once},
13 time::{Duration, Instant},
14};
15
16use ::util::ResultExt;
17use anyhow::Context;
18use blade_graphics as gpu;
19use futures::channel::oneshot::{self, Receiver};
20use itertools::Itertools;
21use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
22use smallvec::SmallVec;
23use std::result::Result;
24use windows::{
25 core::*,
26 Win32::{
27 Foundation::*,
28 Graphics::Gdi::*,
29 System::{Com::*, Ole::*, SystemServices::*},
30 UI::{
31 Controls::*,
32 HiDpi::*,
33 Input::{Ime::*, KeyboardAndMouse::*},
34 Shell::*,
35 WindowsAndMessaging::*,
36 },
37 },
38};
39
40use crate::platform::blade::BladeRenderer;
41use crate::*;
42
43pub(crate) struct WindowsWindowInner {
44 hwnd: HWND,
45 origin: Cell<Point<DevicePixels>>,
46 physical_size: Cell<Size<DevicePixels>>,
47 scale_factor: Cell<f32>,
48 input_handler: Cell<Option<PlatformInputHandler>>,
49 renderer: RefCell<BladeRenderer>,
50 callbacks: RefCell<Callbacks>,
51 platform_inner: Rc<WindowsPlatformInner>,
52 pub(crate) handle: AnyWindowHandle,
53 hide_title_bar: bool,
54 display: RefCell<Rc<WindowsDisplay>>,
55 click_state: RefCell<ClickState>,
56 fullscreen: Cell<Option<StyleAndBounds>>,
57}
58
59impl WindowsWindowInner {
60 fn new(
61 hwnd: HWND,
62 cs: &CREATESTRUCTW,
63 platform_inner: Rc<WindowsPlatformInner>,
64 handle: AnyWindowHandle,
65 hide_title_bar: bool,
66 display: Rc<WindowsDisplay>,
67 ) -> Self {
68 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
69 let origin = Cell::new(Point {
70 x: DevicePixels(cs.x),
71 y: DevicePixels(cs.y),
72 });
73 let physical_size = Cell::new(Size {
74 width: DevicePixels(cs.cx),
75 height: DevicePixels(cs.cy),
76 });
77 let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
78 let input_handler = Cell::new(None);
79 struct RawWindow {
80 hwnd: *mut c_void,
81 }
82 unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
83 fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
84 let mut handle = blade_rwh::Win32WindowHandle::empty();
85 handle.hwnd = self.hwnd;
86 handle.into()
87 }
88 }
89 unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
90 fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
91 blade_rwh::WindowsDisplayHandle::empty().into()
92 }
93 }
94 let raw = RawWindow { hwnd: hwnd.0 as _ };
95 let gpu = Arc::new(
96 unsafe {
97 gpu::Context::init_windowed(
98 &raw,
99 gpu::ContextDesc {
100 validation: false,
101 capture: false,
102 overlay: false,
103 },
104 )
105 }
106 .unwrap(),
107 );
108 let extent = gpu::Extent {
109 width: 1,
110 height: 1,
111 depth: 1,
112 };
113 let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
114 let callbacks = RefCell::new(Callbacks::default());
115 let display = RefCell::new(display);
116 let click_state = RefCell::new(ClickState::new());
117 let fullscreen = Cell::new(None);
118 Self {
119 hwnd,
120 origin,
121 physical_size,
122 scale_factor,
123 input_handler,
124 renderer,
125 callbacks,
126 platform_inner,
127 handle,
128 hide_title_bar,
129 display,
130 click_state,
131 fullscreen,
132 }
133 }
134
135 fn is_maximized(&self) -> bool {
136 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
137 }
138
139 fn is_minimized(&self) -> bool {
140 unsafe { IsIconic(self.hwnd) }.as_bool()
141 }
142
143 fn is_fullscreen(&self) -> bool {
144 let fullscreen = self.fullscreen.take();
145 let is_fullscreen = fullscreen.is_some();
146 self.fullscreen.set(fullscreen);
147 is_fullscreen
148 }
149
150 async fn toggle_fullscreen(self: Rc<Self>) {
151 let StyleAndBounds {
152 style,
153 x,
154 y,
155 cx,
156 cy,
157 } = if let Some(state) = self.fullscreen.take() {
158 state
159 } else {
160 let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _);
161 let mut rc = RECT::default();
162 unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err();
163 self.fullscreen.set(Some(StyleAndBounds {
164 style,
165 x: rc.left,
166 y: rc.top,
167 cx: rc.right - rc.left,
168 cy: rc.bottom - rc.top,
169 }));
170 let style = style
171 & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION);
172 let bounds = self.display.borrow().clone().bounds();
173 StyleAndBounds {
174 style,
175 x: bounds.left().0,
176 y: bounds.top().0,
177 cx: bounds.size.width.0,
178 cy: bounds.size.height.0,
179 }
180 };
181 unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) };
182 unsafe {
183 SetWindowPos(
184 self.hwnd,
185 HWND::default(),
186 x,
187 y,
188 cx,
189 cy,
190 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
191 )
192 }
193 .log_err();
194 }
195
196 pub(crate) fn title_bar_padding(&self) -> Pixels {
197 // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
198 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
199 px(padding as f32)
200 }
201
202 pub(crate) fn title_bar_top_offset(&self) -> Pixels {
203 if self.is_maximized() {
204 self.title_bar_padding() * 2
205 } else {
206 px(0.)
207 }
208 }
209
210 pub(crate) fn title_bar_height(&self) -> Pixels {
211 // todo(windows) this is hard set to match the ui title bar
212 // in the future the ui title bar component will report the size
213 px(32.) + self.title_bar_top_offset()
214 }
215
216 pub(crate) fn caption_button_width(&self) -> Pixels {
217 // todo(windows) this is hard set to match the ui title bar
218 // in the future the ui title bar component will report the size
219 px(36.)
220 }
221
222 fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
223 let height = self.title_bar_height();
224 let mut rect = RECT::default();
225 unsafe { GetClientRect(self.hwnd, &mut rect) }?;
226 rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32);
227 Ok(rect)
228 }
229
230 fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
231 unsafe { GetKeyState(vkey.0 as i32) < 0 }
232 }
233
234 fn current_modifiers(&self) -> Modifiers {
235 Modifiers {
236 control: self.is_virtual_key_pressed(VK_CONTROL),
237 alt: self.is_virtual_key_pressed(VK_MENU),
238 shift: self.is_virtual_key_pressed(VK_SHIFT),
239 platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
240 function: false,
241 }
242 }
243
244 /// mark window client rect to be re-drawn
245 /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
246 pub(crate) fn invalidate_client_area(&self) {
247 unsafe { InvalidateRect(self.hwnd, None, FALSE) };
248 }
249
250 fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
251 let handled = match msg {
252 WM_ACTIVATE => self.handle_activate_msg(wparam),
253 WM_CREATE => self.handle_create_msg(lparam),
254 WM_MOVE => self.handle_move_msg(lparam),
255 WM_SIZE => self.handle_size_msg(lparam),
256 WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam),
257 WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam),
258 WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
259 WM_PAINT => self.handle_paint_msg(),
260 WM_CLOSE => self.handle_close_msg(),
261 WM_DESTROY => self.handle_destroy_msg(),
262 WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
263 WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam),
264 WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam),
265 WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam),
266 WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam),
267 WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam),
268 WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam),
269 WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam),
270 WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
271 WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
272 WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
273 WM_XBUTTONDOWN => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg),
274 WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
275 WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
276 WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
277 WM_XBUTTONUP => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg),
278 WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
279 WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
280 WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam),
281 WM_SYSKEYUP => self.handle_syskeyup_msg(wparam),
282 WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
283 WM_KEYUP => self.handle_keyup_msg(msg, wparam),
284 WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
285 WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
286 WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
287 WM_SETCURSOR => self.handle_set_cursor(lparam),
288 _ => None,
289 };
290 if let Some(n) = handled {
291 LRESULT(n)
292 } else {
293 unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
294 }
295 }
296
297 fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
298 let x = lparam.signed_loword() as i32;
299 let y = lparam.signed_hiword() as i32;
300 self.origin.set(Point {
301 x: DevicePixels(x),
302 y: DevicePixels(y),
303 });
304 let size = self.physical_size.get();
305 let center_x = x + size.width.0 / 2;
306 let center_y = y + size.height.0 / 2;
307 let monitor_bounds = self.display.borrow().bounds();
308 if center_x < monitor_bounds.left().0
309 || center_x > monitor_bounds.right().0
310 || center_y < monitor_bounds.top().0
311 || center_y > monitor_bounds.bottom().0
312 {
313 // center of the window may have moved to another monitor
314 let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) };
315 if !monitor.is_invalid() && self.display.borrow().handle != monitor {
316 // we will get the same monitor if we only have one
317 (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor));
318 }
319 }
320 let mut callbacks = self.callbacks.borrow_mut();
321 if let Some(callback) = callbacks.moved.as_mut() {
322 callback()
323 }
324 Some(0)
325 }
326
327 fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
328 let width = lparam.loword().max(1) as i32;
329 let height = lparam.hiword().max(1) as i32;
330 let scale_factor = self.scale_factor.get();
331 let new_physical_size = Size {
332 width: DevicePixels(width),
333 height: DevicePixels(height),
334 };
335 self.physical_size.set(new_physical_size);
336 self.renderer.borrow_mut().update_drawable_size(Size {
337 width: width as f64,
338 height: height as f64,
339 });
340 let mut callbacks = self.callbacks.borrow_mut();
341 if let Some(callback) = callbacks.resize.as_mut() {
342 let logical_size = logical_size(new_physical_size, scale_factor);
343 callback(logical_size, scale_factor);
344 }
345 self.invalidate_client_area();
346 Some(0)
347 }
348
349 fn handle_paint_msg(&self) -> Option<isize> {
350 let mut paint_struct = PAINTSTRUCT::default();
351 let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
352 let mut callbacks = self.callbacks.borrow_mut();
353 if let Some(request_frame) = callbacks.request_frame.as_mut() {
354 request_frame();
355 }
356 unsafe { EndPaint(self.hwnd, &paint_struct) };
357 Some(0)
358 }
359
360 fn handle_close_msg(&self) -> Option<isize> {
361 let mut callbacks = self.callbacks.borrow_mut();
362 if let Some(callback) = callbacks.should_close.as_mut() {
363 if callback() {
364 return Some(0);
365 }
366 }
367 None
368 }
369
370 fn handle_destroy_msg(&self) -> Option<isize> {
371 let mut callbacks = self.callbacks.borrow_mut();
372 if let Some(callback) = callbacks.close.take() {
373 callback()
374 }
375 let index = self
376 .platform_inner
377 .raw_window_handles
378 .read()
379 .iter()
380 .position(|handle| *handle == self.hwnd)
381 .unwrap();
382 self.platform_inner.raw_window_handles.write().remove(index);
383 if self.platform_inner.raw_window_handles.read().is_empty() {
384 self.platform_inner
385 .foreground_executor
386 .spawn(async {
387 unsafe { PostQuitMessage(0) };
388 })
389 .detach();
390 }
391 Some(1)
392 }
393
394 fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
395 let mut callbacks = self.callbacks.borrow_mut();
396 if let Some(callback) = callbacks.input.as_mut() {
397 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
398 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
399 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
400 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
401 flags if flags.contains(MK_XBUTTON1) => {
402 Some(MouseButton::Navigate(NavigationDirection::Back))
403 }
404 flags if flags.contains(MK_XBUTTON2) => {
405 Some(MouseButton::Navigate(NavigationDirection::Forward))
406 }
407 _ => None,
408 };
409 let x = lparam.signed_loword() as f32;
410 let y = lparam.signed_hiword() as f32;
411 let scale_factor = self.scale_factor.get();
412 let event = MouseMoveEvent {
413 position: logical_point(x, y, scale_factor),
414 pressed_button,
415 modifiers: self.current_modifiers(),
416 };
417 if callback(PlatformInput::MouseMove(event)).default_prevented {
418 return Some(0);
419 }
420 }
421 Some(1)
422 }
423
424 fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
425 let modifiers = self.current_modifiers();
426 if !modifiers.alt {
427 // on Windows, F10 can trigger this event, not just the alt key
428 // and we just don't care about F10
429 return None;
430 }
431
432 let vk_code = wparam.loword();
433 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
434 if basic_key.is_some() {
435 return basic_key;
436 }
437
438 let key = match VIRTUAL_KEY(vk_code) {
439 VK_BACK => Some("backspace"),
440 VK_RETURN => Some("enter"),
441 VK_TAB => Some("tab"),
442 VK_UP => Some("up"),
443 VK_DOWN => Some("down"),
444 VK_RIGHT => Some("right"),
445 VK_LEFT => Some("left"),
446 VK_HOME => Some("home"),
447 VK_END => Some("end"),
448 VK_PRIOR => Some("pageup"),
449 VK_NEXT => Some("pagedown"),
450 VK_ESCAPE => Some("escape"),
451 VK_INSERT => Some("insert"),
452 _ => None,
453 };
454
455 if let Some(key) = key {
456 Some(Keystroke {
457 modifiers,
458 key: key.to_string(),
459 ime_key: None,
460 })
461 } else {
462 None
463 }
464 }
465
466 fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
467 let vk_code = wparam.loword();
468
469 let modifiers = self.current_modifiers();
470 if modifiers.control || modifiers.alt {
471 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
472 if basic_key.is_some() {
473 return basic_key;
474 }
475 }
476
477 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
478 let offset = vk_code - VK_F1.0;
479 return Some(Keystroke {
480 modifiers,
481 key: format!("f{}", offset + 1),
482 ime_key: None,
483 });
484 }
485
486 let key = match VIRTUAL_KEY(vk_code) {
487 VK_BACK => Some("backspace"),
488 VK_RETURN => Some("enter"),
489 VK_TAB => Some("tab"),
490 VK_UP => Some("up"),
491 VK_DOWN => Some("down"),
492 VK_RIGHT => Some("right"),
493 VK_LEFT => Some("left"),
494 VK_HOME => Some("home"),
495 VK_END => Some("end"),
496 VK_PRIOR => Some("pageup"),
497 VK_NEXT => Some("pagedown"),
498 VK_ESCAPE => Some("escape"),
499 VK_INSERT => Some("insert"),
500 VK_DELETE => Some("delete"),
501 _ => None,
502 };
503
504 if let Some(key) = key {
505 Some(Keystroke {
506 modifiers,
507 key: key.to_string(),
508 ime_key: None,
509 })
510 } else {
511 None
512 }
513 }
514
515 fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
516 let src = [wparam.0 as u16];
517 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
518 return None;
519 };
520 if first_char.is_control() {
521 None
522 } else {
523 let mut modifiers = self.current_modifiers();
524 // for characters that use 'shift' to type it is expected that the
525 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
526 if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
527 modifiers.shift = false;
528 }
529 let key = match first_char {
530 ' ' => "space".to_string(),
531 first_char => first_char.to_lowercase().to_string(),
532 };
533 Some(Keystroke {
534 modifiers,
535 key,
536 ime_key: Some(first_char.to_string()),
537 })
538 }
539 }
540
541 fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
542 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
543 // shortcuts.
544 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
545 return None;
546 };
547 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
548 return None;
549 };
550 let event = KeyDownEvent {
551 keystroke,
552 is_held: lparam.0 & (0x1 << 30) > 0,
553 };
554 if func(PlatformInput::KeyDown(event)).default_prevented {
555 self.invalidate_client_area();
556 return Some(0);
557 }
558 None
559 }
560
561 fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option<isize> {
562 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
563 // shortcuts.
564 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
565 return None;
566 };
567 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
568 return None;
569 };
570 let event = KeyUpEvent { keystroke };
571 if func(PlatformInput::KeyUp(event)).default_prevented {
572 self.invalidate_client_area();
573 return Some(0);
574 }
575 None
576 }
577
578 fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
579 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
580 return Some(1);
581 };
582 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
583 return Some(1);
584 };
585 let event = KeyDownEvent {
586 keystroke,
587 is_held: lparam.0 & (0x1 << 30) > 0,
588 };
589 if func(PlatformInput::KeyDown(event)).default_prevented {
590 self.invalidate_client_area();
591 return Some(0);
592 }
593 Some(1)
594 }
595
596 fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option<isize> {
597 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
598 return Some(1);
599 };
600 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
601 return Some(1);
602 };
603 let event = KeyUpEvent { keystroke };
604 if func(PlatformInput::KeyUp(event)).default_prevented {
605 self.invalidate_client_area();
606 return Some(0);
607 }
608 Some(1)
609 }
610
611 fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
612 let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
613 return Some(1);
614 };
615 let mut callbacks = self.callbacks.borrow_mut();
616 let Some(ref mut func) = callbacks.input else {
617 return Some(1);
618 };
619 let ime_key = keystroke.ime_key.clone();
620 let event = KeyDownEvent {
621 keystroke,
622 is_held: lparam.0 & (0x1 << 30) > 0,
623 };
624
625 let dispatch_event_result = func(PlatformInput::KeyDown(event));
626 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
627 self.invalidate_client_area();
628 return Some(0);
629 }
630 drop(callbacks);
631 let Some(ime_char) = ime_key else {
632 return Some(1);
633 };
634 let Some(mut input_handler) = self.input_handler.take() else {
635 return Some(1);
636 };
637 input_handler.replace_text_in_range(None, &ime_char);
638 self.input_handler.set(Some(input_handler));
639 self.invalidate_client_area();
640 Some(0)
641 }
642
643 fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
644 let mut callbacks = self.callbacks.borrow_mut();
645 if let Some(callback) = callbacks.input.as_mut() {
646 let x = lparam.signed_loword() as f32;
647 let y = lparam.signed_hiword() as f32;
648 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
649 let click_count = self.click_state.borrow_mut().update(button, physical_point);
650 let scale_factor = self.scale_factor.get();
651 let event = MouseDownEvent {
652 button,
653 position: logical_point(x, y, scale_factor),
654 modifiers: self.current_modifiers(),
655 click_count,
656 first_mouse: false,
657 };
658 if callback(PlatformInput::MouseDown(event)).default_prevented {
659 return Some(0);
660 }
661 }
662 Some(1)
663 }
664
665 fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
666 let mut callbacks = self.callbacks.borrow_mut();
667 if let Some(callback) = callbacks.input.as_mut() {
668 let x = lparam.signed_loword() as f32;
669 let y = lparam.signed_hiword() as f32;
670 let scale_factor = self.scale_factor.get();
671 let event = MouseUpEvent {
672 button,
673 position: logical_point(x, y, scale_factor),
674 modifiers: self.current_modifiers(),
675 click_count: 1,
676 };
677 if callback(PlatformInput::MouseUp(event)).default_prevented {
678 return Some(0);
679 }
680 }
681 Some(1)
682 }
683
684 fn handle_xbutton_msg(
685 &self,
686 wparam: WPARAM,
687 lparam: LPARAM,
688 handler: impl Fn(&Self, MouseButton, LPARAM) -> Option<isize>,
689 ) -> Option<isize> {
690 let nav_dir = match wparam.hiword() {
691 XBUTTON1 => NavigationDirection::Back,
692 XBUTTON2 => NavigationDirection::Forward,
693 _ => return Some(1),
694 };
695 handler(self, MouseButton::Navigate(nav_dir), lparam)
696 }
697
698 fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
699 let mut callbacks = self.callbacks.borrow_mut();
700 if let Some(callback) = callbacks.input.as_mut() {
701 let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
702 * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
703 let mut cursor_point = POINT {
704 x: lparam.signed_loword().into(),
705 y: lparam.signed_hiword().into(),
706 };
707 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
708 let scale_factor = self.scale_factor.get();
709 let event = crate::ScrollWheelEvent {
710 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
711 delta: ScrollDelta::Lines(Point {
712 x: 0.0,
713 y: wheel_distance,
714 }),
715 modifiers: self.current_modifiers(),
716 touch_phase: TouchPhase::Moved,
717 };
718 callback(PlatformInput::ScrollWheel(event));
719 return Some(0);
720 }
721 Some(1)
722 }
723
724 fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
725 let mut callbacks = self.callbacks.borrow_mut();
726 if let Some(callback) = callbacks.input.as_mut() {
727 let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
728 * self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
729 let mut cursor_point = POINT {
730 x: lparam.signed_loword().into(),
731 y: lparam.signed_hiword().into(),
732 };
733 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
734 let scale_factor = self.scale_factor.get();
735 let event = crate::ScrollWheelEvent {
736 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
737 delta: ScrollDelta::Lines(Point {
738 x: wheel_distance,
739 y: 0.0,
740 }),
741 modifiers: self.current_modifiers(),
742 touch_phase: TouchPhase::Moved,
743 };
744 if callback(PlatformInput::ScrollWheel(event)).default_prevented {
745 return Some(0);
746 }
747 }
748 Some(1)
749 }
750
751 fn handle_ime_position(&self) -> Option<isize> {
752 unsafe {
753 let ctx = ImmGetContext(self.hwnd);
754 let Some(mut input_handler) = self.input_handler.take() else {
755 return Some(1);
756 };
757 // we are composing, this should never fail
758 let caret_range = input_handler.selected_text_range().unwrap();
759 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
760 self.input_handler.set(Some(input_handler));
761 let scale_factor = self.scale_factor.get();
762 let config = CANDIDATEFORM {
763 dwStyle: CFS_CANDIDATEPOS,
764 // logical to physical
765 ptCurrentPos: POINT {
766 x: (caret_position.origin.x.0 * scale_factor) as i32,
767 y: (caret_position.origin.y.0 * scale_factor) as i32
768 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
769 },
770 ..Default::default()
771 };
772 ImmSetCandidateWindow(ctx, &config as _);
773 ImmReleaseContext(self.hwnd, ctx);
774 Some(0)
775 }
776 }
777
778 fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
779 unsafe {
780 let ctx = ImmGetContext(self.hwnd);
781 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
782 let result = if string_len >= 0 {
783 let mut buffer = vec![0u8; string_len as usize + 2];
784 ImmGetCompositionStringW(
785 ctx,
786 GCS_COMPSTR,
787 Some(buffer.as_mut_ptr() as _),
788 string_len as _,
789 );
790 let wstring = std::slice::from_raw_parts::<u16>(
791 buffer.as_mut_ptr().cast::<u16>(),
792 string_len as usize / 2,
793 );
794 let string = String::from_utf16_lossy(wstring);
795 Some((string, string_len as usize / 2))
796 } else {
797 None
798 };
799 ImmReleaseContext(self.hwnd, ctx);
800 result
801 }
802 }
803
804 fn retrieve_composition_cursor_position(&self) -> usize {
805 unsafe {
806 let ctx = ImmGetContext(self.hwnd);
807 let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
808 ImmReleaseContext(self.hwnd, ctx);
809 ret as usize
810 }
811 }
812
813 fn parse_ime_compostion_result(&self) -> Option<String> {
814 unsafe {
815 let ctx = ImmGetContext(self.hwnd);
816 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
817 let result = if string_len >= 0 {
818 let mut buffer = vec![0u8; string_len as usize + 2];
819 ImmGetCompositionStringW(
820 ctx,
821 GCS_RESULTSTR,
822 Some(buffer.as_mut_ptr() as _),
823 string_len as _,
824 );
825 let wstring = std::slice::from_raw_parts::<u16>(
826 buffer.as_mut_ptr().cast::<u16>(),
827 string_len as usize / 2,
828 );
829 let string = String::from_utf16_lossy(wstring);
830 Some(string)
831 } else {
832 None
833 };
834 ImmReleaseContext(self.hwnd, ctx);
835 result
836 }
837 }
838
839 fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
840 let mut ime_input = None;
841 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
842 let Some((string, string_len)) = self.parse_ime_compostion_string() else {
843 return None;
844 };
845 let Some(mut input_handler) = self.input_handler.take() else {
846 return None;
847 };
848 input_handler.replace_and_mark_text_in_range(
849 None,
850 string.as_str(),
851 Some(0..string_len),
852 );
853 self.input_handler.set(Some(input_handler));
854 ime_input = Some(string);
855 }
856 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
857 let Some(ref comp_string) = ime_input else {
858 return None;
859 };
860 let caret_pos = self.retrieve_composition_cursor_position();
861 let Some(mut input_handler) = self.input_handler.take() else {
862 return None;
863 };
864 input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
865 self.input_handler.set(Some(input_handler));
866 }
867 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
868 let Some(comp_result) = self.parse_ime_compostion_result() else {
869 return None;
870 };
871 let Some(mut input_handler) = self.input_handler.take() else {
872 return Some(1);
873 };
874 input_handler.replace_text_in_range(None, &comp_result);
875 self.input_handler.set(Some(input_handler));
876 self.invalidate_client_area();
877 return Some(0);
878 }
879 // currently, we don't care other stuff
880 None
881 }
882
883 fn handle_drag_drop(&self, input: PlatformInput) {
884 let mut callbacks = self.callbacks.borrow_mut();
885 let Some(ref mut func) = callbacks.input else {
886 return;
887 };
888 func(input);
889 }
890
891 /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
892 fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
893 if !self.hide_title_bar || self.is_fullscreen() {
894 return None;
895 }
896
897 if wparam.0 == 0 {
898 return None;
899 }
900
901 let dpi = unsafe { GetDpiForWindow(self.hwnd) };
902
903 let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
904 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
905 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
906
907 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
908 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
909 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
910
911 requested_client_rect[0].right -= frame_x + padding;
912 requested_client_rect[0].left += frame_x + padding;
913 requested_client_rect[0].bottom -= frame_y + padding;
914
915 Some(0)
916 }
917
918 fn handle_activate_msg(&self, wparam: WPARAM) -> Option<isize> {
919 if self.hide_title_bar {
920 if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
921 unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
922 }
923 }
924 let activated = wparam.loword() > 0;
925 let mut callbacks = self.callbacks.borrow_mut();
926 if let Some(mut cb) = callbacks.active_status_change.as_mut() {
927 cb(activated);
928 }
929 None
930 }
931
932 fn handle_create_msg(&self, _lparam: LPARAM) -> Option<isize> {
933 let mut size_rect = RECT::default();
934 unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
935
936 let width = size_rect.right - size_rect.left;
937 let height = size_rect.bottom - size_rect.top;
938
939 self.physical_size.set(Size {
940 width: DevicePixels(width),
941 height: DevicePixels(height),
942 });
943
944 if self.hide_title_bar {
945 // Inform the application of the frame change to force redrawing with the new
946 // client area that is extended into the title bar
947 unsafe {
948 SetWindowPos(
949 self.hwnd,
950 HWND::default(),
951 size_rect.left,
952 size_rect.top,
953 width,
954 height,
955 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
956 )
957 .log_err()
958 };
959 }
960
961 Some(0)
962 }
963
964 fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
965 let new_dpi = wparam.loword() as f32;
966 let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
967 self.scale_factor.set(scale_factor);
968 let rect = unsafe { &*(lparam.0 as *const RECT) };
969 let width = rect.right - rect.left;
970 let height = rect.bottom - rect.top;
971 // this will emit `WM_SIZE` and `WM_MOVE` right here
972 // even before this function returns
973 // the new size is handled in `WM_SIZE`
974 unsafe {
975 SetWindowPos(
976 self.hwnd,
977 None,
978 rect.left,
979 rect.top,
980 width,
981 height,
982 SWP_NOZORDER | SWP_NOACTIVATE,
983 )
984 .context("unable to set window position after dpi has changed")
985 .log_err();
986 }
987 self.invalidate_client_area();
988 Some(0)
989 }
990
991 fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
992 if !self.hide_title_bar {
993 return None;
994 }
995
996 // default handler for resize areas
997 let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
998 if matches!(
999 hit.0 as u32,
1000 HTNOWHERE
1001 | HTRIGHT
1002 | HTLEFT
1003 | HTTOPLEFT
1004 | HTTOP
1005 | HTTOPRIGHT
1006 | HTBOTTOMRIGHT
1007 | HTBOTTOM
1008 | HTBOTTOMLEFT
1009 ) {
1010 return Some(hit.0);
1011 }
1012
1013 if self.is_fullscreen() {
1014 return Some(HTCLIENT as _);
1015 }
1016
1017 let dpi = unsafe { GetDpiForWindow(self.hwnd) };
1018 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
1019 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1020
1021 let mut cursor_point = POINT {
1022 x: lparam.signed_loword().into(),
1023 y: lparam.signed_hiword().into(),
1024 };
1025 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1026 if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
1027 return Some(HTTOP as _);
1028 }
1029
1030 let titlebar_rect = self.get_titlebar_rect();
1031 if let Ok(titlebar_rect) = titlebar_rect {
1032 if cursor_point.y < titlebar_rect.bottom {
1033 let caption_btn_width =
1034 (self.caption_button_width().0 * self.scale_factor.get()) as i32;
1035 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
1036 return Some(HTCLOSE as _);
1037 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
1038 return Some(HTMAXBUTTON as _);
1039 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
1040 return Some(HTMINBUTTON as _);
1041 }
1042
1043 return Some(HTCAPTION as _);
1044 }
1045 }
1046
1047 Some(HTCLIENT as _)
1048 }
1049
1050 fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option<isize> {
1051 if !self.hide_title_bar {
1052 return None;
1053 }
1054
1055 let mut callbacks = self.callbacks.borrow_mut();
1056 if let Some(callback) = callbacks.input.as_mut() {
1057 let mut cursor_point = POINT {
1058 x: lparam.signed_loword().into(),
1059 y: lparam.signed_hiword().into(),
1060 };
1061 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1062 let scale_factor = self.scale_factor.get();
1063 let event = MouseMoveEvent {
1064 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1065 pressed_button: None,
1066 modifiers: self.current_modifiers(),
1067 };
1068 if callback(PlatformInput::MouseMove(event)).default_prevented {
1069 return Some(0);
1070 }
1071 }
1072 None
1073 }
1074
1075 fn handle_nc_mouse_down_msg(
1076 &self,
1077 button: MouseButton,
1078 wparam: WPARAM,
1079 lparam: LPARAM,
1080 ) -> Option<isize> {
1081 if !self.hide_title_bar {
1082 return None;
1083 }
1084
1085 let mut callbacks = self.callbacks.borrow_mut();
1086 if let Some(callback) = callbacks.input.as_mut() {
1087 let mut cursor_point = POINT {
1088 x: lparam.signed_loword().into(),
1089 y: lparam.signed_hiword().into(),
1090 };
1091 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1092 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1093 let click_count = self.click_state.borrow_mut().update(button, physical_point);
1094 let scale_factor = self.scale_factor.get();
1095 let event = MouseDownEvent {
1096 button,
1097 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1098 modifiers: self.current_modifiers(),
1099 click_count,
1100 first_mouse: false,
1101 };
1102 if callback(PlatformInput::MouseDown(event)).default_prevented {
1103 return Some(0);
1104 }
1105 }
1106
1107 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1108 matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
1109 }
1110
1111 fn handle_nc_mouse_up_msg(
1112 &self,
1113 button: MouseButton,
1114 wparam: WPARAM,
1115 lparam: LPARAM,
1116 ) -> Option<isize> {
1117 if !self.hide_title_bar {
1118 return None;
1119 }
1120
1121 let mut callbacks = self.callbacks.borrow_mut();
1122 if let Some(callback) = callbacks.input.as_mut() {
1123 let mut cursor_point = POINT {
1124 x: lparam.signed_loword().into(),
1125 y: lparam.signed_hiword().into(),
1126 };
1127 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1128 let scale_factor = self.scale_factor.get();
1129 let event = MouseUpEvent {
1130 button,
1131 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1132 modifiers: self.current_modifiers(),
1133 click_count: 1,
1134 };
1135 if callback(PlatformInput::MouseUp(event)).default_prevented {
1136 return Some(0);
1137 }
1138 }
1139 drop(callbacks);
1140
1141 if button == MouseButton::Left {
1142 match wparam.0 as u32 {
1143 HTMINBUTTON => unsafe {
1144 ShowWindowAsync(self.hwnd, SW_MINIMIZE);
1145 },
1146 HTMAXBUTTON => unsafe {
1147 if self.is_maximized() {
1148 ShowWindowAsync(self.hwnd, SW_NORMAL);
1149 } else {
1150 ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
1151 }
1152 },
1153 HTCLOSE => unsafe {
1154 PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
1155 .log_err();
1156 },
1157 _ => return None,
1158 };
1159 return Some(0);
1160 }
1161
1162 None
1163 }
1164
1165 fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
1166 if matches!(
1167 lparam.loword() as u32,
1168 HTLEFT
1169 | HTRIGHT
1170 | HTTOP
1171 | HTTOPLEFT
1172 | HTTOPRIGHT
1173 | HTBOTTOM
1174 | HTBOTTOMLEFT
1175 | HTBOTTOMRIGHT
1176 ) {
1177 return None;
1178 }
1179 unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
1180 Some(1)
1181 }
1182}
1183
1184#[derive(Default)]
1185struct Callbacks {
1186 request_frame: Option<Box<dyn FnMut()>>,
1187 input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
1188 active_status_change: Option<Box<dyn FnMut(bool)>>,
1189 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
1190 fullscreen: Option<Box<dyn FnMut(bool)>>,
1191 moved: Option<Box<dyn FnMut()>>,
1192 should_close: Option<Box<dyn FnMut() -> bool>>,
1193 close: Option<Box<dyn FnOnce()>>,
1194 appearance_changed: Option<Box<dyn FnMut()>>,
1195}
1196
1197pub(crate) struct WindowsWindow {
1198 inner: Rc<WindowsWindowInner>,
1199 drag_drop_handler: IDropTarget,
1200}
1201
1202struct WindowCreateContext {
1203 inner: Option<Rc<WindowsWindowInner>>,
1204 platform_inner: Rc<WindowsPlatformInner>,
1205 handle: AnyWindowHandle,
1206 hide_title_bar: bool,
1207 display: Rc<WindowsDisplay>,
1208}
1209
1210impl WindowsWindow {
1211 pub(crate) fn new(
1212 platform_inner: Rc<WindowsPlatformInner>,
1213 handle: AnyWindowHandle,
1214 options: WindowParams,
1215 ) -> Self {
1216 let classname = register_wnd_class(platform_inner.icon);
1217 let hide_title_bar = options
1218 .titlebar
1219 .as_ref()
1220 .map(|titlebar| titlebar.appears_transparent)
1221 .unwrap_or(false);
1222 let windowname = HSTRING::from(
1223 options
1224 .titlebar
1225 .as_ref()
1226 .and_then(|titlebar| titlebar.title.as_ref())
1227 .map(|title| title.as_ref())
1228 .unwrap_or(""),
1229 );
1230 let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1231 let x = options.bounds.origin.x.0;
1232 let y = options.bounds.origin.y.0;
1233 let nwidth = options.bounds.size.width.0;
1234 let nheight = options.bounds.size.height.0;
1235 let hwndparent = HWND::default();
1236 let hmenu = HMENU::default();
1237 let hinstance = HINSTANCE::default();
1238 let mut context = WindowCreateContext {
1239 inner: None,
1240 platform_inner: platform_inner.clone(),
1241 handle,
1242 hide_title_bar,
1243 // todo(windows) move window to target monitor
1244 // options.display_id
1245 display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1246 };
1247 let lpparam = Some(&context as *const _ as *const _);
1248 unsafe {
1249 CreateWindowExW(
1250 WS_EX_APPWINDOW,
1251 classname,
1252 &windowname,
1253 dwstyle,
1254 x,
1255 y,
1256 nwidth,
1257 nheight,
1258 hwndparent,
1259 hmenu,
1260 hinstance,
1261 lpparam,
1262 )
1263 };
1264 let drag_drop_handler = {
1265 let inner = context.inner.as_ref().unwrap();
1266 let handler = WindowsDragDropHandler(Rc::clone(inner));
1267 let drag_drop_handler: IDropTarget = handler.into();
1268 unsafe {
1269 RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1270 .expect("unable to register drag-drop event")
1271 };
1272 drag_drop_handler
1273 };
1274 let wnd = Self {
1275 inner: context.inner.unwrap(),
1276 drag_drop_handler,
1277 };
1278 platform_inner
1279 .raw_window_handles
1280 .write()
1281 .push(wnd.inner.hwnd);
1282
1283 unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1284 wnd
1285 }
1286}
1287
1288impl HasWindowHandle for WindowsWindow {
1289 fn window_handle(
1290 &self,
1291 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
1292 let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
1293 NonZeroIsize::new_unchecked(self.inner.hwnd.0)
1294 })
1295 .into();
1296 Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
1297 }
1298}
1299
1300// todo(windows)
1301impl HasDisplayHandle for WindowsWindow {
1302 fn display_handle(
1303 &self,
1304 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
1305 unimplemented!()
1306 }
1307}
1308
1309impl Drop for WindowsWindow {
1310 fn drop(&mut self) {
1311 unsafe {
1312 let _ = RevokeDragDrop(self.inner.hwnd);
1313 self.inner.renderer.borrow_mut().destroy();
1314 }
1315 }
1316}
1317
1318impl PlatformWindow for WindowsWindow {
1319 fn bounds(&self) -> Bounds<DevicePixels> {
1320 Bounds {
1321 origin: self.inner.origin.get(),
1322 size: self.inner.physical_size.get(),
1323 }
1324 }
1325
1326 fn is_maximized(&self) -> bool {
1327 self.inner.is_maximized()
1328 }
1329
1330 fn is_minimized(&self) -> bool {
1331 self.inner.is_minimized()
1332 }
1333
1334 /// get the logical size of the app's drawable area.
1335 ///
1336 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1337 /// whether the mouse collides with other elements of GPUI).
1338 fn content_size(&self) -> Size<Pixels> {
1339 logical_size(
1340 self.inner.physical_size.get(),
1341 self.inner.scale_factor.get(),
1342 )
1343 }
1344
1345 fn scale_factor(&self) -> f32 {
1346 self.inner.scale_factor.get()
1347 }
1348
1349 // todo(windows)
1350 fn appearance(&self) -> WindowAppearance {
1351 WindowAppearance::Dark
1352 }
1353
1354 fn display(&self) -> Rc<dyn PlatformDisplay> {
1355 self.inner.display.borrow().clone()
1356 }
1357
1358 fn mouse_position(&self) -> Point<Pixels> {
1359 let point = unsafe {
1360 let mut point: POINT = std::mem::zeroed();
1361 GetCursorPos(&mut point)
1362 .context("unable to get cursor position")
1363 .log_err();
1364 ScreenToClient(self.inner.hwnd, &mut point);
1365 point
1366 };
1367 logical_point(
1368 point.x as f32,
1369 point.y as f32,
1370 self.inner.scale_factor.get(),
1371 )
1372 }
1373
1374 // todo(windows)
1375 fn modifiers(&self) -> Modifiers {
1376 Modifiers::none()
1377 }
1378
1379 fn as_any_mut(&mut self) -> &mut dyn Any {
1380 self
1381 }
1382
1383 // todo(windows)
1384 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1385 self.inner.input_handler.set(Some(input_handler));
1386 }
1387
1388 // todo(windows)
1389 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1390 self.inner.input_handler.take()
1391 }
1392
1393 fn prompt(
1394 &self,
1395 level: PromptLevel,
1396 msg: &str,
1397 detail: Option<&str>,
1398 answers: &[&str],
1399 ) -> Option<Receiver<usize>> {
1400 let (done_tx, done_rx) = oneshot::channel();
1401 let msg = msg.to_string();
1402 let detail_string = match detail {
1403 Some(info) => Some(info.to_string()),
1404 None => None,
1405 };
1406 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1407 let handle = self.inner.hwnd;
1408 self.inner
1409 .platform_inner
1410 .foreground_executor
1411 .spawn(async move {
1412 unsafe {
1413 let mut config;
1414 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1415 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1416 config.hwndParent = handle;
1417 let title;
1418 let main_icon;
1419 match level {
1420 crate::PromptLevel::Info => {
1421 title = windows::core::w!("Info");
1422 main_icon = TD_INFORMATION_ICON;
1423 }
1424 crate::PromptLevel::Warning => {
1425 title = windows::core::w!("Warning");
1426 main_icon = TD_WARNING_ICON;
1427 }
1428 crate::PromptLevel::Critical => {
1429 title = windows::core::w!("Critical");
1430 main_icon = TD_ERROR_ICON;
1431 }
1432 };
1433 config.pszWindowTitle = title;
1434 config.Anonymous1.pszMainIcon = main_icon;
1435 let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1436 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1437 let hints_encoded;
1438 if let Some(ref hints) = detail_string {
1439 hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1440 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1441 };
1442 let mut buttons = Vec::new();
1443 let mut btn_encoded = Vec::new();
1444 for (index, btn_string) in answers.iter().enumerate() {
1445 let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1446 buttons.push(TASKDIALOG_BUTTON {
1447 nButtonID: index as _,
1448 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1449 });
1450 btn_encoded.push(encoded);
1451 }
1452 config.cButtons = buttons.len() as _;
1453 config.pButtons = buttons.as_ptr();
1454
1455 config.pfCallback = None;
1456 let mut res = std::mem::zeroed();
1457 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1458 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1459
1460 let _ = done_tx.send(res as usize);
1461 }
1462 })
1463 .detach();
1464
1465 Some(done_rx)
1466 }
1467
1468 fn activate(&self) {
1469 unsafe { SetActiveWindow(self.inner.hwnd) };
1470 unsafe { SetFocus(self.inner.hwnd) };
1471 unsafe { SetForegroundWindow(self.inner.hwnd) };
1472 }
1473
1474 fn is_active(&self) -> bool {
1475 self.inner.hwnd == unsafe { GetActiveWindow() }
1476 }
1477
1478 // todo(windows)
1479 fn set_title(&mut self, title: &str) {
1480 unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1481 .inspect_err(|e| log::error!("Set title failed: {e}"))
1482 .ok();
1483 }
1484
1485 fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
1486 // todo(windows)
1487 }
1488
1489 // todo(windows)
1490 fn set_edited(&mut self, _edited: bool) {}
1491
1492 // todo(windows)
1493 fn show_character_palette(&self) {}
1494
1495 fn minimize(&self) {
1496 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1497 }
1498
1499 fn zoom(&self) {
1500 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1501 }
1502
1503 fn toggle_fullscreen(&self) {
1504 self.inner
1505 .platform_inner
1506 .foreground_executor
1507 .spawn(self.inner.clone().toggle_fullscreen())
1508 .detach();
1509 }
1510
1511 fn is_fullscreen(&self) -> bool {
1512 self.inner.is_fullscreen()
1513 }
1514
1515 // todo(windows)
1516 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1517 self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1518 }
1519
1520 // todo(windows)
1521 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1522 self.inner.callbacks.borrow_mut().input = Some(callback);
1523 }
1524
1525 // todo(windows)
1526 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1527 self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1528 }
1529
1530 // todo(windows)
1531 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1532 self.inner.callbacks.borrow_mut().resize = Some(callback);
1533 }
1534
1535 // todo(windows)
1536 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
1537 self.inner.callbacks.borrow_mut().fullscreen = Some(callback);
1538 }
1539
1540 // todo(windows)
1541 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1542 self.inner.callbacks.borrow_mut().moved = Some(callback);
1543 }
1544
1545 // todo(windows)
1546 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1547 self.inner.callbacks.borrow_mut().should_close = Some(callback);
1548 }
1549
1550 // todo(windows)
1551 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1552 self.inner.callbacks.borrow_mut().close = Some(callback);
1553 }
1554
1555 // todo(windows)
1556 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1557 self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1558 }
1559
1560 // todo(windows)
1561 fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
1562 true
1563 }
1564
1565 // todo(windows)
1566 fn draw(&self, scene: &Scene) {
1567 self.inner.renderer.borrow_mut().draw(scene)
1568 }
1569
1570 // todo(windows)
1571 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1572 self.inner.renderer.borrow().sprite_atlas().clone()
1573 }
1574
1575 fn get_raw_handle(&self) -> HWND {
1576 self.inner.hwnd
1577 }
1578}
1579
1580#[implement(IDropTarget)]
1581struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1582
1583#[allow(non_snake_case)]
1584impl IDropTarget_Impl for WindowsDragDropHandler {
1585 fn DragEnter(
1586 &self,
1587 pdataobj: Option<&IDataObject>,
1588 _grfkeystate: MODIFIERKEYS_FLAGS,
1589 pt: &POINTL,
1590 pdweffect: *mut DROPEFFECT,
1591 ) -> windows::core::Result<()> {
1592 unsafe {
1593 let Some(idata_obj) = pdataobj else {
1594 log::info!("no dragging file or directory detected");
1595 return Ok(());
1596 };
1597 let config = FORMATETC {
1598 cfFormat: CF_HDROP.0,
1599 ptd: std::ptr::null_mut() as _,
1600 dwAspect: DVASPECT_CONTENT.0,
1601 lindex: -1,
1602 tymed: TYMED_HGLOBAL.0 as _,
1603 };
1604 let mut paths = SmallVec::<[PathBuf; 2]>::new();
1605 if idata_obj.QueryGetData(&config as _) == S_OK {
1606 *pdweffect = DROPEFFECT_LINK;
1607 let Ok(mut idata) = idata_obj.GetData(&config as _) else {
1608 return Ok(());
1609 };
1610 if idata.u.hGlobal.is_invalid() {
1611 return Ok(());
1612 }
1613 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1614 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1615 for file_index in 0..file_count {
1616 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1617 let mut buffer = vec![0u16; filename_length + 1];
1618 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1619 if ret == 0 {
1620 log::error!("unable to read file name");
1621 continue;
1622 }
1623 if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) {
1624 if let Ok(path) = PathBuf::from_str(&file_name) {
1625 paths.push(path);
1626 }
1627 }
1628 }
1629 ReleaseStgMedium(&mut idata);
1630 let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered {
1631 position: Point {
1632 x: Pixels(pt.x as _),
1633 y: Pixels(pt.y as _),
1634 },
1635 paths: crate::ExternalPaths(paths),
1636 });
1637 self.0.handle_drag_drop(input);
1638 } else {
1639 *pdweffect = DROPEFFECT_NONE;
1640 }
1641 }
1642 Ok(())
1643 }
1644
1645 fn DragOver(
1646 &self,
1647 _grfkeystate: MODIFIERKEYS_FLAGS,
1648 pt: &POINTL,
1649 _pdweffect: *mut DROPEFFECT,
1650 ) -> windows::core::Result<()> {
1651 let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending {
1652 position: Point {
1653 x: Pixels(pt.x as _),
1654 y: Pixels(pt.y as _),
1655 },
1656 });
1657 self.0.handle_drag_drop(input);
1658
1659 Ok(())
1660 }
1661
1662 fn DragLeave(&self) -> windows::core::Result<()> {
1663 let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited);
1664 self.0.handle_drag_drop(input);
1665
1666 Ok(())
1667 }
1668
1669 fn Drop(
1670 &self,
1671 _pdataobj: Option<&IDataObject>,
1672 _grfkeystate: MODIFIERKEYS_FLAGS,
1673 pt: &POINTL,
1674 _pdweffect: *mut DROPEFFECT,
1675 ) -> windows::core::Result<()> {
1676 let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit {
1677 position: Point {
1678 x: Pixels(pt.x as _),
1679 y: Pixels(pt.y as _),
1680 },
1681 });
1682 self.0.handle_drag_drop(input);
1683
1684 Ok(())
1685 }
1686}
1687
1688#[derive(Debug)]
1689struct ClickState {
1690 button: MouseButton,
1691 last_click: Instant,
1692 last_position: Point<DevicePixels>,
1693 current_count: usize,
1694}
1695
1696impl ClickState {
1697 pub fn new() -> Self {
1698 ClickState {
1699 button: MouseButton::Left,
1700 last_click: Instant::now(),
1701 last_position: Point::default(),
1702 current_count: 0,
1703 }
1704 }
1705
1706 /// update self and return the needed click count
1707 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1708 if self.button == button && self.is_double_click(new_position) {
1709 self.current_count += 1;
1710 } else {
1711 self.current_count = 1;
1712 }
1713 self.last_click = Instant::now();
1714 self.last_position = new_position;
1715 self.button = button;
1716
1717 self.current_count
1718 }
1719
1720 #[inline]
1721 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1722 let diff = self.last_position - new_position;
1723
1724 self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1725 && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1726 && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1727 }
1728}
1729
1730fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1731 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1732
1733 static ONCE: Once = Once::new();
1734 ONCE.call_once(|| {
1735 let wc = WNDCLASSW {
1736 lpfnWndProc: Some(wnd_proc),
1737 hIcon: icon_handle,
1738 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1739 style: CS_HREDRAW | CS_VREDRAW,
1740 ..Default::default()
1741 };
1742 unsafe { RegisterClassW(&wc) };
1743 });
1744
1745 CLASS_NAME
1746}
1747
1748unsafe extern "system" fn wnd_proc(
1749 hwnd: HWND,
1750 msg: u32,
1751 wparam: WPARAM,
1752 lparam: LPARAM,
1753) -> LRESULT {
1754 if msg == WM_NCCREATE {
1755 let cs = lparam.0 as *const CREATESTRUCTW;
1756 let cs = unsafe { &*cs };
1757 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1758 let ctx = unsafe { &mut *ctx };
1759 let inner = Rc::new(WindowsWindowInner::new(
1760 hwnd,
1761 cs,
1762 ctx.platform_inner.clone(),
1763 ctx.handle,
1764 ctx.hide_title_bar,
1765 ctx.display.clone(),
1766 ));
1767 let weak = Box::new(Rc::downgrade(&inner));
1768 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1769 ctx.inner = Some(inner);
1770 return LRESULT(1);
1771 }
1772 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1773 if ptr.is_null() {
1774 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1775 }
1776 let inner = unsafe { &*ptr };
1777 let r = if let Some(inner) = inner.upgrade() {
1778 inner.handle_msg(msg, wparam, lparam)
1779 } else {
1780 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1781 };
1782 if msg == WM_NCDESTROY {
1783 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1784 unsafe { drop(Box::from_raw(ptr)) };
1785 }
1786 r
1787}
1788
1789pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1790 if hwnd == HWND(0) {
1791 return None;
1792 }
1793
1794 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1795 if !ptr.is_null() {
1796 let inner = unsafe { &*ptr };
1797 inner.upgrade()
1798 } else {
1799 None
1800 }
1801}
1802
1803fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1804 match code {
1805 // VK_0 - VK_9
1806 48..=57 => Some(Keystroke {
1807 modifiers,
1808 key: format!("{}", code - VK_0.0),
1809 ime_key: None,
1810 }),
1811 // VK_A - VK_Z
1812 65..=90 => Some(Keystroke {
1813 modifiers,
1814 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1815 ime_key: None,
1816 }),
1817 // VK_F1 - VK_F24
1818 112..=135 => Some(Keystroke {
1819 modifiers,
1820 key: format!("f{}", code - VK_F1.0 + 1),
1821 ime_key: None,
1822 }),
1823 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1824 _ => {
1825 if let Some(key) = oemkey_vkcode_to_string(code) {
1826 Some(Keystroke {
1827 modifiers,
1828 key,
1829 ime_key: None,
1830 })
1831 } else {
1832 None
1833 }
1834 }
1835 }
1836}
1837
1838fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1839 match code {
1840 186 => Some(";".to_string()), // VK_OEM_1
1841 187 => Some("=".to_string()), // VK_OEM_PLUS
1842 188 => Some(",".to_string()), // VK_OEM_COMMA
1843 189 => Some("-".to_string()), // VK_OEM_MINUS
1844 190 => Some(".".to_string()), // VK_OEM_PERIOD
1845 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1846 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1847 192 => Some("`".to_string()), // VK_OEM_3
1848 219 => Some("[".to_string()), // VK_OEM_4
1849 220 => Some("\\".to_string()), // VK_OEM_5
1850 221 => Some("]".to_string()), // VK_OEM_6
1851 222 => Some("'".to_string()), // VK_OEM_7
1852 _ => None,
1853 }
1854}
1855
1856#[inline]
1857fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1858 Size {
1859 width: px(physical_size.width.0 as f32 / scale_factor),
1860 height: px(physical_size.height.0 as f32 / scale_factor),
1861 }
1862}
1863
1864#[inline]
1865fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1866 Point {
1867 x: px(x / scale_factor),
1868 y: px(y / scale_factor),
1869 }
1870}
1871
1872struct StyleAndBounds {
1873 style: WINDOW_STYLE,
1874 x: i32,
1875 y: i32,
1876 cx: i32,
1877 cy: i32,
1878}
1879
1880// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1881const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1882// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1883const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1884// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1885const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1886
1887#[cfg(test)]
1888mod tests {
1889 use super::ClickState;
1890 use crate::{point, DevicePixels, MouseButton};
1891 use std::time::Duration;
1892
1893 #[test]
1894 fn test_double_click_interval() {
1895 let mut state = ClickState::new();
1896 assert_eq!(
1897 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1898 1
1899 );
1900 assert_eq!(
1901 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1902 1
1903 );
1904 assert_eq!(
1905 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1906 1
1907 );
1908 assert_eq!(
1909 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1910 2
1911 );
1912 state.last_click -= Duration::from_millis(700);
1913 assert_eq!(
1914 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1915 1
1916 );
1917 }
1918
1919 #[test]
1920 fn test_double_click_spatial_tolerance() {
1921 let mut state = ClickState::new();
1922 assert_eq!(
1923 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1924 1
1925 );
1926 assert_eq!(
1927 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1928 2
1929 );
1930 assert_eq!(
1931 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1932 1
1933 );
1934 assert_eq!(
1935 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1936 1
1937 );
1938 }
1939}