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