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