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