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