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