1#![deny(unsafe_op_in_unsafe_fn)]
2
3use std::{
4 cell::RefCell,
5 num::NonZeroIsize,
6 path::PathBuf,
7 rc::{Rc, Weak},
8 str::FromStr,
9 sync::{Arc, Once},
10 time::{Duration, Instant},
11};
12
13use ::util::ResultExt;
14use anyhow::{Context as _, Result};
15use async_task::Runnable;
16use futures::channel::oneshot::{self, Receiver};
17use raw_window_handle as rwh;
18use smallvec::SmallVec;
19use windows::{
20 Win32::{
21 Foundation::*,
22 Graphics::Gdi::*,
23 System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
24 UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
25 },
26 core::*,
27};
28
29use crate::*;
30
31pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
32
33pub struct WindowsWindowState {
34 pub origin: Point<Pixels>,
35 pub logical_size: Size<Pixels>,
36 pub min_size: Option<Size<Pixels>>,
37 pub fullscreen_restore_bounds: Bounds<Pixels>,
38 pub border_offset: WindowBorderOffset,
39 pub appearance: WindowAppearance,
40 pub scale_factor: f32,
41 pub restore_from_minimized: Option<Box<dyn FnMut(RequestFrameOptions)>>,
42
43 pub callbacks: Callbacks,
44 pub input_handler: Option<PlatformInputHandler>,
45 pub pending_surrogate: Option<u16>,
46 pub last_reported_modifiers: Option<Modifiers>,
47 pub last_reported_capslock: Option<Capslock>,
48 pub system_key_handled: bool,
49 pub hovered: bool,
50
51 pub renderer: DirectXRenderer,
52
53 pub click_state: ClickState,
54 pub system_settings: WindowsSystemSettings,
55 pub current_cursor: Option<HCURSOR>,
56 pub nc_button_pressed: Option<u32>,
57
58 pub display: WindowsDisplay,
59 fullscreen: Option<StyleAndBounds>,
60 initial_placement: Option<WindowOpenStatus>,
61 hwnd: HWND,
62}
63
64pub(crate) struct WindowsWindowStatePtr {
65 hwnd: HWND,
66 this: Weak<Self>,
67 drop_target_helper: IDropTargetHelper,
68 pub(crate) state: RefCell<WindowsWindowState>,
69 pub(crate) handle: AnyWindowHandle,
70 pub(crate) hide_title_bar: bool,
71 pub(crate) is_movable: bool,
72 pub(crate) executor: ForegroundExecutor,
73 pub(crate) windows_version: WindowsVersion,
74 pub(crate) validation_number: usize,
75 pub(crate) main_receiver: flume::Receiver<Runnable>,
76 pub(crate) main_thread_id_win32: u32,
77}
78
79impl WindowsWindowState {
80 fn new(
81 hwnd: HWND,
82 cs: &CREATESTRUCTW,
83 current_cursor: Option<HCURSOR>,
84 display: WindowsDisplay,
85 min_size: Option<Size<Pixels>>,
86 appearance: WindowAppearance,
87 ) -> Result<Self> {
88 let scale_factor = {
89 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
90 monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
91 };
92 let origin = logical_point(cs.x as f32, cs.y as f32, scale_factor);
93 let logical_size = {
94 let physical_size = size(DevicePixels(cs.cx), DevicePixels(cs.cy));
95 physical_size.to_pixels(scale_factor)
96 };
97 let fullscreen_restore_bounds = Bounds {
98 origin,
99 size: logical_size,
100 };
101 let border_offset = WindowBorderOffset::default();
102 let restore_from_minimized = None;
103 let renderer = DirectXRenderer::new(hwnd)?;
104 let callbacks = Callbacks::default();
105 let input_handler = None;
106 let pending_surrogate = None;
107 let last_reported_modifiers = None;
108 let last_reported_capslock = None;
109 let system_key_handled = false;
110 let hovered = false;
111 let click_state = ClickState::new();
112 let system_settings = WindowsSystemSettings::new(display);
113 let nc_button_pressed = None;
114 let fullscreen = None;
115 let initial_placement = None;
116
117 Ok(Self {
118 origin,
119 logical_size,
120 fullscreen_restore_bounds,
121 border_offset,
122 appearance,
123 scale_factor,
124 restore_from_minimized,
125 min_size,
126 callbacks,
127 input_handler,
128 pending_surrogate,
129 last_reported_modifiers,
130 last_reported_capslock,
131 system_key_handled,
132 hovered,
133 renderer,
134 click_state,
135 system_settings,
136 current_cursor,
137 nc_button_pressed,
138 display,
139 fullscreen,
140 initial_placement,
141 hwnd,
142 })
143 }
144
145 #[inline]
146 pub(crate) fn is_fullscreen(&self) -> bool {
147 self.fullscreen.is_some()
148 }
149
150 pub(crate) fn is_maximized(&self) -> bool {
151 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
152 }
153
154 fn bounds(&self) -> Bounds<Pixels> {
155 Bounds {
156 origin: self.origin,
157 size: self.logical_size,
158 }
159 }
160
161 // Calculate the bounds used for saving and whether the window is maximized.
162 fn calculate_window_bounds(&self) -> (Bounds<Pixels>, bool) {
163 let placement = unsafe {
164 let mut placement = WINDOWPLACEMENT {
165 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
166 ..Default::default()
167 };
168 GetWindowPlacement(self.hwnd, &mut placement).log_err();
169 placement
170 };
171 (
172 calculate_client_rect(
173 placement.rcNormalPosition,
174 self.border_offset,
175 self.scale_factor,
176 ),
177 placement.showCmd == SW_SHOWMAXIMIZED.0 as u32,
178 )
179 }
180
181 fn window_bounds(&self) -> WindowBounds {
182 let (bounds, maximized) = self.calculate_window_bounds();
183
184 if self.is_fullscreen() {
185 WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
186 } else if maximized {
187 WindowBounds::Maximized(bounds)
188 } else {
189 WindowBounds::Windowed(bounds)
190 }
191 }
192
193 /// get the logical size of the app's drawable area.
194 ///
195 /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
196 /// whether the mouse collides with other elements of GPUI).
197 fn content_size(&self) -> Size<Pixels> {
198 self.logical_size
199 }
200}
201
202impl WindowsWindowStatePtr {
203 fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
204 let state = RefCell::new(WindowsWindowState::new(
205 hwnd,
206 cs,
207 context.current_cursor,
208 context.display,
209 context.min_size,
210 context.appearance,
211 )?);
212
213 Ok(Rc::new_cyclic(|this| Self {
214 hwnd,
215 this: this.clone(),
216 drop_target_helper: context.drop_target_helper.clone(),
217 state,
218 handle: context.handle,
219 hide_title_bar: context.hide_title_bar,
220 is_movable: context.is_movable,
221 executor: context.executor.clone(),
222 windows_version: context.windows_version,
223 validation_number: context.validation_number,
224 main_receiver: context.main_receiver.clone(),
225 main_thread_id_win32: context.main_thread_id_win32,
226 }))
227 }
228
229 fn toggle_fullscreen(&self) {
230 let Some(state_ptr) = self.this.upgrade() else {
231 log::error!("Unable to toggle fullscreen: window has been dropped");
232 return;
233 };
234 self.executor
235 .spawn(async move {
236 let mut lock = state_ptr.state.borrow_mut();
237 let StyleAndBounds {
238 style,
239 x,
240 y,
241 cx,
242 cy,
243 } = if let Some(state) = lock.fullscreen.take() {
244 state
245 } else {
246 let (window_bounds, _) = lock.calculate_window_bounds();
247 lock.fullscreen_restore_bounds = window_bounds;
248 let style =
249 WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
250 let mut rc = RECT::default();
251 unsafe { GetWindowRect(state_ptr.hwnd, &mut rc) }.log_err();
252 let _ = lock.fullscreen.insert(StyleAndBounds {
253 style,
254 x: rc.left,
255 y: rc.top,
256 cx: rc.right - rc.left,
257 cy: rc.bottom - rc.top,
258 });
259 let style = style
260 & !(WS_THICKFRAME
261 | WS_SYSMENU
262 | WS_MAXIMIZEBOX
263 | WS_MINIMIZEBOX
264 | WS_CAPTION);
265 let physical_bounds = lock.display.physical_bounds();
266 StyleAndBounds {
267 style,
268 x: physical_bounds.left().0,
269 y: physical_bounds.top().0,
270 cx: physical_bounds.size.width.0,
271 cy: physical_bounds.size.height.0,
272 }
273 };
274 drop(lock);
275 unsafe { set_window_long(state_ptr.hwnd, GWL_STYLE, style.0 as isize) };
276 unsafe {
277 SetWindowPos(
278 state_ptr.hwnd,
279 None,
280 x,
281 y,
282 cx,
283 cy,
284 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
285 )
286 }
287 .log_err();
288 })
289 .detach();
290 }
291
292 fn set_window_placement(&self) -> Result<()> {
293 let Some(open_status) = self.state.borrow_mut().initial_placement.take() else {
294 return Ok(());
295 };
296 match open_status.state {
297 WindowOpenState::Maximized => unsafe {
298 SetWindowPlacement(self.hwnd, &open_status.placement)?;
299 ShowWindowAsync(self.hwnd, SW_MAXIMIZE).ok()?;
300 },
301 WindowOpenState::Fullscreen => {
302 unsafe { SetWindowPlacement(self.hwnd, &open_status.placement)? };
303 self.toggle_fullscreen();
304 }
305 WindowOpenState::Windowed => unsafe {
306 SetWindowPlacement(self.hwnd, &open_status.placement)?;
307 },
308 }
309 Ok(())
310 }
311}
312
313#[derive(Default)]
314pub(crate) struct Callbacks {
315 pub(crate) request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
316 pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
317 pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
318 pub(crate) hovered_status_change: Option<Box<dyn FnMut(bool)>>,
319 pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
320 pub(crate) moved: Option<Box<dyn FnMut()>>,
321 pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
322 pub(crate) close: Option<Box<dyn FnOnce()>>,
323 pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
324 pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
325}
326
327struct WindowCreateContext {
328 inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
329 handle: AnyWindowHandle,
330 hide_title_bar: bool,
331 display: WindowsDisplay,
332 is_movable: bool,
333 min_size: Option<Size<Pixels>>,
334 executor: ForegroundExecutor,
335 current_cursor: Option<HCURSOR>,
336 windows_version: WindowsVersion,
337 drop_target_helper: IDropTargetHelper,
338 validation_number: usize,
339 main_receiver: flume::Receiver<Runnable>,
340 main_thread_id_win32: u32,
341 appearance: WindowAppearance,
342}
343
344impl WindowsWindow {
345 pub(crate) fn new(
346 handle: AnyWindowHandle,
347 params: WindowParams,
348 creation_info: WindowCreationInfo,
349 ) -> Result<Self> {
350 let WindowCreationInfo {
351 icon,
352 executor,
353 current_cursor,
354 windows_version,
355 drop_target_helper,
356 validation_number,
357 main_receiver,
358 main_thread_id_win32,
359 } = creation_info;
360 let classname = register_wnd_class(icon);
361 let hide_title_bar = params
362 .titlebar
363 .as_ref()
364 .map(|titlebar| titlebar.appears_transparent)
365 .unwrap_or(true);
366 let windowname = HSTRING::from(
367 params
368 .titlebar
369 .as_ref()
370 .and_then(|titlebar| titlebar.title.as_ref())
371 .map(|title| title.as_ref())
372 .unwrap_or(""),
373 );
374 let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
375 (
376 WS_EX_TOOLWINDOW | WS_EX_NOREDIRECTIONBITMAP,
377 WINDOW_STYLE(0x0),
378 )
379 } else {
380 (
381 WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP,
382 WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
383 )
384 };
385
386 let hinstance = get_module_handle();
387 let display = if let Some(display_id) = params.display_id {
388 // if we obtain a display_id, then this ID must be valid.
389 WindowsDisplay::new(display_id).unwrap()
390 } else {
391 WindowsDisplay::primary_monitor().unwrap()
392 };
393 let appearance = system_appearance().unwrap_or_default();
394 let mut context = WindowCreateContext {
395 inner: None,
396 handle,
397 hide_title_bar,
398 display,
399 is_movable: params.is_movable,
400 min_size: params.window_min_size,
401 executor,
402 current_cursor,
403 windows_version,
404 drop_target_helper,
405 validation_number,
406 main_receiver,
407 main_thread_id_win32,
408 appearance,
409 };
410 let lpparam = Some(&context as *const _ as *const _);
411 let creation_result = unsafe {
412 CreateWindowExW(
413 dwexstyle,
414 classname,
415 &windowname,
416 dwstyle,
417 CW_USEDEFAULT,
418 CW_USEDEFAULT,
419 CW_USEDEFAULT,
420 CW_USEDEFAULT,
421 None,
422 None,
423 Some(hinstance.into()),
424 lpparam,
425 )
426 };
427 // We should call `?` on state_ptr first, then call `?` on hwnd.
428 // Or, we will lose the error info reported by `WindowsWindowState::new`
429 let state_ptr = context.inner.take().unwrap()?;
430 let hwnd = creation_result?;
431 register_drag_drop(state_ptr.clone())?;
432 configure_dwm_dark_mode(hwnd, appearance);
433 state_ptr.state.borrow_mut().border_offset.update(hwnd)?;
434 let placement = retrieve_window_placement(
435 hwnd,
436 display,
437 params.bounds,
438 state_ptr.state.borrow().scale_factor,
439 state_ptr.state.borrow().border_offset,
440 )?;
441 if params.show {
442 unsafe { SetWindowPlacement(hwnd, &placement)? };
443 } else {
444 state_ptr.state.borrow_mut().initial_placement = Some(WindowOpenStatus {
445 placement,
446 state: WindowOpenState::Windowed,
447 });
448 }
449
450 Ok(Self(state_ptr))
451 }
452}
453
454impl rwh::HasWindowHandle for WindowsWindow {
455 fn window_handle(&self) -> std::result::Result<rwh::WindowHandle<'_>, rwh::HandleError> {
456 let raw = rwh::Win32WindowHandle::new(unsafe {
457 NonZeroIsize::new_unchecked(self.0.hwnd.0 as isize)
458 })
459 .into();
460 Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
461 }
462}
463
464// todo(windows)
465impl rwh::HasDisplayHandle for WindowsWindow {
466 fn display_handle(&self) -> std::result::Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
467 unimplemented!()
468 }
469}
470
471impl Drop for WindowsWindow {
472 fn drop(&mut self) {
473 // clone this `Rc` to prevent early release of the pointer
474 let this = self.0.clone();
475 self.0
476 .executor
477 .spawn(async move {
478 let handle = this.hwnd;
479 unsafe {
480 RevokeDragDrop(handle).log_err();
481 DestroyWindow(handle).log_err();
482 }
483 })
484 .detach();
485 }
486}
487
488impl PlatformWindow for WindowsWindow {
489 fn bounds(&self) -> Bounds<Pixels> {
490 self.0.state.borrow().bounds()
491 }
492
493 fn is_maximized(&self) -> bool {
494 self.0.state.borrow().is_maximized()
495 }
496
497 fn window_bounds(&self) -> WindowBounds {
498 self.0.state.borrow().window_bounds()
499 }
500
501 /// get the logical size of the app's drawable area.
502 ///
503 /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
504 /// whether the mouse collides with other elements of GPUI).
505 fn content_size(&self) -> Size<Pixels> {
506 self.0.state.borrow().content_size()
507 }
508
509 fn resize(&mut self, size: Size<Pixels>) {
510 let hwnd = self.0.hwnd;
511 let bounds =
512 crate::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor());
513 let rect = calculate_window_rect(bounds, self.0.state.borrow().border_offset);
514
515 self.0
516 .executor
517 .spawn(async move {
518 unsafe {
519 SetWindowPos(
520 hwnd,
521 None,
522 bounds.origin.x.0,
523 bounds.origin.y.0,
524 rect.right - rect.left,
525 rect.bottom - rect.top,
526 SWP_NOMOVE,
527 )
528 .context("unable to set window content size")
529 .log_err();
530 }
531 })
532 .detach();
533 }
534
535 fn scale_factor(&self) -> f32 {
536 self.0.state.borrow().scale_factor
537 }
538
539 fn appearance(&self) -> WindowAppearance {
540 self.0.state.borrow().appearance
541 }
542
543 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
544 Some(Rc::new(self.0.state.borrow().display))
545 }
546
547 fn mouse_position(&self) -> Point<Pixels> {
548 let scale_factor = self.scale_factor();
549 let point = unsafe {
550 let mut point: POINT = std::mem::zeroed();
551 GetCursorPos(&mut point)
552 .context("unable to get cursor position")
553 .log_err();
554 ScreenToClient(self.0.hwnd, &mut point).ok().log_err();
555 point
556 };
557 logical_point(point.x as f32, point.y as f32, scale_factor)
558 }
559
560 fn modifiers(&self) -> Modifiers {
561 current_modifiers()
562 }
563
564 fn capslock(&self) -> Capslock {
565 current_capslock()
566 }
567
568 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
569 self.0.state.borrow_mut().input_handler = Some(input_handler);
570 }
571
572 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
573 self.0.state.borrow_mut().input_handler.take()
574 }
575
576 fn prompt(
577 &self,
578 level: PromptLevel,
579 msg: &str,
580 detail: Option<&str>,
581 answers: &[PromptButton],
582 ) -> Option<Receiver<usize>> {
583 let (done_tx, done_rx) = oneshot::channel();
584 let msg = msg.to_string();
585 let detail_string = match detail {
586 Some(info) => Some(info.to_string()),
587 None => None,
588 };
589 let handle = self.0.hwnd;
590 let answers = answers.to_vec();
591 self.0
592 .executor
593 .spawn(async move {
594 unsafe {
595 let mut config = TASKDIALOGCONFIG::default();
596 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
597 config.hwndParent = handle;
598 let title;
599 let main_icon;
600 match level {
601 crate::PromptLevel::Info => {
602 title = windows::core::w!("Info");
603 main_icon = TD_INFORMATION_ICON;
604 }
605 crate::PromptLevel::Warning => {
606 title = windows::core::w!("Warning");
607 main_icon = TD_WARNING_ICON;
608 }
609 crate::PromptLevel::Critical => {
610 title = windows::core::w!("Critical");
611 main_icon = TD_ERROR_ICON;
612 }
613 };
614 config.pszWindowTitle = title;
615 config.Anonymous1.pszMainIcon = main_icon;
616 let instruction = HSTRING::from(msg);
617 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
618 let hints_encoded;
619 if let Some(ref hints) = detail_string {
620 hints_encoded = HSTRING::from(hints);
621 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
622 };
623 let mut button_id_map = Vec::with_capacity(answers.len());
624 let mut buttons = Vec::new();
625 let mut btn_encoded = Vec::new();
626 for (index, btn) in answers.iter().enumerate() {
627 let encoded = HSTRING::from(btn.label().as_ref());
628 let button_id = if btn.is_cancel() {
629 IDCANCEL.0
630 } else {
631 index as i32 - 100
632 };
633 button_id_map.push(button_id);
634 buttons.push(TASKDIALOG_BUTTON {
635 nButtonID: button_id,
636 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
637 });
638 btn_encoded.push(encoded);
639 }
640 config.cButtons = buttons.len() as _;
641 config.pButtons = buttons.as_ptr();
642
643 config.pfCallback = None;
644 let mut res = std::mem::zeroed();
645 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
646 .context("unable to create task dialog")
647 .log_err();
648
649 let clicked = button_id_map
650 .iter()
651 .position(|&button_id| button_id == res)
652 .unwrap();
653 let _ = done_tx.send(clicked);
654 }
655 })
656 .detach();
657
658 Some(done_rx)
659 }
660
661 fn activate(&self) {
662 let hwnd = self.0.hwnd;
663 let this = self.0.clone();
664 self.0
665 .executor
666 .spawn(async move {
667 this.set_window_placement().log_err();
668 unsafe { SetActiveWindow(hwnd).log_err() };
669 unsafe { SetFocus(Some(hwnd)).log_err() };
670 // todo(windows)
671 // crate `windows 0.56` reports true as Err
672 unsafe { SetForegroundWindow(hwnd).as_bool() };
673 })
674 .detach();
675 }
676
677 fn is_active(&self) -> bool {
678 self.0.hwnd == unsafe { GetActiveWindow() }
679 }
680
681 fn is_hovered(&self) -> bool {
682 self.0.state.borrow().hovered
683 }
684
685 fn set_title(&mut self, title: &str) {
686 unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
687 .inspect_err(|e| log::error!("Set title failed: {e}"))
688 .ok();
689 }
690
691 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
692 let hwnd = self.0.hwnd;
693
694 match background_appearance {
695 WindowBackgroundAppearance::Opaque => {
696 // ACCENT_DISABLED
697 set_window_composition_attribute(hwnd, None, 0);
698 }
699 WindowBackgroundAppearance::Transparent => {
700 // Use ACCENT_ENABLE_TRANSPARENTGRADIENT for transparent background
701 set_window_composition_attribute(hwnd, None, 2);
702 }
703 WindowBackgroundAppearance::Blurred => {
704 // Enable acrylic blur
705 // ACCENT_ENABLE_ACRYLICBLURBEHIND
706 set_window_composition_attribute(hwnd, Some((0, 0, 0, 0)), 4);
707 }
708 }
709 }
710
711 fn minimize(&self) {
712 unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() };
713 }
714
715 fn zoom(&self) {
716 unsafe {
717 if IsWindowVisible(self.0.hwnd).as_bool() {
718 ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE).ok().log_err();
719 } else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
720 status.state = WindowOpenState::Maximized;
721 }
722 }
723 }
724
725 fn toggle_fullscreen(&self) {
726 if unsafe { IsWindowVisible(self.0.hwnd).as_bool() } {
727 self.0.toggle_fullscreen();
728 } else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
729 status.state = WindowOpenState::Fullscreen;
730 }
731 }
732
733 fn is_fullscreen(&self) -> bool {
734 self.0.state.borrow().is_fullscreen()
735 }
736
737 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
738 self.0.state.borrow_mut().callbacks.request_frame = Some(callback);
739 }
740
741 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
742 self.0.state.borrow_mut().callbacks.input = Some(callback);
743 }
744
745 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
746 self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
747 }
748
749 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
750 self.0.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
751 }
752
753 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
754 self.0.state.borrow_mut().callbacks.resize = Some(callback);
755 }
756
757 fn on_moved(&self, callback: Box<dyn FnMut()>) {
758 self.0.state.borrow_mut().callbacks.moved = Some(callback);
759 }
760
761 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
762 self.0.state.borrow_mut().callbacks.should_close = Some(callback);
763 }
764
765 fn on_close(&self, callback: Box<dyn FnOnce()>) {
766 self.0.state.borrow_mut().callbacks.close = Some(callback);
767 }
768
769 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
770 self.0.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
771 }
772
773 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
774 self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
775 }
776
777 fn draw(&self, scene: &Scene) {
778 self.0.state.borrow_mut().renderer.draw(scene).log_err();
779 }
780
781 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
782 self.0.state.borrow().renderer.sprite_atlas()
783 }
784
785 fn get_raw_handle(&self) -> HWND {
786 self.0.hwnd
787 }
788
789 fn gpu_specs(&self) -> Option<GpuSpecs> {
790 self.0.state.borrow().renderer.gpu_specs().log_err()
791 }
792
793 fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
794 // There is no such thing on Windows.
795 }
796}
797
798#[implement(IDropTarget)]
799struct WindowsDragDropHandler(pub Rc<WindowsWindowStatePtr>);
800
801impl WindowsDragDropHandler {
802 fn handle_drag_drop(&self, input: PlatformInput) {
803 let mut lock = self.0.state.borrow_mut();
804 if let Some(mut func) = lock.callbacks.input.take() {
805 drop(lock);
806 func(input);
807 self.0.state.borrow_mut().callbacks.input = Some(func);
808 }
809 }
810}
811
812#[allow(non_snake_case)]
813impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
814 fn DragEnter(
815 &self,
816 pdataobj: windows::core::Ref<IDataObject>,
817 _grfkeystate: MODIFIERKEYS_FLAGS,
818 pt: &POINTL,
819 pdweffect: *mut DROPEFFECT,
820 ) -> windows::core::Result<()> {
821 unsafe {
822 let idata_obj = pdataobj.ok()?;
823 let config = FORMATETC {
824 cfFormat: CF_HDROP.0,
825 ptd: std::ptr::null_mut() as _,
826 dwAspect: DVASPECT_CONTENT.0,
827 lindex: -1,
828 tymed: TYMED_HGLOBAL.0 as _,
829 };
830 let cursor_position = POINT { x: pt.x, y: pt.y };
831 if idata_obj.QueryGetData(&config as _) == S_OK {
832 *pdweffect = DROPEFFECT_COPY;
833 let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
834 return Ok(());
835 };
836 if idata.u.hGlobal.is_invalid() {
837 return Ok(());
838 }
839 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
840 let mut paths = SmallVec::<[PathBuf; 2]>::new();
841 with_file_names(*hdrop, |file_name| {
842 if let Some(path) = PathBuf::from_str(&file_name).log_err() {
843 paths.push(path);
844 }
845 });
846 ReleaseStgMedium(&mut idata);
847 let mut cursor_position = cursor_position;
848 ScreenToClient(self.0.hwnd, &mut cursor_position)
849 .ok()
850 .log_err();
851 let scale_factor = self.0.state.borrow().scale_factor;
852 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
853 position: logical_point(
854 cursor_position.x as f32,
855 cursor_position.y as f32,
856 scale_factor,
857 ),
858 paths: ExternalPaths(paths),
859 });
860 self.handle_drag_drop(input);
861 } else {
862 *pdweffect = DROPEFFECT_NONE;
863 }
864 self.0
865 .drop_target_helper
866 .DragEnter(self.0.hwnd, idata_obj, &cursor_position, *pdweffect)
867 .log_err();
868 }
869 Ok(())
870 }
871
872 fn DragOver(
873 &self,
874 _grfkeystate: MODIFIERKEYS_FLAGS,
875 pt: &POINTL,
876 pdweffect: *mut DROPEFFECT,
877 ) -> windows::core::Result<()> {
878 let mut cursor_position = POINT { x: pt.x, y: pt.y };
879 unsafe {
880 *pdweffect = DROPEFFECT_COPY;
881 self.0
882 .drop_target_helper
883 .DragOver(&cursor_position, *pdweffect)
884 .log_err();
885 ScreenToClient(self.0.hwnd, &mut cursor_position)
886 .ok()
887 .log_err();
888 }
889 let scale_factor = self.0.state.borrow().scale_factor;
890 let input = PlatformInput::FileDrop(FileDropEvent::Pending {
891 position: logical_point(
892 cursor_position.x as f32,
893 cursor_position.y as f32,
894 scale_factor,
895 ),
896 });
897 self.handle_drag_drop(input);
898
899 Ok(())
900 }
901
902 fn DragLeave(&self) -> windows::core::Result<()> {
903 unsafe {
904 self.0.drop_target_helper.DragLeave().log_err();
905 }
906 let input = PlatformInput::FileDrop(FileDropEvent::Exited);
907 self.handle_drag_drop(input);
908
909 Ok(())
910 }
911
912 fn Drop(
913 &self,
914 pdataobj: windows::core::Ref<IDataObject>,
915 _grfkeystate: MODIFIERKEYS_FLAGS,
916 pt: &POINTL,
917 pdweffect: *mut DROPEFFECT,
918 ) -> windows::core::Result<()> {
919 let idata_obj = pdataobj.ok()?;
920 let mut cursor_position = POINT { x: pt.x, y: pt.y };
921 unsafe {
922 *pdweffect = DROPEFFECT_COPY;
923 self.0
924 .drop_target_helper
925 .Drop(idata_obj, &cursor_position, *pdweffect)
926 .log_err();
927 ScreenToClient(self.0.hwnd, &mut cursor_position)
928 .ok()
929 .log_err();
930 }
931 let scale_factor = self.0.state.borrow().scale_factor;
932 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
933 position: logical_point(
934 cursor_position.x as f32,
935 cursor_position.y as f32,
936 scale_factor,
937 ),
938 });
939 self.handle_drag_drop(input);
940
941 Ok(())
942 }
943}
944
945#[derive(Debug, Clone, Copy)]
946pub(crate) struct ClickState {
947 button: MouseButton,
948 last_click: Instant,
949 last_position: Point<DevicePixels>,
950 double_click_spatial_tolerance_width: i32,
951 double_click_spatial_tolerance_height: i32,
952 double_click_interval: Duration,
953 pub(crate) current_count: usize,
954}
955
956impl ClickState {
957 pub fn new() -> Self {
958 let double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
959 let double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
960 let double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
961
962 ClickState {
963 button: MouseButton::Left,
964 last_click: Instant::now(),
965 last_position: Point::default(),
966 double_click_spatial_tolerance_width,
967 double_click_spatial_tolerance_height,
968 double_click_interval,
969 current_count: 0,
970 }
971 }
972
973 /// update self and return the needed click count
974 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
975 if self.button == button && self.is_double_click(new_position) {
976 self.current_count += 1;
977 } else {
978 self.current_count = 1;
979 }
980 self.last_click = Instant::now();
981 self.last_position = new_position;
982 self.button = button;
983
984 self.current_count
985 }
986
987 pub fn system_update(&mut self, wparam: usize) {
988 match wparam {
989 // SPI_SETDOUBLECLKWIDTH
990 29 => {
991 self.double_click_spatial_tolerance_width =
992 unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }
993 }
994 // SPI_SETDOUBLECLKHEIGHT
995 30 => {
996 self.double_click_spatial_tolerance_height =
997 unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }
998 }
999 // SPI_SETDOUBLECLICKTIME
1000 32 => {
1001 self.double_click_interval =
1002 Duration::from_millis(unsafe { GetDoubleClickTime() } as u64)
1003 }
1004 _ => {}
1005 }
1006 }
1007
1008 #[inline]
1009 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1010 let diff = self.last_position - new_position;
1011
1012 self.last_click.elapsed() < self.double_click_interval
1013 && diff.x.0.abs() <= self.double_click_spatial_tolerance_width
1014 && diff.y.0.abs() <= self.double_click_spatial_tolerance_height
1015 }
1016}
1017
1018struct StyleAndBounds {
1019 style: WINDOW_STYLE,
1020 x: i32,
1021 y: i32,
1022 cx: i32,
1023 cy: i32,
1024}
1025
1026#[repr(C)]
1027struct WINDOWCOMPOSITIONATTRIBDATA {
1028 attrib: u32,
1029 pv_data: *mut std::ffi::c_void,
1030 cb_data: usize,
1031}
1032
1033#[repr(C)]
1034struct AccentPolicy {
1035 accent_state: u32,
1036 accent_flags: u32,
1037 gradient_color: u32,
1038 animation_id: u32,
1039}
1040
1041type Color = (u8, u8, u8, u8);
1042
1043#[derive(Debug, Default, Clone, Copy)]
1044pub(crate) struct WindowBorderOffset {
1045 pub(crate) width_offset: i32,
1046 pub(crate) height_offset: i32,
1047}
1048
1049impl WindowBorderOffset {
1050 pub(crate) fn update(&mut self, hwnd: HWND) -> anyhow::Result<()> {
1051 let window_rect = unsafe {
1052 let mut rect = std::mem::zeroed();
1053 GetWindowRect(hwnd, &mut rect)?;
1054 rect
1055 };
1056 let client_rect = unsafe {
1057 let mut rect = std::mem::zeroed();
1058 GetClientRect(hwnd, &mut rect)?;
1059 rect
1060 };
1061 self.width_offset =
1062 (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
1063 self.height_offset =
1064 (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
1065 Ok(())
1066 }
1067}
1068
1069struct WindowOpenStatus {
1070 placement: WINDOWPLACEMENT,
1071 state: WindowOpenState,
1072}
1073
1074enum WindowOpenState {
1075 Maximized,
1076 Fullscreen,
1077 Windowed,
1078}
1079
1080fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1081 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1082
1083 static ONCE: Once = Once::new();
1084 ONCE.call_once(|| {
1085 let wc = WNDCLASSW {
1086 lpfnWndProc: Some(wnd_proc),
1087 hIcon: icon_handle,
1088 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1089 style: CS_HREDRAW | CS_VREDRAW,
1090 hInstance: get_module_handle().into(),
1091 hbrBackground: unsafe { CreateSolidBrush(COLORREF(0x00000000)) },
1092 ..Default::default()
1093 };
1094 unsafe { RegisterClassW(&wc) };
1095 });
1096
1097 CLASS_NAME
1098}
1099
1100unsafe extern "system" fn wnd_proc(
1101 hwnd: HWND,
1102 msg: u32,
1103 wparam: WPARAM,
1104 lparam: LPARAM,
1105) -> LRESULT {
1106 if msg == WM_NCCREATE {
1107 let cs = lparam.0 as *const CREATESTRUCTW;
1108 let cs = unsafe { &*cs };
1109 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1110 let ctx = unsafe { &mut *ctx };
1111 let creation_result = WindowsWindowStatePtr::new(ctx, hwnd, cs);
1112 if creation_result.is_err() {
1113 ctx.inner = Some(creation_result);
1114 return LRESULT(0);
1115 }
1116 let weak = Box::new(Rc::downgrade(creation_result.as_ref().unwrap()));
1117 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1118 ctx.inner = Some(creation_result);
1119 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1120 }
1121 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
1122 if ptr.is_null() {
1123 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1124 }
1125 let inner = unsafe { &*ptr };
1126 let r = if let Some(state) = inner.upgrade() {
1127 handle_msg(hwnd, msg, wparam, lparam, state)
1128 } else {
1129 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1130 };
1131 if msg == WM_NCDESTROY {
1132 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1133 unsafe { drop(Box::from_raw(ptr)) };
1134 }
1135 r
1136}
1137
1138pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
1139 if hwnd.is_invalid() {
1140 return None;
1141 }
1142
1143 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
1144 if !ptr.is_null() {
1145 let inner = unsafe { &*ptr };
1146 inner.upgrade()
1147 } else {
1148 None
1149 }
1150}
1151
1152fn get_module_handle() -> HMODULE {
1153 unsafe {
1154 let mut h_module = std::mem::zeroed();
1155 GetModuleHandleExW(
1156 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1157 windows::core::w!("ZedModule"),
1158 &mut h_module,
1159 )
1160 .expect("Unable to get module handle"); // this should never fail
1161
1162 h_module
1163 }
1164}
1165
1166fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) -> Result<()> {
1167 let window_handle = state_ptr.hwnd;
1168 let handler = WindowsDragDropHandler(state_ptr);
1169 // The lifetime of `IDropTarget` is handled by Windows, it won't release until
1170 // we call `RevokeDragDrop`.
1171 // So, it's safe to drop it here.
1172 let drag_drop_handler: IDropTarget = handler.into();
1173 unsafe {
1174 RegisterDragDrop(window_handle, &drag_drop_handler)
1175 .context("unable to register drag-drop event")?;
1176 }
1177 Ok(())
1178}
1179
1180fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
1181 // NOTE:
1182 // The reason we're not using `AdjustWindowRectEx()` here is
1183 // that the size reported by this function is incorrect.
1184 // You can test it, and there are similar discussions online.
1185 // See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi
1186 //
1187 // So we manually calculate these values here.
1188 let mut rect = RECT {
1189 left: bounds.left().0,
1190 top: bounds.top().0,
1191 right: bounds.right().0,
1192 bottom: bounds.bottom().0,
1193 };
1194 let left_offset = border_offset.width_offset / 2;
1195 let top_offset = border_offset.height_offset / 2;
1196 let right_offset = border_offset.width_offset - left_offset;
1197 let bottom_offset = border_offset.height_offset - top_offset;
1198 rect.left -= left_offset;
1199 rect.top -= top_offset;
1200 rect.right += right_offset;
1201 rect.bottom += bottom_offset;
1202 rect
1203}
1204
1205fn calculate_client_rect(
1206 rect: RECT,
1207 border_offset: WindowBorderOffset,
1208 scale_factor: f32,
1209) -> Bounds<Pixels> {
1210 let left_offset = border_offset.width_offset / 2;
1211 let top_offset = border_offset.height_offset / 2;
1212 let right_offset = border_offset.width_offset - left_offset;
1213 let bottom_offset = border_offset.height_offset - top_offset;
1214 let left = rect.left + left_offset;
1215 let top = rect.top + top_offset;
1216 let right = rect.right - right_offset;
1217 let bottom = rect.bottom - bottom_offset;
1218 let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top));
1219 Bounds {
1220 origin: logical_point(left as f32, top as f32, scale_factor),
1221 size: physical_size.to_pixels(scale_factor),
1222 }
1223}
1224
1225fn retrieve_window_placement(
1226 hwnd: HWND,
1227 display: WindowsDisplay,
1228 initial_bounds: Bounds<Pixels>,
1229 scale_factor: f32,
1230 border_offset: WindowBorderOffset,
1231) -> Result<WINDOWPLACEMENT> {
1232 let mut placement = WINDOWPLACEMENT {
1233 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
1234 ..Default::default()
1235 };
1236 unsafe { GetWindowPlacement(hwnd, &mut placement)? };
1237 // the bounds may be not inside the display
1238 let bounds = if display.check_given_bounds(initial_bounds) {
1239 initial_bounds
1240 } else {
1241 display.default_bounds()
1242 };
1243 let bounds = bounds.to_device_pixels(scale_factor);
1244 placement.rcNormalPosition = calculate_window_rect(bounds, border_offset);
1245 Ok(placement)
1246}
1247
1248fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32) {
1249 let mut version = unsafe { std::mem::zeroed() };
1250 let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut version) };
1251 if !status.is_ok() || version.dwBuildNumber < 17763 {
1252 return;
1253 }
1254
1255 unsafe {
1256 type SetWindowCompositionAttributeType =
1257 unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
1258 let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8);
1259 if let Some(user32) = GetModuleHandleA(module_name)
1260 .context("Unable to get user32.dll handle")
1261 .log_err()
1262 {
1263 let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8);
1264 let set_window_composition_attribute: SetWindowCompositionAttributeType =
1265 std::mem::transmute(GetProcAddress(user32, func_name));
1266 let mut color = color.unwrap_or_default();
1267 let is_acrylic = state == 4;
1268 if is_acrylic && color.3 == 0 {
1269 color.3 = 1;
1270 }
1271 let accent = AccentPolicy {
1272 accent_state: state,
1273 accent_flags: if is_acrylic { 0 } else { 2 },
1274 gradient_color: (color.0 as u32)
1275 | ((color.1 as u32) << 8)
1276 | ((color.2 as u32) << 16)
1277 | ((color.3 as u32) << 24),
1278 animation_id: 0,
1279 };
1280 let mut data = WINDOWCOMPOSITIONATTRIBDATA {
1281 attrib: 0x13,
1282 pv_data: &accent as *const _ as *mut _,
1283 cb_data: std::mem::size_of::<AccentPolicy>(),
1284 };
1285 let _ = set_window_composition_attribute(hwnd, &mut data as *mut _ as _);
1286 }
1287 }
1288}
1289
1290#[cfg(test)]
1291mod tests {
1292 use super::ClickState;
1293 use crate::{DevicePixels, MouseButton, point};
1294 use std::time::Duration;
1295
1296 #[test]
1297 fn test_double_click_interval() {
1298 let mut state = ClickState::new();
1299 assert_eq!(
1300 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1301 1
1302 );
1303 assert_eq!(
1304 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1305 1
1306 );
1307 assert_eq!(
1308 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1309 1
1310 );
1311 assert_eq!(
1312 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1313 2
1314 );
1315 state.last_click -= Duration::from_millis(700);
1316 assert_eq!(
1317 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1318 1
1319 );
1320 }
1321
1322 #[test]
1323 fn test_double_click_spatial_tolerance() {
1324 let mut state = ClickState::new();
1325 assert_eq!(
1326 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1327 1
1328 );
1329 assert_eq!(
1330 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1331 2
1332 );
1333 assert_eq!(
1334 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1335 1
1336 );
1337 assert_eq!(
1338 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1339 1
1340 );
1341 }
1342}