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