1use std::{
2 cell::RefCell,
3 ffi::OsStr,
4 path::{Path, PathBuf},
5 rc::{Rc, Weak},
6 sync::Arc,
7};
8
9use ::util::{ResultExt, paths::SanitizedPath};
10use anyhow::{Context as _, Result, anyhow};
11use async_task::Runnable;
12use futures::channel::oneshot::{self, Receiver};
13use itertools::Itertools;
14use parking_lot::RwLock;
15use smallvec::SmallVec;
16use windows::{
17 UI::ViewManagement::UISettings,
18 Win32::{
19 Foundation::*,
20 Graphics::Gdi::*,
21 Security::Credentials::*,
22 System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*},
23 UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
24 },
25 core::*,
26};
27
28use crate::*;
29
30pub(crate) struct WindowsPlatform {
31 inner: Rc<WindowsPlatformInner>,
32 raw_window_handles: Arc<RwLock<SmallVec<[SafeHwnd; 4]>>>,
33 // The below members will never change throughout the entire lifecycle of the app.
34 icon: HICON,
35 background_executor: BackgroundExecutor,
36 foreground_executor: ForegroundExecutor,
37 text_system: Arc<DirectWriteTextSystem>,
38 windows_version: WindowsVersion,
39 drop_target_helper: IDropTargetHelper,
40 handle: HWND,
41 disable_direct_composition: bool,
42}
43
44struct WindowsPlatformInner {
45 state: RefCell<WindowsPlatformState>,
46 raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
47 // The below members will never change throughout the entire lifecycle of the app.
48 validation_number: usize,
49 main_receiver: flume::Receiver<Runnable>,
50}
51
52pub(crate) struct WindowsPlatformState {
53 callbacks: PlatformCallbacks,
54 menus: Vec<OwnedMenu>,
55 jump_list: JumpList,
56 // NOTE: standard cursor handles don't need to close.
57 pub(crate) current_cursor: Option<HCURSOR>,
58}
59
60#[derive(Default)]
61struct PlatformCallbacks {
62 open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
63 quit: Option<Box<dyn FnMut()>>,
64 reopen: Option<Box<dyn FnMut()>>,
65 app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
66 will_open_app_menu: Option<Box<dyn FnMut()>>,
67 validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
68 keyboard_layout_change: Option<Box<dyn FnMut()>>,
69}
70
71impl WindowsPlatformState {
72 fn new() -> Self {
73 let callbacks = PlatformCallbacks::default();
74 let jump_list = JumpList::new();
75 let current_cursor = load_cursor(CursorStyle::Arrow);
76
77 Self {
78 callbacks,
79 jump_list,
80 current_cursor,
81 menus: Vec::new(),
82 }
83 }
84}
85
86impl WindowsPlatform {
87 pub(crate) fn new() -> Result<Self> {
88 unsafe {
89 OleInitialize(None).context("unable to initialize Windows OLE")?;
90 }
91 let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
92 let validation_number = rand::random::<usize>();
93 let raw_window_handles = Arc::new(RwLock::new(SmallVec::new()));
94 register_platform_window_class();
95 let mut context = PlatformWindowCreateContext {
96 inner: None,
97 raw_window_handles: Arc::downgrade(&raw_window_handles),
98 validation_number,
99 main_receiver: Some(main_receiver),
100 };
101 let result = unsafe {
102 CreateWindowExW(
103 WINDOW_EX_STYLE(0),
104 PLATFORM_WINDOW_CLASS_NAME,
105 None,
106 WINDOW_STYLE(0),
107 0,
108 0,
109 0,
110 0,
111 Some(HWND_MESSAGE),
112 None,
113 None,
114 Some(&context as *const _ as *const _),
115 )
116 };
117 let inner = context.inner.take().unwrap()?;
118 let handle = result?;
119 let dispatcher = Arc::new(WindowsDispatcher::new(
120 main_sender,
121 handle,
122 validation_number,
123 ));
124 let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
125 .is_ok_and(|value| value == "true" || value == "1");
126 let background_executor = BackgroundExecutor::new(dispatcher.clone());
127 let foreground_executor = ForegroundExecutor::new(dispatcher);
128 let directx_devices = DirectXDevices::new(disable_direct_composition)
129 .context("Unable to init directx devices.")?;
130 let text_system = Arc::new(
131 DirectWriteTextSystem::new(&directx_devices)
132 .context("Error creating DirectWriteTextSystem")?,
133 );
134 let drop_target_helper: IDropTargetHelper = unsafe {
135 CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
136 .context("Error creating drop target helper.")?
137 };
138 let icon = load_icon().unwrap_or_default();
139 let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
140
141 Ok(Self {
142 inner,
143 handle,
144 raw_window_handles,
145 icon,
146 background_executor,
147 foreground_executor,
148 text_system,
149 disable_direct_composition,
150 windows_version,
151 drop_target_helper,
152 })
153 }
154
155 pub fn window_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
156 self.raw_window_handles
157 .read()
158 .iter()
159 .find(|entry| entry.as_raw() == hwnd)
160 .and_then(|hwnd| window_from_hwnd(hwnd.as_raw()))
161 }
162
163 #[inline]
164 fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {
165 self.raw_window_handles
166 .read()
167 .iter()
168 .for_each(|handle| unsafe {
169 PostMessageW(Some(handle.as_raw()), message, wparam, lparam).log_err();
170 });
171 }
172
173 fn generate_creation_info(&self) -> WindowCreationInfo {
174 WindowCreationInfo {
175 icon: self.icon,
176 executor: self.foreground_executor.clone(),
177 current_cursor: self.inner.state.borrow().current_cursor,
178 windows_version: self.windows_version,
179 drop_target_helper: self.drop_target_helper.clone(),
180 validation_number: self.inner.validation_number,
181 main_receiver: self.inner.main_receiver.clone(),
182 platform_window_handle: self.handle,
183 disable_direct_composition: self.disable_direct_composition,
184 }
185 }
186
187 fn set_dock_menus(&self, menus: Vec<MenuItem>) {
188 let mut actions = Vec::new();
189 menus.into_iter().for_each(|menu| {
190 if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
191 actions.push(dock_menu);
192 }
193 });
194 let mut lock = self.inner.state.borrow_mut();
195 lock.jump_list.dock_menus = actions;
196 update_jump_list(&lock.jump_list).log_err();
197 }
198
199 fn update_jump_list(
200 &self,
201 menus: Vec<MenuItem>,
202 entries: Vec<SmallVec<[PathBuf; 2]>>,
203 ) -> Vec<SmallVec<[PathBuf; 2]>> {
204 let mut actions = Vec::new();
205 menus.into_iter().for_each(|menu| {
206 if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
207 actions.push(dock_menu);
208 }
209 });
210 let mut lock = self.inner.state.borrow_mut();
211 lock.jump_list.dock_menus = actions;
212 lock.jump_list.recent_workspaces = entries;
213 update_jump_list(&lock.jump_list)
214 .log_err()
215 .unwrap_or_default()
216 }
217
218 fn find_current_active_window(&self) -> Option<HWND> {
219 let active_window_hwnd = unsafe { GetActiveWindow() };
220 if active_window_hwnd.is_invalid() {
221 return None;
222 }
223 self.raw_window_handles
224 .read()
225 .iter()
226 .find(|hwnd| hwnd.as_raw() == active_window_hwnd)
227 .map(|hwnd| hwnd.as_raw())
228 }
229
230 fn begin_vsync_thread(&self) {
231 let all_windows = Arc::downgrade(&self.raw_window_handles);
232 std::thread::spawn(move || {
233 let vsync_provider = VSyncProvider::new();
234 loop {
235 vsync_provider.wait_for_vsync();
236 let Some(all_windows) = all_windows.upgrade() else {
237 break;
238 };
239 for hwnd in all_windows.read().iter() {
240 unsafe {
241 RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE)
242 .ok()
243 .log_err();
244 }
245 }
246 }
247 });
248 }
249}
250
251impl Platform for WindowsPlatform {
252 fn background_executor(&self) -> BackgroundExecutor {
253 self.background_executor.clone()
254 }
255
256 fn foreground_executor(&self) -> ForegroundExecutor {
257 self.foreground_executor.clone()
258 }
259
260 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
261 self.text_system.clone()
262 }
263
264 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
265 Box::new(
266 WindowsKeyboardLayout::new()
267 .log_err()
268 .unwrap_or(WindowsKeyboardLayout::unknown()),
269 )
270 }
271
272 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
273 Rc::new(WindowsKeyboardMapper::new())
274 }
275
276 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
277 self.inner
278 .state
279 .borrow_mut()
280 .callbacks
281 .keyboard_layout_change = Some(callback);
282 }
283
284 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
285 on_finish_launching();
286 self.begin_vsync_thread();
287
288 let mut msg = MSG::default();
289 unsafe {
290 while GetMessageW(&mut msg, None, 0, 0).as_bool() {
291 DispatchMessageW(&msg);
292 }
293 }
294
295 if let Some(ref mut callback) = self.inner.state.borrow_mut().callbacks.quit {
296 callback();
297 }
298 }
299
300 fn quit(&self) {
301 self.foreground_executor()
302 .spawn(async { unsafe { PostQuitMessage(0) } })
303 .detach();
304 }
305
306 fn restart(&self, binary_path: Option<PathBuf>) {
307 let pid = std::process::id();
308 let Some(app_path) = binary_path.or(self.app_path().log_err()) else {
309 return;
310 };
311 let script = format!(
312 r#"
313 $pidToWaitFor = {}
314 $exePath = "{}"
315
316 while ($true) {{
317 $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue
318 if (-not $process) {{
319 Start-Process -FilePath $exePath
320 break
321 }}
322 Start-Sleep -Seconds 0.1
323 }}
324 "#,
325 pid,
326 app_path.display(),
327 );
328 let restart_process = util::command::new_std_command("powershell.exe")
329 .arg("-command")
330 .arg(script)
331 .spawn();
332
333 match restart_process {
334 Ok(_) => self.quit(),
335 Err(e) => log::error!("failed to spawn restart script: {:?}", e),
336 }
337 }
338
339 fn activate(&self, _ignoring_other_apps: bool) {}
340
341 fn hide(&self) {}
342
343 // todo(windows)
344 fn hide_other_apps(&self) {
345 unimplemented!()
346 }
347
348 // todo(windows)
349 fn unhide_other_apps(&self) {
350 unimplemented!()
351 }
352
353 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
354 WindowsDisplay::displays()
355 }
356
357 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
358 WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
359 }
360
361 #[cfg(feature = "screen-capture")]
362 fn is_screen_capture_supported(&self) -> bool {
363 true
364 }
365
366 #[cfg(feature = "screen-capture")]
367 fn screen_capture_sources(
368 &self,
369 ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
370 crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
371 }
372
373 fn active_window(&self) -> Option<AnyWindowHandle> {
374 let active_window_hwnd = unsafe { GetActiveWindow() };
375 self.window_from_hwnd(active_window_hwnd)
376 .map(|inner| inner.handle)
377 }
378
379 fn open_window(
380 &self,
381 handle: AnyWindowHandle,
382 options: WindowParams,
383 ) -> Result<Box<dyn PlatformWindow>> {
384 let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
385 let handle = window.get_raw_handle();
386 self.raw_window_handles.write().push(handle.into());
387
388 Ok(Box::new(window))
389 }
390
391 fn window_appearance(&self) -> WindowAppearance {
392 system_appearance().log_err().unwrap_or_default()
393 }
394
395 fn open_url(&self, url: &str) {
396 if url.is_empty() {
397 return;
398 }
399 let url_string = url.to_string();
400 self.background_executor()
401 .spawn(async move {
402 open_target(&url_string)
403 .with_context(|| format!("Opening url: {}", url_string))
404 .log_err();
405 })
406 .detach();
407 }
408
409 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
410 self.inner.state.borrow_mut().callbacks.open_urls = Some(callback);
411 }
412
413 fn prompt_for_paths(
414 &self,
415 options: PathPromptOptions,
416 ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
417 let (tx, rx) = oneshot::channel();
418 let window = self.find_current_active_window();
419 self.foreground_executor()
420 .spawn(async move {
421 let _ = tx.send(file_open_dialog(options, window));
422 })
423 .detach();
424
425 rx
426 }
427
428 fn prompt_for_new_path(
429 &self,
430 directory: &Path,
431 suggested_name: Option<&str>,
432 ) -> Receiver<Result<Option<PathBuf>>> {
433 let directory = directory.to_owned();
434 let suggested_name = suggested_name.map(|s| s.to_owned());
435 let (tx, rx) = oneshot::channel();
436 let window = self.find_current_active_window();
437 self.foreground_executor()
438 .spawn(async move {
439 let _ = tx.send(file_save_dialog(directory, suggested_name, window));
440 })
441 .detach();
442
443 rx
444 }
445
446 fn can_select_mixed_files_and_dirs(&self) -> bool {
447 // The FOS_PICKFOLDERS flag toggles between "only files" and "only folders".
448 false
449 }
450
451 fn reveal_path(&self, path: &Path) {
452 if path.as_os_str().is_empty() {
453 return;
454 }
455 let path = path.to_path_buf();
456 self.background_executor()
457 .spawn(async move {
458 open_target_in_explorer(&path)
459 .with_context(|| format!("Revealing path {} in explorer", path.display()))
460 .log_err();
461 })
462 .detach();
463 }
464
465 fn open_with_system(&self, path: &Path) {
466 if path.as_os_str().is_empty() {
467 return;
468 }
469 let path = path.to_path_buf();
470 self.background_executor()
471 .spawn(async move {
472 open_target(&path)
473 .with_context(|| format!("Opening {} with system", path.display()))
474 .log_err();
475 })
476 .detach();
477 }
478
479 fn on_quit(&self, callback: Box<dyn FnMut()>) {
480 self.inner.state.borrow_mut().callbacks.quit = Some(callback);
481 }
482
483 fn on_reopen(&self, callback: Box<dyn FnMut()>) {
484 self.inner.state.borrow_mut().callbacks.reopen = Some(callback);
485 }
486
487 fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
488 self.inner.state.borrow_mut().menus = menus.into_iter().map(|menu| menu.owned()).collect();
489 }
490
491 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
492 Some(self.inner.state.borrow().menus.clone())
493 }
494
495 fn set_dock_menu(&self, menus: Vec<MenuItem>, _keymap: &Keymap) {
496 self.set_dock_menus(menus);
497 }
498
499 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
500 self.inner.state.borrow_mut().callbacks.app_menu_action = Some(callback);
501 }
502
503 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
504 self.inner.state.borrow_mut().callbacks.will_open_app_menu = Some(callback);
505 }
506
507 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
508 self.inner
509 .state
510 .borrow_mut()
511 .callbacks
512 .validate_app_menu_command = Some(callback);
513 }
514
515 fn app_path(&self) -> Result<PathBuf> {
516 Ok(std::env::current_exe()?)
517 }
518
519 // todo(windows)
520 fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
521 anyhow::bail!("not yet implemented");
522 }
523
524 fn set_cursor_style(&self, style: CursorStyle) {
525 let hcursor = load_cursor(style);
526 let mut lock = self.inner.state.borrow_mut();
527 if lock.current_cursor.map(|c| c.0) != hcursor.map(|c| c.0) {
528 self.post_message(
529 WM_GPUI_CURSOR_STYLE_CHANGED,
530 WPARAM(0),
531 LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
532 );
533 lock.current_cursor = hcursor;
534 }
535 }
536
537 fn should_auto_hide_scrollbars(&self) -> bool {
538 should_auto_hide_scrollbars().log_err().unwrap_or(false)
539 }
540
541 fn write_to_clipboard(&self, item: ClipboardItem) {
542 write_to_clipboard(item);
543 }
544
545 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
546 read_from_clipboard()
547 }
548
549 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
550 let mut password = password.to_vec();
551 let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
552 let mut target_name = windows_credentials_target_name(url)
553 .encode_utf16()
554 .chain(Some(0))
555 .collect_vec();
556 self.foreground_executor().spawn(async move {
557 let credentials = CREDENTIALW {
558 LastWritten: unsafe { GetSystemTimeAsFileTime() },
559 Flags: CRED_FLAGS(0),
560 Type: CRED_TYPE_GENERIC,
561 TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
562 CredentialBlobSize: password.len() as u32,
563 CredentialBlob: password.as_ptr() as *mut _,
564 Persist: CRED_PERSIST_LOCAL_MACHINE,
565 UserName: PWSTR::from_raw(username.as_mut_ptr()),
566 ..CREDENTIALW::default()
567 };
568 unsafe { CredWriteW(&credentials, 0) }?;
569 Ok(())
570 })
571 }
572
573 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
574 let mut target_name = windows_credentials_target_name(url)
575 .encode_utf16()
576 .chain(Some(0))
577 .collect_vec();
578 self.foreground_executor().spawn(async move {
579 let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
580 unsafe {
581 CredReadW(
582 PCWSTR::from_raw(target_name.as_ptr()),
583 CRED_TYPE_GENERIC,
584 None,
585 &mut credentials,
586 )?
587 };
588
589 if credentials.is_null() {
590 Ok(None)
591 } else {
592 let username: String = unsafe { (*credentials).UserName.to_string()? };
593 let credential_blob = unsafe {
594 std::slice::from_raw_parts(
595 (*credentials).CredentialBlob,
596 (*credentials).CredentialBlobSize as usize,
597 )
598 };
599 let password = credential_blob.to_vec();
600 unsafe { CredFree(credentials as *const _ as _) };
601 Ok(Some((username, password)))
602 }
603 })
604 }
605
606 fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
607 let mut target_name = windows_credentials_target_name(url)
608 .encode_utf16()
609 .chain(Some(0))
610 .collect_vec();
611 self.foreground_executor().spawn(async move {
612 unsafe {
613 CredDeleteW(
614 PCWSTR::from_raw(target_name.as_ptr()),
615 CRED_TYPE_GENERIC,
616 None,
617 )?
618 };
619 Ok(())
620 })
621 }
622
623 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
624 Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
625 }
626
627 fn perform_dock_menu_action(&self, action: usize) {
628 unsafe {
629 PostMessageW(
630 Some(self.handle),
631 WM_GPUI_DOCK_MENU_ACTION,
632 WPARAM(self.inner.validation_number),
633 LPARAM(action as isize),
634 )
635 .log_err();
636 }
637 }
638
639 fn update_jump_list(
640 &self,
641 menus: Vec<MenuItem>,
642 entries: Vec<SmallVec<[PathBuf; 2]>>,
643 ) -> Vec<SmallVec<[PathBuf; 2]>> {
644 self.update_jump_list(menus, entries)
645 }
646}
647
648impl WindowsPlatformInner {
649 fn new(context: &mut PlatformWindowCreateContext) -> Result<Rc<Self>> {
650 let state = RefCell::new(WindowsPlatformState::new());
651 Ok(Rc::new(Self {
652 state,
653 raw_window_handles: context.raw_window_handles.clone(),
654 validation_number: context.validation_number,
655 main_receiver: context.main_receiver.take().unwrap(),
656 }))
657 }
658
659 fn handle_msg(
660 self: &Rc<Self>,
661 handle: HWND,
662 msg: u32,
663 wparam: WPARAM,
664 lparam: LPARAM,
665 ) -> LRESULT {
666 let handled = match msg {
667 WM_GPUI_CLOSE_ONE_WINDOW
668 | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD
669 | WM_GPUI_DOCK_MENU_ACTION
670 | WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_gpui_events(msg, wparam, lparam),
671 _ => None,
672 };
673 if let Some(result) = handled {
674 LRESULT(result)
675 } else {
676 unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
677 }
678 }
679
680 fn handle_gpui_events(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
681 if wparam.0 != self.validation_number {
682 log::error!("Wrong validation number while processing message: {message}");
683 return None;
684 }
685 match message {
686 WM_GPUI_CLOSE_ONE_WINDOW => {
687 if self.close_one_window(HWND(lparam.0 as _)) {
688 unsafe { PostQuitMessage(0) };
689 }
690 Some(0)
691 }
692 WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(),
693 WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _),
694 WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_keyboard_layout_change(),
695 _ => unreachable!(),
696 }
697 }
698
699 fn close_one_window(&self, target_window: HWND) -> bool {
700 let Some(all_windows) = self.raw_window_handles.upgrade() else {
701 log::error!("Failed to upgrade raw window handles");
702 return false;
703 };
704 let mut lock = all_windows.write();
705 let index = lock
706 .iter()
707 .position(|handle| handle.as_raw() == target_window)
708 .unwrap();
709 lock.remove(index);
710
711 lock.is_empty()
712 }
713
714 #[inline]
715 fn run_foreground_task(&self) -> Option<isize> {
716 for runnable in self.main_receiver.drain() {
717 runnable.run();
718 }
719 Some(0)
720 }
721
722 fn handle_dock_action_event(&self, action_idx: usize) -> Option<isize> {
723 let mut lock = self.state.borrow_mut();
724 let mut callback = lock.callbacks.app_menu_action.take()?;
725 let Some(action) = lock
726 .jump_list
727 .dock_menus
728 .get(action_idx)
729 .map(|dock_menu| dock_menu.action.boxed_clone())
730 else {
731 lock.callbacks.app_menu_action = Some(callback);
732 log::error!("Dock menu for index {action_idx} not found");
733 return Some(1);
734 };
735 drop(lock);
736 callback(&*action);
737 self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
738 Some(0)
739 }
740
741 fn handle_keyboard_layout_change(&self) -> Option<isize> {
742 let mut callback = self
743 .state
744 .borrow_mut()
745 .callbacks
746 .keyboard_layout_change
747 .take()?;
748 callback();
749 self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
750 Some(0)
751 }
752}
753
754impl Drop for WindowsPlatform {
755 fn drop(&mut self) {
756 unsafe {
757 DestroyWindow(self.handle)
758 .context("Destroying platform window")
759 .log_err();
760 OleUninitialize();
761 }
762 }
763}
764
765pub(crate) struct WindowCreationInfo {
766 pub(crate) icon: HICON,
767 pub(crate) executor: ForegroundExecutor,
768 pub(crate) current_cursor: Option<HCURSOR>,
769 pub(crate) windows_version: WindowsVersion,
770 pub(crate) drop_target_helper: IDropTargetHelper,
771 pub(crate) validation_number: usize,
772 pub(crate) main_receiver: flume::Receiver<Runnable>,
773 pub(crate) platform_window_handle: HWND,
774 pub(crate) disable_direct_composition: bool,
775}
776
777struct PlatformWindowCreateContext {
778 inner: Option<Result<Rc<WindowsPlatformInner>>>,
779 raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
780 validation_number: usize,
781 main_receiver: Option<flume::Receiver<Runnable>>,
782}
783
784fn open_target(target: impl AsRef<OsStr>) -> Result<()> {
785 let target = target.as_ref();
786 let ret = unsafe {
787 ShellExecuteW(
788 None,
789 windows::core::w!("open"),
790 &HSTRING::from(target),
791 None,
792 None,
793 SW_SHOWDEFAULT,
794 )
795 };
796 if ret.0 as isize <= 32 {
797 Err(anyhow::anyhow!(
798 "Unable to open target: {}",
799 std::io::Error::last_os_error()
800 ))
801 } else {
802 Ok(())
803 }
804}
805
806fn open_target_in_explorer(target: &Path) -> Result<()> {
807 let dir = target.parent().context("No parent folder found")?;
808 let desktop = unsafe { SHGetDesktopFolder()? };
809
810 let mut dir_item = std::ptr::null_mut();
811 unsafe {
812 desktop.ParseDisplayName(
813 HWND::default(),
814 None,
815 &HSTRING::from(dir),
816 None,
817 &mut dir_item,
818 std::ptr::null_mut(),
819 )?;
820 }
821
822 let mut file_item = std::ptr::null_mut();
823 unsafe {
824 desktop.ParseDisplayName(
825 HWND::default(),
826 None,
827 &HSTRING::from(target),
828 None,
829 &mut file_item,
830 std::ptr::null_mut(),
831 )?;
832 }
833
834 let highlight = [file_item as *const _];
835 unsafe { SHOpenFolderAndSelectItems(dir_item as _, Some(&highlight), 0) }.or_else(|err| {
836 if err.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
837 // On some systems, the above call mysteriously fails with "file not
838 // found" even though the file is there. In these cases, ShellExecute()
839 // seems to work as a fallback (although it won't select the file).
840 open_target(dir).context("Opening target parent folder")
841 } else {
842 Err(anyhow::anyhow!("Can not open target path: {}", err))
843 }
844 })
845}
846
847fn file_open_dialog(
848 options: PathPromptOptions,
849 window: Option<HWND>,
850) -> Result<Option<Vec<PathBuf>>> {
851 let folder_dialog: IFileOpenDialog =
852 unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? };
853
854 let mut dialog_options = FOS_FILEMUSTEXIST;
855 if options.multiple {
856 dialog_options |= FOS_ALLOWMULTISELECT;
857 }
858 if options.directories {
859 dialog_options |= FOS_PICKFOLDERS;
860 }
861
862 unsafe {
863 folder_dialog.SetOptions(dialog_options)?;
864
865 if let Some(prompt) = options.prompt {
866 let prompt: &str = &prompt;
867 folder_dialog.SetOkButtonLabel(&HSTRING::from(prompt))?;
868 }
869
870 if folder_dialog.Show(window).is_err() {
871 // User cancelled
872 return Ok(None);
873 }
874 }
875
876 let results = unsafe { folder_dialog.GetResults()? };
877 let file_count = unsafe { results.GetCount()? };
878 if file_count == 0 {
879 return Ok(None);
880 }
881
882 let mut paths = Vec::with_capacity(file_count as usize);
883 for i in 0..file_count {
884 let item = unsafe { results.GetItemAt(i)? };
885 let path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
886 paths.push(PathBuf::from(path));
887 }
888
889 Ok(Some(paths))
890}
891
892fn file_save_dialog(
893 directory: PathBuf,
894 suggested_name: Option<String>,
895 window: Option<HWND>,
896) -> Result<Option<PathBuf>> {
897 let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
898 if !directory.to_string_lossy().is_empty()
899 && let Some(full_path) = directory.canonicalize().log_err()
900 {
901 let full_path = SanitizedPath::new(&full_path);
902 let full_path_string = full_path.to_string();
903 let path_item: IShellItem =
904 unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? };
905 unsafe { dialog.SetFolder(&path_item).log_err() };
906 }
907
908 if let Some(suggested_name) = suggested_name {
909 unsafe { dialog.SetFileName(&HSTRING::from(suggested_name)).log_err() };
910 }
911
912 unsafe {
913 dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
914 pszName: windows::core::w!("All files"),
915 pszSpec: windows::core::w!("*.*"),
916 }])?;
917 if dialog.Show(window).is_err() {
918 // User cancelled
919 return Ok(None);
920 }
921 }
922 let shell_item = unsafe { dialog.GetResult()? };
923 let file_path_string = unsafe {
924 let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?;
925 let string = pwstr.to_string()?;
926 CoTaskMemFree(Some(pwstr.0 as _));
927 string
928 };
929 Ok(Some(PathBuf::from(file_path_string)))
930}
931
932fn load_icon() -> Result<HICON> {
933 let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
934 let handle = unsafe {
935 LoadImageW(
936 Some(module.into()),
937 windows::core::PCWSTR(1 as _),
938 IMAGE_ICON,
939 0,
940 0,
941 LR_DEFAULTSIZE | LR_SHARED,
942 )
943 .context("unable to load icon file")?
944 };
945 Ok(HICON(handle.0))
946}
947
948#[inline]
949fn should_auto_hide_scrollbars() -> Result<bool> {
950 let ui_settings = UISettings::new()?;
951 Ok(ui_settings.AutoHideScrollBars()?)
952}
953
954const PLATFORM_WINDOW_CLASS_NAME: PCWSTR = w!("Zed::PlatformWindow");
955
956fn register_platform_window_class() {
957 let wc = WNDCLASSW {
958 lpfnWndProc: Some(window_procedure),
959 lpszClassName: PCWSTR(PLATFORM_WINDOW_CLASS_NAME.as_ptr()),
960 ..Default::default()
961 };
962 unsafe { RegisterClassW(&wc) };
963}
964
965unsafe extern "system" fn window_procedure(
966 hwnd: HWND,
967 msg: u32,
968 wparam: WPARAM,
969 lparam: LPARAM,
970) -> LRESULT {
971 if msg == WM_NCCREATE {
972 let params = lparam.0 as *const CREATESTRUCTW;
973 let params = unsafe { &*params };
974 let creation_context = params.lpCreateParams as *mut PlatformWindowCreateContext;
975 let creation_context = unsafe { &mut *creation_context };
976 return match WindowsPlatformInner::new(creation_context) {
977 Ok(inner) => {
978 let weak = Box::new(Rc::downgrade(&inner));
979 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
980 creation_context.inner = Some(Ok(inner));
981 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
982 }
983 Err(error) => {
984 creation_context.inner = Some(Err(error));
985 LRESULT(0)
986 }
987 };
988 }
989
990 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsPlatformInner>;
991 if ptr.is_null() {
992 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
993 }
994 let inner = unsafe { &*ptr };
995 let result = if let Some(inner) = inner.upgrade() {
996 inner.handle_msg(hwnd, msg, wparam, lparam)
997 } else {
998 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
999 };
1000
1001 if msg == WM_NCDESTROY {
1002 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1003 unsafe { drop(Box::from_raw(ptr)) };
1004 }
1005
1006 result
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 use crate::{ClipboardItem, read_from_clipboard, write_to_clipboard};
1012
1013 #[test]
1014 fn test_clipboard() {
1015 let item = ClipboardItem::new_string("你好,我是张小白".to_string());
1016 write_to_clipboard(item.clone());
1017 assert_eq!(read_from_clipboard(), Some(item));
1018
1019 let item = ClipboardItem::new_string("12345".to_string());
1020 write_to_clipboard(item.clone());
1021 assert_eq!(read_from_clipboard(), Some(item));
1022
1023 let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]);
1024 write_to_clipboard(item.clone());
1025 assert_eq!(read_from_clipboard(), Some(item));
1026 }
1027}