Skip to content

Commit f45630c

Browse files
committed
Add Windows user clipboard agent
Signed-off-by: Tu Dinh <[email protected]>
1 parent 75eb9f7 commit f45630c

File tree

8 files changed

+431
-0
lines changed

8 files changed

+431
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
"publishers/publisher-xenstore",
1616
"publishers/publisher-console",
1717
"xen-win-utils",
18+
"xen-win-clipboard",
1819
]
1920

2021
[profile.release]

xen-win-clipboard/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "xen-win-clipboard"
3+
version = "0.5.0-dev"
4+
edition = "2021"
5+
rust-version = "1.76"
6+
license = "AGPL-3.0-only"
7+
8+
[dependencies]
9+
anyhow = "1.0"
10+
# not sure why explicitly specifying std is needed here
11+
log = { version = "0.4.0", features = ["std"] }
12+
windows = { version = "0.61", features = [
13+
"Win32_Foundation",
14+
"Win32_System_Diagnostics_Debug",
15+
"Win32_UI_WindowsAndMessaging",
16+
"Win32_Graphics_Gdi",
17+
"Win32_System_DataExchange",
18+
"Win32_System_LibraryLoader",
19+
"Win32_System_Ole",
20+
"Win32_System_Memory",
21+
"Win32_System_Recovery",
22+
] }
23+
xen-win-utils = { path = "../xen-win-utils" }
24+
25+
[build-dependencies]
26+
winres = "0.1"

xen-win-clipboard/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Pipe talker message format
2+
3+
UTF-8 string containing clipboard contents.
4+
This is eventually converted into UTF-16 by xen-win-clipboard for use with CF_UNICODETEXT.

xen-win-clipboard/build.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#[cfg(windows)]
2+
extern crate winres;
3+
4+
#[cfg(windows)]
5+
fn main() {
6+
let mut res = winres::WindowsResource::new();
7+
res.set_manifest_file("manifest.xml");
8+
res.compile().unwrap();
9+
}
10+
11+
#[cfg(unix)]
12+
fn main() {}

xen-win-clipboard/manifest.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
4+
<security>
5+
<requestedPrivileges>
6+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
7+
</requestedPrivileges>
8+
</security>
9+
</trustInfo>
10+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
11+
<application>
12+
<!-- Windows 10 -->
13+
<supportedOS
14+
Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
15+
</application>
16+
</compatibility>
17+
</assembly>

