This is my source code:
extern crate user32;
extern crate kernel32;
use std::io::prelude::*;
use std::net::TcpStream;
use std::ptr;
fn main() {
let mut message = get_data();
loop {
if message != get_data() {
let mut client = TcpStream::connect("127.0.0.1:8080").unwrap();
message = get_data();
println!("{}", message);
let _ = client.write(message.as_bytes());
println!("Sent!");
}
}
}
fn get_data() -> String {
let value: String;
unsafe {
let open = user32::OpenClipboard(ptr::null_mut());
if open == 0 {
println!("{}", kernel32::GetLastError());
user32::CloseClipboard();
return "NULL".to_string()
}
let var = user32::GetClipboardData(13 as u32);
if var.is_null() {
println!("{}", kernel32::GetLastError());
user32::CloseClipboard();
return "NULL".to_string()
}
let data = kernel32::GlobalLock(var) as *mut u16;
let len = rust_strlen16(data);
let raws = std::slice::from_raw_parts(data, len);
value = String::from_utf16_lossy(raws);
kernel32::GlobalUnlock(var);
user32::CloseClipboard();
}
value
}
#[inline(always)] // used from clipboard-win, not mine
unsafe fn rust_strlen16(buff_p: *mut u16) -> usize {
let mut i: isize = 0;
while *buff_p.offset(i) != 0 {
i += 1;
}
return i as usize
}
When I initialize message
at line 1 of main
, everything is fine, but when I start copying text, things start to get a little weird.
From the OpenClipboard
function in get_data()
, the errors I would get from GetLastError
would either be 6 (ERROR_INVALID_HANDLE
) or 5 (ERROR_ACCESS_DENIED
) for an unknown reason.
From the GetClipboardData
function I would consistently get error code 1418 (ERROR_CLIPBOARD_NOT_OPEN
) from GetLastError
. You would think that I would get this error from getting an error code 6 or 5 from OpenClipboard
, however it wouldn't print 1418 since it can't go that far in the function, right? I might be wrong but that's how I see it.
Something you should also note is that these errors show up when comparing message
in the loop, but the if
statement continues anyway and the message gets assigned the actual copied data, here is some sample output.
1418 // this is when message and get_data() are being checked
println!("{}", result); // this is the actual copied data which is message
Sent! // sent!
5
NULL
Sent!
println!("Got Connection");
Sent!
Here is my guess as to what is happening: the system is forcibly closing your open clipboard handle.
As far as I have been able to tell, the clipboard is a truly global shared resource, even across processes. I can't trace how far back the OpenClipboard
function came into existence, but it works on at least Windows 2000 according to MSDN. I wouldn't be surprised if it actually originated in Windows 98 / 95 or even before that. The clipboard is an old concept!
However, a conceptual problem arises: what if one program opens the clipboard and forgets to close it? Then your entire system would be prevented from using the clipboard, which would be pretty terrible. I believe (with no proof) that the OS tracks how much you've had the clipboard open and decides to take the clipboard back from you when you are hogging it.
When you run your program, you can see that it is pegged at 100% CPU, as you've constructed a busy loop that has the clipboard open for the majority of the time. That would be enough to trigger a simple abuse heuristic. Adding (::std::thread::sleep_ms(100)
) to the end of your loop seems to make everything behave much nicer.
However, you do still get error code 5 (ERROR_ACCESS_DENIED
) sporadically. Again, I believe this to be caused by the global shared nature of the clipboard. Some other process has opened the clipboard for some reason and you cannot have it. The correct thing there is to handle not being able to always get immediate access to the resource.
Making those changes seem to get your program working, but there's more to be done:
extern crate user32;
extern crate kernel32;
use std::ptr;
use std::marker::PhantomData;
fn main() {
let mut sequence_val = None;
loop {
let clip = match Clipboard::open() {
Err(WinApiError(ERROR_ACCESS_DENIED)) |
Err(WinApiError(ERROR_CLIPBOARD_NOT_OPEN)) => {
println!("Someone else is using the clipboard; continuing");
continue;
},
Err(e) => panic!("Unknown error while opening the clipboard: {:?}", e),
Ok(c) => c,
};
let next_sequence_val = Some(clip.sequence_number());
if sequence_val != next_sequence_val {
println!("Clipboard advanced from {:?} to {:?}", sequence_val, next_sequence_val);
sequence_val = next_sequence_val;
let all_formats: Result<Vec<_>, _> = clip.formats().collect();
println!("Available formats: {:?}", all_formats);
let message = clip.get_text().expect("Cannot read from clipboard");
match message {
Some(message) => println!("Got clipboard text: {}", message),
None => println!("Clipboard did not contain textual data"),
}
}
::std::thread::sleep_ms(250);
}
}
#[derive(Debug, Copy, Clone)]
struct WinApiError(u32);
const ERROR_ACCESS_DENIED: u32 = 0x5;
const ERROR_CLIPBOARD_NOT_OPEN: u32 = 0x58A;
impl WinApiError {
fn from_global() -> Result<(), WinApiError> {
let val = unsafe { kernel32::GetLastError() };
if val != 0 {
Err(WinApiError(val))
} else {
Ok(())
}
}
}
// PhantomData is used here to prevent creating the struct with
// `Clipboard`.
struct Clipboard {
marker: PhantomData<()>,
}
const CF_UNICODETEXT: u32 = 13;
impl Clipboard {
fn open() -> Result<Clipboard, WinApiError> {
unsafe {
user32::OpenClipboard(ptr::null_mut());
try!(WinApiError::from_global());
Ok(Clipboard { marker: PhantomData })
}
}
fn formats(&self) -> ClipboardFormats {
ClipboardFormats { clip: PhantomData, fmt: 0 }
}
fn sequence_number(&self) -> u32 {
unsafe { user32::GetClipboardSequenceNumber() }
}
fn get_text(&self) -> Result<Option<String>, WinApiError> {
for fmt in self.formats() {
let fmt = try!(fmt);
if fmt == CF_UNICODETEXT {
unsafe {
let var = user32::GetClipboardData(CF_UNICODETEXT);
try!(WinApiError::from_global());
// I don't believe this lock is actually
// needed. In searching around, it only seems to
// be used when you call `GlobalAlloc` and then
// set the clipboard with that data.
// If it's needed, consider making another RAII
// type to handle automatically unlocking.
let data = kernel32::GlobalLock(var) as *mut u16;
try!(WinApiError::from_global());
let len = rust_strlen16(data);
let raws = std::slice::from_raw_parts(data, len);
let value = String::from_utf16_lossy(raws);
kernel32::GlobalUnlock(var);
try!(WinApiError::from_global());
return Ok(Some(value));
}
}
}
Ok(None)
}
}
impl Drop for Clipboard {
fn drop(&mut self) {
unsafe {
// Ignore failure to close as we can't really do anything
// about it
user32::CloseClipboard();
let _ = WinApiError::from_global();
}
}
}
unsafe fn rust_strlen16(buff_p: *mut u16) -> usize {
let mut i = 0;
while *buff_p.offset(i) != 0 { i += 1 }
i as usize
}
struct ClipboardFormats<'a>{
// PhantomData is used here to prevent outliving the opened
// clipboard
clip: PhantomData<&'a Clipboard>,
fmt: u32,
}
impl<'a> Iterator for ClipboardFormats<'a> {
type Item = Result<u32, WinApiError>;
fn next(&mut self) -> Option<Self::Item> {
let next_fmt = unsafe { user32::EnumClipboardFormats(self.fmt) };
if next_fmt == 0 {
match WinApiError::from_global() {
Ok(_) => None,
Err(e) => Some(Err(e)),
}
} else {
self.fmt = next_fmt;
Some(Ok(next_fmt))
}
}
}
Namely I created a convenience type WinApiError
that checks GetLastError
automatically and ties it into the Rust-standard Result
type. This is used pervasively along with try!
.
I introduced a Clipboard
struct to have a type to implement Drop
on. This makes it certain that we will always close the clipboard when we are done with it. You may want to even add a close
method that makes it very obvious and easy if you want to close it earlier:
fn close(self) {
// Automatically dropped as it goes out of scope.
}
To improve performance slightly, I used GetClipboardSequenceNumber
which returns an integer timestamp for the clipboard. This allows for a lighter-weight check to know if the clipboard has changed, instead of creating a String
each time. It also prevents you from having a magic string of "NULL"
. You may still want to keep track of the last string to know if the user has copied the same text again.
When retrieving data from the clipboard, you should iterate through all the available formats and take action on the first you care about. Although you only care about the UCS-2 format, I added this while attempting to debug so I figured I'd leave it.
There is a better solution than just sleeping that I have not implemented. AddClipboardFormatListener
can notify a message loop when the clipboard changes. This is probably the correct solution, but does require you to create a window (even if it's used only for messages).