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