Skip to content

Instantly share code, notes, and snippets.

@eliaxelang007
Created January 10, 2024 09:24
Show Gist options
  • Save eliaxelang007/f359e1cf4e31afc90cbe99e1ce68b9ca to your computer and use it in GitHub Desktop.
Save eliaxelang007/f359e1cf4e31afc90cbe99e1ce68b9ca to your computer and use it in GitHub Desktop.
Copy Text File To Clipboard On Windows In Rust!
[package]
name = "ditto"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0.56"
windows-sys = { version = "0.52.0", features = [
"Win32_Foundation",
"Win32_System",
"Win32_System_Memory",
"Win32_System_DataExchange",
"Win32_System_Ole",
] }
use std::env::args;
use std::fs::read_to_string;
use std::iter::once;
use std::ops::{Deref, DerefMut};
use std::slice::from_raw_parts_mut;
use windows_sys::Win32::Foundation::{GetLastError, NOERROR, WIN32_ERROR};
use windows_sys::Win32::System::DataExchange::{CloseClipboard, SetClipboardData};
use windows_sys::Win32::{
Foundation::{GlobalFree, HGLOBAL},
System::{
DataExchange::OpenClipboard,
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock},
Ole::CF_UNICODETEXT,
},
};
#[derive(Debug)]
struct GlobalMemoryHandle {
handle: HGLOBAL,
size: usize,
}
enum MemoryInit {
Zeroed = 0x0040,
Random = 0x0000,
}
enum MemoryType {
Fixed = 0x0000,
Movable = 0x0002,
}
#[derive(Debug)]
struct GlobalMemoryGuard<'a> {
global_memory_handle: &'a GlobalMemoryHandle,
global_memory: &'a mut [u8],
}
impl<'a> GlobalMemoryGuard<'a> {
fn new(global_memory_handle: &'a GlobalMemoryHandle) -> Self {
GlobalMemoryGuard {
global_memory_handle,
global_memory: {
let raw_global_memory = unsafe { GlobalLock(global_memory_handle.handle) };
unsafe {
from_raw_parts_mut(raw_global_memory as *mut u8, global_memory_handle.size)
}
},
}
}
}
impl<'a> DerefMut for GlobalMemoryGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.global_memory
}
}
impl<'a> Deref for GlobalMemoryGuard<'a> {
type Target = &'a mut [u8];
fn deref(&self) -> &Self::Target {
&self.global_memory
}
}
impl<'a> Drop for GlobalMemoryGuard<'a> {
fn drop(&mut self) {
let result = unsafe { GlobalUnlock(self.global_memory_handle.handle) };
if result > 1 {
panic!("Failed to fully unlock global memory!");
} else if unsafe { GetLastError() } != NOERROR {
panic!("Failed to unlock global memory!");
}
}
}
impl GlobalMemoryHandle {
fn new(
kind: MemoryType,
init: MemoryInit,
size: usize,
) -> Result<GlobalMemoryHandle, WIN32_ERROR> {
let maybe_memory_handle = unsafe { GlobalAlloc(kind as u32 | init as u32, size) };
if maybe_memory_handle.is_null() {
return Err(unsafe { GetLastError() });
}
Ok(GlobalMemoryHandle {
handle: maybe_memory_handle,
size: size,
})
}
fn lock(&self) -> GlobalMemoryGuard {
GlobalMemoryGuard::new(self)
}
}
impl Drop for GlobalMemoryHandle {
fn drop(&mut self) {
if !unsafe { GlobalFree(self.handle) }.is_null() {
panic!("Failed to free global memory!");
}
}
}
struct Clipboard;
enum ClipboardCommand<'a> {
Set(&'a str),
}
struct ClipboardCommandGuard<'a>(ClipboardCommand<'a>);
impl<'a> ClipboardCommandGuard<'a> {
fn new(command: ClipboardCommand<'a>) -> Result<Self, WIN32_ERROR> {
if unsafe { OpenClipboard(0) } == 0 {
return Err(unsafe { GetLastError() });
}
Ok(ClipboardCommandGuard(command))
}
fn with(&self) -> Result<(), WIN32_ERROR> {
use ClipboardCommand as C;
Ok(match self.0 {
C::Set(text) => {
let unicode = text
.encode_utf16()
.chain(once(0))
.flat_map(|grapheme| grapheme.to_le_bytes())
.collect::<Vec<u8>>();
let windows_memory_handle = GlobalMemoryHandle::new(
MemoryType::Movable,
MemoryInit::Random,
unicode.len(),
)?;
{
let mut windows_memory = windows_memory_handle.lock();
windows_memory.copy_from_slice(&unicode);
}
let result = unsafe {
SetClipboardData(
CF_UNICODETEXT as u32,
windows_memory_handle.handle as isize,
)
};
if result == 0 {
return Err(unsafe { GetLastError() });
}
}
})
}
}
impl<'a> Drop for ClipboardCommandGuard<'a> {
fn drop(&mut self) {
if unsafe { CloseClipboard() } == 0 {
panic!("Failed to close clipboard!");
}
}
}
impl Clipboard {
fn execute(command: ClipboardCommand) -> Result<(), WIN32_ERROR> {
Ok(ClipboardCommandGuard::new(command)?.with()?)
}
}
fn main() {
let opened_with = args()
.skip(1)
.next()
.expect("Expected file as command line argument!");
let file_contents = read_to_string(opened_with).expect("Failed to read file as string!");
Clipboard::execute(ClipboardCommand::Set(file_contents.as_ref()))
.expect("Failed write file to clipboard!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment