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