windows: Refactor clipboard implementation (#14347)
This PR provides a similar implementation to the macOS clipboard implementation, adds support for metadata and includes tests. Release Notes: - N/A
This commit is contained in:
parent
ba09eabfba
commit
315692d112
7 changed files with 180 additions and 35 deletions
|
@ -12,7 +12,6 @@ use std::{
|
|||
|
||||
use ::util::ResultExt;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clipboard_win::{get_clipboard_string, set_clipboard_string};
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::RwLock;
|
||||
|
@ -22,9 +21,22 @@ use windows::{
|
|||
core::*,
|
||||
Win32::{
|
||||
Foundation::*,
|
||||
Globalization::u_memcpy,
|
||||
Graphics::Gdi::*,
|
||||
Security::Credentials::*,
|
||||
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
|
||||
System::{
|
||||
Com::*,
|
||||
DataExchange::{
|
||||
CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard,
|
||||
RegisterClipboardFormatW, SetClipboardData,
|
||||
},
|
||||
LibraryLoader::*,
|
||||
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
|
||||
Ole::*,
|
||||
SystemInformation::*,
|
||||
Threading::*,
|
||||
Time::*,
|
||||
},
|
||||
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
|
||||
},
|
||||
UI::ViewManagement::UISettings,
|
||||
|
@ -40,6 +52,8 @@ pub(crate) struct WindowsPlatform {
|
|||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<dyn PlatformTextSystem>,
|
||||
clipboard_hash_format: u32,
|
||||
clipboard_metadata_format: u32,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowsPlatformState {
|
||||
|
@ -88,6 +102,9 @@ impl WindowsPlatform {
|
|||
let icon = load_icon().unwrap_or_default();
|
||||
let state = RefCell::new(WindowsPlatformState::new());
|
||||
let raw_window_handles = RwLock::new(SmallVec::new());
|
||||
let clipboard_hash_format = register_clipboard_format(CLIPBOARD_HASH_FORMAT).unwrap();
|
||||
let clipboard_metadata_format =
|
||||
register_clipboard_format(CLIPBOARD_METADATA_FORMAT).unwrap();
|
||||
|
||||
Self {
|
||||
state,
|
||||
|
@ -96,6 +113,8 @@ impl WindowsPlatform {
|
|||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
clipboard_hash_format,
|
||||
clipboard_metadata_format,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,17 +517,15 @@ impl Platform for WindowsPlatform {
|
|||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
if item.text.len() > 0 {
|
||||
set_clipboard_string(item.text()).unwrap();
|
||||
}
|
||||
write_to_clipboard(
|
||||
item,
|
||||
self.clipboard_hash_format,
|
||||
self.clipboard_metadata_format,
|
||||
);
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
let text = get_clipboard_string().ok()?;
|
||||
Some(ClipboardItem {
|
||||
text,
|
||||
metadata: None,
|
||||
})
|
||||
read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
|
||||
}
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||
|
@ -586,9 +603,8 @@ impl Platform for WindowsPlatform {
|
|||
|
||||
impl Drop for WindowsPlatform {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
OleUninitialize();
|
||||
}
|
||||
self.text_system.destroy();
|
||||
unsafe { OleUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -680,3 +696,133 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
|
|||
let ui_settings = UISettings::new()?;
|
||||
Ok(ui_settings.AutoHideScrollBars()?)
|
||||
}
|
||||
|
||||
fn register_clipboard_format(format: PCWSTR) -> Result<u32> {
|
||||
let ret = unsafe { RegisterClipboardFormatW(format) };
|
||||
if ret == 0 {
|
||||
Err(anyhow::anyhow!(
|
||||
"Error when registering clipboard format: {}",
|
||||
std::io::Error::last_os_error()
|
||||
))
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_clipboard(item: ClipboardItem, hash_format: u32, metadata_format: u32) {
|
||||
write_to_clipboard_inner(item, hash_format, metadata_format).log_err();
|
||||
unsafe { CloseClipboard().log_err() };
|
||||
}
|
||||
|
||||
fn write_to_clipboard_inner(
|
||||
item: ClipboardItem,
|
||||
hash_format: u32,
|
||||
metadata_format: u32,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
OpenClipboard(None)?;
|
||||
EmptyClipboard()?;
|
||||
let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec();
|
||||
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
|
||||
|
||||
if let Some(ref metadata) = item.metadata {
|
||||
let hash_result = {
|
||||
let hash = ClipboardItem::text_hash(&item.text);
|
||||
hash.to_ne_bytes()
|
||||
};
|
||||
let encode_wide = std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4);
|
||||
set_data_to_clipboard(encode_wide, hash_format)?;
|
||||
|
||||
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
|
||||
set_data_to_clipboard(&metadata_wide, metadata_format)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_data_to_clipboard(data: &[u16], format: u32) -> Result<()> {
|
||||
unsafe {
|
||||
let global = GlobalAlloc(GMEM_MOVEABLE, data.len() * 2)?;
|
||||
let handle = GlobalLock(global);
|
||||
u_memcpy(handle as _, data.as_ptr(), data.len() as _);
|
||||
let _ = GlobalUnlock(global);
|
||||
SetClipboardData(format, HANDLE(global.0 as isize))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_from_clipboard(hash_format: u32, metadata_format: u32) -> Option<ClipboardItem> {
|
||||
let result = read_from_clipboard_inner(hash_format, metadata_format).log_err();
|
||||
unsafe { CloseClipboard().log_err() };
|
||||
result
|
||||
}
|
||||
|
||||
fn read_from_clipboard_inner(hash_format: u32, metadata_format: u32) -> Result<ClipboardItem> {
|
||||
unsafe {
|
||||
OpenClipboard(None)?;
|
||||
let text = {
|
||||
let handle = GetClipboardData(CF_UNICODETEXT.0 as u32)?;
|
||||
let text = PCWSTR(handle.0 as *const u16);
|
||||
String::from_utf16_lossy(text.as_wide())
|
||||
};
|
||||
let mut item = ClipboardItem {
|
||||
text,
|
||||
metadata: None,
|
||||
};
|
||||
let Some(hash) = read_hash_from_clipboard(hash_format) else {
|
||||
return Ok(item);
|
||||
};
|
||||
let Some(metadata) = read_metadata_from_clipboard(metadata_format) else {
|
||||
return Ok(item);
|
||||
};
|
||||
if hash == ClipboardItem::text_hash(&item.text) {
|
||||
item.metadata = Some(metadata);
|
||||
}
|
||||
Ok(item)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_hash_from_clipboard(hash_format: u32) -> Option<u64> {
|
||||
unsafe {
|
||||
let handle = GetClipboardData(hash_format).log_err()?;
|
||||
let raw_ptr = handle.0 as *const u16;
|
||||
let hash_bytes: [u8; 8] = std::slice::from_raw_parts(raw_ptr.cast::<u8>(), 8)
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.log_err()?;
|
||||
Some(u64::from_ne_bytes(hash_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_metadata_from_clipboard(metadata_format: u32) -> Option<String> {
|
||||
unsafe {
|
||||
let handle = GetClipboardData(metadata_format).log_err()?;
|
||||
let text = PCWSTR(handle.0 as *const u16);
|
||||
Some(String::from_utf16_lossy(text.as_wide()))
|
||||
}
|
||||
}
|
||||
|
||||
// clipboard
|
||||
pub const CLIPBOARD_HASH_FORMAT: PCWSTR = windows::core::w!("zed-text-hash");
|
||||
pub const CLIPBOARD_METADATA_FORMAT: PCWSTR = windows::core::w!("zed-metadata");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ClipboardItem, Platform, WindowsPlatform};
|
||||
|
||||
#[test]
|
||||
fn test_clipboard() {
|
||||
let platform = WindowsPlatform::new();
|
||||
let item = ClipboardItem::new("你好".to_string());
|
||||
platform.write_to_clipboard(item.clone());
|
||||
assert_eq!(platform.read_from_clipboard(), Some(item));
|
||||
|
||||
let item = ClipboardItem::new("12345".to_string());
|
||||
platform.write_to_clipboard(item.clone());
|
||||
assert_eq!(platform.read_from_clipboard(), Some(item));
|
||||
|
||||
let item = ClipboardItem::new("abcdef".to_string()).with_metadata(vec![3, 4]);
|
||||
platform.write_to_clipboard(item.clone());
|
||||
assert_eq!(platform.read_from_clipboard(), Some(item));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue