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 as i32),
72 y: DevicePixels(cs.y as i32),
73 });
74 let physical_size = Cell::new(Size {
75 width: DevicePixels(cs.cx as i32),
76 height: DevicePixels(cs.cy as i32),
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 as i32,
179 y: bounds.top().0 as i32,
180 cx: bounds.size.width.0 as i32,
181 cy: bounds.size.height.0 as i32,
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 command: 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 as i32),
928 height: DevicePixels(height as i32),
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(
1080 DevicePixels(cursor_point.x as i32),
1081 DevicePixels(cursor_point.y as i32),
1082 );
1083 let click_count = self.click_state.borrow_mut().update(button, physical_point);
1084 let scale_factor = self.scale_factor.get();
1085 let event = MouseDownEvent {
1086 button,
1087 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1088 modifiers: self.current_modifiers(),
1089 click_count,
1090 first_mouse: false,
1091 };
1092 if callback(PlatformInput::MouseDown(event)).default_prevented {
1093 return Some(0);
1094 }
1095 }
1096
1097 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1098 matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
1099 }
1100
1101 fn handle_nc_mouse_up_msg(
1102 &self,
1103 button: MouseButton,
1104 wparam: WPARAM,
1105 lparam: LPARAM,
1106 ) -> Option<isize> {
1107 if !self.hide_title_bar {
1108 return None;
1109 }
1110
1111 let mut callbacks = self.callbacks.borrow_mut();
1112 if let Some(callback) = callbacks.input.as_mut() {
1113 let mut cursor_point = POINT {
1114 x: lparam.signed_loword().into(),
1115 y: lparam.signed_hiword().into(),
1116 };
1117 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1118 let scale_factor = self.scale_factor.get();
1119 let event = MouseUpEvent {
1120 button,
1121 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1122 modifiers: self.current_modifiers(),
1123 click_count: 1,
1124 };
1125 if callback(PlatformInput::MouseUp(event)).default_prevented {
1126 return Some(0);
1127 }
1128 }
1129 drop(callbacks);
1130
1131 if button == MouseButton::Left {
1132 match wparam.0 as u32 {
1133 HTMINBUTTON => unsafe {
1134 ShowWindowAsync(self.hwnd, SW_MINIMIZE);
1135 },
1136 HTMAXBUTTON => unsafe {
1137 if self.is_maximized() {
1138 ShowWindowAsync(self.hwnd, SW_NORMAL);
1139 } else {
1140 ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
1141 }
1142 },
1143 HTCLOSE => unsafe {
1144 PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
1145 .log_err();
1146 },
1147 _ => return None,
1148 };
1149 return Some(0);
1150 }
1151
1152 None
1153 }
1154
1155 fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
1156 if matches!(
1157 lparam.loword() as u32,
1158 HTLEFT
1159 | HTRIGHT
1160 | HTTOP
1161 | HTTOPLEFT
1162 | HTTOPRIGHT
1163 | HTBOTTOM
1164 | HTBOTTOMLEFT
1165 | HTBOTTOMRIGHT
1166 ) {
1167 return None;
1168 }
1169 unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
1170 Some(1)
1171 }
1172}
1173
1174#[derive(Default)]
1175struct Callbacks {
1176 request_frame: Option<Box<dyn FnMut()>>,
1177 input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
1178 active_status_change: Option<Box<dyn FnMut(bool)>>,
1179 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
1180 fullscreen: Option<Box<dyn FnMut(bool)>>,
1181 moved: Option<Box<dyn FnMut()>>,
1182 should_close: Option<Box<dyn FnMut() -> bool>>,
1183 close: Option<Box<dyn FnOnce()>>,
1184 appearance_changed: Option<Box<dyn FnMut()>>,
1185}
1186
1187pub(crate) struct WindowsWindow {
1188 inner: Rc<WindowsWindowInner>,
1189 drag_drop_handler: IDropTarget,
1190}
1191
1192struct WindowCreateContext {
1193 inner: Option<Rc<WindowsWindowInner>>,
1194 platform_inner: Rc<WindowsPlatformInner>,
1195 handle: AnyWindowHandle,
1196 hide_title_bar: bool,
1197 display: Rc<WindowsDisplay>,
1198}
1199
1200impl WindowsWindow {
1201 pub(crate) fn new(
1202 platform_inner: Rc<WindowsPlatformInner>,
1203 handle: AnyWindowHandle,
1204 options: WindowParams,
1205 ) -> Self {
1206 let classname = register_wnd_class(platform_inner.icon);
1207 let hide_title_bar = options
1208 .titlebar
1209 .as_ref()
1210 .map(|titlebar| titlebar.appears_transparent)
1211 .unwrap_or(false);
1212 let windowname = HSTRING::from(
1213 options
1214 .titlebar
1215 .as_ref()
1216 .and_then(|titlebar| titlebar.title.as_ref())
1217 .map(|title| title.as_ref())
1218 .unwrap_or(""),
1219 );
1220 let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1221 let x = options.bounds.origin.x.0 as i32;
1222 let y = options.bounds.origin.y.0 as i32;
1223 let nwidth = options.bounds.size.width.0 as i32;
1224 let nheight = options.bounds.size.height.0 as i32;
1225 let hwndparent = HWND::default();
1226 let hmenu = HMENU::default();
1227 let hinstance = HINSTANCE::default();
1228 let mut context = WindowCreateContext {
1229 inner: None,
1230 platform_inner: platform_inner.clone(),
1231 handle,
1232 hide_title_bar,
1233 // todo(windows) move window to target monitor
1234 // options.display_id
1235 display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1236 };
1237 let lpparam = Some(&context as *const _ as *const _);
1238 unsafe {
1239 CreateWindowExW(
1240 WS_EX_APPWINDOW,
1241 classname,
1242 &windowname,
1243 dwstyle,
1244 x,
1245 y,
1246 nwidth,
1247 nheight,
1248 hwndparent,
1249 hmenu,
1250 hinstance,
1251 lpparam,
1252 )
1253 };
1254 let drag_drop_handler = {
1255 let inner = context.inner.as_ref().unwrap();
1256 let handler = WindowsDragDropHandler(Rc::clone(inner));
1257 let drag_drop_handler: IDropTarget = handler.into();
1258 unsafe {
1259 RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1260 .expect("unable to register drag-drop event")
1261 };
1262 drag_drop_handler
1263 };
1264 let wnd = Self {
1265 inner: context.inner.unwrap(),
1266 drag_drop_handler,
1267 };
1268 platform_inner
1269 .raw_window_handles
1270 .write()
1271 .push(wnd.inner.hwnd);
1272
1273 unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1274 wnd
1275 }
1276}
1277
1278impl HasWindowHandle for WindowsWindow {
1279 fn window_handle(
1280 &self,
1281 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
1282 let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
1283 NonZeroIsize::new_unchecked(self.inner.hwnd.0)
1284 })
1285 .into();
1286 Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
1287 }
1288}
1289
1290// todo(windows)
1291impl HasDisplayHandle for WindowsWindow {
1292 fn display_handle(
1293 &self,
1294 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
1295 unimplemented!()
1296 }
1297}
1298
1299impl Drop for WindowsWindow {
1300 fn drop(&mut self) {
1301 unsafe {
1302 let _ = RevokeDragDrop(self.inner.hwnd);
1303 }
1304 }
1305}
1306
1307impl PlatformWindow for WindowsWindow {
1308 fn bounds(&self) -> Bounds<DevicePixels> {
1309 Bounds {
1310 origin: self.inner.origin.get(),
1311 size: self.inner.physical_size.get(),
1312 }
1313 }
1314
1315 fn is_maximized(&self) -> bool {
1316 self.inner.is_maximized()
1317 }
1318
1319 fn is_minimized(&self) -> bool {
1320 self.inner.is_minimized()
1321 }
1322
1323 /// get the logical size of the app's drawable area.
1324 ///
1325 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1326 /// whether the mouse collides with other elements of GPUI).
1327 fn content_size(&self) -> Size<Pixels> {
1328 logical_size(
1329 self.inner.physical_size.get(),
1330 self.inner.scale_factor.get(),
1331 )
1332 }
1333
1334 fn scale_factor(&self) -> f32 {
1335 self.inner.scale_factor.get()
1336 }
1337
1338 // todo(windows)
1339 fn appearance(&self) -> WindowAppearance {
1340 WindowAppearance::Dark
1341 }
1342
1343 fn display(&self) -> Rc<dyn PlatformDisplay> {
1344 self.inner.display.borrow().clone()
1345 }
1346
1347 fn mouse_position(&self) -> Point<Pixels> {
1348 let point = unsafe {
1349 let mut point: POINT = std::mem::zeroed();
1350 GetCursorPos(&mut point)
1351 .context("unable to get cursor position")
1352 .log_err();
1353 ScreenToClient(self.inner.hwnd, &mut point);
1354 point
1355 };
1356 logical_point(
1357 point.x as f32,
1358 point.y as f32,
1359 self.inner.scale_factor.get(),
1360 )
1361 }
1362
1363 // todo(windows)
1364 fn modifiers(&self) -> Modifiers {
1365 Modifiers::none()
1366 }
1367
1368 fn as_any_mut(&mut self) -> &mut dyn Any {
1369 self
1370 }
1371
1372 // todo(windows)
1373 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1374 self.inner.input_handler.set(Some(input_handler));
1375 }
1376
1377 // todo(windows)
1378 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1379 self.inner.input_handler.take()
1380 }
1381
1382 fn prompt(
1383 &self,
1384 level: PromptLevel,
1385 msg: &str,
1386 detail: Option<&str>,
1387 answers: &[&str],
1388 ) -> Option<Receiver<usize>> {
1389 let (done_tx, done_rx) = oneshot::channel();
1390 let msg = msg.to_string();
1391 let detail_string = match detail {
1392 Some(info) => Some(info.to_string()),
1393 None => None,
1394 };
1395 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1396 let handle = self.inner.hwnd;
1397 self.inner
1398 .platform_inner
1399 .foreground_executor
1400 .spawn(async move {
1401 unsafe {
1402 let mut config;
1403 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1404 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1405 config.hwndParent = handle;
1406 let title;
1407 let main_icon;
1408 match level {
1409 crate::PromptLevel::Info => {
1410 title = windows::core::w!("Info");
1411 main_icon = TD_INFORMATION_ICON;
1412 }
1413 crate::PromptLevel::Warning => {
1414 title = windows::core::w!("Warning");
1415 main_icon = TD_WARNING_ICON;
1416 }
1417 crate::PromptLevel::Critical => {
1418 title = windows::core::w!("Critical");
1419 main_icon = TD_ERROR_ICON;
1420 }
1421 };
1422 config.pszWindowTitle = title;
1423 config.Anonymous1.pszMainIcon = main_icon;
1424 let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1425 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1426 let hints_encoded;
1427 if let Some(ref hints) = detail_string {
1428 hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1429 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1430 };
1431 let mut buttons = Vec::new();
1432 let mut btn_encoded = Vec::new();
1433 for (index, btn_string) in answers.iter().enumerate() {
1434 let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1435 buttons.push(TASKDIALOG_BUTTON {
1436 nButtonID: index as _,
1437 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1438 });
1439 btn_encoded.push(encoded);
1440 }
1441 config.cButtons = buttons.len() as _;
1442 config.pButtons = buttons.as_ptr();
1443
1444 config.pfCallback = None;
1445 let mut res = std::mem::zeroed();
1446 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1447 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1448
1449 let _ = done_tx.send(res as usize);
1450 }
1451 })
1452 .detach();
1453
1454 Some(done_rx)
1455 }
1456
1457 fn activate(&self) {
1458 unsafe { SetActiveWindow(self.inner.hwnd) };
1459 unsafe { SetFocus(self.inner.hwnd) };
1460 unsafe { SetForegroundWindow(self.inner.hwnd) };
1461 }
1462
1463 fn is_active(&self) -> bool {
1464 self.inner.hwnd == unsafe { GetActiveWindow() }
1465 }
1466
1467 // todo(windows)
1468 fn set_title(&mut self, title: &str) {
1469 unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1470 .inspect_err(|e| log::error!("Set title failed: {e}"))
1471 .ok();
1472 }
1473
1474 // todo(windows)
1475 fn set_edited(&mut self, _edited: bool) {}
1476
1477 // todo(windows)
1478 fn show_character_palette(&self) {}
1479
1480 fn minimize(&self) {
1481 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1482 }
1483
1484 fn zoom(&self) {
1485 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1486 }
1487
1488 fn toggle_fullscreen(&self) {
1489 self.inner
1490 .platform_inner
1491 .foreground_executor
1492 .spawn(self.inner.clone().toggle_fullscreen())
1493 .detach();
1494 }
1495
1496 fn is_fullscreen(&self) -> bool {
1497 self.inner.is_fullscreen()
1498 }
1499
1500 // todo(windows)
1501 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1502 self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1503 }
1504
1505 // todo(windows)
1506 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1507 self.inner.callbacks.borrow_mut().input = Some(callback);
1508 }
1509
1510 // todo(windows)
1511 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1512 self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1513 }
1514
1515 // todo(windows)
1516 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1517 self.inner.callbacks.borrow_mut().resize = Some(callback);
1518 }
1519
1520 // todo(windows)
1521 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
1522 self.inner.callbacks.borrow_mut().fullscreen = Some(callback);
1523 }
1524
1525 // todo(windows)
1526 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1527 self.inner.callbacks.borrow_mut().moved = Some(callback);
1528 }
1529
1530 // todo(windows)
1531 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1532 self.inner.callbacks.borrow_mut().should_close = Some(callback);
1533 }
1534
1535 // todo(windows)
1536 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1537 self.inner.callbacks.borrow_mut().close = Some(callback);
1538 }
1539
1540 // todo(windows)
1541 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1542 self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1543 }
1544
1545 // todo(windows)
1546 fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
1547 true
1548 }
1549
1550 // todo(windows)
1551 fn draw(&self, scene: &Scene) {
1552 self.inner.renderer.borrow_mut().draw(scene)
1553 }
1554
1555 // todo(windows)
1556 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1557 self.inner.renderer.borrow().sprite_atlas().clone()
1558 }
1559
1560 fn get_raw_handle(&self) -> HWND {
1561 self.inner.hwnd
1562 }
1563}
1564
1565#[implement(IDropTarget)]
1566struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1567
1568#[allow(non_snake_case)]
1569impl IDropTarget_Impl for WindowsDragDropHandler {
1570 fn DragEnter(
1571 &self,
1572 pdataobj: Option<&IDataObject>,
1573 _grfkeystate: MODIFIERKEYS_FLAGS,
1574 pt: &POINTL,
1575 pdweffect: *mut DROPEFFECT,
1576 ) -> windows::core::Result<()> {
1577 unsafe {
1578 let Some(idata_obj) = pdataobj else {
1579 log::info!("no dragging file or directory detected");
1580 return Ok(());
1581 };
1582 let config = FORMATETC {
1583 cfFormat: CF_HDROP.0,
1584 ptd: std::ptr::null_mut() as _,
1585 dwAspect: DVASPECT_CONTENT.0,
1586 lindex: -1,
1587 tymed: TYMED_HGLOBAL.0 as _,
1588 };
1589 let mut paths = SmallVec::<[PathBuf; 2]>::new();
1590 if idata_obj.QueryGetData(&config as _) == S_OK {
1591 *pdweffect = DROPEFFECT_LINK;
1592 let Ok(mut idata) = idata_obj.GetData(&config as _) else {
1593 return Ok(());
1594 };
1595 if idata.u.hGlobal.is_invalid() {
1596 return Ok(());
1597 }
1598 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1599 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1600 for file_index in 0..file_count {
1601 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1602 let mut buffer = vec![0u16; filename_length + 1];
1603 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1604 if ret == 0 {
1605 log::error!("unable to read file name");
1606 continue;
1607 }
1608 if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) {
1609 if let Ok(path) = PathBuf::from_str(&file_name) {
1610 paths.push(path);
1611 }
1612 }
1613 }
1614 ReleaseStgMedium(&mut idata);
1615 let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered {
1616 position: Point {
1617 x: Pixels(pt.x as _),
1618 y: Pixels(pt.y as _),
1619 },
1620 paths: crate::ExternalPaths(paths),
1621 });
1622 self.0.handle_drag_drop(input);
1623 } else {
1624 *pdweffect = DROPEFFECT_NONE;
1625 }
1626 }
1627 Ok(())
1628 }
1629
1630 fn DragOver(
1631 &self,
1632 _grfkeystate: MODIFIERKEYS_FLAGS,
1633 pt: &POINTL,
1634 _pdweffect: *mut DROPEFFECT,
1635 ) -> windows::core::Result<()> {
1636 let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending {
1637 position: Point {
1638 x: Pixels(pt.x as _),
1639 y: Pixels(pt.y as _),
1640 },
1641 });
1642 self.0.handle_drag_drop(input);
1643
1644 Ok(())
1645 }
1646
1647 fn DragLeave(&self) -> windows::core::Result<()> {
1648 let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited);
1649 self.0.handle_drag_drop(input);
1650
1651 Ok(())
1652 }
1653
1654 fn Drop(
1655 &self,
1656 _pdataobj: Option<&IDataObject>,
1657 _grfkeystate: MODIFIERKEYS_FLAGS,
1658 pt: &POINTL,
1659 _pdweffect: *mut DROPEFFECT,
1660 ) -> windows::core::Result<()> {
1661 let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit {
1662 position: Point {
1663 x: Pixels(pt.x as _),
1664 y: Pixels(pt.y as _),
1665 },
1666 });
1667 self.0.handle_drag_drop(input);
1668
1669 Ok(())
1670 }
1671}
1672
1673#[derive(Debug)]
1674struct ClickState {
1675 button: MouseButton,
1676 last_click: Instant,
1677 last_position: Point<DevicePixels>,
1678 current_count: usize,
1679}
1680
1681impl ClickState {
1682 pub fn new() -> Self {
1683 ClickState {
1684 button: MouseButton::Left,
1685 last_click: Instant::now(),
1686 last_position: Point::default(),
1687 current_count: 0,
1688 }
1689 }
1690
1691 /// update self and return the needed click count
1692 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1693 if self.button == button && self.is_double_click(new_position) {
1694 self.current_count += 1;
1695 } else {
1696 self.current_count = 1;
1697 }
1698 self.last_click = Instant::now();
1699 self.last_position = new_position;
1700 self.button = button;
1701
1702 self.current_count
1703 }
1704
1705 #[inline]
1706 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1707 let diff = self.last_position - new_position;
1708
1709 self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1710 && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1711 && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1712 }
1713}
1714
1715fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1716 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1717
1718 static ONCE: Once = Once::new();
1719 ONCE.call_once(|| {
1720 let wc = WNDCLASSW {
1721 lpfnWndProc: Some(wnd_proc),
1722 hIcon: icon_handle,
1723 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1724 style: CS_HREDRAW | CS_VREDRAW,
1725 ..Default::default()
1726 };
1727 unsafe { RegisterClassW(&wc) };
1728 });
1729
1730 CLASS_NAME
1731}
1732
1733unsafe extern "system" fn wnd_proc(
1734 hwnd: HWND,
1735 msg: u32,
1736 wparam: WPARAM,
1737 lparam: LPARAM,
1738) -> LRESULT {
1739 if msg == WM_NCCREATE {
1740 let cs = lparam.0 as *const CREATESTRUCTW;
1741 let cs = unsafe { &*cs };
1742 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1743 let ctx = unsafe { &mut *ctx };
1744 let inner = Rc::new(WindowsWindowInner::new(
1745 hwnd,
1746 cs,
1747 ctx.platform_inner.clone(),
1748 ctx.handle,
1749 ctx.hide_title_bar,
1750 ctx.display.clone(),
1751 ));
1752 let weak = Box::new(Rc::downgrade(&inner));
1753 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1754 ctx.inner = Some(inner);
1755 return LRESULT(1);
1756 }
1757 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1758 if ptr.is_null() {
1759 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1760 }
1761 let inner = unsafe { &*ptr };
1762 let r = if let Some(inner) = inner.upgrade() {
1763 inner.handle_msg(msg, wparam, lparam)
1764 } else {
1765 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1766 };
1767 if msg == WM_NCDESTROY {
1768 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1769 unsafe { drop(Box::from_raw(ptr)) };
1770 }
1771 r
1772}
1773
1774pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1775 if hwnd == HWND(0) {
1776 return None;
1777 }
1778
1779 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1780 if !ptr.is_null() {
1781 let inner = unsafe { &*ptr };
1782 inner.upgrade()
1783 } else {
1784 None
1785 }
1786}
1787
1788fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1789 match code {
1790 // VK_0 - VK_9
1791 48..=57 => Some(Keystroke {
1792 modifiers,
1793 key: format!("{}", code - VK_0.0),
1794 ime_key: None,
1795 }),
1796 // VK_A - VK_Z
1797 65..=90 => Some(Keystroke {
1798 modifiers,
1799 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1800 ime_key: None,
1801 }),
1802 // VK_F1 - VK_F24
1803 112..=135 => Some(Keystroke {
1804 modifiers,
1805 key: format!("f{}", code - VK_F1.0 + 1),
1806 ime_key: None,
1807 }),
1808 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1809 _ => {
1810 if let Some(key) = oemkey_vkcode_to_string(code) {
1811 Some(Keystroke {
1812 modifiers,
1813 key,
1814 ime_key: None,
1815 })
1816 } else {
1817 None
1818 }
1819 }
1820 }
1821}
1822
1823fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1824 match code {
1825 186 => Some(";".to_string()), // VK_OEM_1
1826 187 => Some("=".to_string()), // VK_OEM_PLUS
1827 188 => Some(",".to_string()), // VK_OEM_COMMA
1828 189 => Some("-".to_string()), // VK_OEM_MINUS
1829 190 => Some(".".to_string()), // VK_OEM_PERIOD
1830 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1831 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1832 192 => Some("`".to_string()), // VK_OEM_3
1833 219 => Some("[".to_string()), // VK_OEM_4
1834 220 => Some("\\".to_string()), // VK_OEM_5
1835 221 => Some("]".to_string()), // VK_OEM_6
1836 222 => Some("'".to_string()), // VK_OEM_7
1837 _ => None,
1838 }
1839}
1840
1841#[inline]
1842fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1843 Size {
1844 width: px(physical_size.width.0 as f32 / scale_factor),
1845 height: px(physical_size.height.0 as f32 / scale_factor),
1846 }
1847}
1848
1849#[inline]
1850fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1851 Point {
1852 x: px(x / scale_factor),
1853 y: px(y / scale_factor),
1854 }
1855}
1856
1857struct StyleAndBounds {
1858 style: WINDOW_STYLE,
1859 x: i32,
1860 y: i32,
1861 cx: i32,
1862 cy: i32,
1863}
1864
1865// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1866const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1867// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1868const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1869// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1870const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1871
1872#[cfg(test)]
1873mod tests {
1874 use super::ClickState;
1875 use crate::{point, DevicePixels, MouseButton};
1876 use std::time::Duration;
1877
1878 #[test]
1879 fn test_double_click_interval() {
1880 let mut state = ClickState::new();
1881 assert_eq!(
1882 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1883 1
1884 );
1885 assert_eq!(
1886 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1887 1
1888 );
1889 assert_eq!(
1890 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1891 1
1892 );
1893 assert_eq!(
1894 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1895 2
1896 );
1897 state.last_click -= Duration::from_millis(700);
1898 assert_eq!(
1899 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1900 1
1901 );
1902 }
1903
1904 #[test]
1905 fn test_double_click_spatial_tolerance() {
1906 let mut state = ClickState::new();
1907 assert_eq!(
1908 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1909 1
1910 );
1911 assert_eq!(
1912 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1913 2
1914 );
1915 assert_eq!(
1916 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1917 1
1918 );
1919 assert_eq!(
1920 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1921 1
1922 );
1923 }
1924}