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