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