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;
15use futures::channel::oneshot::{self, Receiver};
16use itertools::Itertools;
17use raw_window_handle as rwh;
18use smallvec::SmallVec;
19use windows::{
20 core::*,
21 Win32::{
22 Foundation::*,
23 Graphics::Gdi::*,
24 System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
25 UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
26 },
27};
28
29use crate::platform::blade::BladeRenderer;
30use crate::*;
31
32pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
33
34pub struct WindowsWindowState {
35 pub origin: Point<DevicePixels>,
36 pub physical_size: Size<DevicePixels>,
37 pub fullscreen_restore_bounds: Bounds<DevicePixels>,
38 pub scale_factor: f32,
39
40 pub callbacks: Callbacks,
41 pub input_handler: Option<PlatformInputHandler>,
42
43 pub renderer: BladeRenderer,
44
45 pub click_state: ClickState,
46 pub system_settings: WindowsSystemSettings,
47 pub current_cursor: HCURSOR,
48
49 pub display: WindowsDisplay,
50 fullscreen: Option<StyleAndBounds>,
51 hwnd: HWND,
52}
53
54pub(crate) struct WindowsWindowStatePtr {
55 hwnd: HWND,
56 pub(crate) state: RefCell<WindowsWindowState>,
57 pub(crate) handle: AnyWindowHandle,
58 pub(crate) hide_title_bar: bool,
59 pub(crate) executor: ForegroundExecutor,
60}
61
62impl WindowsWindowState {
63 fn new(
64 hwnd: HWND,
65 transparent: bool,
66 cs: &CREATESTRUCTW,
67 current_cursor: HCURSOR,
68 display: WindowsDisplay,
69 ) -> Self {
70 let origin = point(cs.x.into(), cs.y.into());
71 let physical_size = size(cs.cx.into(), cs.cy.into());
72 let fullscreen_restore_bounds = Bounds {
73 origin,
74 size: physical_size,
75 };
76 let scale_factor = {
77 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
78 monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
79 };
80 let renderer = windows_renderer::windows_renderer(hwnd, transparent);
81 let callbacks = Callbacks::default();
82 let input_handler = None;
83 let click_state = ClickState::new();
84 let system_settings = WindowsSystemSettings::new();
85 let fullscreen = None;
86
87 Self {
88 origin,
89 physical_size,
90 fullscreen_restore_bounds,
91 scale_factor,
92 callbacks,
93 input_handler,
94 renderer,
95 click_state,
96 system_settings,
97 current_cursor,
98 display,
99 fullscreen,
100 hwnd,
101 }
102 }
103
104 #[inline]
105 pub(crate) fn is_fullscreen(&self) -> bool {
106 self.fullscreen.is_some()
107 }
108
109 pub(crate) fn is_maximized(&self) -> bool {
110 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
111 }
112
113 fn bounds(&self) -> Bounds<DevicePixels> {
114 Bounds {
115 origin: self.origin,
116 size: self.physical_size,
117 }
118 }
119
120 fn window_bounds(&self) -> WindowBounds {
121 let placement = unsafe {
122 let mut placement = WINDOWPLACEMENT {
123 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
124 ..Default::default()
125 };
126 GetWindowPlacement(self.hwnd, &mut placement).log_err();
127 placement
128 };
129 let bounds = Bounds {
130 origin: point(
131 DevicePixels(placement.rcNormalPosition.left),
132 DevicePixels(placement.rcNormalPosition.top),
133 ),
134 size: size(
135 DevicePixels(placement.rcNormalPosition.right - placement.rcNormalPosition.left),
136 DevicePixels(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top),
137 ),
138 };
139
140 if self.is_fullscreen() {
141 WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
142 } else if placement.showCmd == SW_SHOWMAXIMIZED.0 as u32 {
143 WindowBounds::Maximized(bounds)
144 } else {
145 WindowBounds::Windowed(bounds)
146 }
147 }
148
149 /// get the logical size of the app's drawable area.
150 ///
151 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
152 /// whether the mouse collides with other elements of GPUI).
153 fn content_size(&self) -> Size<Pixels> {
154 logical_size(self.physical_size, self.scale_factor)
155 }
156
157 fn title_bar_padding(&self) -> Pixels {
158 // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
159 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
160 px(padding as f32)
161 }
162
163 fn title_bar_top_offset(&self) -> Pixels {
164 if self.is_maximized() {
165 self.title_bar_padding() * 2
166 } else {
167 px(0.)
168 }
169 }
170
171 fn title_bar_height(&self) -> Pixels {
172 // todo(windows) this is hard set to match the ui title bar
173 // in the future the ui title bar component will report the size
174 px(32.) + self.title_bar_top_offset()
175 }
176
177 pub(crate) fn caption_button_width(&self) -> Pixels {
178 // todo(windows) this is hard set to match the ui title bar
179 // in the future the ui title bar component will report the size
180 px(36.)
181 }
182
183 pub(crate) fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
184 let height = self.title_bar_height();
185 let mut rect = RECT::default();
186 unsafe { GetClientRect(self.hwnd, &mut rect) }?;
187 rect.bottom = rect.top + ((height.0 * self.scale_factor).round() as i32);
188 Ok(rect)
189 }
190}
191
192impl WindowsWindowStatePtr {
193 fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Rc<Self> {
194 let state = RefCell::new(WindowsWindowState::new(
195 hwnd,
196 context.transparent,
197 cs,
198 context.current_cursor,
199 context.display,
200 ));
201
202 Rc::new(Self {
203 state,
204 hwnd,
205 handle: context.handle,
206 hide_title_bar: context.hide_title_bar,
207 executor: context.executor.clone(),
208 })
209 }
210}
211
212#[derive(Default)]
213pub(crate) struct Callbacks {
214 pub(crate) request_frame: Option<Box<dyn FnMut()>>,
215 pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
216 pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
217 pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
218 pub(crate) moved: Option<Box<dyn FnMut()>>,
219 pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
220 pub(crate) close: Option<Box<dyn FnOnce()>>,
221 pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
222}
223
224struct WindowCreateContext {
225 inner: Option<Rc<WindowsWindowStatePtr>>,
226 handle: AnyWindowHandle,
227 hide_title_bar: bool,
228 display: WindowsDisplay,
229 transparent: bool,
230 executor: ForegroundExecutor,
231 current_cursor: HCURSOR,
232}
233
234impl WindowsWindow {
235 pub(crate) fn new(
236 handle: AnyWindowHandle,
237 params: WindowParams,
238 icon: HICON,
239 executor: ForegroundExecutor,
240 current_cursor: HCURSOR,
241 ) -> Self {
242 let classname = register_wnd_class(icon);
243 let hide_title_bar = params
244 .titlebar
245 .as_ref()
246 .map(|titlebar| titlebar.appears_transparent)
247 .unwrap_or(false);
248 let windowname = HSTRING::from(
249 params
250 .titlebar
251 .as_ref()
252 .and_then(|titlebar| titlebar.title.as_ref())
253 .map(|title| title.as_ref())
254 .unwrap_or(""),
255 );
256 let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
257 let hinstance = get_module_handle();
258 let display = if let Some(display_id) = params.display_id {
259 // if we obtain a display_id, then this ID must be valid.
260 WindowsDisplay::new(display_id).unwrap()
261 } else {
262 WindowsDisplay::primary_monitor().unwrap()
263 };
264 let mut context = WindowCreateContext {
265 inner: None,
266 handle,
267 hide_title_bar,
268 display,
269 transparent: params.window_background != WindowBackgroundAppearance::Opaque,
270 executor,
271 current_cursor,
272 };
273 let lpparam = Some(&context as *const _ as *const _);
274 let raw_hwnd = unsafe {
275 CreateWindowExW(
276 WS_EX_APPWINDOW,
277 classname,
278 &windowname,
279 dwstyle,
280 CW_USEDEFAULT,
281 CW_USEDEFAULT,
282 CW_USEDEFAULT,
283 CW_USEDEFAULT,
284 None,
285 None,
286 hinstance,
287 lpparam,
288 )
289 };
290 let state_ptr = Rc::clone(context.inner.as_ref().unwrap());
291 register_drag_drop(state_ptr.clone());
292 let wnd = Self(state_ptr);
293
294 unsafe {
295 let mut placement = WINDOWPLACEMENT {
296 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
297 ..Default::default()
298 };
299 GetWindowPlacement(raw_hwnd, &mut placement).log_err();
300 // the bounds may be not inside the display
301 let bounds = if display.check_given_bounds(params.bounds) {
302 params.bounds
303 } else {
304 display.default_bounds()
305 };
306 placement.rcNormalPosition.left = bounds.left().0;
307 placement.rcNormalPosition.right = bounds.right().0;
308 placement.rcNormalPosition.top = bounds.top().0;
309 placement.rcNormalPosition.bottom = bounds.bottom().0;
310 SetWindowPlacement(raw_hwnd, &placement).log_err();
311 }
312 unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok().log_err() };
313
314 wnd
315 }
316}
317
318impl rwh::HasWindowHandle for WindowsWindow {
319 fn window_handle(&self) -> std::result::Result<rwh::WindowHandle<'_>, rwh::HandleError> {
320 let raw =
321 rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.0.hwnd.0) })
322 .into();
323 Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
324 }
325}
326
327// todo(windows)
328impl rwh::HasDisplayHandle for WindowsWindow {
329 fn display_handle(&self) -> std::result::Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
330 unimplemented!()
331 }
332}
333
334impl Drop for WindowsWindow {
335 fn drop(&mut self) {
336 self.0.state.borrow_mut().renderer.destroy();
337 // clone this `Rc` to prevent early release of the pointer
338 let this = self.0.clone();
339 self.0
340 .executor
341 .spawn(async move {
342 let handle = this.hwnd;
343 unsafe {
344 RevokeDragDrop(handle).log_err();
345 DestroyWindow(handle).log_err();
346 }
347 })
348 .detach();
349 }
350}
351
352impl PlatformWindow for WindowsWindow {
353 fn bounds(&self) -> Bounds<DevicePixels> {
354 self.0.state.borrow().bounds()
355 }
356
357 fn is_maximized(&self) -> bool {
358 self.0.state.borrow().is_maximized()
359 }
360
361 fn window_bounds(&self) -> WindowBounds {
362 self.0.state.borrow().window_bounds()
363 }
364
365 /// get the logical size of the app's drawable area.
366 ///
367 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
368 /// whether the mouse collides with other elements of GPUI).
369 fn content_size(&self) -> Size<Pixels> {
370 self.0.state.borrow().content_size()
371 }
372
373 fn scale_factor(&self) -> f32 {
374 self.0.state.borrow().scale_factor
375 }
376
377 // todo(windows)
378 fn appearance(&self) -> WindowAppearance {
379 WindowAppearance::Dark
380 }
381
382 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
383 Some(Rc::new(self.0.state.borrow().display))
384 }
385
386 fn mouse_position(&self) -> Point<Pixels> {
387 let scale_factor = self.scale_factor();
388 let point = unsafe {
389 let mut point: POINT = std::mem::zeroed();
390 GetCursorPos(&mut point)
391 .context("unable to get cursor position")
392 .log_err();
393 ScreenToClient(self.0.hwnd, &mut point).ok().log_err();
394 point
395 };
396 logical_point(point.x as f32, point.y as f32, scale_factor)
397 }
398
399 // todo(windows)
400 fn modifiers(&self) -> Modifiers {
401 Modifiers::none()
402 }
403
404 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
405 self.0.state.borrow_mut().input_handler = Some(input_handler);
406 }
407
408 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
409 self.0.state.borrow_mut().input_handler.take()
410 }
411
412 fn prompt(
413 &self,
414 level: PromptLevel,
415 msg: &str,
416 detail: Option<&str>,
417 answers: &[&str],
418 ) -> Option<Receiver<usize>> {
419 let (done_tx, done_rx) = oneshot::channel();
420 let msg = msg.to_string();
421 let detail_string = match detail {
422 Some(info) => Some(info.to_string()),
423 None => None,
424 };
425 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
426 let handle = self.0.hwnd;
427 self.0
428 .executor
429 .spawn(async move {
430 unsafe {
431 let mut config;
432 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
433 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
434 config.hwndParent = handle;
435 let title;
436 let main_icon;
437 match level {
438 crate::PromptLevel::Info => {
439 title = windows::core::w!("Info");
440 main_icon = TD_INFORMATION_ICON;
441 }
442 crate::PromptLevel::Warning => {
443 title = windows::core::w!("Warning");
444 main_icon = TD_WARNING_ICON;
445 }
446 crate::PromptLevel::Critical => {
447 title = windows::core::w!("Critical");
448 main_icon = TD_ERROR_ICON;
449 }
450 };
451 config.pszWindowTitle = title;
452 config.Anonymous1.pszMainIcon = main_icon;
453 let instruction = msg.encode_utf16().chain(Some(0)).collect_vec();
454 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
455 let hints_encoded;
456 if let Some(ref hints) = detail_string {
457 hints_encoded = hints.encode_utf16().chain(Some(0)).collect_vec();
458 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
459 };
460 let mut buttons = Vec::new();
461 let mut btn_encoded = Vec::new();
462 for (index, btn_string) in answers.iter().enumerate() {
463 let encoded = btn_string.encode_utf16().chain(Some(0)).collect_vec();
464 buttons.push(TASKDIALOG_BUTTON {
465 nButtonID: index as _,
466 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
467 });
468 btn_encoded.push(encoded);
469 }
470 config.cButtons = buttons.len() as _;
471 config.pButtons = buttons.as_ptr();
472
473 config.pfCallback = None;
474 let mut res = std::mem::zeroed();
475 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
476 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
477
478 let _ = done_tx.send(res as usize);
479 }
480 })
481 .detach();
482
483 Some(done_rx)
484 }
485
486 fn activate(&self) {
487 let hwnd = self.0.hwnd;
488 unsafe { SetActiveWindow(hwnd) };
489 unsafe { SetFocus(hwnd) };
490 // todo(windows)
491 // crate `windows 0.56` reports true as Err
492 unsafe { SetForegroundWindow(hwnd).as_bool() };
493 }
494
495 fn is_active(&self) -> bool {
496 self.0.hwnd == unsafe { GetActiveWindow() }
497 }
498
499 fn set_title(&mut self, title: &str) {
500 unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
501 .inspect_err(|e| log::error!("Set title failed: {e}"))
502 .ok();
503 }
504
505 fn set_app_id(&mut self, _app_id: &str) {}
506
507 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
508 self.0
509 .state
510 .borrow_mut()
511 .renderer
512 .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
513 }
514
515 // todo(windows)
516 fn set_edited(&mut self, _edited: bool) {}
517
518 // todo(windows)
519 fn show_character_palette(&self) {}
520
521 fn minimize(&self) {
522 unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() };
523 }
524
525 fn zoom(&self) {
526 unsafe { ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE).ok().log_err() };
527 }
528
529 fn toggle_fullscreen(&self) {
530 let state_ptr = self.0.clone();
531 self.0
532 .executor
533 .spawn(async move {
534 let mut lock = state_ptr.state.borrow_mut();
535 lock.fullscreen_restore_bounds = Bounds {
536 origin: lock.origin,
537 size: lock.physical_size,
538 };
539 let StyleAndBounds {
540 style,
541 x,
542 y,
543 cx,
544 cy,
545 } = if let Some(state) = lock.fullscreen.take() {
546 state
547 } else {
548 let style =
549 WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
550 let mut rc = RECT::default();
551 unsafe { GetWindowRect(state_ptr.hwnd, &mut rc) }.log_err();
552 let _ = lock.fullscreen.insert(StyleAndBounds {
553 style,
554 x: rc.left,
555 y: rc.top,
556 cx: rc.right - rc.left,
557 cy: rc.bottom - rc.top,
558 });
559 let style = style
560 & !(WS_THICKFRAME
561 | WS_SYSMENU
562 | WS_MAXIMIZEBOX
563 | WS_MINIMIZEBOX
564 | WS_CAPTION);
565 let bounds = lock.display.bounds();
566 StyleAndBounds {
567 style,
568 x: bounds.left().0,
569 y: bounds.top().0,
570 cx: bounds.size.width.0,
571 cy: bounds.size.height.0,
572 }
573 };
574 drop(lock);
575 unsafe { set_window_long(state_ptr.hwnd, GWL_STYLE, style.0 as isize) };
576 unsafe {
577 SetWindowPos(
578 state_ptr.hwnd,
579 HWND::default(),
580 x,
581 y,
582 cx,
583 cy,
584 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
585 )
586 }
587 .log_err();
588 })
589 .detach();
590 }
591
592 fn is_fullscreen(&self) -> bool {
593 self.0.state.borrow().is_fullscreen()
594 }
595
596 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
597 self.0.state.borrow_mut().callbacks.request_frame = Some(callback);
598 }
599
600 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
601 self.0.state.borrow_mut().callbacks.input = Some(callback);
602 }
603
604 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
605 self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
606 }
607
608 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
609 self.0.state.borrow_mut().callbacks.resize = Some(callback);
610 }
611
612 fn on_moved(&self, callback: Box<dyn FnMut()>) {
613 self.0.state.borrow_mut().callbacks.moved = Some(callback);
614 }
615
616 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
617 self.0.state.borrow_mut().callbacks.should_close = Some(callback);
618 }
619
620 fn on_close(&self, callback: Box<dyn FnOnce()>) {
621 self.0.state.borrow_mut().callbacks.close = Some(callback);
622 }
623
624 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
625 self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
626 }
627
628 fn draw(&self, scene: &Scene) {
629 self.0.state.borrow_mut().renderer.draw(scene)
630 }
631
632 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
633 self.0.state.borrow().renderer.sprite_atlas().clone()
634 }
635
636 fn get_raw_handle(&self) -> HWND {
637 self.0.hwnd
638 }
639
640 fn show_window_menu(&self, _position: Point<Pixels>) {}
641
642 fn start_system_move(&self) {}
643
644 fn should_render_window_controls(&self) -> bool {
645 false
646 }
647}
648
649#[implement(IDropTarget)]
650struct WindowsDragDropHandler(pub Rc<WindowsWindowStatePtr>);
651
652impl WindowsDragDropHandler {
653 fn handle_drag_drop(&self, input: PlatformInput) {
654 let mut lock = self.0.state.borrow_mut();
655 if let Some(mut func) = lock.callbacks.input.take() {
656 drop(lock);
657 func(input);
658 self.0.state.borrow_mut().callbacks.input = Some(func);
659 }
660 }
661}
662
663#[allow(non_snake_case)]
664impl IDropTarget_Impl for WindowsDragDropHandler {
665 fn DragEnter(
666 &self,
667 pdataobj: Option<&IDataObject>,
668 _grfkeystate: MODIFIERKEYS_FLAGS,
669 pt: &POINTL,
670 pdweffect: *mut DROPEFFECT,
671 ) -> windows::core::Result<()> {
672 unsafe {
673 let Some(idata_obj) = pdataobj else {
674 log::info!("no dragging file or directory detected");
675 return Ok(());
676 };
677 let config = FORMATETC {
678 cfFormat: CF_HDROP.0,
679 ptd: std::ptr::null_mut() as _,
680 dwAspect: DVASPECT_CONTENT.0,
681 lindex: -1,
682 tymed: TYMED_HGLOBAL.0 as _,
683 };
684 if idata_obj.QueryGetData(&config as _) == S_OK {
685 *pdweffect = DROPEFFECT_LINK;
686 let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
687 return Ok(());
688 };
689 if idata.u.hGlobal.is_invalid() {
690 return Ok(());
691 }
692 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
693 let mut paths = SmallVec::<[PathBuf; 2]>::new();
694 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
695 for file_index in 0..file_count {
696 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
697 let mut buffer = vec![0u16; filename_length + 1];
698 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
699 if ret == 0 {
700 log::error!("unable to read file name");
701 continue;
702 }
703 if let Some(file_name) =
704 String::from_utf16(&buffer[0..filename_length]).log_err()
705 {
706 if let Some(path) = PathBuf::from_str(&file_name).log_err() {
707 paths.push(path);
708 }
709 }
710 }
711 ReleaseStgMedium(&mut idata);
712 let mut cursor_position = POINT { x: pt.x, y: pt.y };
713 ScreenToClient(self.0.hwnd, &mut cursor_position)
714 .ok()
715 .log_err();
716 let scale_factor = self.0.state.borrow().scale_factor;
717 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
718 position: logical_point(
719 cursor_position.x as f32,
720 cursor_position.y as f32,
721 scale_factor,
722 ),
723 paths: ExternalPaths(paths),
724 });
725 self.handle_drag_drop(input);
726 } else {
727 *pdweffect = DROPEFFECT_NONE;
728 }
729 }
730 Ok(())
731 }
732
733 fn DragOver(
734 &self,
735 _grfkeystate: MODIFIERKEYS_FLAGS,
736 pt: &POINTL,
737 _pdweffect: *mut DROPEFFECT,
738 ) -> windows::core::Result<()> {
739 let mut cursor_position = POINT { x: pt.x, y: pt.y };
740 unsafe {
741 ScreenToClient(self.0.hwnd, &mut cursor_position)
742 .ok()
743 .log_err();
744 }
745 let scale_factor = self.0.state.borrow().scale_factor;
746 let input = PlatformInput::FileDrop(FileDropEvent::Pending {
747 position: logical_point(
748 cursor_position.x as f32,
749 cursor_position.y as f32,
750 scale_factor,
751 ),
752 });
753 self.handle_drag_drop(input);
754
755 Ok(())
756 }
757
758 fn DragLeave(&self) -> windows::core::Result<()> {
759 let input = PlatformInput::FileDrop(FileDropEvent::Exited);
760 self.handle_drag_drop(input);
761
762 Ok(())
763 }
764
765 fn Drop(
766 &self,
767 _pdataobj: Option<&IDataObject>,
768 _grfkeystate: MODIFIERKEYS_FLAGS,
769 pt: &POINTL,
770 _pdweffect: *mut DROPEFFECT,
771 ) -> windows::core::Result<()> {
772 let mut cursor_position = POINT { x: pt.x, y: pt.y };
773 unsafe {
774 ScreenToClient(self.0.hwnd, &mut cursor_position)
775 .ok()
776 .log_err();
777 }
778 let scale_factor = self.0.state.borrow().scale_factor;
779 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
780 position: logical_point(
781 cursor_position.x as f32,
782 cursor_position.y as f32,
783 scale_factor,
784 ),
785 });
786 self.handle_drag_drop(input);
787
788 Ok(())
789 }
790}
791
792#[derive(Debug)]
793pub(crate) struct ClickState {
794 button: MouseButton,
795 last_click: Instant,
796 last_position: Point<DevicePixels>,
797 pub(crate) current_count: usize,
798}
799
800impl ClickState {
801 pub fn new() -> Self {
802 ClickState {
803 button: MouseButton::Left,
804 last_click: Instant::now(),
805 last_position: Point::default(),
806 current_count: 0,
807 }
808 }
809
810 /// update self and return the needed click count
811 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
812 if self.button == button && self.is_double_click(new_position) {
813 self.current_count += 1;
814 } else {
815 self.current_count = 1;
816 }
817 self.last_click = Instant::now();
818 self.last_position = new_position;
819 self.button = button;
820
821 self.current_count
822 }
823
824 #[inline]
825 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
826 let diff = self.last_position - new_position;
827
828 self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
829 && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
830 && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
831 }
832}
833
834struct StyleAndBounds {
835 style: WINDOW_STYLE,
836 x: i32,
837 y: i32,
838 cx: i32,
839 cy: i32,
840}
841
842fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
843 const CLASS_NAME: PCWSTR = w!("Zed::Window");
844
845 static ONCE: Once = Once::new();
846 ONCE.call_once(|| {
847 let wc = WNDCLASSW {
848 lpfnWndProc: Some(wnd_proc),
849 hIcon: icon_handle,
850 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
851 style: CS_HREDRAW | CS_VREDRAW,
852 hInstance: get_module_handle().into(),
853 ..Default::default()
854 };
855 unsafe { RegisterClassW(&wc) };
856 });
857
858 CLASS_NAME
859}
860
861unsafe extern "system" fn wnd_proc(
862 hwnd: HWND,
863 msg: u32,
864 wparam: WPARAM,
865 lparam: LPARAM,
866) -> LRESULT {
867 if msg == WM_NCCREATE {
868 let cs = lparam.0 as *const CREATESTRUCTW;
869 let cs = unsafe { &*cs };
870 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
871 let ctx = unsafe { &mut *ctx };
872 let state_ptr = WindowsWindowStatePtr::new(ctx, hwnd, cs);
873 let weak = Box::new(Rc::downgrade(&state_ptr));
874 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
875 ctx.inner = Some(state_ptr);
876 return LRESULT(1);
877 }
878 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
879 if ptr.is_null() {
880 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
881 }
882 let inner = unsafe { &*ptr };
883 let r = if let Some(state) = inner.upgrade() {
884 handle_msg(hwnd, msg, wparam, lparam, state)
885 } else {
886 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
887 };
888 if msg == WM_NCDESTROY {
889 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
890 unsafe { drop(Box::from_raw(ptr)) };
891 }
892 r
893}
894
895pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
896 if hwnd == HWND(0) {
897 return None;
898 }
899
900 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
901 if !ptr.is_null() {
902 let inner = unsafe { &*ptr };
903 inner.upgrade()
904 } else {
905 None
906 }
907}
908
909fn get_module_handle() -> HMODULE {
910 unsafe {
911 let mut h_module = std::mem::zeroed();
912 GetModuleHandleExW(
913 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
914 windows::core::w!("ZedModule"),
915 &mut h_module,
916 )
917 .expect("Unable to get module handle"); // this should never fail
918
919 h_module
920 }
921}
922
923fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) {
924 let window_handle = state_ptr.hwnd;
925 let handler = WindowsDragDropHandler(state_ptr);
926 // The lifetime of `IDropTarget` is handled by Windows, it wont release untill
927 // we call `RevokeDragDrop`.
928 // So, it's safe to drop it here.
929 let drag_drop_handler: IDropTarget = handler.into();
930 unsafe {
931 RegisterDragDrop(window_handle, &drag_drop_handler)
932 .expect("unable to register drag-drop event")
933 };
934}
935
936// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
937const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
938// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
939const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
940// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
941const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
942
943mod windows_renderer {
944 use std::{num::NonZeroIsize, sync::Arc};
945
946 use blade_graphics as gpu;
947 use raw_window_handle as rwh;
948 use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
949
950 use crate::{
951 get_window_long,
952 platform::blade::{BladeRenderer, BladeSurfaceConfig},
953 };
954
955 pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> BladeRenderer {
956 let raw = RawWindow { hwnd: hwnd.0 };
957 let gpu: Arc<gpu::Context> = Arc::new(
958 unsafe {
959 gpu::Context::init_windowed(
960 &raw,
961 gpu::ContextDesc {
962 validation: false,
963 capture: false,
964 overlay: false,
965 },
966 )
967 }
968 .unwrap(),
969 );
970 let config = BladeSurfaceConfig {
971 size: gpu::Extent::default(),
972 transparent,
973 };
974
975 BladeRenderer::new(gpu, config)
976 }
977
978 struct RawWindow {
979 hwnd: isize,
980 }
981
982 impl rwh::HasWindowHandle for RawWindow {
983 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
984 Ok(unsafe {
985 let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
986 let mut handle = rwh::Win32WindowHandle::new(hwnd);
987 let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
988 handle.hinstance = NonZeroIsize::new(hinstance);
989 rwh::WindowHandle::borrow_raw(handle.into())
990 })
991 }
992 }
993
994 impl rwh::HasDisplayHandle for RawWindow {
995 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
996 let handle = rwh::WindowsDisplayHandle::new();
997 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
998 }
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use super::ClickState;
1005 use crate::{point, DevicePixels, MouseButton};
1006 use std::time::Duration;
1007
1008 #[test]
1009 fn test_double_click_interval() {
1010 let mut state = ClickState::new();
1011 assert_eq!(
1012 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1013 1
1014 );
1015 assert_eq!(
1016 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1017 1
1018 );
1019 assert_eq!(
1020 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1021 1
1022 );
1023 assert_eq!(
1024 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1025 2
1026 );
1027 state.last_click -= Duration::from_millis(700);
1028 assert_eq!(
1029 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1030 1
1031 );
1032 }
1033
1034 #[test]
1035 fn test_double_click_spatial_tolerance() {
1036 let mut state = ClickState::new();
1037 assert_eq!(
1038 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1039 1
1040 );
1041 assert_eq!(
1042 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1043 2
1044 );
1045 assert_eq!(
1046 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1047 1
1048 );
1049 assert_eq!(
1050 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1051 1
1052 );
1053 }
1054}