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