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-dragqueryfilewconst DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF; // Clipboard formatsstatic 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 setsstatic 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, // TODO: ImageFormat::Svg ImageFormat::Bmp => image::ImageFormat::Bmp, ImageFormat::Tiff => image::ImageFormat::Tiff, _ => unreachable!(), } }}
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,
// TODO: ImageFormat::Svg
ImageFormat::Bmp => image::ImageFormat::Bmp,
ImageFormat::Tiff => image::ImageFormat::Tiff,
_ => unreachable!(),
}
}
}