From 8a8aa0d8584aa6551e0b22798676f31601d0cb1b Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 25 Feb 2017 16:15:00 +0300 Subject: [PATCH] v2 First drop for #6 --- .travis.yml | 8 - Cargo.toml | 17 +- README.md | 4 +- appveyor.yml | 15 +- build.ps1 | 102 ------ src/constants.rs | 10 - src/{clipboard_formats.rs => formats.rs} | 144 ++++----- src/lib.rs | 198 ++++++------ src/raw.rs | 385 +++++++++++++++++++++++ src/utils.rs | 6 + src/wrapper.rs | 362 --------------------- tests/clipboard.rs | 108 +++---- tests/format.rs | 65 ++++ 13 files changed, 698 insertions(+), 726 deletions(-) delete mode 100644 .travis.yml delete mode 100644 build.ps1 delete mode 100644 src/constants.rs rename src/{clipboard_formats.rs => formats.rs} (95%) create mode 100644 src/raw.rs create mode 100644 src/utils.rs delete mode 100644 src/wrapper.rs create mode 100644 tests/format.rs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3a67464..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: rust -rust: - - stable - - beta - - nightly -matrix: - allow_failure: - - rust: nightly diff --git a/Cargo.toml b/Cargo.toml index 2264556..c3b1a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,25 @@ [package] name = "clipboard-win" -version = "1.8.1" +version = "2.0.0" authors = ["Douman "] -description = "Library to interact with Windows clipboard." -repository = "https://github.com/DoumanAsh/clipboard-win" -documentation = "https://docs.rs/crate/clipboard-win/" +description = "Provides simple way to interact with Windows clipboard." license = "MIT" + keywords = ["Windows", "winapi", "clipboard"] +categories = [] + +repository = "https://github.com/DoumanAsh/clipboard-win" +documentation = "https://docs.rs/crate/clipboard-win/" + +readme = "README.md" + +[badges] +appveyor = { repository = "https://github.com/DoumanAsh/clipboard-win", branch = "master", service = "github" } [target.'cfg(windows)'.dependencies] winapi = "^0.2.5" user32-sys = "^0.2.0" kernel32-sys = "^0.2.1" -windows-error = "^1.0.0" [lib] name = "clipboard_win" diff --git a/README.md b/README.md index 2bc0483..1a86bd9 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ clipboard-win [![Build status](https://ci.appveyor.com/api/projects/status/5mkbp9mh5vwpohtn?svg=true)](https://ci.appveyor.com/project/DoumanAsh/clipboard-win) -Library to interact with Windows clipboard. +[Documentation](https://docs.rs/clipboard-win/1.8.1/x86_64-pc-windows-msvc/clipboard_win/) + +Provides simple way to interact with Windows clipboard. diff --git a/appveyor.yml b/appveyor.yml index 2f2b1c7..16b9e2e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,18 +7,22 @@ branches: environment: matrix: - TARGET: x86_64-pc-windows-msvc + CHANNEL: beta - TARGET: i686-pc-windows-gnu + CHANNEL: beta - TARGET: x86_64-pc-windows-gnu + CHANNEL: beta api: secure: ZQiyxjBbshVpIVZbZl9h23yGuqlq+8j615c0B2z7VxdEAW1wggIfkebJqpgZclk2 git_token: secure: H5PQSeh6rHOoDLktlYlVLYu/iJMTwzzNVk8Wr//nqbYC7xrJuGDwKwiev/0Bl2d3 install: - - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-beta-${env:TARGET}.exe" - - rust-beta-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Rust" - - ps: Remove-Item "rust-beta-$env:TARGET.exe" - - ps: $env:PATH="$env:PATH;C:\Rust\bin" + - curl -sSf -o rustup-init.exe https://win.rustup.rs + - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -Vv + - cargo -V build: false @@ -26,6 +30,3 @@ test_script: - ps: $env:RUST_TEST_THREADS=1 - ps: echo "let's save some clipboard!" | Clip - cargo test - -on_success: - - powershell -version 3 -File build.ps1 bot diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index d86f6b9..0000000 --- a/build.ps1 +++ /dev/null @@ -1,102 +0,0 @@ -Write-Host "------------------------------" -Write-Host "Clipboard-win build script" -Write-Host "------------------------------" - -if ( $args.length -lt 1 ) { - Write-Host "Usage: build command [options]" - Write-Host "" - Write-Host "Commands:" - Write-Host " test Run cargo test without threading." - Write-Host " build Run cargo build." - Write-Host " doc Generate docs." - Write-Host "" - Write-Host "Options:" - Write-Host " --update_pages For doc command copy documents to branch gh-pages." - Write-Host "" - exit -} - -Set-Location $PSScriptRoot #just in case set the location to script(Clipboard-win directory) - -switch ($args[0]) -{ - "test" { - echo "let's save some clipboard!" | Clip - $env:RUST_TEST_THREADS=1 - cargo test - } - "build" { - cargo build - } - "doc" { - $master_hash = git log -1 --format="%s(%h %cd)" --date=short - #remove old documentation just in case - Remove-Item -Recurse -ErrorAction SilentlyContinue -Force target\doc\ - cargo doc --no-deps -p windows-error -p clipboard-win - if ($args[1] -eq "--update-pages") { - git checkout gh-pages -q - Remove-Item -Recurse -ErrorAction SilentlyContinue -Force doc\ - Copy-item -Recurse target\doc\ doc\ - git diff --quiet HEAD - if ($LASTEXITCODE -eq 1) { - #commit change in docs - git add doc/ - git commit -m "Auto-update" -m "Commit: $master_hash" - git push origin HEAD - } - else { - echo "" - echo "Documents are up-to-date" - } - git checkout master -q - } - } - "bot" { - if ( -Not $env:APPVEYOR) { - echo "Bot is supposed to run in AppVeyor. Exit..." - return - } - elseif ($env:APPVEYOR_PULL_REQUEST_TITLE) { - echo "Skip pull request" - return - } - elseif ( $env:TARGET -ne "x86_64-pc-windows-gnu") { - return - } - - git config --global credential.helper store - Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:git_token):x-oauth-basic@github.com\n" - git config --global user.name "AppVeyor bot" - git config --global user.email "douman@gmx.se" - git config remote.origin.url "https://$($env:git_token)@github.com/DoumanAsh/clipboard-win.git" - echo "" - echo "Build is done" - - $crates_io_ver=cargo search clipboard-win - $crates_io_ver = [regex]::match($crates_io_ver, "(\d{1,3}.\d{1,3}.\d{1,3})").Groups[1].Value - $crates_io_ver = $crates_io_ver.split('.') - - $crate = select-string Cargo.toml -Pattern "\d{1,3}.\d{1,3}.\d{1,3}" - $crate = $crate[0].tostring().split('=')[1].substring(2) - $crate = $crate.substring(0, $crate.indexof('"')).split('.') - - $crates_io_ver = [System.Tuple]::Create($crates_io_ver[0], $crates_io_ver[1], $crates_io_ver[2]) - $crate = [System.Tuple]::Create($crate[0], $crate[1], $crate[2]) - if ( $crate[0] -gt $crates_io_ver[0]) { - cargo login $env:api - cargo publish - if ($LASTEXITCODE -eq 1) { - echo "" - echo "Unable to publish :(" - } - else { - .\build.ps1 doc --update-pages - } - } - else { - echo "" - echo "Crate is up-to-date on crates.io" - } - } - default { echo (">>>{0}: Incorrect command" -f $args[0]) } -} diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index f278114..0000000 --- a/src/constants.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![allow(dead_code)] -use winapi::{DWORD}; - -// FormatMessage constants from https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351%28v=vs.85%29.aspx -pub const FORMAT_MESSAGE_ALLOCATE_BUFFER: DWORD = 0x00000100; -pub const FORMAT_MESSAGE_ARGUMENT_ARRAY: DWORD = 0x00002000; -pub const FORMAT_MESSAGE_FROM_HMODULE: DWORD = 0x00000800; -pub const FORMAT_MESSAGE_FROM_STRING: DWORD = 0x00000400; -pub const FORMAT_MESSAGE_FROM_SYSTEM: DWORD = 0x00001000; -pub const FORMAT_MESSAGE_IGNORE_INSERTS: DWORD = 0x00000200; diff --git a/src/clipboard_formats.rs b/src/formats.rs similarity index 95% rename from src/clipboard_formats.rs rename to src/formats.rs index dfd55bf..53dcd8f 100644 --- a/src/clipboard_formats.rs +++ b/src/formats.rs @@ -1,72 +1,72 @@ -#![allow(dead_code)] -//! Standart clipboard formats. -//! -//! Header: Winuser.h -//! -//! Description is taken from [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) - -///A handle to a bitmap (HBITMAP). -pub const CF_BITMAP: u32 = 2; -///A memory object containing a BITMAPINFO structure followed by the bitmap bits. -pub const CF_DIB: u32 = 8; -///A memory object containing a BITMAPV5HEADER structure followed by the bitmap color space -///information and the bitmap bits. -pub const CF_DIBV5: u32 = 17; -///Software Arts' Data Interchange Format. -pub const CF_DIF: u32 = 5; -///Bitmap display format associated with a private format. The hMem parameter must be a handle to -///data that can be displayed in bitmap format in lieu of the privately formatted data. -pub const CF_DSPBITMAP: u32 = 0x0082; -///Enhanced metafile display format associated with a private format. The *hMem* parameter must be a -///handle to data that can be displayed in enhanced metafile format in lieu of the privately -///formatted data. -pub const CF_DSPENHMETAFILE: u32 = 0x008E; -///Metafile-picture display format associated with a private format. The hMem parameter must be a -///handle to data that can be displayed in metafile-picture format in lieu of the privately -///formatted data. -pub const CF_DSPMETAFILEPICT: u32 = 0x0083; -///Text display format associated with a private format. The *hMem* parameter must be a handle to -///data that can be displayed in text format in lieu of the privately formatted data. -pub const CF_DSPTEXT: u32 = 0x0081; -///A handle to an enhanced metafile (HENHMETAFILE). -pub const CF_ENHMETAFILE: u32 = 14; -///Start of a range of integer values for application-defined GDI object clipboard formats. -pub const CF_GDIOBJFIRST: u32 = 0x0300; -///End of a range of integer values for application-defined GDI object clipboard formats. -pub const CF_GDIOBJLAST: u32 = 0x03FF; -///A handle to type HDROP that identifies a list of files. -pub const CF_HDROP: u32 = 15; -///The data is a handle to the locale identifier associated with text in the clipboard. -/// -///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) -pub const CF_LOCALE: u32 = 16; -///Handle to a metafile picture format as defined by the METAFILEPICT structure. -pub const CF_METAFILEPICT: u32 = 3; -///Text format containing characters in the OEM character set. -pub const CF_OEMTEXT: u32 = 7; -///Owner-display format. -/// -///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) -pub const CF_OWNERDISPLAY: u32 = 0x0080; -///Handle to a color palette. -/// -///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) -pub const CF_PALETTE: u32 = 9; -///Data for the pen extensions to the Microsoft Windows for Pen Computing. -pub const CF_PENDATA: u32 = 10; -///Start of a range of integer values for private clipboard formats. -pub const CF_PRIVATEFIRST: u32 = 0x0200; -///End of a range of integer values for private clipboard formats. -pub const CF_PRIVATELAST: u32 = 0x02FF; -///Represents audio data more complex than can be represented in a ```CF_WAVE``` standard wave format. -pub const CF_RIFF: u32 = 11; -///Microsoft Symbolic Link (SYLK) format. -pub const CF_SYLK: u32 = 4; -///ANSI text format. -pub const CF_TEXT: u32 = 1; -///Tagged-image file format. -pub const CF_TIFF: u32 = 6; -///UTF16 text format. -pub const CF_UNICODETEXT: u32 = 13; -///Represents audio data in one of the standard wave formats. -pub const CF_WAVE: u32 = 12; +#![allow(dead_code)] +//! Standard clipboard formats. +//! +//! Header: Winuser.h +//! +//! Description is taken from [Standard Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) + +///A handle to a bitmap (HBITMAP). +pub const CF_BITMAP: u32 = 2; +///A memory object containing a BITMAPINFO structure followed by the bitmap bits. +pub const CF_DIB: u32 = 8; +///A memory object containing a BITMAPV5HEADER structure followed by the bitmap color space +///information and the bitmap bits. +pub const CF_DIBV5: u32 = 17; +///Software Arts' Data Interchange Format. +pub const CF_DIF: u32 = 5; +///Bitmap display format associated with a private format. The hMem parameter must be a handle to +///data that can be displayed in bitmap format in lieu of the privately formatted data. +pub const CF_DSPBITMAP: u32 = 0x0082; +///Enhanced metafile display format associated with a private format. The *hMem* parameter must be a +///handle to data that can be displayed in enhanced metafile format in lieu of the privately +///formatted data. +pub const CF_DSPENHMETAFILE: u32 = 0x008E; +///Metafile-picture display format associated with a private format. The hMem parameter must be a +///handle to data that can be displayed in metafile-picture format in lieu of the privately +///formatted data. +pub const CF_DSPMETAFILEPICT: u32 = 0x0083; +///Text display format associated with a private format. The *hMem* parameter must be a handle to +///data that can be displayed in text format in lieu of the privately formatted data. +pub const CF_DSPTEXT: u32 = 0x0081; +///A handle to an enhanced metafile (HENHMETAFILE). +pub const CF_ENHMETAFILE: u32 = 14; +///Start of a range of integer values for application-defined GDI object clipboard formats. +pub const CF_GDIOBJFIRST: u32 = 0x0300; +///End of a range of integer values for application-defined GDI object clipboard formats. +pub const CF_GDIOBJLAST: u32 = 0x03FF; +///A handle to type HDROP that identifies a list of files. +pub const CF_HDROP: u32 = 15; +///The data is a handle to the locale identifier associated with text in the clipboard. +/// +///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) +pub const CF_LOCALE: u32 = 16; +///Handle to a metafile picture format as defined by the METAFILEPICT structure. +pub const CF_METAFILEPICT: u32 = 3; +///Text format containing characters in the OEM character set. +pub const CF_OEMTEXT: u32 = 7; +///Owner-display format. +/// +///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) +pub const CF_OWNERDISPLAY: u32 = 0x0080; +///Handle to a color palette. +/// +///For details see [Standart Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx) +pub const CF_PALETTE: u32 = 9; +///Data for the pen extensions to the Microsoft Windows for Pen Computing. +pub const CF_PENDATA: u32 = 10; +///Start of a range of integer values for private clipboard formats. +pub const CF_PRIVATEFIRST: u32 = 0x0200; +///End of a range of integer values for private clipboard formats. +pub const CF_PRIVATELAST: u32 = 0x02FF; +///Represents audio data more complex than can be represented in a ```CF_WAVE``` standard wave format. +pub const CF_RIFF: u32 = 11; +///Microsoft Symbolic Link (SYLK) format. +pub const CF_SYLK: u32 = 4; +///ANSI text format. +pub const CF_TEXT: u32 = 1; +///Tagged-image file format. +pub const CF_TIFF: u32 = 6; +///UTF16 text format. +pub const CF_UNICODETEXT: u32 = 13; +///Represents audio data in one of the standard wave formats. +pub const CF_WAVE: u32 = 12; diff --git a/src/lib.rs b/src/lib.rs index 992ac74..241fa9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,115 +1,135 @@ #![cfg(windows)] -//! Clipboard WinAPI -//! //! This crate provide simple means to operate with Windows clipboard. //! -//! # Example: -//! ``` +//!# Clipboard +//! +//! All read and write access to Windows clipboard requires user to open it. +//! +//! For your convenience you can use [Clipboard](struct.Clipboard.html) struct that opens it at creation +//! and closes on its drop. +//! +//! Alternatively you can access all functionality directly through [raw module](raw/index.html). +//! +//! Below you can find examples of usage. +//! +//!## Empty clipboard +//! +//! ```rust //! extern crate clipboard_win; //! -//! use clipboard_win::set_clipboard; +//! use clipboard_win::Clipboard; //! //! fn main() { -//! println!("I set some clipboard text like a boss!"); -//! set_clipboard("for my waifu!"); +//! Clipboard::new().unwrap().empty(); //! } //! ``` +//!## Set and get raw data +//! ```rust +//! extern crate clipboard_win; +//! use clipboard_win::formats; +//! +//! use clipboard_win::Clipboard; +//! +//! use std::str; +//! +//! fn main() { +//! let text = "For my waifu!\0"; //For text we need to pass C-like string +//! Clipboard::new().unwrap().set(formats::CF_TEXT, text.as_bytes()); +//! +//! let mut buffer = [0u8; 52]; +//! let result = Clipboard::new().unwrap().get(formats::CF_TEXT, &mut buffer).unwrap(); +//! assert_eq!(str::from_utf8(&buffer[..result]).unwrap(), text); +//! } extern crate winapi; extern crate user32; extern crate kernel32; -extern crate windows_error; -use windows_error::WindowsError; +use std::io; -mod constants; -pub mod clipboard_formats; +mod utils; +pub mod formats; +pub mod raw; -pub mod wrapper; +pub use raw::{ + register_format +}; -///try clone that returns None instead of Err -macro_rules! try_none { - ($expr:expr) => (match $expr { - Ok(val) => val, - Err(_) => return None, - }) +///Clipboard accessor. +/// +///# Note: +/// +///You can have only one such accessor across your program. +pub struct Clipboard { + inner: () } -///Checks format availability. -/// -///Returns WinError if it is not available. -macro_rules! check_format { - ($format:expr) => { - if !wrapper::is_format_avail($format) { - return Err(WindowsError::new(0)) - } +impl Clipboard { + ///Initializes new clipboard accessor. + /// + ///Attempts to open clipboard. + #[inline] + pub fn new() -> io::Result { + raw::open().map(|_| Clipboard {inner: ()}) } -} -#[inline] -///Set clipboard with text. -pub fn set_clipboard>(text: &T) -> Result<(), WindowsError> { - try!(wrapper::open_clipboard()); - let result = wrapper::set_clipboard(text); - try!(wrapper::close_clipboard()); - result -} + ///Empties clipboard. + #[inline] + pub fn empty(&self) -> io::Result<&Clipboard> { + raw::empty().map(|_| self) + } -#[inline] -///Retrieves clipboard content in UTF16 format and convert it to String. -/// -///# Return result: -/// -///* ```Ok``` Content of clipboard which is stored in ```String```. -///* ```Err``` Contains ```WindowsError```. -pub fn get_clipboard_string() -> Result { - //If there is no such format on clipboard then we can return right now - //From point of view Windows this is not an error case, but we still unable to get anything. - check_format!(clipboard_formats::CF_UNICODETEXT); - - try!(wrapper::open_clipboard()); - let result = wrapper::get_clipboard_string(); - try!(wrapper::close_clipboard()); - result -} + ///Retrieves size of clipboard content. + #[inline] + pub fn size(&self, format: u32) -> Option { + raw::size(format) + } -#[inline] -///Retrieves clipboard content. -/// -///# Parameters: -/// -///* ```format``` clipboard format code. -/// -///# Return result: -/// -///* ```Ok``` Contains buffer with raw data. -///* ```Err``` Contains ```WindowsError```. -pub fn get_clipboard(format: u32) -> Result, WindowsError> { - //see comment get_clipboard_string() - check_format!(format); - - try!(wrapper::open_clipboard()); - let result = wrapper::get_clipboard(format); - try!(wrapper::close_clipboard()); - result -} + ///Sets data onto clipboard with specified format. + /// + ///Wraps `raw::set()` + #[inline] + pub fn set(&self, format: u32, data: &[u8]) -> io::Result<&Clipboard> { + raw::set(format, data).map(|_| self) + } + + ///Retrieves data of specified format from clipboard. + /// + ///Wraps `raw::get()` + #[inline] + pub fn get(&self, format: u32, data: &mut [u8]) -> io::Result { + raw::get(format, data) + } + + ///Enumerator over all formats on clipboard.. + #[inline] + pub fn enum_formats(&self) -> raw::EnumFormats { + raw::EnumFormats::new() + } + + ///Returns Clipboard sequence number. + #[inline] + pub fn seq_num() -> Option { + raw::seq_num() + } + + ///Determines whenever provided clipboard format is available on clipboard or not. + #[inline] + pub fn is_format_avail(format: u32) -> bool { + raw::is_format_avail(format) + } + + ///Retrieves number of currently available formats on clipboard. + #[inline] + pub fn count_formats() -> io::Result { + raw::count_formats() + } -#[inline] -///Extracts available clipboard formats. -/// -///# Return result: -/// -///* ```Ok``` Vector of available formats. -///* ```Err``` Error description. -pub fn get_clipboard_formats() -> Result, WindowsError> { - try!(wrapper::open_clipboard()); - let result = wrapper::get_clipboard_formats(); - try!(wrapper::close_clipboard()); - result } -//Re-export functions that do not require open/close -pub use wrapper::{get_format_name, - count_formats, - register_format, - is_format_avail}; +impl Drop for Clipboard { + fn drop(&mut self) { + let _ = raw::close(); + self.inner + } +} diff --git a/src/raw.rs b/src/raw.rs new file mode 100644 index 0000000..2bf485f --- /dev/null +++ b/src/raw.rs @@ -0,0 +1,385 @@ +//!Raw bindings to Windows clipboard. +//! +//!## General information +//! +//!All pre & post conditions are stated in description of functions. +//! +//!### Open clipboard +//! To access any information inside clipboard it is necessary to open it by means of +//! `open()`. +//! +//! After that Clipboard cannot be opened any more until `close()` is called. + +use ::std; +use std::os::windows::ffi::OsStrExt; +use std::os::raw::{ + c_int, + c_uint +}; +use std::ptr; +use std::io; + +use ::utils; +use ::formats; + +use kernel32::{ + GlobalSize, + GlobalLock, + GlobalUnlock, + GlobalAlloc, + GlobalFree +}; + +use user32::{ + OpenClipboard, + CloseClipboard, + EmptyClipboard, + GetClipboardSequenceNumber, + CountClipboardFormats, + IsClipboardFormatAvailable, + EnumClipboardFormats, + RegisterClipboardFormatW, + GetClipboardFormatNameW, + GetClipboardData, + SetClipboardData +}; + +#[inline] +///Opens clipboard. +/// +///Wrapper around ```OpenClipboard```. +/// +///# Pre-conditions: +/// +///* Clipboard is not opened yet. +/// +///# Post-conditions: +/// +///* Clipboard can be accessed for read and write operations. +pub fn open() -> io::Result<()> { + unsafe { + if OpenClipboard(ptr::null_mut()) == 0 { + return Err(utils::get_last_error()); + } + } + + Ok(()) +} + +#[inline] +///Closes clipboard. +/// +///Wrapper around ```CloseClipboard```. +/// +///# Pre-conditions: +/// +///* `open` has been called. +pub fn close() -> io::Result<()> { + unsafe { + if CloseClipboard() == 0 { + return Err(utils::get_last_error()); + } + } + + Ok(()) +} + +#[inline] +///Empties clipboard. +/// +///Wrapper around ```EmptyClipboard```. +/// +///# Pre-conditions: +/// +///* `open` has been called. +pub fn empty() -> io::Result<()> { + unsafe { + if EmptyClipboard() == 0 { + return Err(utils::get_last_error()); + } + } + + Ok(()) +} + +#[inline] +///Retrieves clipboard sequence number. +/// +///Wrapper around ```GetClipboardSequenceNumber```. +/// +///# Returns: +/// +///* ```Some``` Contains return value of ```GetClipboardSequenceNumber```. +///* ```None``` In case if you do not have access. It means that zero is returned by system. +pub fn seq_num() -> Option { + let result: u32 = unsafe { GetClipboardSequenceNumber() }; + + if result == 0 { + return None; + } + + Some(result) +} + +#[inline] +///Retrieves size of clipboard data for specified format. +/// +///# Pre-conditions: +/// +///* `open` has been called. +/// +///# Returns: +/// +///Size in bytes if format presents on clipboard. +pub fn size(format: u32) -> Option { + let clipboard_data = unsafe {GetClipboardData(format)}; + + if clipboard_data.is_null() { + None + } + else { + unsafe { + Some(GlobalSize(clipboard_data)) + } + } +} + +///Retrieves data of specified format from clipboard. +/// +///Wrapper around ```GetClipboardData```. +/// +///# Pre-conditions: +/// +///* `open_clipboard` has been called. +/// +///# Note: +/// +///Clipboard data is truncated by the size of provided storage. +/// +///# Returns: +/// +///Number of copied bytes. +pub fn get(format: u32, result: &mut [u8]) -> io::Result { + let clipboard_data = unsafe { GetClipboardData(format as c_uint) }; + + if clipboard_data.is_null() { + Err(utils::get_last_error()) + } + else { + unsafe { + let data_ptr = GlobalLock(clipboard_data) as *const u8; + + if data_ptr.is_null() { + return Err(utils::get_last_error()); + } + + let data_size = GlobalSize(clipboard_data) as usize; + let data_size = if data_size > result.len() { + result.len() + } + else { + data_size + }; + + ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), data_size); + GlobalUnlock(clipboard_data); + + Ok(data_size) + } + } +} + +///Sets data onto clipboard with specified format. +/// +///Wrapper around ```SetClipboardData```. +/// +///# Pre-conditions: +/// +///* `open_clipboard` has been called. +pub fn set(format: u32, data: &[u8]) -> io::Result<()> { + const GHND: c_uint = 0x42; + let size = data.len(); + + let alloc_handle = unsafe { GlobalAlloc(GHND, size as u64) }; + + if alloc_handle.is_null() { + Err(utils::get_last_error()) + } + else { + unsafe { + let lock = GlobalLock(alloc_handle) as *mut u8; + + ptr::copy_nonoverlapping(data.as_ptr(), lock, size); + GlobalUnlock(alloc_handle); + EmptyClipboard(); + + if SetClipboardData(format, alloc_handle).is_null() { + let result = utils::get_last_error(); + GlobalFree(alloc_handle); + Err(result) + } + else { + Ok(()) + } + } + } +} + +#[inline(always)] +///Determines whenever provided clipboard format is available on clipboard or not. +pub fn is_format_avail(format: u32) -> bool { + unsafe { IsClipboardFormatAvailable(format) != 0 } +} + +#[inline] +///Retrieves number of currently available formats on clipboard. +pub fn count_formats() -> io::Result { + let result = unsafe { CountClipboardFormats() }; + + if result == 0 { + let error = utils::get_last_error(); + + if let Some(raw_error) = error.raw_os_error() { + if raw_error != 0 { + return Err(error) + } + } + } + + Ok(result) +} + +///Enumerator over available clipboard formats. +/// +///# Pre-conditions: +/// +///* `open` has been called. +pub struct EnumFormats { + idx: u32 +} + +impl EnumFormats { + /// Constructs enumerator over all available formats. + pub fn new() -> EnumFormats { + EnumFormats { idx: 0 } + } + + /// Constructs enumerator that starts from format. + pub fn from(format: u32) -> EnumFormats { + EnumFormats { idx: format } + } + + /// Resets enumerator to list all available formats. + pub fn reset(&mut self) -> &EnumFormats { + self.idx = 0; + self + } +} + +impl Iterator for EnumFormats { + type Item = u32; + + /// Returns next format on clipboard. + /// + /// In case of failure (e.g. clipboard is closed) returns `None`. + fn next(&mut self) -> Option { + self.idx = unsafe { EnumClipboardFormats(self.idx) }; + + if self.idx == 0 { + None + } + else { + Some(self.idx) + } + } + + /// Relies on `count_formats` so it is only reliable + /// when hinting size for enumeration of all formats. + /// + /// Doesn't require opened clipboard. + fn size_hint(&self) -> (usize, Option) { + (0, count_formats().ok().map(|val| val as usize)) + } +} + +macro_rules! match_format_name { + ( $name:expr, $( $f:ident ),* ) => { + match $name { + $( formats::$f => Some(stringify!($f).to_string()),)* + formats::CF_GDIOBJFIRST ... formats::CF_GDIOBJLAST => Some(format!("CF_GDIOBJ{}", $name - formats::CF_GDIOBJFIRST)), + formats::CF_PRIVATEFIRST ... formats::CF_PRIVATELAST => Some(format!("CF_PRIVATE{}", $name - formats::CF_PRIVATEFIRST)), + _ => { + let format_buff = [0u16; 52]; + unsafe { + let buff_p = format_buff.as_ptr() as *mut u16; + + if GetClipboardFormatNameW($name, buff_p, format_buff.len() as c_int) == 0 { + None + } + else { + Some(String::from_utf16_lossy(&format_buff)) + } + } + } + } + } +} + +///Returns format name based on it's code. +/// +///# Parameters: +/// +///* ```format``` clipboard format code. +/// +///# Return result: +/// +///* ```Some``` Name of valid format. +///* ```None``` Format is invalid or doesn't exist. +pub fn format_name(format: u32) -> Option { + match_format_name!(format, + CF_BITMAP, + CF_DIB, + CF_DIBV5, + CF_DIF, + CF_DSPBITMAP, + CF_DSPENHMETAFILE, + CF_DSPMETAFILEPICT, + CF_DSPTEXT, + CF_ENHMETAFILE, + CF_HDROP, + CF_LOCALE, + CF_METAFILEPICT, + CF_OEMTEXT, + CF_OWNERDISPLAY, + CF_PALETTE, + CF_PENDATA, + CF_RIFF, + CF_SYLK, + CF_TEXT, + CF_WAVE, + CF_TIFF, + CF_UNICODETEXT) +} + +///Registers a new clipboard format with specified name. +/// +///# Returns: +/// +///Newly registered format identifier. +/// +///# Note: +/// +///Custom format identifier is in range `0xC000...0xFFFF`. +pub fn register_format>(name: &T) -> io::Result { + let mut utf16_buff: Vec = name.as_ref().encode_wide().collect(); + utf16_buff.push(0); + + let result = unsafe { RegisterClipboardFormatW(utf16_buff.as_ptr()) }; + + if result == 0 { + Err(utils::get_last_error()) + } + else { + Ok(result) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..c2e9b5a --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,6 @@ +use std::io; + +#[inline(always)] +pub fn get_last_error() -> io::Error { + io::Error::last_os_error() +} diff --git a/src/wrapper.rs b/src/wrapper.rs deleted file mode 100644 index 2ae2b5f..0000000 --- a/src/wrapper.rs +++ /dev/null @@ -1,362 +0,0 @@ -//!Provides direct wrappers to WinAPI functions. -//! -//!These functions omit calls to ```OpenClipboard``` and ```CloseClipboard```. -//! -//!Due to that most functions have requirements for them to be called. - -extern crate user32; -extern crate kernel32; -extern crate windows_error; - -use super::windows_error::WindowsError; - -use winapi::minwindef::{HGLOBAL, UINT}; -use winapi::winnt::HANDLE; -use winapi::basetsd::SIZE_T; - -use kernel32::{GlobalFree, GlobalAlloc, GlobalLock, GlobalUnlock, GetLastError}; -use user32::{RegisterClipboardFormatW, CountClipboardFormats, IsClipboardFormatAvailable, GetClipboardFormatNameW, EnumClipboardFormats, GetClipboardSequenceNumber, SetClipboardData, EmptyClipboard, OpenClipboard, GetClipboardData, CloseClipboard}; - -use std; -use std::os::windows::ffi::OsStrExt; - -use super::clipboard_formats::*; - -///Wrapper around ```GetLastError```. -/// -///# Return result: -/// -///* ```WindowsError``` with last windows error -#[inline] -pub fn get_last_error() -> WindowsError { - WindowsError::from_last_err() -} - -#[inline] -///Generic strlen -/// -///# Note: -/// -///```Default``` is used instead of ```Zero``` -unsafe fn rust_strlen(buff_p: *const T) -> usize { - let zero = T::default(); - let mut idx: isize = 0; - - while *buff_p.offset(idx) != zero { - idx += 1; - } - - idx as usize -} - -#[inline] -///Wrapper around ```GetClipboardSequenceNumber```. -/// -///# Return result: -/// -///* ```Some``` Contains return value of ```GetClipboardSequenceNumber```. -///* ```None``` In case if you do not have access. It means that zero is returned by system. -pub fn get_clipboard_seq_num() -> Option { - let result: u32 = unsafe { GetClipboardSequenceNumber() }; - - if result == 0 { - return None; - } - - Some(result) -} - -#[inline] -///Wrapper around ```OpenClipboard```. -/// -///# Post-conditions: -/// -///* Clipboard can be accessed for read and write operations. -pub fn open_clipboard() -> Result<(), WindowsError> { - unsafe { - if OpenClipboard(std::ptr::null_mut()) == 0 { - return Err(get_last_error()); - } - } - - Ok(()) -} - -#[inline] -///Wrapper around ```CloseClipboard```. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn close_clipboard() -> Result<(), WindowsError> { - unsafe { - if CloseClipboard() == 0 { - return Err(get_last_error()); - } - } - - Ok(()) -} - -#[inline] -///Wrapper around ```EmptyClipboard```. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn empty_clipboard() -> Result<(), WindowsError> { - unsafe { - if EmptyClipboard() == 0 { - return Err(get_last_error()); - } - } - - Ok(()) -} - -///Wrapper around ```SetClipboardData``` to set unicode text. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn set_clipboard>(text: &T) -> Result<(), WindowsError> { - const FLAG_GHND: UINT = 66; - let text = text.as_ref(); - unsafe { - //allocate buffer and copy string to it. - let utf16_buff: Vec = text.encode_wide().collect(); - let len: usize = (utf16_buff.len()+1) * 2; - let handler: HGLOBAL = GlobalAlloc(FLAG_GHND, len as SIZE_T); - if handler.is_null() { - return Err(get_last_error()); - } - else { - let lock = GlobalLock(handler) as *mut u16; - - let len: usize = (len - 1) / 2; - //src, dest, len - std::ptr::copy_nonoverlapping(utf16_buff.as_ptr(), lock, len); - drop(utf16_buff); - *lock.offset(len as isize) = 0; - - GlobalUnlock(handler); - - //Set new clipboard text. - EmptyClipboard(); - if SetClipboardData(CF_UNICODETEXT, handler).is_null() { - let result = Err(get_last_error()); - GlobalFree(handler); - return result; - } - } - } - Ok(()) -} - -///Wrapper around ```SetClipboardData``` to set raw data. -/// -///# Parameters: -/// -///* ```data``` buffer with raw data to be copied. -///* ```format``` clipboard format code. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn set_clipboard_raw(data: &[u8], format: u32) -> Result<(), WindowsError> { - const FLAG_GHND: UINT = 66; - unsafe { - //allocate buffer and copy string to it. - let len: usize = data.len() + 1; - let handler: HGLOBAL = GlobalAlloc(FLAG_GHND, len as SIZE_T); - - if handler.is_null() { - return Err(get_last_error()); - } - else { - let lock = GlobalLock(handler) as *mut u8; - - let len: usize = len - 1; - //src, dest, len - std::ptr::copy_nonoverlapping(data.as_ptr(), lock, len); - *lock.offset(len as isize) = 0; - - GlobalUnlock(handler); - - //Set new clipboard text. - EmptyClipboard(); - if SetClipboardData(format, handler).is_null() { - let result = Err(get_last_error()); - GlobalFree(handler); - return result; - } - } - } - Ok(()) -} - -///Wrapper around ```GetClipboardData``` with hardcoded UTF16 format. -/// -///# Return result: -/// -///* ```Ok``` Content of clipboard which is stored in ```String```. -///* ```Err``` Contains ```WindowsError```. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn get_clipboard_string() -> Result { - let result: Result; - unsafe { - let text_handler: HANDLE = GetClipboardData(CF_UNICODETEXT as u32); - - if text_handler.is_null() { - result = Err(get_last_error()); - } - else { - let text_p = GlobalLock(text_handler) as *const u16; - let text_s = std::slice::from_raw_parts(text_p, rust_strlen(text_p)); - - result = Ok(String::from_utf16_lossy(text_s)); - GlobalUnlock(text_handler); - } - } - - result -} - -///Wrapper around ```GetClipboardData```. -/// -///# Parameters: -/// -///* ```format``` clipboard format code. -/// -///# Return result: -/// -///* ```Ok``` Contains buffer with raw data. -///* ```Err``` Contains ```WindowsError```. -/// -///# Pre-conditions: -/// -///* `open_clipboard` has been called. -pub fn get_clipboard(format: u32) -> Result, WindowsError> { - let result: Result, WindowsError>; - unsafe { - let text_handler: HANDLE = GetClipboardData(format as UINT); - - if text_handler.is_null() { - result = Err(get_last_error()); - } - else { - let text_p = GlobalLock(text_handler) as *const u8; - let text_vec: Vec = std::slice::from_raw_parts(text_p, rust_strlen(text_p)).to_vec(); - - result = Ok(text_vec); - GlobalUnlock(text_handler); - } - } - - result -} - -///Extracts available clipboard formats. -/// -///# Return result: -/// -///* ```Ok``` Vector of available formats. -///* ```Err``` Contains ```WindowsError```. -pub fn get_clipboard_formats() -> Result, WindowsError> { - let mut result: Vec = vec![]; - unsafe { - let mut clip_format: u32 = EnumClipboardFormats(0); - - while clip_format != 0 { - result.push(clip_format); - clip_format = EnumClipboardFormats(clip_format); - } - - let error = GetLastError(); - - if error != 0 { - return Err(WindowsError::new(0)); - } - } - - Ok(result) -} - -///Returns format name based on it's code. -/// -///# Note: -///It is not possible to retrieve name of predefined clipboard format. -/// -///# Parameters: -/// -///* ```format``` clipboard format code. -/// -///# Return result: -/// -///* ```Some``` String which contains the format's name. -///* ```None``` If format name is incorrect or predefined. -pub fn get_format_name(format: u32) -> Option { - let format_buff: [u16; 30] = [0; 30]; - unsafe { - let buff_p = format_buff.as_ptr() as *mut u16; - - if GetClipboardFormatNameW(format, buff_p, 30) == 0 { - return None; - } - - } - - Some(String::from_utf16_lossy(&format_buff)) -} - -#[inline(always)] -///Determines whenever provided clipboard format is available on clipboard or not. -/// -///# Return result: -/// -///* ```true``` Such format exists. -///* ```false``` No such format. -pub fn is_format_avail(format: u32) -> bool { - unsafe { IsClipboardFormatAvailable(format) != 0 } -} - -#[inline] -///Retrieves number of currently available formats on clipboard. -/// -///# Return result: -/// -///* ```Ok``` Contains number of formats. -///* ```Err``` Contains ```WindowsError```. -pub fn count_formats() -> Result { - let result = unsafe { CountClipboardFormats() }; - - if result == 0 { - Err(get_last_error()) - } - else { - Ok(result) - } -} - -///Registers a new clipboard format with specified name. -/// -///# Return result: -/// -///* ```Ok``` Contains a ```u32``` number of newly created format. -///* ```Err``` Contains ```WindowsError```. -pub fn register_format>(text: &T) -> Result { - let mut utf16_buff: Vec = text.as_ref().encode_wide().collect(); - utf16_buff.push(0); - - let result = unsafe { RegisterClipboardFormatW(utf16_buff.as_ptr()) }; - - if result == 0 { - Err(get_last_error()) - } - else { - Ok(result) - } -} diff --git a/tests/clipboard.rs b/tests/clipboard.rs index e62b5db..0a2d0c0 100644 --- a/tests/clipboard.rs +++ b/tests/clipboard.rs @@ -1,91 +1,59 @@ extern crate clipboard_win; -use clipboard_win::*; -use clipboard_win::wrapper::{open_clipboard, close_clipboard, set_clipboard_raw, get_clipboard_seq_num}; +use std::str; -//NOTE: parallel running may cause fail. +use clipboard_win::Clipboard; +use clipboard_win::formats; +use clipboard_win::raw; #[test] -fn get_clipboard_formats_test() { - let clipboard_formats = get_clipboard_formats(); +fn seq_num() { + let result = raw::seq_num(); - assert!(clipboard_formats.is_ok()); - - let clipboard_formats = clipboard_formats.unwrap(); - println!("get_clipboard_formats_test: clipboard formats: {:?}", clipboard_formats); - for format in clipboard_formats { - if let Some(format_name) = get_format_name(format) { - println!("{}={}", format, format_name); - } - } -} - -#[test] -fn get_clipboard_seq_num_test() { - assert!(get_clipboard_seq_num().is_some()); + assert!(result.is_some()); + assert!(result.unwrap() != 0); } #[test] -fn set_clipboard_test() { - let test_array = vec!["ololo", "1234", "1234567891234567891234567891234567891", "12345678912345678912345678912345678912"]; - for expected_string in test_array { - assert!(set_clipboard(expected_string).is_ok()); - - let result = get_clipboard_string(); - assert!(result.is_ok()); - let result = result.unwrap(); - - println!("set_clipboard_test: Clipboard: {}", result); - println!("set_clipboard_test: Expected: {}", expected_string); - assert!(result == expected_string); - } -} - -#[test] -fn get_clipboard_test() { - let result = get_clipboard_string(); - println!("{:?}", result); +fn set_data() { + let format = formats::CF_TEXT; + let text = "For my waifu!\0"; //For text we need to pass C-like string + let data = text.as_bytes(); + let mut buff = [0u8; 52]; + let mut small_buff = [0u8; 4]; + + let clipboard = Clipboard::new(); + assert!(clipboard.is_ok()); + let clipboard = clipboard.unwrap(); + + let result = clipboard.empty(); assert!(result.is_ok()); + let format_num = clipboard.enum_formats().count(); + assert_eq!(format_num, 0); + let result = Clipboard::count_formats(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 0); - println!("get_clipboard_test: Clipboard: {}", result.unwrap()); -} + let seq_num_before = Clipboard::seq_num(); -#[test] -fn is_format_avail_test() { - assert!(is_format_avail(13)); //default unicode format - assert!(!is_format_avail(66613666)); -} + let result = clipboard.set(format, data); + assert!(result.is_ok()); -#[test] -fn count_formats_test() { - let result = count_formats(); + let seq_num_after = Clipboard::seq_num(); + assert!(seq_num_before != seq_num_after); + let result = clipboard.get(format, &mut buff); assert!(result.is_ok()); - let result = result.unwrap(); - println!("count_formats_test: number of formats={}", result); - assert!(result > 0); -} - -#[test] -fn register_format_test() { - let new_format = register_format("text"); - assert!(new_format.is_ok()); - - let new_format = new_format.unwrap(); - println!("register_format_test: new_format={}", new_format); - assert!(open_clipboard().is_ok()); - - let expect_buf = [13, 12, 122, 1]; - println!("register_format_test: set clipboard={:?}", expect_buf); - assert!(set_clipboard_raw(&expect_buf, new_format).is_ok()); - assert!(close_clipboard().is_ok()); - assert!(is_format_avail(new_format)); + assert_eq!(result, data.len()); + let result = str::from_utf8(&buff[..result]).unwrap(); + assert_eq!(text, result); - let result = get_clipboard(new_format); + let result = clipboard.get(format, &mut small_buff); assert!(result.is_ok()); let result = result.unwrap(); + assert_eq!(result, small_buff.len()); + let result = str::from_utf8(&buff[..result]).unwrap(); + assert_eq!(&text[..small_buff.len()], result); - assert!(result == expect_buf); - println!("register_format_test: saved clipboard={:?}", result); } diff --git a/tests/format.rs b/tests/format.rs new file mode 100644 index 0000000..039736b --- /dev/null +++ b/tests/format.rs @@ -0,0 +1,65 @@ +extern crate clipboard_win; + +use clipboard_win::Clipboard; +use clipboard_win::formats; +use clipboard_win::raw; + +#[test] +fn get_format_name() { + let default_formats = [ + (formats::CF_TEXT, "CF_TEXT"), + (formats::CF_OWNERDISPLAY, "CF_OWNERDISPLAY"), + (formats::CF_BITMAP, "CF_BITMAP"), + (formats::CF_DIB, "CF_DIB"), + (formats::CF_DIBV5, "CF_DIBV5"), + (formats::CF_DIF, "CF_DIF"), + (formats::CF_DSPBITMAP, "CF_DSPBITMAP"), + (formats::CF_DSPENHMETAFILE, "CF_DSPENHMETAFILE"), + (formats::CF_DSPMETAFILEPICT, "CF_DSPMETAFILEPICT"), + (formats::CF_DSPTEXT, "CF_DSPTEXT"), + (formats::CF_ENHMETAFILE, "CF_ENHMETAFILE"), + (formats::CF_HDROP, "CF_HDROP"), + (formats::CF_LOCALE, "CF_LOCALE"), + (formats::CF_METAFILEPICT, "CF_METAFILEPICT"), + (formats::CF_OEMTEXT, "CF_OEMTEXT"), + (formats::CF_OWNERDISPLAY, "CF_OWNERDISPLAY"), + (formats::CF_PALETTE, "CF_PALETTE"), + (formats::CF_PENDATA, "CF_PENDATA"), + (formats::CF_RIFF, "CF_RIFF"), + (formats::CF_SYLK, "CF_SYLK"), + (formats::CF_WAVE, "CF_WAVE"), + (formats::CF_TIFF, "CF_TIFF"), + (formats::CF_UNICODETEXT, "CF_UNICODETEXT"), + (formats::CF_GDIOBJFIRST, "CF_GDIOBJ0"), + (formats::CF_GDIOBJFIRST + 55, "CF_GDIOBJ55"), + (formats::CF_PRIVATEFIRST, "CF_PRIVATE0"), + (formats::CF_PRIVATEFIRST + 63, "CF_PRIVATE63"), + ]; + + for format in default_formats.iter() { + let result = raw::format_name(format.0); + assert!(result.is_some()); + assert_eq!(result.unwrap(), format.1); + } +} + +#[test] +fn count_formats() { + let result = raw::count_formats(); + + assert!(result.is_ok()); + + let num_formats = result.unwrap() as usize; + assert!(num_formats != 0); + + let enumerator = Clipboard::new().unwrap().enum_formats(); + assert_eq!((0, Some(num_formats)), enumerator.size_hint()); + + let formats = Clipboard::new().unwrap().enum_formats().collect::>(); + + assert_eq!(formats.len(), num_formats); + + for format in formats { + assert!(raw::is_format_avail(format)); + } +}