1#![deny(unsafe_op_in_unsafe_fn)]
2// todo(windows): remove
3#![allow(unused_variables)]
4
5use std::{
6 any::Any,
7 cell::{Cell, RefCell},
8 ffi::c_void,
9 iter::once,
10 num::NonZeroIsize,
11 path::PathBuf,
12 rc::{Rc, Weak},
13 str::FromStr,
14 sync::{Arc, Once},
15};
16
17use blade_graphics as gpu;
18use futures::channel::oneshot::{self, Receiver};
19use itertools::Itertools;
20use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
21use smallvec::SmallVec;
22use windows::{
23 core::{implement, w, HSTRING, PCWSTR},
24 Win32::{
25 Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, POINT, POINTL, S_OK, WPARAM},
26 Graphics::Gdi::{BeginPaint, EndPaint, InvalidateRect, PAINTSTRUCT},
27 System::{
28 Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL},
29 Ole::{
30 IDropTarget, IDropTarget_Impl, RegisterDragDrop, ReleaseStgMedium, RevokeDragDrop,
31 CF_HDROP, DROPEFFECT, DROPEFFECT_LINK, DROPEFFECT_NONE,
32 },
33 SystemServices::{
34 MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS,
35 },
36 },
37 UI::{
38 Controls::{
39 TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON,
40 TD_INFORMATION_ICON, TD_WARNING_ICON,
41 },
42 Input::{
43 Ime::{
44 ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
45 ImmSetCandidateWindow, CANDIDATEFORM, CFS_CANDIDATEPOS, GCS_COMPSTR,
46 },
47 KeyboardAndMouse::{
48 GetKeyState, VIRTUAL_KEY, VK_0, VK_A, VK_BACK, VK_CONTROL, VK_DOWN, VK_END,
49 VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU,
50 VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_TAB, VK_UP,
51 },
52 },
53 Shell::{DragQueryFileW, HDROP},
54 WindowsAndMessaging::{
55 CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage,
56 RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW,
57 GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA,
58 WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_IME_CHAR,
59 WM_IME_COMPOSITION, WM_IME_STARTCOMPOSITION, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN,
60 WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
61 WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, WM_PAINT, WM_RBUTTONDOWN,
62 WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP,
63 WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, XBUTTON1, XBUTTON2,
64 },
65 },
66 },
67};
68
69use crate::{
70 platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, KeyDownEvent,
71 KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
72 NavigationDirection, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
73 PlatformInputHandler, PlatformWindow, Point, PromptLevel, Scene, ScrollDelta, Size, TouchPhase,
74 WindowAppearance, WindowParams, WindowsDisplay, WindowsPlatformInner,
75};
76
77pub(crate) struct WindowsWindowInner {
78 hwnd: HWND,
79 origin: Cell<Point<GlobalPixels>>,
80 size: Cell<Size<GlobalPixels>>,
81 mouse_position: Cell<Point<Pixels>>,
82 input_handler: Cell<Option<PlatformInputHandler>>,
83 renderer: RefCell<BladeRenderer>,
84 callbacks: RefCell<Callbacks>,
85 platform_inner: Rc<WindowsPlatformInner>,
86 handle: AnyWindowHandle,
87}
88
89impl WindowsWindowInner {
90 fn new(
91 hwnd: HWND,
92 cs: &CREATESTRUCTW,
93 platform_inner: Rc<WindowsPlatformInner>,
94 handle: AnyWindowHandle,
95 ) -> Self {
96 let origin = Cell::new(Point::new((cs.x as f64).into(), (cs.y as f64).into()));
97 let size = Cell::new(Size {
98 width: (cs.cx as f64).into(),
99 height: (cs.cy as f64).into(),
100 });
101 let mouse_position = Cell::new(Point::default());
102 let input_handler = Cell::new(None);
103 struct RawWindow {
104 hwnd: *mut c_void,
105 }
106 unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
107 fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
108 let mut handle = blade_rwh::Win32WindowHandle::empty();
109 handle.hwnd = self.hwnd;
110 handle.into()
111 }
112 }
113 unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
114 fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
115 blade_rwh::WindowsDisplayHandle::empty().into()
116 }
117 }
118 let raw = RawWindow { hwnd: hwnd.0 as _ };
119 let gpu = Arc::new(
120 unsafe {
121 gpu::Context::init_windowed(
122 &raw,
123 gpu::ContextDesc {
124 validation: false,
125 capture: false,
126 overlay: false,
127 },
128 )
129 }
130 .unwrap(),
131 );
132 let extent = gpu::Extent {
133 width: 1,
134 height: 1,
135 depth: 1,
136 };
137 let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
138 let callbacks = RefCell::new(Callbacks::default());
139 Self {
140 hwnd,
141 origin,
142 size,
143 mouse_position,
144 input_handler,
145 renderer,
146 callbacks,
147 platform_inner,
148 handle,
149 }
150 }
151
152 fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
153 unsafe { GetKeyState(vkey.0 as i32) < 0 }
154 }
155
156 fn current_modifiers(&self) -> Modifiers {
157 Modifiers {
158 control: self.is_virtual_key_pressed(VK_CONTROL),
159 alt: self.is_virtual_key_pressed(VK_MENU),
160 shift: self.is_virtual_key_pressed(VK_SHIFT),
161 command: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
162 function: false,
163 }
164 }
165
166 /// mark window client rect to be re-drawn
167 /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
168 pub(crate) fn invalidate_client_area(&self) {
169 unsafe { InvalidateRect(self.hwnd, None, FALSE) };
170 }
171
172 fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
173 log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
174 match msg {
175 WM_MOVE => self.handle_move_msg(lparam),
176 WM_SIZE => self.handle_size_msg(lparam),
177 WM_PAINT => self.handle_paint_msg(),
178 WM_CLOSE => self.handle_close_msg(msg, wparam, lparam),
179 WM_DESTROY => self.handle_destroy_msg(),
180 WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
181 WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
182 WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
183 WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
184 WM_XBUTTONDOWN => {
185 let nav_dir = match wparam.hiword() {
186 XBUTTON1 => Some(NavigationDirection::Forward),
187 XBUTTON2 => Some(NavigationDirection::Back),
188 _ => None,
189 };
190
191 if let Some(nav_dir) = nav_dir {
192 self.handle_mouse_down_msg(MouseButton::Navigate(nav_dir), lparam)
193 } else {
194 LRESULT(1)
195 }
196 }
197 WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
198 WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
199 WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
200 WM_XBUTTONUP => {
201 let nav_dir = match wparam.hiword() {
202 XBUTTON1 => Some(NavigationDirection::Back),
203 XBUTTON2 => Some(NavigationDirection::Forward),
204 _ => None,
205 };
206
207 if let Some(nav_dir) = nav_dir {
208 self.handle_mouse_up_msg(MouseButton::Navigate(nav_dir), lparam)
209 } else {
210 LRESULT(1)
211 }
212 }
213 WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
214 WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
215 WM_SYSKEYDOWN => self.handle_syskeydown_msg(msg, wparam, lparam),
216 WM_SYSKEYUP => self.handle_syskeyup_msg(msg, wparam, lparam),
217 WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
218 WM_KEYUP => self.handle_keyup_msg(msg, wparam),
219 WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
220 WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
221 WM_IME_COMPOSITION => self.handle_ime_composition(msg, wparam, lparam),
222 WM_IME_CHAR => self.handle_ime_char(wparam),
223 _ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
224 }
225 }
226
227 fn handle_move_msg(&self, lparam: LPARAM) -> LRESULT {
228 let x = lparam.signed_loword() as f64;
229 let y = lparam.signed_hiword() as f64;
230 self.origin.set(Point::new(x.into(), y.into()));
231 let mut callbacks = self.callbacks.borrow_mut();
232 if let Some(callback) = callbacks.moved.as_mut() {
233 callback()
234 }
235 LRESULT(0)
236 }
237
238 fn handle_size_msg(&self, lparam: LPARAM) -> LRESULT {
239 let width = lparam.loword().max(1) as f64;
240 let height = lparam.hiword().max(1) as f64;
241 self.renderer
242 .borrow_mut()
243 .update_drawable_size(Size { width, height });
244 let width = width.into();
245 let height = height.into();
246 self.size.set(Size { width, height });
247 let mut callbacks = self.callbacks.borrow_mut();
248 if let Some(callback) = callbacks.resize.as_mut() {
249 callback(
250 Size {
251 width: Pixels(width.0),
252 height: Pixels(height.0),
253 },
254 1.0,
255 );
256 }
257 self.invalidate_client_area();
258 LRESULT(0)
259 }
260
261 fn handle_paint_msg(&self) -> LRESULT {
262 let mut paint_struct = PAINTSTRUCT::default();
263 let hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
264 let mut callbacks = self.callbacks.borrow_mut();
265 if let Some(request_frame) = callbacks.request_frame.as_mut() {
266 request_frame();
267 }
268 unsafe { EndPaint(self.hwnd, &paint_struct) };
269 LRESULT(0)
270 }
271
272 fn handle_close_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
273 let mut callbacks = self.callbacks.borrow_mut();
274 if let Some(callback) = callbacks.should_close.as_mut() {
275 if callback() {
276 return LRESULT(0);
277 }
278 }
279 drop(callbacks);
280 unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
281 }
282
283 fn handle_destroy_msg(&self) -> LRESULT {
284 let mut callbacks = self.callbacks.borrow_mut();
285 if let Some(callback) = callbacks.close.take() {
286 callback()
287 }
288 let index = self
289 .platform_inner
290 .raw_window_handles
291 .read()
292 .iter()
293 .position(|handle| *handle == self.hwnd)
294 .unwrap();
295 self.platform_inner.raw_window_handles.write().remove(index);
296 if self.platform_inner.raw_window_handles.read().is_empty() {
297 self.platform_inner
298 .foreground_executor
299 .spawn(async {
300 unsafe { PostQuitMessage(0) };
301 })
302 .detach();
303 }
304 LRESULT(1)
305 }
306
307 fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> LRESULT {
308 let x = Pixels::from(lparam.signed_loword() as f32);
309 let y = Pixels::from(lparam.signed_hiword() as f32);
310 self.mouse_position.set(Point { x, y });
311 let mut callbacks = self.callbacks.borrow_mut();
312 if let Some(callback) = callbacks.input.as_mut() {
313 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
314 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
315 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
316 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
317 flags if flags.contains(MK_XBUTTON1) => {
318 Some(MouseButton::Navigate(NavigationDirection::Back))
319 }
320 flags if flags.contains(MK_XBUTTON2) => {
321 Some(MouseButton::Navigate(NavigationDirection::Forward))
322 }
323 _ => None,
324 };
325 let event = MouseMoveEvent {
326 position: Point { x, y },
327 pressed_button,
328 modifiers: self.current_modifiers(),
329 };
330 if callback(PlatformInput::MouseMove(event)) {
331 return LRESULT(0);
332 }
333 }
334 LRESULT(1)
335 }
336
337 fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
338 let modifiers = self.current_modifiers();
339 if !modifiers.alt {
340 // on Windows, F10 can trigger this event, not just the alt key
341 // and we just don't care about F10
342 return None;
343 }
344
345 let vk_code = wparam.loword();
346 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
347 if basic_key.is_some() {
348 return basic_key;
349 }
350
351 let key = match VIRTUAL_KEY(vk_code) {
352 VK_BACK => Some("backspace"),
353 VK_RETURN => Some("enter"),
354 VK_TAB => Some("tab"),
355 VK_UP => Some("up"),
356 VK_DOWN => Some("down"),
357 VK_RIGHT => Some("right"),
358 VK_LEFT => Some("left"),
359 VK_HOME => Some("home"),
360 VK_END => Some("end"),
361 VK_PRIOR => Some("pageup"),
362 VK_NEXT => Some("pagedown"),
363 VK_ESCAPE => Some("escape"),
364 VK_INSERT => Some("insert"),
365 _ => None,
366 };
367
368 if let Some(key) = key {
369 Some(Keystroke {
370 modifiers,
371 key: key.to_string(),
372 ime_key: None,
373 })
374 } else {
375 None
376 }
377 }
378
379 fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
380 let vk_code = wparam.loword();
381
382 let modifiers = self.current_modifiers();
383 if modifiers.control || modifiers.alt {
384 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
385 if basic_key.is_some() {
386 return basic_key;
387 }
388 }
389
390 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
391 let offset = vk_code - VK_F1.0;
392 return Some(Keystroke {
393 modifiers,
394 key: format!("f{}", offset + 1),
395 ime_key: None,
396 });
397 }
398
399 let key = match VIRTUAL_KEY(vk_code) {
400 VK_BACK => Some("backspace"),
401 VK_RETURN => Some("enter"),
402 VK_TAB => Some("tab"),
403 VK_UP => Some("up"),
404 VK_DOWN => Some("down"),
405 VK_RIGHT => Some("right"),
406 VK_LEFT => Some("left"),
407 VK_HOME => Some("home"),
408 VK_END => Some("end"),
409 VK_PRIOR => Some("pageup"),
410 VK_NEXT => Some("pagedown"),
411 VK_ESCAPE => Some("escape"),
412 VK_INSERT => Some("insert"),
413 _ => None,
414 };
415
416 if let Some(key) = key {
417 Some(Keystroke {
418 modifiers,
419 key: key.to_string(),
420 ime_key: None,
421 })
422 } else {
423 None
424 }
425 }
426
427 fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
428 let src = [wparam.0 as u16];
429 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
430 return None;
431 };
432 if first_char.is_control() {
433 None
434 } else {
435 Some(Keystroke {
436 modifiers: self.current_modifiers(),
437 key: first_char.to_lowercase().to_string(),
438 ime_key: Some(first_char.to_string()),
439 })
440 }
441 }
442
443 fn handle_syskeydown_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
444 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
445 // shortcuts.
446 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
447 return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
448 };
449 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
450 return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
451 };
452 let event = KeyDownEvent {
453 keystroke,
454 is_held: lparam.0 & (0x1 << 30) > 0,
455 };
456 if func(PlatformInput::KeyDown(event)) {
457 self.invalidate_client_area();
458 return LRESULT(0);
459 }
460 unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }
461 }
462
463 fn handle_syskeyup_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
464 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
465 // shortcuts.
466 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
467 return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
468 };
469 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
470 return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
471 };
472 let event = KeyUpEvent { keystroke };
473 if func(PlatformInput::KeyUp(event)) {
474 self.invalidate_client_area();
475 return LRESULT(0);
476 }
477 unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }
478 }
479
480 fn handle_keydown_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
481 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
482 return LRESULT(1);
483 };
484 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
485 return LRESULT(1);
486 };
487 let event = KeyDownEvent {
488 keystroke,
489 is_held: lparam.0 & (0x1 << 30) > 0,
490 };
491 if func(PlatformInput::KeyDown(event)) {
492 self.invalidate_client_area();
493 return LRESULT(0);
494 }
495 LRESULT(1)
496 }
497
498 fn handle_keyup_msg(&self, message: u32, wparam: WPARAM) -> LRESULT {
499 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
500 return LRESULT(1);
501 };
502 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
503 return LRESULT(1);
504 };
505 let event = KeyUpEvent { keystroke };
506 if func(PlatformInput::KeyUp(event)) {
507 self.invalidate_client_area();
508 return LRESULT(0);
509 }
510 LRESULT(1)
511 }
512
513 fn handle_char_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
514 let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
515 return LRESULT(1);
516 };
517 let mut callbacks = self.callbacks.borrow_mut();
518 let Some(ref mut func) = callbacks.input else {
519 return LRESULT(1);
520 };
521 let ime_key = keystroke.ime_key.clone();
522 let event = KeyDownEvent {
523 keystroke,
524 is_held: lparam.0 & (0x1 << 30) > 0,
525 };
526 if func(PlatformInput::KeyDown(event)) {
527 self.invalidate_client_area();
528 return LRESULT(0);
529 }
530 drop(callbacks);
531 let Some(ime_char) = ime_key else {
532 return LRESULT(1);
533 };
534 let Some(mut input_handler) = self.input_handler.take() else {
535 return LRESULT(1);
536 };
537 input_handler.replace_text_in_range(None, &ime_char);
538 self.input_handler.set(Some(input_handler));
539 self.invalidate_client_area();
540 LRESULT(0)
541 }
542
543 fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT {
544 let mut callbacks = self.callbacks.borrow_mut();
545 if let Some(callback) = callbacks.input.as_mut() {
546 let x = Pixels::from(lparam.signed_loword() as f32);
547 let y = Pixels::from(lparam.signed_hiword() as f32);
548 let event = MouseDownEvent {
549 button,
550 position: Point { x, y },
551 modifiers: self.current_modifiers(),
552 click_count: 1,
553 };
554 if callback(PlatformInput::MouseDown(event)) {
555 return LRESULT(0);
556 }
557 }
558 LRESULT(1)
559 }
560
561 fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT {
562 let mut callbacks = self.callbacks.borrow_mut();
563 if let Some(callback) = callbacks.input.as_mut() {
564 let x = Pixels::from(lparam.signed_loword() as f32);
565 let y = Pixels::from(lparam.signed_hiword() as f32);
566 let event = MouseUpEvent {
567 button,
568 position: Point { x, y },
569 modifiers: self.current_modifiers(),
570 click_count: 1,
571 };
572 if callback(PlatformInput::MouseUp(event)) {
573 return LRESULT(0);
574 }
575 }
576 LRESULT(1)
577 }
578
579 fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
580 let mut callbacks = self.callbacks.borrow_mut();
581 if let Some(callback) = callbacks.input.as_mut() {
582 let x = Pixels::from(lparam.signed_loword() as f32);
583 let y = Pixels::from(lparam.signed_hiword() as f32);
584 let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
585 * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
586 let event = crate::ScrollWheelEvent {
587 position: Point { x, y },
588 delta: ScrollDelta::Lines(Point {
589 x: 0.0,
590 y: wheel_distance,
591 }),
592 modifiers: self.current_modifiers(),
593 touch_phase: TouchPhase::Moved,
594 };
595 callback(PlatformInput::ScrollWheel(event));
596 return LRESULT(0);
597 }
598 LRESULT(1)
599 }
600
601 fn handle_mouse_horizontal_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_chars as f32;
608 let event = crate::ScrollWheelEvent {
609 position: Point { x, y },
610 delta: ScrollDelta::Lines(Point {
611 x: wheel_distance,
612 y: 0.0,
613 }),
614 modifiers: self.current_modifiers(),
615 touch_phase: TouchPhase::Moved,
616 };
617 if callback(PlatformInput::ScrollWheel(event)) {
618 return LRESULT(0);
619 }
620 }
621 LRESULT(1)
622 }
623
624 fn handle_ime_position(&self) -> LRESULT {
625 unsafe {
626 let ctx = ImmGetContext(self.hwnd);
627 let Some(mut input_handler) = self.input_handler.take() else {
628 return LRESULT(1);
629 };
630 // we are composing, this should never fail
631 let caret_range = input_handler.selected_text_range().unwrap();
632 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
633 self.input_handler.set(Some(input_handler));
634 let config = CANDIDATEFORM {
635 dwStyle: CFS_CANDIDATEPOS,
636 ptCurrentPos: POINT {
637 x: caret_position.origin.x.0 as i32,
638 y: caret_position.origin.y.0 as i32 + (caret_position.size.height.0 as i32 / 2),
639 },
640 ..Default::default()
641 };
642 ImmSetCandidateWindow(ctx, &config as _);
643 ImmReleaseContext(self.hwnd, ctx);
644 LRESULT(0)
645 }
646 }
647
648 fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
649 unsafe {
650 let ctx = ImmGetContext(self.hwnd);
651 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
652 let result = if string_len >= 0 {
653 let mut buffer = vec![0u8; string_len as usize + 2];
654 // let mut buffer = [0u8; MAX_PATH as _];
655 ImmGetCompositionStringW(
656 ctx,
657 GCS_COMPSTR,
658 Some(buffer.as_mut_ptr() as _),
659 string_len as _,
660 );
661 let wstring = std::slice::from_raw_parts::<u16>(
662 buffer.as_mut_ptr().cast::<u16>(),
663 string_len as usize / 2,
664 );
665 let string = String::from_utf16_lossy(wstring);
666 Some((string, string_len as usize / 2))
667 } else {
668 None
669 };
670 ImmReleaseContext(self.hwnd, ctx);
671 result
672 }
673 }
674
675 fn handle_ime_composition(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
676 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
677 let Some((string, string_len)) = self.parse_ime_compostion_string() else {
678 return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
679 };
680 let Some(mut input_handler) = self.input_handler.take() else {
681 return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
682 };
683 input_handler.replace_and_mark_text_in_range(
684 None,
685 string.as_str(),
686 Some(0..string_len),
687 );
688 self.input_handler.set(Some(input_handler));
689 unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
690 } else {
691 // currently, we don't care other stuff
692 unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
693 }
694 }
695
696 fn parse_ime_char(&self, wparam: WPARAM) -> Option<String> {
697 let src = [wparam.0 as u16];
698 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
699 return None;
700 };
701 Some(first_char.to_string())
702 }
703
704 fn handle_ime_char(&self, wparam: WPARAM) -> LRESULT {
705 let Some(ime_char) = self.parse_ime_char(wparam) else {
706 return LRESULT(1);
707 };
708 let Some(mut input_handler) = self.input_handler.take() else {
709 return LRESULT(1);
710 };
711 input_handler.replace_text_in_range(None, &ime_char);
712 self.input_handler.set(Some(input_handler));
713 self.invalidate_client_area();
714 LRESULT(0)
715 }
716
717 fn handle_drag_drop(&self, input: PlatformInput) {
718 let mut callbacks = self.callbacks.borrow_mut();
719 let Some(ref mut func) = callbacks.input else {
720 return;
721 };
722 func(input);
723 }
724}
725
726#[derive(Default)]
727struct Callbacks {
728 request_frame: Option<Box<dyn FnMut()>>,
729 input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
730 active_status_change: Option<Box<dyn FnMut(bool)>>,
731 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
732 fullscreen: Option<Box<dyn FnMut(bool)>>,
733 moved: Option<Box<dyn FnMut()>>,
734 should_close: Option<Box<dyn FnMut() -> bool>>,
735 close: Option<Box<dyn FnOnce()>>,
736 appearance_changed: Option<Box<dyn FnMut()>>,
737}
738
739pub(crate) struct WindowsWindow {
740 inner: Rc<WindowsWindowInner>,
741 drag_drop_handler: IDropTarget,
742 display: Rc<WindowsDisplay>,
743}
744
745struct WindowCreateContext {
746 inner: Option<Rc<WindowsWindowInner>>,
747 platform_inner: Rc<WindowsPlatformInner>,
748 handle: AnyWindowHandle,
749}
750
751impl WindowsWindow {
752 pub(crate) fn new(
753 platform_inner: Rc<WindowsPlatformInner>,
754 handle: AnyWindowHandle,
755 options: WindowParams,
756 ) -> Self {
757 let dwexstyle = WINDOW_EX_STYLE::default();
758 let classname = register_wnd_class();
759 let windowname = HSTRING::from(
760 options
761 .titlebar
762 .as_ref()
763 .and_then(|titlebar| titlebar.title.as_ref())
764 .map(|title| title.as_ref())
765 .unwrap_or(""),
766 );
767 let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE;
768 let x = options.bounds.origin.x.0 as i32;
769 let y = options.bounds.origin.y.0 as i32;
770 let nwidth = options.bounds.size.width.0 as i32;
771 let nheight = options.bounds.size.height.0 as i32;
772 let hwndparent = HWND::default();
773 let hmenu = HMENU::default();
774 let hinstance = HINSTANCE::default();
775 let mut context = WindowCreateContext {
776 inner: None,
777 platform_inner: platform_inner.clone(),
778 handle,
779 };
780 let lpparam = Some(&context as *const _ as *const _);
781 unsafe {
782 CreateWindowExW(
783 dwexstyle,
784 classname,
785 &windowname,
786 dwstyle,
787 x,
788 y,
789 nwidth,
790 nheight,
791 hwndparent,
792 hmenu,
793 hinstance,
794 lpparam,
795 )
796 };
797 let drag_drop_handler = {
798 let inner = context.inner.as_ref().unwrap();
799 let handler = WindowsDragDropHandler(Rc::clone(inner));
800 let drag_drop_handler: IDropTarget = handler.into();
801 unsafe {
802 RegisterDragDrop(inner.hwnd, &drag_drop_handler)
803 .expect("unable to register drag-drop event")
804 };
805 drag_drop_handler
806 };
807 // todo(windows) move window to target monitor
808 // options.display_id
809 let wnd = Self {
810 inner: context.inner.unwrap(),
811 drag_drop_handler,
812 display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
813 };
814 platform_inner
815 .raw_window_handles
816 .write()
817 .push(wnd.inner.hwnd);
818
819 unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
820 wnd
821 }
822
823 fn maximize(&self) {
824 unsafe { ShowWindow(self.inner.hwnd, SW_MAXIMIZE) };
825 }
826}
827
828impl HasWindowHandle for WindowsWindow {
829 fn window_handle(
830 &self,
831 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
832 let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
833 NonZeroIsize::new_unchecked(self.inner.hwnd.0)
834 })
835 .into();
836 Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
837 }
838}
839
840// todo(windows)
841impl HasDisplayHandle for WindowsWindow {
842 fn display_handle(
843 &self,
844 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
845 unimplemented!()
846 }
847}
848
849impl Drop for WindowsWindow {
850 fn drop(&mut self) {
851 unsafe {
852 let _ = RevokeDragDrop(self.inner.hwnd);
853 }
854 }
855}
856
857impl PlatformWindow for WindowsWindow {
858 fn bounds(&self) -> Bounds<GlobalPixels> {
859 Bounds {
860 origin: self.inner.origin.get(),
861 size: self.inner.size.get(),
862 }
863 }
864
865 // todo(windows)
866 fn content_size(&self) -> Size<Pixels> {
867 let size = self.inner.size.get();
868 Size {
869 width: size.width.0.into(),
870 height: size.height.0.into(),
871 }
872 }
873
874 // todo(windows)
875 fn scale_factor(&self) -> f32 {
876 1.0
877 }
878
879 // todo(windows)
880 fn titlebar_height(&self) -> Pixels {
881 20.0.into()
882 }
883
884 // todo(windows)
885 fn appearance(&self) -> WindowAppearance {
886 WindowAppearance::Dark
887 }
888
889 fn display(&self) -> Rc<dyn PlatformDisplay> {
890 self.display.clone()
891 }
892
893 fn mouse_position(&self) -> Point<Pixels> {
894 self.inner.mouse_position.get()
895 }
896
897 // todo(windows)
898 fn modifiers(&self) -> Modifiers {
899 Modifiers::none()
900 }
901
902 fn as_any_mut(&mut self) -> &mut dyn Any {
903 self
904 }
905
906 // todo(windows)
907 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
908 self.inner.input_handler.set(Some(input_handler));
909 }
910
911 // todo(windows)
912 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
913 self.inner.input_handler.take()
914 }
915
916 fn prompt(
917 &self,
918 level: PromptLevel,
919 msg: &str,
920 detail: Option<&str>,
921 answers: &[&str],
922 ) -> Option<Receiver<usize>> {
923 let (done_tx, done_rx) = oneshot::channel();
924 let msg = msg.to_string();
925 let detail_string = match detail {
926 Some(info) => Some(info.to_string()),
927 None => None,
928 };
929 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
930 let handle = self.inner.hwnd;
931 self.inner
932 .platform_inner
933 .foreground_executor
934 .spawn(async move {
935 unsafe {
936 let mut config;
937 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
938 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
939 config.hwndParent = handle;
940 let title;
941 let main_icon;
942 match level {
943 crate::PromptLevel::Info => {
944 title = windows::core::w!("Info");
945 main_icon = TD_INFORMATION_ICON;
946 }
947 crate::PromptLevel::Warning => {
948 title = windows::core::w!("Warning");
949 main_icon = TD_WARNING_ICON;
950 }
951 crate::PromptLevel::Critical => {
952 title = windows::core::w!("Critical");
953 main_icon = TD_ERROR_ICON;
954 }
955 };
956 config.pszWindowTitle = title;
957 config.Anonymous1.pszMainIcon = main_icon;
958 let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
959 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
960 let hints_encoded;
961 if let Some(ref hints) = detail_string {
962 hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
963 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
964 };
965 let mut buttons = Vec::new();
966 let mut btn_encoded = Vec::new();
967 for (index, btn_string) in answers.iter().enumerate() {
968 let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
969 buttons.push(TASKDIALOG_BUTTON {
970 nButtonID: index as _,
971 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
972 });
973 btn_encoded.push(encoded);
974 }
975 config.cButtons = buttons.len() as _;
976 config.pButtons = buttons.as_ptr();
977
978 config.pfCallback = None;
979 let mut res = std::mem::zeroed();
980 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
981 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
982
983 let _ = done_tx.send(res as usize);
984 }
985 })
986 .detach();
987
988 Some(done_rx)
989 }
990
991 // todo(windows)
992 fn activate(&self) {}
993
994 // todo(windows)
995 fn set_title(&mut self, title: &str) {
996 unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
997 .inspect_err(|e| log::error!("Set title failed: {e}"))
998 .ok();
999 }
1000
1001 // todo(windows)
1002 fn set_edited(&mut self, edited: bool) {}
1003
1004 // todo(windows)
1005 fn show_character_palette(&self) {}
1006
1007 // todo(windows)
1008 fn minimize(&self) {}
1009
1010 // todo(windows)
1011 fn zoom(&self) {}
1012
1013 // todo(windows)
1014 fn toggle_fullscreen(&self) {}
1015
1016 // todo(windows)
1017 fn is_fullscreen(&self) -> bool {
1018 false
1019 }
1020
1021 // todo(windows)
1022 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1023 self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1024 }
1025
1026 // todo(windows)
1027 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
1028 self.inner.callbacks.borrow_mut().input = Some(callback);
1029 }
1030
1031 // todo(windows)
1032 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1033 self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1034 }
1035
1036 // todo(windows)
1037 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1038 self.inner.callbacks.borrow_mut().resize = Some(callback);
1039 }
1040
1041 // todo(windows)
1042 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
1043 self.inner.callbacks.borrow_mut().fullscreen = Some(callback);
1044 }
1045
1046 // todo(windows)
1047 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1048 self.inner.callbacks.borrow_mut().moved = Some(callback);
1049 }
1050
1051 // todo(windows)
1052 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1053 self.inner.callbacks.borrow_mut().should_close = Some(callback);
1054 }
1055
1056 // todo(windows)
1057 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1058 self.inner.callbacks.borrow_mut().close = Some(callback);
1059 }
1060
1061 // todo(windows)
1062 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1063 self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1064 }
1065
1066 // todo(windows)
1067 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
1068 true
1069 }
1070
1071 // todo(windows)
1072 fn draw(&self, scene: &Scene) {
1073 self.inner.renderer.borrow_mut().draw(scene)
1074 }
1075
1076 // todo(windows)
1077 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1078 self.inner.renderer.borrow().sprite_atlas().clone()
1079 }
1080}
1081
1082#[implement(IDropTarget)]
1083struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1084
1085impl IDropTarget_Impl for WindowsDragDropHandler {
1086 fn DragEnter(
1087 &self,
1088 pdataobj: Option<&IDataObject>,
1089 _grfkeystate: MODIFIERKEYS_FLAGS,
1090 pt: &POINTL,
1091 pdweffect: *mut DROPEFFECT,
1092 ) -> windows::core::Result<()> {
1093 unsafe {
1094 let Some(idata_obj) = pdataobj else {
1095 log::info!("no dragging file or directory detected");
1096 return Ok(());
1097 };
1098 let config = FORMATETC {
1099 cfFormat: CF_HDROP.0,
1100 ptd: std::ptr::null_mut() as _,
1101 dwAspect: DVASPECT_CONTENT.0,
1102 lindex: -1,
1103 tymed: TYMED_HGLOBAL.0 as _,
1104 };
1105 let mut paths = SmallVec::<[PathBuf; 2]>::new();
1106 if idata_obj.QueryGetData(&config as _) == S_OK {
1107 *pdweffect = DROPEFFECT_LINK;
1108 let Ok(mut idata) = idata_obj.GetData(&config as _) else {
1109 return Ok(());
1110 };
1111 if idata.u.hGlobal.is_invalid() {
1112 return Ok(());
1113 }
1114 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1115 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1116 for file_index in 0..file_count {
1117 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1118 let mut buffer = vec![0u16; filename_length + 1];
1119 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1120 if ret == 0 {
1121 log::error!("unable to read file name");
1122 continue;
1123 }
1124 if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) {
1125 if let Ok(path) = PathBuf::from_str(&file_name) {
1126 paths.push(path);
1127 }
1128 }
1129 }
1130 ReleaseStgMedium(&mut idata);
1131 let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered {
1132 position: Point {
1133 x: Pixels(pt.x as _),
1134 y: Pixels(pt.y as _),
1135 },
1136 paths: crate::ExternalPaths(paths),
1137 });
1138 self.0.handle_drag_drop(input);
1139 } else {
1140 *pdweffect = DROPEFFECT_NONE;
1141 }
1142 }
1143 Ok(())
1144 }
1145
1146 fn DragOver(
1147 &self,
1148 _grfkeystate: MODIFIERKEYS_FLAGS,
1149 pt: &POINTL,
1150 _pdweffect: *mut DROPEFFECT,
1151 ) -> windows::core::Result<()> {
1152 let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending {
1153 position: Point {
1154 x: Pixels(pt.x as _),
1155 y: Pixels(pt.y as _),
1156 },
1157 });
1158 self.0.handle_drag_drop(input);
1159
1160 Ok(())
1161 }
1162
1163 fn DragLeave(&self) -> windows::core::Result<()> {
1164 let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited);
1165 self.0.handle_drag_drop(input);
1166
1167 Ok(())
1168 }
1169
1170 fn Drop(
1171 &self,
1172 _pdataobj: Option<&IDataObject>,
1173 _grfkeystate: MODIFIERKEYS_FLAGS,
1174 pt: &POINTL,
1175 _pdweffect: *mut DROPEFFECT,
1176 ) -> windows::core::Result<()> {
1177 let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit {
1178 position: Point {
1179 x: Pixels(pt.x as _),
1180 y: Pixels(pt.y as _),
1181 },
1182 });
1183 self.0.handle_drag_drop(input);
1184
1185 Ok(())
1186 }
1187}
1188
1189fn register_wnd_class() -> PCWSTR {
1190 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1191
1192 static ONCE: Once = Once::new();
1193 ONCE.call_once(|| {
1194 let wc = WNDCLASSW {
1195 lpfnWndProc: Some(wnd_proc),
1196 hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() },
1197 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1198 ..Default::default()
1199 };
1200 unsafe { RegisterClassW(&wc) };
1201 });
1202
1203 CLASS_NAME
1204}
1205
1206unsafe extern "system" fn wnd_proc(
1207 hwnd: HWND,
1208 msg: u32,
1209 wparam: WPARAM,
1210 lparam: LPARAM,
1211) -> LRESULT {
1212 if msg == WM_NCCREATE {
1213 let cs = lparam.0 as *const CREATESTRUCTW;
1214 let cs = unsafe { &*cs };
1215 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1216 let ctx = unsafe { &mut *ctx };
1217 let inner = Rc::new(WindowsWindowInner::new(
1218 hwnd,
1219 cs,
1220 ctx.platform_inner.clone(),
1221 ctx.handle,
1222 ));
1223 let weak = Box::new(Rc::downgrade(&inner));
1224 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1225 ctx.inner = Some(inner);
1226 return LRESULT(1);
1227 }
1228 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1229 if ptr.is_null() {
1230 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1231 }
1232 let inner = unsafe { &*ptr };
1233 let r = if let Some(inner) = inner.upgrade() {
1234 inner.handle_msg(msg, wparam, lparam)
1235 } else {
1236 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1237 };
1238 if msg == WM_NCDESTROY {
1239 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1240 unsafe { std::mem::drop(Box::from_raw(ptr)) };
1241 }
1242 r
1243}
1244
1245pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1246 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1247 if !ptr.is_null() {
1248 let inner = unsafe { &*ptr };
1249 inner.upgrade()
1250 } else {
1251 None
1252 }
1253}
1254
1255unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
1256 #[cfg(target_pointer_width = "64")]
1257 unsafe {
1258 GetWindowLongPtrW(hwnd, nindex)
1259 }
1260 #[cfg(target_pointer_width = "32")]
1261 unsafe {
1262 GetWindowLongW(hwnd, nindex) as isize
1263 }
1264}
1265
1266unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize {
1267 #[cfg(target_pointer_width = "64")]
1268 unsafe {
1269 SetWindowLongPtrW(hwnd, nindex, dwnewlong)
1270 }
1271 #[cfg(target_pointer_width = "32")]
1272 unsafe {
1273 SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize
1274 }
1275}
1276
1277fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1278 match code {
1279 // VK_0 - VK_9
1280 48..=57 => Some(Keystroke {
1281 modifiers,
1282 key: format!("{}", code - VK_0.0),
1283 ime_key: None,
1284 }),
1285 // VK_A - VK_Z
1286 65..=90 => Some(Keystroke {
1287 modifiers,
1288 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1289 ime_key: None,
1290 }),
1291 // VK_F1 - VK_F24
1292 112..=135 => Some(Keystroke {
1293 modifiers,
1294 key: format!("f{}", code - VK_F1.0 + 1),
1295 ime_key: None,
1296 }),
1297 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1298 _ => {
1299 if let Some(key) = oemkey_vkcode_to_string(code) {
1300 Some(Keystroke {
1301 modifiers,
1302 key,
1303 ime_key: None,
1304 })
1305 } else {
1306 None
1307 }
1308 }
1309 }
1310}
1311
1312fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1313 match code {
1314 186 => Some(";".to_string()), // VK_OEM_1
1315 187 => Some("=".to_string()), // VK_OEM_PLUS
1316 188 => Some(",".to_string()), // VK_OEM_COMMA
1317 189 => Some("-".to_string()), // VK_OEM_MINUS
1318 190 => Some(".".to_string()), // VK_OEM_PERIOD
1319 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1320 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1321 192 => Some("`".to_string()), // VK_OEM_3
1322 219 => Some("[".to_string()), // VK_OEM_4
1323 220 => Some("\\".to_string()), // VK_OEM_5
1324 221 => Some("]".to_string()), // VK_OEM_6
1325 222 => Some("'".to_string()), // VK_OEM_7
1326 _ => None,
1327 }
1328}
1329
1330// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1331const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;