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