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