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