1// todo(windows): remove
2#![allow(unused_variables)]
3
4use std::{
5 cell::{Cell, RefCell},
6 ffi::{c_void, OsString},
7 mem::ManuallyDrop,
8 os::windows::ffi::{OsStrExt, OsStringExt},
9 path::{Path, PathBuf},
10 rc::Rc,
11 sync::Arc,
12};
13
14use ::util::ResultExt;
15use anyhow::{anyhow, Context, Result};
16use futures::channel::oneshot::{self, Receiver};
17use itertools::Itertools;
18use parking_lot::RwLock;
19use smallvec::SmallVec;
20use windows::{
21 core::*,
22 Win32::{
23 Foundation::*,
24 Globalization::u_memcpy,
25 Graphics::{
26 Gdi::*,
27 Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
28 },
29 Security::Credentials::*,
30 System::{
31 Com::*,
32 DataExchange::{
33 CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard,
34 RegisterClipboardFormatW, SetClipboardData,
35 },
36 LibraryLoader::*,
37 Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
38 Ole::*,
39 SystemInformation::*,
40 Threading::*,
41 },
42 UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
43 },
44 UI::ViewManagement::UISettings,
45};
46
47use crate::*;
48
49pub(crate) struct WindowsPlatform {
50 state: RefCell<WindowsPlatformState>,
51 raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
52 // The below members will never change throughout the entire lifecycle of the app.
53 icon: HICON,
54 background_executor: BackgroundExecutor,
55 foreground_executor: ForegroundExecutor,
56 text_system: Arc<DirectWriteTextSystem>,
57 clipboard_hash_format: u32,
58 clipboard_metadata_format: u32,
59 windows_version: WindowsVersion,
60 bitmap_factory: ManuallyDrop<IWICImagingFactory>,
61 validation_number: usize,
62}
63
64pub(crate) struct WindowsPlatformState {
65 callbacks: PlatformCallbacks,
66 // NOTE: standard cursor handles don't need to close.
67 pub(crate) current_cursor: HCURSOR,
68}
69
70#[derive(Default)]
71struct PlatformCallbacks {
72 open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
73 quit: Option<Box<dyn FnMut()>>,
74 reopen: Option<Box<dyn FnMut()>>,
75 app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
76 will_open_app_menu: Option<Box<dyn FnMut()>>,
77 validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
78}
79
80impl WindowsPlatformState {
81 fn new() -> Self {
82 let callbacks = PlatformCallbacks::default();
83 let current_cursor = load_cursor(CursorStyle::Arrow);
84
85 Self {
86 callbacks,
87 current_cursor,
88 }
89 }
90}
91
92impl WindowsPlatform {
93 pub(crate) fn new() -> Self {
94 unsafe {
95 OleInitialize(None).expect("unable to initialize Windows OLE");
96 }
97 let dispatcher = Arc::new(WindowsDispatcher::new());
98 let background_executor = BackgroundExecutor::new(dispatcher.clone());
99 let foreground_executor = ForegroundExecutor::new(dispatcher);
100 let bitmap_factory = ManuallyDrop::new(unsafe {
101 CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
102 .expect("Error creating bitmap factory.")
103 });
104 let text_system = Arc::new(
105 DirectWriteTextSystem::new(&bitmap_factory)
106 .expect("Error creating DirectWriteTextSystem"),
107 );
108 let icon = load_icon().unwrap_or_default();
109 let state = RefCell::new(WindowsPlatformState::new());
110 let raw_window_handles = RwLock::new(SmallVec::new());
111 let clipboard_hash_format = register_clipboard_format(CLIPBOARD_HASH_FORMAT).unwrap();
112 let clipboard_metadata_format =
113 register_clipboard_format(CLIPBOARD_METADATA_FORMAT).unwrap();
114 let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
115 let validation_number = rand::random::<usize>();
116
117 Self {
118 state,
119 raw_window_handles,
120 icon,
121 background_executor,
122 foreground_executor,
123 text_system,
124 clipboard_hash_format,
125 clipboard_metadata_format,
126 windows_version,
127 bitmap_factory,
128 validation_number,
129 }
130 }
131
132 fn redraw_all(&self) {
133 for handle in self.raw_window_handles.read().iter() {
134 unsafe {
135 RedrawWindow(
136 *handle,
137 None,
138 HRGN::default(),
139 RDW_INVALIDATE | RDW_UPDATENOW,
140 )
141 .ok()
142 .log_err();
143 }
144 }
145 }
146
147 pub fn try_get_windows_inner_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
148 self.raw_window_handles
149 .read()
150 .iter()
151 .find(|entry| *entry == &hwnd)
152 .and_then(|hwnd| try_get_window_inner(*hwnd))
153 }
154
155 #[inline]
156 fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {
157 self.raw_window_handles
158 .read()
159 .iter()
160 .for_each(|handle| unsafe {
161 PostMessageW(*handle, message, wparam, lparam).log_err();
162 });
163 }
164
165 fn close_one_window(
166 &self,
167 target_window: HWND,
168 validation_number: usize,
169 msg: *const MSG,
170 ) -> bool {
171 if validation_number != self.validation_number {
172 unsafe { DispatchMessageW(msg) };
173 return false;
174 }
175 let mut lock = self.raw_window_handles.write();
176 let index = lock
177 .iter()
178 .position(|handle| *handle == target_window)
179 .unwrap();
180 lock.remove(index);
181
182 lock.is_empty()
183 }
184}
185
186impl Platform for WindowsPlatform {
187 fn background_executor(&self) -> BackgroundExecutor {
188 self.background_executor.clone()
189 }
190
191 fn foreground_executor(&self) -> ForegroundExecutor {
192 self.foreground_executor.clone()
193 }
194
195 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
196 self.text_system.clone()
197 }
198
199 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
200 on_finish_launching();
201 let vsync_event = unsafe { Owned::new(CreateEventW(None, false, false, None).unwrap()) };
202 begin_vsync(*vsync_event);
203 'a: loop {
204 let wait_result = unsafe {
205 MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
206 };
207
208 match wait_result {
209 // compositor clock ticked so we should draw a frame
210 WAIT_EVENT(0) => {
211 self.redraw_all();
212 }
213 // Windows thread messages are posted
214 WAIT_EVENT(1) => {
215 let mut msg = MSG::default();
216 unsafe {
217 while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
218 match msg.message {
219 WM_QUIT => break 'a,
220 CLOSE_ONE_WINDOW => {
221 if self.close_one_window(
222 HWND(msg.lParam.0 as _),
223 msg.wParam.0,
224 &msg,
225 ) {
226 break 'a;
227 }
228 }
229 _ => {
230 // todo(windows)
231 // crate `windows 0.56` reports true as Err
232 TranslateMessage(&msg).as_bool();
233 DispatchMessageW(&msg);
234 }
235 }
236 }
237 }
238 }
239 _ => {
240 log::error!("Something went wrong while waiting {:?}", wait_result);
241 break;
242 }
243 }
244 }
245
246 if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
247 callback();
248 }
249 }
250
251 fn quit(&self) {
252 self.foreground_executor()
253 .spawn(async { unsafe { PostQuitMessage(0) } })
254 .detach();
255 }
256
257 fn restart(&self, _: Option<PathBuf>) {
258 let pid = std::process::id();
259 let Some(app_path) = self.app_path().log_err() else {
260 return;
261 };
262 let script = format!(
263 r#"
264 $pidToWaitFor = {}
265 $exePath = "{}"
266
267 while ($true) {{
268 $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue
269 if (-not $process) {{
270 Start-Process -FilePath $exePath
271 break
272 }}
273 Start-Sleep -Seconds 0.1
274 }}
275 "#,
276 pid,
277 app_path.display(),
278 );
279 let restart_process = std::process::Command::new("powershell.exe")
280 .arg("-command")
281 .arg(script)
282 .spawn();
283
284 match restart_process {
285 Ok(_) => self.quit(),
286 Err(e) => log::error!("failed to spawn restart script: {:?}", e),
287 }
288 }
289
290 // todo(windows)
291 fn activate(&self, ignoring_other_apps: bool) {}
292
293 // todo(windows)
294 fn hide(&self) {
295 unimplemented!()
296 }
297
298 // todo(windows)
299 fn hide_other_apps(&self) {
300 unimplemented!()
301 }
302
303 // todo(windows)
304 fn unhide_other_apps(&self) {
305 unimplemented!()
306 }
307
308 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
309 WindowsDisplay::displays()
310 }
311
312 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
313 WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
314 }
315
316 fn active_window(&self) -> Option<AnyWindowHandle> {
317 let active_window_hwnd = unsafe { GetActiveWindow() };
318 self.try_get_windows_inner_from_hwnd(active_window_hwnd)
319 .map(|inner| inner.handle)
320 }
321
322 fn open_window(
323 &self,
324 handle: AnyWindowHandle,
325 options: WindowParams,
326 ) -> Result<Box<dyn PlatformWindow>> {
327 let lock = self.state.borrow();
328 let window = WindowsWindow::new(
329 handle,
330 options,
331 self.icon,
332 self.foreground_executor.clone(),
333 lock.current_cursor,
334 self.windows_version,
335 self.validation_number,
336 )?;
337 drop(lock);
338 let handle = window.get_raw_handle();
339 self.raw_window_handles.write().push(handle);
340
341 Ok(Box::new(window))
342 }
343
344 fn window_appearance(&self) -> WindowAppearance {
345 system_appearance().log_err().unwrap_or_default()
346 }
347
348 fn open_url(&self, url: &str) {
349 let url_string = url.to_string();
350 self.background_executor()
351 .spawn(async move {
352 if url_string.is_empty() {
353 return;
354 }
355 open_target(url_string.as_str());
356 })
357 .detach();
358 }
359
360 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
361 self.state.borrow_mut().callbacks.open_urls = Some(callback);
362 }
363
364 fn prompt_for_paths(
365 &self,
366 options: PathPromptOptions,
367 ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
368 let (tx, rx) = oneshot::channel();
369
370 self.foreground_executor()
371 .spawn(async move {
372 let tx = Cell::new(Some(tx));
373
374 // create file open dialog
375 let folder_dialog: IFileOpenDialog = unsafe {
376 CoCreateInstance::<std::option::Option<&IUnknown>, IFileOpenDialog>(
377 &FileOpenDialog,
378 None,
379 CLSCTX_ALL,
380 )
381 .unwrap()
382 };
383
384 // dialog options
385 let mut dialog_options: FILEOPENDIALOGOPTIONS = FOS_FILEMUSTEXIST;
386 if options.multiple {
387 dialog_options |= FOS_ALLOWMULTISELECT;
388 }
389 if options.directories {
390 dialog_options |= FOS_PICKFOLDERS;
391 }
392
393 unsafe {
394 folder_dialog.SetOptions(dialog_options).unwrap();
395 folder_dialog
396 .SetTitle(&HSTRING::from(OsString::from("Select a folder")))
397 .unwrap();
398 }
399
400 let hr = unsafe { folder_dialog.Show(None) };
401
402 if hr.is_err() {
403 if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
404 // user canceled error
405 if let Some(tx) = tx.take() {
406 tx.send(Ok(None)).unwrap();
407 }
408 return;
409 }
410 }
411
412 let mut results = unsafe { folder_dialog.GetResults().unwrap() };
413
414 let mut paths: Vec<PathBuf> = Vec::new();
415 for i in 0..unsafe { results.GetCount().unwrap() } {
416 let mut item: IShellItem = unsafe { results.GetItemAt(i).unwrap() };
417 let mut path: PWSTR =
418 unsafe { item.GetDisplayName(SIGDN_FILESYSPATH).unwrap() };
419 let mut path_os_string = OsString::from_wide(unsafe { path.as_wide() });
420
421 paths.push(PathBuf::from(path_os_string));
422 }
423
424 if let Some(tx) = tx.take() {
425 if paths.is_empty() {
426 tx.send(Ok(None)).unwrap();
427 } else {
428 tx.send(Ok(Some(paths))).unwrap();
429 }
430 }
431 })
432 .detach();
433
434 rx
435 }
436
437 fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
438 let directory = directory.to_owned();
439 let (tx, rx) = oneshot::channel();
440 self.foreground_executor()
441 .spawn(async move {
442 unsafe {
443 let Ok(dialog) = show_savefile_dialog(directory) else {
444 let _ = tx.send(Ok(None));
445 return;
446 };
447 let Ok(_) = dialog.Show(None) else {
448 let _ = tx.send(Ok(None)); // user cancel
449 return;
450 };
451 if let Ok(shell_item) = dialog.GetResult() {
452 if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
453 let _ = tx.send(Ok(Some(PathBuf::from(file.to_string().unwrap()))));
454 return;
455 }
456 }
457 let _ = tx.send(Ok(None));
458 }
459 })
460 .detach();
461
462 rx
463 }
464
465 fn reveal_path(&self, path: &Path) {
466 let Ok(file_full_path) = path.canonicalize() else {
467 log::error!("unable to parse file path");
468 return;
469 };
470 self.background_executor()
471 .spawn(async move {
472 let Some(path) = file_full_path.to_str() else {
473 return;
474 };
475 if path.is_empty() {
476 return;
477 }
478 open_target_in_explorer(path);
479 })
480 .detach();
481 }
482
483 fn on_quit(&self, callback: Box<dyn FnMut()>) {
484 self.state.borrow_mut().callbacks.quit = Some(callback);
485 }
486
487 fn on_reopen(&self, callback: Box<dyn FnMut()>) {
488 self.state.borrow_mut().callbacks.reopen = Some(callback);
489 }
490
491 // todo(windows)
492 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
493 fn set_dock_menu(&self, menus: Vec<MenuItem>, keymap: &Keymap) {}
494
495 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
496 self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
497 }
498
499 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
500 self.state.borrow_mut().callbacks.will_open_app_menu = Some(callback);
501 }
502
503 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
504 self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback);
505 }
506
507 fn app_path(&self) -> Result<PathBuf> {
508 Ok(std::env::current_exe()?)
509 }
510
511 // todo(windows)
512 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
513 Err(anyhow!("not yet implemented"))
514 }
515
516 fn set_cursor_style(&self, style: CursorStyle) {
517 let hcursor = load_cursor(style);
518 let mut lock = self.state.borrow_mut();
519 if lock.current_cursor.0 != hcursor.0 {
520 self.post_message(CURSOR_STYLE_CHANGED, WPARAM(0), LPARAM(hcursor.0 as isize));
521 lock.current_cursor = hcursor;
522 }
523 }
524
525 fn should_auto_hide_scrollbars(&self) -> bool {
526 should_auto_hide_scrollbars().log_err().unwrap_or(false)
527 }
528
529 fn write_to_clipboard(&self, item: ClipboardItem) {
530 write_to_clipboard(
531 item,
532 self.clipboard_hash_format,
533 self.clipboard_metadata_format,
534 );
535 }
536
537 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
538 read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
539 }
540
541 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
542 let mut password = password.to_vec();
543 let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
544 let mut target_name = windows_credentials_target_name(url)
545 .encode_utf16()
546 .chain(Some(0))
547 .collect_vec();
548 self.foreground_executor().spawn(async move {
549 let credentials = CREDENTIALW {
550 LastWritten: unsafe { GetSystemTimeAsFileTime() },
551 Flags: CRED_FLAGS(0),
552 Type: CRED_TYPE_GENERIC,
553 TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
554 CredentialBlobSize: password.len() as u32,
555 CredentialBlob: password.as_ptr() as *mut _,
556 Persist: CRED_PERSIST_LOCAL_MACHINE,
557 UserName: PWSTR::from_raw(username.as_mut_ptr()),
558 ..CREDENTIALW::default()
559 };
560 unsafe { CredWriteW(&credentials, 0) }?;
561 Ok(())
562 })
563 }
564
565 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
566 let mut target_name = windows_credentials_target_name(url)
567 .encode_utf16()
568 .chain(Some(0))
569 .collect_vec();
570 self.foreground_executor().spawn(async move {
571 let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
572 unsafe {
573 CredReadW(
574 PCWSTR::from_raw(target_name.as_ptr()),
575 CRED_TYPE_GENERIC,
576 0,
577 &mut credentials,
578 )?
579 };
580
581 if credentials.is_null() {
582 Ok(None)
583 } else {
584 let username: String = unsafe { (*credentials).UserName.to_string()? };
585 let credential_blob = unsafe {
586 std::slice::from_raw_parts(
587 (*credentials).CredentialBlob,
588 (*credentials).CredentialBlobSize as usize,
589 )
590 };
591 let password = credential_blob.to_vec();
592 unsafe { CredFree(credentials as *const c_void) };
593 Ok(Some((username, password)))
594 }
595 })
596 }
597
598 fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
599 let mut target_name = windows_credentials_target_name(url)
600 .encode_utf16()
601 .chain(Some(0))
602 .collect_vec();
603 self.foreground_executor().spawn(async move {
604 unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? };
605 Ok(())
606 })
607 }
608
609 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
610 Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
611 }
612}
613
614impl Drop for WindowsPlatform {
615 fn drop(&mut self) {
616 unsafe {
617 ManuallyDrop::drop(&mut self.bitmap_factory);
618 OleUninitialize();
619 }
620 }
621}
622
623fn open_target(target: &str) {
624 unsafe {
625 let ret = ShellExecuteW(
626 None,
627 windows::core::w!("open"),
628 &HSTRING::from(target),
629 None,
630 None,
631 SW_SHOWDEFAULT,
632 );
633 if ret.0 as isize <= 32 {
634 log::error!("Unable to open target: {}", std::io::Error::last_os_error());
635 }
636 }
637}
638
639fn open_target_in_explorer(target: &str) {
640 unsafe {
641 let ret = ShellExecuteW(
642 None,
643 windows::core::w!("open"),
644 windows::core::w!("explorer.exe"),
645 &HSTRING::from(format!("/select,{}", target).as_str()),
646 None,
647 SW_SHOWDEFAULT,
648 );
649 if ret.0 as isize <= 32 {
650 log::error!(
651 "Unable to open target in explorer: {}",
652 std::io::Error::last_os_error()
653 );
654 }
655 }
656}
657
658unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
659 let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
660 let bind_context = CreateBindCtx(0)?;
661 let Ok(full_path) = directory.canonicalize() else {
662 return Ok(dialog);
663 };
664 let dir_str = full_path.into_os_string();
665 if dir_str.is_empty() {
666 return Ok(dialog);
667 }
668 let dir_vec = dir_str.encode_wide().collect_vec();
669 let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
670 .inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
671 if ret.is_ok() {
672 let dir_shell_item: IShellItem = ret.unwrap();
673 let _ = dialog
674 .SetFolder(&dir_shell_item)
675 .inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
676 }
677
678 Ok(dialog)
679}
680
681fn begin_vsync(vsync_event: HANDLE) {
682 let event: SafeHandle = vsync_event.into();
683 std::thread::spawn(move || unsafe {
684 loop {
685 windows::Win32::Graphics::Dwm::DwmFlush().log_err();
686 SetEvent(*event).log_err();
687 }
688 });
689}
690
691fn load_icon() -> Result<HICON> {
692 let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
693 let handle = unsafe {
694 LoadImageW(
695 module,
696 IDI_APPLICATION,
697 IMAGE_ICON,
698 0,
699 0,
700 LR_DEFAULTSIZE | LR_SHARED,
701 )
702 .context("unable to load icon file")?
703 };
704 Ok(HICON(handle.0))
705}
706
707#[inline]
708fn should_auto_hide_scrollbars() -> Result<bool> {
709 let ui_settings = UISettings::new()?;
710 Ok(ui_settings.AutoHideScrollBars()?)
711}
712
713fn register_clipboard_format(format: PCWSTR) -> Result<u32> {
714 let ret = unsafe { RegisterClipboardFormatW(format) };
715 if ret == 0 {
716 Err(anyhow::anyhow!(
717 "Error when registering clipboard format: {}",
718 std::io::Error::last_os_error()
719 ))
720 } else {
721 Ok(ret)
722 }
723}
724
725fn write_to_clipboard(item: ClipboardItem, hash_format: u32, metadata_format: u32) {
726 write_to_clipboard_inner(item, hash_format, metadata_format).log_err();
727 unsafe { CloseClipboard().log_err() };
728}
729
730fn write_to_clipboard_inner(
731 item: ClipboardItem,
732 hash_format: u32,
733 metadata_format: u32,
734) -> Result<()> {
735 unsafe {
736 OpenClipboard(None)?;
737 EmptyClipboard()?;
738 let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec();
739 set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
740
741 if let Some(ref metadata) = item.metadata {
742 let hash_result = {
743 let hash = ClipboardItem::text_hash(&item.text);
744 hash.to_ne_bytes()
745 };
746 let encode_wide = std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4);
747 set_data_to_clipboard(encode_wide, hash_format)?;
748
749 let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
750 set_data_to_clipboard(&metadata_wide, metadata_format)?;
751 }
752 }
753 Ok(())
754}
755
756fn set_data_to_clipboard(data: &[u16], format: u32) -> Result<()> {
757 unsafe {
758 let global = GlobalAlloc(GMEM_MOVEABLE, data.len() * 2)?;
759 let handle = GlobalLock(global);
760 u_memcpy(handle as _, data.as_ptr(), data.len() as _);
761 let _ = GlobalUnlock(global);
762 SetClipboardData(format, HANDLE(global.0))?;
763 }
764 Ok(())
765}
766
767fn read_from_clipboard(hash_format: u32, metadata_format: u32) -> Option<ClipboardItem> {
768 let result = read_from_clipboard_inner(hash_format, metadata_format).log_err();
769 unsafe { CloseClipboard().log_err() };
770 result
771}
772
773fn read_from_clipboard_inner(hash_format: u32, metadata_format: u32) -> Result<ClipboardItem> {
774 unsafe {
775 OpenClipboard(None)?;
776 let text = {
777 let handle = GetClipboardData(CF_UNICODETEXT.0 as u32)?;
778 let text = PCWSTR(handle.0 as *const u16);
779 String::from_utf16_lossy(text.as_wide())
780 };
781 let mut item = ClipboardItem {
782 text,
783 metadata: None,
784 };
785 let Some(hash) = read_hash_from_clipboard(hash_format) else {
786 return Ok(item);
787 };
788 let Some(metadata) = read_metadata_from_clipboard(metadata_format) else {
789 return Ok(item);
790 };
791 if hash == ClipboardItem::text_hash(&item.text) {
792 item.metadata = Some(metadata);
793 }
794 Ok(item)
795 }
796}
797
798fn read_hash_from_clipboard(hash_format: u32) -> Option<u64> {
799 unsafe {
800 let handle = GetClipboardData(hash_format).log_err()?;
801 let raw_ptr = handle.0 as *const u16;
802 let hash_bytes: [u8; 8] = std::slice::from_raw_parts(raw_ptr.cast::<u8>(), 8)
803 .to_vec()
804 .try_into()
805 .log_err()?;
806 Some(u64::from_ne_bytes(hash_bytes))
807 }
808}
809
810fn read_metadata_from_clipboard(metadata_format: u32) -> Option<String> {
811 unsafe {
812 let handle = GetClipboardData(metadata_format).log_err()?;
813 let text = PCWSTR(handle.0 as *const u16);
814 Some(String::from_utf16_lossy(text.as_wide()))
815 }
816}
817
818// clipboard
819pub const CLIPBOARD_HASH_FORMAT: PCWSTR = windows::core::w!("zed-text-hash");
820pub const CLIPBOARD_METADATA_FORMAT: PCWSTR = windows::core::w!("zed-metadata");
821
822#[cfg(test)]
823mod tests {
824 use crate::{ClipboardItem, Platform, WindowsPlatform};
825
826 #[test]
827 fn test_clipboard() {
828 let platform = WindowsPlatform::new();
829 let item = ClipboardItem::new("你好".to_string());
830 platform.write_to_clipboard(item.clone());
831 assert_eq!(platform.read_from_clipboard(), Some(item));
832
833 let item = ClipboardItem::new("12345".to_string());
834 platform.write_to_clipboard(item.clone());
835 assert_eq!(platform.read_from_clipboard(), Some(item));
836
837 let item = ClipboardItem::new("abcdef".to_string()).with_metadata(vec![3, 4]);
838 platform.write_to_clipboard(item.clone());
839 assert_eq!(platform.read_from_clipboard(), Some(item));
840 }
841}