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