xen-win-clipboard/src/clipboard.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::marker::PhantomData;
2+
3+
use windows::Win32::{
4+
Foundation::{ERROR_BUFFER_OVERFLOW, HANDLE, HGLOBAL, HWND},
5+
System::{
6+
DataExchange::{CloseClipboard, GetClipboardData, OpenClipboard, SetClipboardData},
7+
Memory::{GlobalAlloc, GMEM_MOVEABLE},
8+
Ole::{CF_UNICODETEXT, CLIPBOARD_FORMAT},
9+
},
10+
};
11+
use xen_win_utils::heap::GlobalLockGuard;
12+
13+
fn as_u8_slice(input: &[u16]) -> windows::core::Result<&[u8]> {
14+
let count = input.len().checked_mul(2).ok_or(ERROR_BUFFER_OVERFLOW)?;
15+
unsafe {
16+
Ok(core::slice::from_raw_parts(
17+
input.as_ptr() as *const u8,
18+
count,
19+
))
20+
}
21+
}
22+
23+
fn as_u16_box(input: &[u8]) -> Box<[u16]> {
24+
let count = input.len() / 2;
25+
let mut bx = vec![0u16; count];
26+
bx.copy_from_slice(unsafe { core::slice::from_raw_parts(input.as_ptr().cast(), count) });
27+
bx.into_boxed_slice()
28+
}
29+
30+
pub(crate) struct Clipboard {
31+
_private: PhantomData<()>,
32+
}
33+
34+
impl Clipboard {
35+
pub fn new(hwnd: HWND) -> windows::core::Result<Self> {
36+
unsafe {
37+
OpenClipboard(Some(hwnd))?;
38+
}
39+
Ok(Self {
40+
_private: PhantomData,
41+
})
42+
}
43+
44+
fn get_raw(&self, format: CLIPBOARD_FORMAT) -> windows::core::Result<GlobalLockGuard<u8>> {
45+
let hcb = unsafe { GetClipboardData(format.0.into()) }?;
46+
let hmem = HGLOBAL(hcb.0);
47+
48+
Ok(GlobalLockGuard::lock(hmem)?)
49+
}
50+
51+
#[allow(dead_code)]
52+
pub fn get(&self, format: CLIPBOARD_FORMAT) -> windows::core::Result<Box<[u8]>> {
53+
let hmem_lock = self.get_raw(format)?;
54+
Ok(hmem_lock.get().to_vec().into_boxed_slice())
55+
}
56+
57+
/// Null-terminated.
58+
pub fn get_wide_z(&self) -> windows::core::Result<Box<[u16]>> {
59+
let hmem_lock = self.get_raw(CF_UNICODETEXT)?;
60+
Ok(as_u16_box(hmem_lock.get()))
61+
}
62+
63+
pub fn set(&self, format: CLIPBOARD_FORMAT, data: &[u8]) -> windows::core::Result<()> {
64+
// ownership transferred to the system
65+
let hmem = unsafe { GlobalAlloc(GMEM_MOVEABLE, data.len())? };
66+
{
67+
let mut hmem_lock = GlobalLockGuard::lock(hmem)?;
68+
hmem_lock.get_mut().copy_from_slice(data);
69+
}
70+
unsafe { SetClipboardData(format.0.into(), Some(HANDLE(hmem.0))) }?;
71+
Ok(())
72+
}
73+
74+
/// Null-terminated.
75+
pub fn set_wide_z(&self, data_z: &[u16]) -> windows::core::Result<()> {
76+
self.set(CF_UNICODETEXT, as_u8_slice(data_z)?)
77+
}
78+
}
79+
80+
impl Drop for Clipboard {
81+
fn drop(&mut self) {
82+
unsafe {
83+
let _ = CloseClipboard();
84+
}
85+
}
86+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use windows::Win32::{
2+
Foundation::HGLOBAL,
3+
System::Memory::{GlobalLock, GlobalSize, GlobalUnlock},
4+
};
5+
6+
pub(crate) struct GlobalLockGuard<'a, T>
7+
where
8+
T: windows::core::Param<HGLOBAL> + Copy,
9+
{
10+
hmem: T,
11+
data: &'a mut [u8],
12+
}
13+
14+
impl<'a, T> GlobalLockGuard<'a, T>
15+
where
16+
T: windows::core::Param<HGLOBAL> + Copy,
17+
{
18+
pub fn lock(hmem: T) -> windows::core::Result<Self> {
19+
let p = unsafe { GlobalLock(hmem) } as *mut u8;
20+
if p.is_null() {
21+
return Err(windows::core::Error::from_win32());
22+
}
23+
let len = unsafe { GlobalSize(hmem) };
24+
assert!(len > 0);
25+
let data = unsafe { core::slice::from_raw_parts_mut(p, len) };
26+
Ok(Self { hmem, data })
27+
}
28+
29+
pub fn get(&self) -> &[u8] {
30+
return self.data;
31+
}
32+
33+
pub fn get_mut(&mut self) -> &mut [u8] {
34+
return self.data;
35+
}
36+
}
37+
38+
impl<'a, T> Drop for GlobalLockGuard<'a, T>
39+
where
40+
T: windows::core::Param<HGLOBAL> + Copy,
41+
{
42+
fn drop(&mut self) {
43+
unsafe {
44+
let _ = GlobalUnlock(self.hmem);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)