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