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