windows: Implement copy/paste images (#17852)
**Clipboard Behavior on Windows Under This PR:** | User Action | Zed’s Behavior | | ------------------- | -------------------------------------------------- | | Paste PNG | Worked | | Paste JPEG | Worked | | Paste WebP | Worked, but not in the way you expect (see Issue section below) | | Paste GIF | Partially worked (see Issue section below) | | Paste SVG | Partially worked (see Issue section below) | | Paste BMP | Worked, but not in the way you expect (see Issue section below) | | Paste TIFF | Worked, but not in the way you expect (see Issue section below) | | Paste Files | Worked, same behavior as macOS | | Copy image in Zed | Not tested, as I couldn’t find a way to copy images | --- **Differences Between the Windows and macOS Clipboard** The clipboard functionality on Windows differs significantly from macOS. On macOS, there can be multiple items in the clipboard, whereas, on Windows, the clipboard holds only a single item. You can retrieve different formats from the clipboard, but they are all just different representations of the same item. For example, when you copy a JPG image from Microsoft Word, the clipboard will contain data in several formats: - Microsoft Office proprietary data - JPG format data - PNG format data - SVG format data Please note that these formats all represent the same image, just in different formats. This is due to compatibility concerns on Windows, as various applications support different formats. Ideally, multiple formats should be placed on the clipboard to support more software. However, in general, supporting PNG will cover 99% of software, like Chrome, which only supports PNG and BMP formats. Additionally, since the clipboard on Windows only contains a single item, special handling is required when copying multiple objects, such as text and images. For instance, if you copy both text and an image simultaneously in Microsoft Word, Microsoft places the following data on the clipboard: - Microsoft Office proprietary data containing a lot of content such as text fonts, sizes, italics, positioning, image size, content, etc. - RTF data representing the above content in RTF format - HTML data representing the content in HTML format - Plain text data Therefore, for the current `ClipboardItem` implementation, if there are multiple `ClipboardEntry` objects to be placed on the clipboard, RTF or HTML formats are required. This PR does not support this scenario, and only supports copying or pasting a single item from the clipboard. --- **Known Issues** - **WebP, BMP, TIFF**: These formats are not explicitly supported in this PR. However, as mentioned earlier, in most cases, there are corresponding PNG format data on the clipboard. This PR retrieves data via PNG format, so users copying images in these formats from other sources will still see the images displayed correctly. - **GIF**: In this PR, GIFs are displayed, but for GIF images with multiple frames, the image will not animate and will freeze on a single frame. Since I observed the same behavior on macOS, I believe this is not an issue with this PR. - **SVG**: In this PR, only the top-left corner of the SVG image is displayed. Again, I observed the same behavior on macOS, so I believe this issue is not specific to this PR. --- I hope this provides a clearer understanding. Any feedback or suggestions on how to improve this are welcome. Release Notes: - N/A
This commit is contained in:
parent
ecb7144b95
commit
77506afd83
5 changed files with 408 additions and 157 deletions
|
@ -1,3 +1,4 @@
|
|||
mod clipboard;
|
||||
mod direct_write;
|
||||
mod dispatcher;
|
||||
mod display;
|
||||
|
@ -8,6 +9,7 @@ mod util;
|
|||
mod window;
|
||||
mod wrapper;
|
||||
|
||||
pub(crate) use clipboard::*;
|
||||
pub(crate) use direct_write::*;
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
|
|
366
crates/gpui/src/platform/windows/clipboard.rs
Normal file
366
crates/gpui/src/platform/windows/clipboard.rs
Normal file
|
@ -0,0 +1,366 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
use itertools::Itertools;
|
||||
use util::ResultExt;
|
||||
use windows::Win32::{
|
||||
Foundation::HANDLE,
|
||||
System::{
|
||||
DataExchange::{
|
||||
CloseClipboard, CountClipboardFormats, EmptyClipboard, EnumClipboardFormats,
|
||||
GetClipboardData, GetClipboardFormatNameW, IsClipboardFormatAvailable, OpenClipboard,
|
||||
RegisterClipboardFormatW, SetClipboardData,
|
||||
},
|
||||
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
|
||||
Ole::{CF_HDROP, CF_UNICODETEXT},
|
||||
},
|
||||
UI::Shell::{DragQueryFileW, HDROP},
|
||||
};
|
||||
use windows_core::PCWSTR;
|
||||
|
||||
use crate::{
|
||||
hash, ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, SmartGlobal,
|
||||
};
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
|
||||
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
||||
|
||||
// Clipboard formats
|
||||
static CLIPBOARD_HASH_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal text hash")));
|
||||
static CLIPBOARD_METADATA_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal metadata")));
|
||||
static CLIPBOARD_SVG_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("image/svg+xml")));
|
||||
static CLIPBOARD_GIF_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("GIF")));
|
||||
static CLIPBOARD_PNG_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("PNG")));
|
||||
static CLIPBOARD_JPG_FORMAT: LazyLock<u32> =
|
||||
LazyLock::new(|| register_clipboard_format(windows::core::w!("JFIF")));
|
||||
|
||||
// Helper maps and sets
|
||||
static FORMATS_MAP: LazyLock<FxHashMap<u32, ClipboardFormatType>> = LazyLock::new(|| {
|
||||
let mut formats_map = FxHashMap::default();
|
||||
formats_map.insert(CF_UNICODETEXT.0 as u32, ClipboardFormatType::Text);
|
||||
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ClipboardFormatType::Image);
|
||||
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ClipboardFormatType::Image);
|
||||
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ClipboardFormatType::Image);
|
||||
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ClipboardFormatType::Image);
|
||||
formats_map.insert(CF_HDROP.0 as u32, ClipboardFormatType::Files);
|
||||
formats_map
|
||||
});
|
||||
static FORMATS_SET: LazyLock<FxHashSet<u32>> = LazyLock::new(|| {
|
||||
let mut formats_map = FxHashSet::default();
|
||||
formats_map.insert(CF_UNICODETEXT.0 as u32);
|
||||
formats_map.insert(*CLIPBOARD_PNG_FORMAT);
|
||||
formats_map.insert(*CLIPBOARD_GIF_FORMAT);
|
||||
formats_map.insert(*CLIPBOARD_JPG_FORMAT);
|
||||
formats_map.insert(*CLIPBOARD_SVG_FORMAT);
|
||||
formats_map.insert(CF_HDROP.0 as u32);
|
||||
formats_map
|
||||
});
|
||||
static IMAGE_FORMATS_MAP: LazyLock<FxHashMap<u32, ImageFormat>> = LazyLock::new(|| {
|
||||
let mut formats_map = FxHashMap::default();
|
||||
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ImageFormat::Png);
|
||||
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ImageFormat::Gif);
|
||||
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ImageFormat::Jpeg);
|
||||
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ImageFormat::Svg);
|
||||
formats_map
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum ClipboardFormatType {
|
||||
Text,
|
||||
Image,
|
||||
Files,
|
||||
}
|
||||
|
||||
pub(crate) fn write_to_clipboard(item: ClipboardItem) {
|
||||
write_to_clipboard_inner(item).log_err();
|
||||
unsafe { CloseClipboard().log_err() };
|
||||
}
|
||||
|
||||
pub(crate) fn read_from_clipboard() -> Option<ClipboardItem> {
|
||||
let result = read_from_clipboard_inner();
|
||||
unsafe { CloseClipboard().log_err() };
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn with_file_names<F>(hdrop: HDROP, mut f: F)
|
||||
where
|
||||
F: FnMut(String),
|
||||
{
|
||||
let file_count = unsafe { DragQueryFileW(hdrop, DRAGDROP_GET_FILES_COUNT, None) };
|
||||
for file_index in 0..file_count {
|
||||
let filename_length = unsafe { DragQueryFileW(hdrop, file_index, None) } as usize;
|
||||
let mut buffer = vec![0u16; filename_length + 1];
|
||||
let ret = unsafe { DragQueryFileW(hdrop, file_index, Some(buffer.as_mut_slice())) };
|
||||
if ret == 0 {
|
||||
log::error!("unable to read file name");
|
||||
continue;
|
||||
}
|
||||
if let Some(file_name) = String::from_utf16(&buffer[0..filename_length]).log_err() {
|
||||
f(file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_clipboard_format(format: PCWSTR) -> u32 {
|
||||
let ret = unsafe { RegisterClipboardFormatW(format) };
|
||||
if ret == 0 {
|
||||
panic!(
|
||||
"Error when registering clipboard format: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_to_type(item_format: u32) -> &'static ClipboardFormatType {
|
||||
FORMATS_MAP.get(&item_format).unwrap()
|
||||
}
|
||||
|
||||
// Currently, we only write the first item.
|
||||
fn write_to_clipboard_inner(item: ClipboardItem) -> Result<()> {
|
||||
unsafe {
|
||||
OpenClipboard(None)?;
|
||||
EmptyClipboard()?;
|
||||
}
|
||||
match item.entries().first() {
|
||||
Some(entry) => match entry {
|
||||
ClipboardEntry::String(string) => {
|
||||
write_string_to_clipboard(string)?;
|
||||
}
|
||||
ClipboardEntry::Image(image) => {
|
||||
write_image_to_clipboard(image)?;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// Writing an empty list of entries just clears the clipboard.
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_string_to_clipboard(item: &ClipboardString) -> Result<()> {
|
||||
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(metadata) = item.metadata.as_ref() {
|
||||
let hash_result = {
|
||||
let hash = ClipboardString::text_hash(&item.text);
|
||||
hash.to_ne_bytes()
|
||||
};
|
||||
let encode_wide =
|
||||
unsafe { std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4) };
|
||||
set_data_to_clipboard(encode_wide, *CLIPBOARD_HASH_FORMAT)?;
|
||||
|
||||
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
|
||||
set_data_to_clipboard(&metadata_wide, *CLIPBOARD_METADATA_FORMAT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_data_to_clipboard<T>(data: &[T], format: u32) -> Result<()> {
|
||||
unsafe {
|
||||
let global = GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(data))?;
|
||||
let handle = GlobalLock(global);
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), handle as _, data.len());
|
||||
let _ = GlobalUnlock(global);
|
||||
SetClipboardData(format, HANDLE(global.0))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Here writing PNG to the clipboard to better support other apps. For more info, please ref to
|
||||
// the PR.
|
||||
fn write_image_to_clipboard(item: &Image) -> Result<()> {
|
||||
match item.format {
|
||||
ImageFormat::Svg => set_data_to_clipboard(item.bytes(), *CLIPBOARD_SVG_FORMAT)?,
|
||||
ImageFormat::Gif => {
|
||||
set_data_to_clipboard(item.bytes(), *CLIPBOARD_GIF_FORMAT)?;
|
||||
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Gif)?;
|
||||
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
|
||||
}
|
||||
ImageFormat::Png => {
|
||||
set_data_to_clipboard(item.bytes(), *CLIPBOARD_PNG_FORMAT)?;
|
||||
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Png)?;
|
||||
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
|
||||
}
|
||||
ImageFormat::Jpeg => {
|
||||
set_data_to_clipboard(item.bytes(), *CLIPBOARD_JPG_FORMAT)?;
|
||||
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Jpeg)?;
|
||||
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
|
||||
}
|
||||
other => {
|
||||
log::warn!(
|
||||
"Clipboard unsupported image format: {:?}, convert to PNG instead.",
|
||||
item.format
|
||||
);
|
||||
let png_bytes = convert_image_to_png_format(item.bytes(), other)?;
|
||||
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_image_to_png_format(bytes: &[u8], image_format: ImageFormat) -> Result<Vec<u8>> {
|
||||
let image = image::load_from_memory_with_format(bytes, image_format.into())?;
|
||||
let mut output_buf = Vec::new();
|
||||
image.write_to(
|
||||
&mut std::io::Cursor::new(&mut output_buf),
|
||||
image::ImageFormat::Png,
|
||||
)?;
|
||||
Ok(output_buf)
|
||||
}
|
||||
|
||||
fn read_from_clipboard_inner() -> Option<ClipboardItem> {
|
||||
unsafe { OpenClipboard(None) }.log_err()?;
|
||||
with_best_match_format(|item_format| match format_to_type(item_format) {
|
||||
ClipboardFormatType::Text => read_string_from_clipboard(),
|
||||
ClipboardFormatType::Image => read_image_from_clipboard(item_format),
|
||||
ClipboardFormatType::Files => read_files_from_clipboard(),
|
||||
})
|
||||
}
|
||||
|
||||
// Here, we enumerate all formats on the clipboard and find the first one that we can process.
|
||||
// The reason we don't use `GetPriorityClipboardFormat` is that it sometimes returns the
|
||||
// wrong format.
|
||||
// For instance, when copying a JPEG image from Microsoft Word, there may be several formats
|
||||
// on the clipboard: Jpeg, Png, Svg.
|
||||
// If we use `GetPriorityClipboardFormat`, it will return Svg, which is not what we want.
|
||||
fn with_best_match_format<F>(f: F) -> Option<ClipboardItem>
|
||||
where
|
||||
F: Fn(u32) -> Option<ClipboardEntry>,
|
||||
{
|
||||
let count = unsafe { CountClipboardFormats() };
|
||||
let mut clipboard_format = 0;
|
||||
for _ in 0..count {
|
||||
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
|
||||
let Some(item_format) = FORMATS_SET.get(&clipboard_format) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(entry) = f(*item_format) {
|
||||
return Some(ClipboardItem {
|
||||
entries: vec![entry],
|
||||
});
|
||||
}
|
||||
}
|
||||
// log the formats that we don't support yet.
|
||||
{
|
||||
clipboard_format = 0;
|
||||
for _ in 0..count {
|
||||
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
|
||||
let mut buffer = [0u16; 64];
|
||||
unsafe { GetClipboardFormatNameW(clipboard_format, &mut buffer) };
|
||||
let format_name = String::from_utf16_lossy(&buffer);
|
||||
log::warn!(
|
||||
"Try to paste with unsupported clipboard format: {}, {}.",
|
||||
clipboard_format,
|
||||
format_name
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn read_string_from_clipboard() -> Option<ClipboardEntry> {
|
||||
let text = {
|
||||
let global = SmartGlobal::from_raw_ptr(
|
||||
unsafe { GetClipboardData(CF_UNICODETEXT.0 as u32).log_err() }?.0,
|
||||
);
|
||||
let text = PCWSTR(global.lock() as *const u16);
|
||||
String::from_utf16_lossy(unsafe { text.as_wide() })
|
||||
};
|
||||
let Some(hash) = read_hash_from_clipboard() else {
|
||||
return Some(ClipboardEntry::String(ClipboardString::new(text)));
|
||||
};
|
||||
let Some(metadata) = read_metadata_from_clipboard() else {
|
||||
return Some(ClipboardEntry::String(ClipboardString::new(text)));
|
||||
};
|
||||
if hash == ClipboardString::text_hash(&text) {
|
||||
Some(ClipboardEntry::String(ClipboardString {
|
||||
text,
|
||||
metadata: Some(metadata),
|
||||
}))
|
||||
} else {
|
||||
Some(ClipboardEntry::String(ClipboardString::new(text)))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_hash_from_clipboard() -> Option<u64> {
|
||||
if unsafe { IsClipboardFormatAvailable(*CLIPBOARD_HASH_FORMAT).is_err() } {
|
||||
return None;
|
||||
}
|
||||
let global =
|
||||
SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(*CLIPBOARD_HASH_FORMAT).log_err() }?.0);
|
||||
let raw_ptr = global.lock() as *const u16;
|
||||
let hash_bytes: [u8; 8] = unsafe {
|
||||
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() -> Option<String> {
|
||||
unsafe { IsClipboardFormatAvailable(*CLIPBOARD_METADATA_FORMAT).log_err()? };
|
||||
let global = SmartGlobal::from_raw_ptr(
|
||||
unsafe { GetClipboardData(*CLIPBOARD_METADATA_FORMAT).log_err() }?.0,
|
||||
);
|
||||
let text = PCWSTR(global.lock() as *const u16);
|
||||
Some(String::from_utf16_lossy(unsafe { text.as_wide() }))
|
||||
}
|
||||
|
||||
fn read_image_from_clipboard(format: u32) -> Option<ClipboardEntry> {
|
||||
let image_format = format_number_to_image_format(format)?;
|
||||
read_image_for_type(format, *image_format)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_number_to_image_format(format_number: u32) -> Option<&'static ImageFormat> {
|
||||
IMAGE_FORMATS_MAP.get(&format_number)
|
||||
}
|
||||
|
||||
fn read_image_for_type(format_number: u32, format: ImageFormat) -> Option<ClipboardEntry> {
|
||||
let global = SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(format_number).log_err() }?.0);
|
||||
let image_ptr = global.lock();
|
||||
let iamge_size = global.size();
|
||||
let bytes =
|
||||
unsafe { std::slice::from_raw_parts(image_ptr as *mut u8 as _, iamge_size).to_vec() };
|
||||
let id = hash(&bytes);
|
||||
Some(ClipboardEntry::Image(Image { format, bytes, id }))
|
||||
}
|
||||
|
||||
fn read_files_from_clipboard() -> Option<ClipboardEntry> {
|
||||
let global =
|
||||
SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(CF_HDROP.0 as u32).log_err() }?.0);
|
||||
let hdrop = HDROP(global.lock());
|
||||
let mut filenames = String::new();
|
||||
with_file_names(hdrop, |file_name| {
|
||||
filenames.push_str(&file_name);
|
||||
});
|
||||
Some(ClipboardEntry::String(ClipboardString {
|
||||
text: filenames,
|
||||
metadata: None,
|
||||
}))
|
||||
}
|
||||
|
||||
impl From<ImageFormat> for image::ImageFormat {
|
||||
fn from(value: ImageFormat) -> Self {
|
||||
match value {
|
||||
ImageFormat::Png => image::ImageFormat::Png,
|
||||
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
|
||||
ImageFormat::Webp => image::ImageFormat::WebP,
|
||||
ImageFormat::Gif => image::ImageFormat::Gif,
|
||||
// ImageFormat::Svg => todo!(),
|
||||
ImageFormat::Bmp => image::ImageFormat::Bmp,
|
||||
ImageFormat::Tiff => image::ImageFormat::Tiff,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,24 +17,12 @@ use windows::{
|
|||
core::*,
|
||||
Win32::{
|
||||
Foundation::*,
|
||||
Globalization::u_memcpy,
|
||||
Graphics::{
|
||||
Gdi::*,
|
||||
Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
|
||||
},
|
||||
Security::Credentials::*,
|
||||
System::{
|
||||
Com::*,
|
||||
DataExchange::{
|
||||
CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard,
|
||||
RegisterClipboardFormatW, SetClipboardData,
|
||||
},
|
||||
LibraryLoader::*,
|
||||
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
|
||||
Ole::*,
|
||||
SystemInformation::*,
|
||||
Threading::*,
|
||||
},
|
||||
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*},
|
||||
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
|
||||
},
|
||||
UI::ViewManagement::UISettings,
|
||||
|
@ -52,8 +40,6 @@ pub(crate) struct WindowsPlatform {
|
|||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<DirectWriteTextSystem>,
|
||||
clipboard_hash_format: u32,
|
||||
clipboard_metadata_format: u32,
|
||||
windows_version: WindowsVersion,
|
||||
bitmap_factory: ManuallyDrop<IWICImagingFactory>,
|
||||
validation_number: usize,
|
||||
|
@ -108,9 +94,6 @@ 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();
|
||||
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
|
||||
let validation_number = rand::random::<usize>();
|
||||
|
||||
|
@ -123,8 +106,6 @@ impl WindowsPlatform {
|
|||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
clipboard_hash_format,
|
||||
clipboard_metadata_format,
|
||||
windows_version,
|
||||
bitmap_factory,
|
||||
validation_number,
|
||||
|
@ -487,15 +468,11 @@ impl Platform for WindowsPlatform {
|
|||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
write_to_clipboard(
|
||||
item,
|
||||
self.clipboard_hash_format,
|
||||
self.clipboard_metadata_format,
|
||||
);
|
||||
write_to_clipboard(item);
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
|
||||
read_from_clipboard()
|
||||
}
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||
|
@ -725,117 +702,6 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
|
|||
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()
|
||||
.unwrap_or_default()
|
||||
.encode_utf16()
|
||||
.chain(Some(0))
|
||||
.collect_vec();
|
||||
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
|
||||
|
||||
if let Some((metadata, text)) = item.metadata().zip(item.text()) {
|
||||
let hash_result = {
|
||||
let hash = ClipboardString::text_hash(&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))?;
|
||||
}
|
||||
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 Some(hash) = read_hash_from_clipboard(hash_format) else {
|
||||
return Ok(ClipboardItem::new_string(text));
|
||||
};
|
||||
let Some(metadata) = read_metadata_from_clipboard(metadata_format) else {
|
||||
return Ok(ClipboardItem::new_string(text));
|
||||
};
|
||||
if hash == ClipboardString::text_hash(&text) {
|
||||
Ok(ClipboardItem::new_string_with_metadata(text, metadata))
|
||||
} else {
|
||||
Ok(ClipboardItem::new_string(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
|
|
|
@ -735,23 +735,11 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
|
|||
}
|
||||
let hdrop = idata.u.hGlobal.0 as *mut HDROP;
|
||||
let mut paths = SmallVec::<[PathBuf; 2]>::new();
|
||||
let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
|
||||
for file_index in 0..file_count {
|
||||
let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
|
||||
let mut buffer = vec![0u16; filename_length + 1];
|
||||
let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
|
||||
if ret == 0 {
|
||||
log::error!("unable to read file name");
|
||||
continue;
|
||||
}
|
||||
if let Some(file_name) =
|
||||
String::from_utf16(&buffer[0..filename_length]).log_err()
|
||||
{
|
||||
with_file_names(*hdrop, |file_name| {
|
||||
if let Some(path) = PathBuf::from_str(&file_name).log_err() {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ReleaseStgMedium(&mut idata);
|
||||
let mut cursor_position = POINT { x: pt.x, y: pt.y };
|
||||
ScreenToClient(self.0.hwnd, &mut cursor_position)
|
||||
|
@ -1069,9 +1057,6 @@ fn calculate_client_rect(
|
|||
}
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
|
||||
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
||||
|
||||
mod windows_renderer {
|
||||
use std::{num::NonZeroIsize, sync::Arc};
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use windows::Win32::{Foundation::HANDLE, UI::WindowsAndMessaging::HCURSOR};
|
||||
use util::ResultExt;
|
||||
use windows::Win32::{
|
||||
Foundation::{HANDLE, HGLOBAL},
|
||||
System::Memory::{GlobalLock, GlobalSize, GlobalUnlock},
|
||||
UI::WindowsAndMessaging::HCURSOR,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SafeHandle {
|
||||
|
@ -45,3 +50,30 @@ impl Deref for SafeCursor {
|
|||
&self.raw
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SmartGlobal {
|
||||
raw: HGLOBAL,
|
||||
}
|
||||
|
||||
impl SmartGlobal {
|
||||
pub(crate) fn from_raw_ptr(ptr: *mut std::ffi::c_void) -> Self {
|
||||
Self { raw: HGLOBAL(ptr) }
|
||||
}
|
||||
|
||||
pub(crate) fn lock(&self) -> *mut std::ffi::c_void {
|
||||
unsafe { GlobalLock(self.raw) }
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
unsafe { GlobalSize(self.raw) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SmartGlobal {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
GlobalUnlock(self.raw).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue