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