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