mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Implement copypasta::Load for macos::Clipboard
This commit is contained in:
parent
9d491f9f67
commit
5ddf525747
4 changed files with 296 additions and 7 deletions
56
copypasta/Cargo.lock
generated
56
copypasta/Cargo.lock
generated
|
@ -1,4 +1,60 @@
|
||||||
[root]
|
[root]
|
||||||
name = "copypasta"
|
name = "copypasta"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "malloc_buf"
|
||||||
|
version = "0.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc-foundation"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc_id"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||||
|
"checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
|
||||||
|
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||||
|
"checksum objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9311aa5acd7bee14476afa0f0557f564e9d0d61218a8b833d9b1f871fa5fba"
|
||||||
|
"checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||||
|
"checksum objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4730aa1c64d722db45f7ccc4113a3e2c465d018de6db4d3e7dfe031e8c8a297"
|
||||||
|
|
|
@ -7,3 +7,8 @@ description = "Forthcoming clipboard library"
|
||||||
keywords = ["clipboard", "copy", "paste"]
|
keywords = ["clipboard", "copy", "paste"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
objc = "0.2"
|
||||||
|
objc_id = "0.1"
|
||||||
|
objc-foundation = "0.1"
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
//! A cross-platform clipboard library
|
//! A cross-platform clipboard library
|
||||||
|
|
||||||
|
// This has to be here due to macro_use
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[macro_use] extern crate objc;
|
||||||
|
|
||||||
/// Types that can get the system clipboard contents
|
/// Types that can get the system clipboard contents
|
||||||
pub trait Load : Sized {
|
pub trait Load : Sized {
|
||||||
/// Errors encountered when working with a clipboard. Each implementation is
|
/// Errors encountered when working with a clipboard. Each implementation is
|
||||||
|
|
|
@ -1,19 +1,243 @@
|
||||||
//! Clipboard access on macOS
|
//! Clipboard access on macOS
|
||||||
//!
|
//!
|
||||||
//! Implemented according to https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PasteboardGuide106/Articles/pbReading.html#//apple_ref/doc/uid/TP40008123-SW1
|
//! Implemented according to https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PasteboardGuide106/Articles/pbReading.html#//apple_ref/doc/uid/TP40008123-SW1
|
||||||
//!
|
|
||||||
//! FIXME implement this :)
|
|
||||||
|
|
||||||
struct Clipboard;
|
mod ns {
|
||||||
|
extern crate objc_id;
|
||||||
|
extern crate objc_foundation;
|
||||||
|
|
||||||
impl Load for Clipboard {
|
#[link(name = "AppKit", kind = "framework")]
|
||||||
type Err = ();
|
extern {}
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use objc::runtime::{Class, Object};
|
||||||
|
use self::objc_id::{Id, Owned};
|
||||||
|
use self::objc_foundation::{NSArray, NSObject, NSDictionary, NSString};
|
||||||
|
use self::objc_foundation::{INSString, INSArray, INSObject};
|
||||||
|
|
||||||
|
/// Rust API for NSPasteboard
|
||||||
|
pub struct Pasteboard(Id<Object>);
|
||||||
|
|
||||||
|
/// Errors occurring when creating a Pasteboard
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NewPasteboardError {
|
||||||
|
GetPasteboardClass,
|
||||||
|
LoadGeneralPasteboard,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors occurring when reading a string from the pasteboard
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReadStringError {
|
||||||
|
GetStringClass,
|
||||||
|
ReadObjectsForClasses,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for reading contents from the pasteboard
|
||||||
|
///
|
||||||
|
/// This is intended to reflect the underlying objective C API
|
||||||
|
/// `readObjectsForClasses:options:`.
|
||||||
|
pub trait PasteboardReadObject<T> {
|
||||||
|
type Err;
|
||||||
|
fn read_object(&self) -> Result<T, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PasteboardReadObject<String> for Pasteboard {
|
||||||
|
type Err = ReadStringError;
|
||||||
|
fn read_object(&self) -> Result<String, ReadStringError> {
|
||||||
|
// Get string class; need this for passing to readObjectsForClasses
|
||||||
|
let ns_string_class = match Class::get("NSString") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Err(ReadStringError::GetStringClass),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ns_string: Id<Object> = unsafe {
|
||||||
|
let ptr: *mut Object = msg_send![ns_string_class, class];
|
||||||
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
return Err(ReadStringError::GetStringClass);
|
||||||
|
} else {
|
||||||
|
Id::from_ptr(ptr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let classes: Id<NSArray<NSObject, Owned>> = unsafe {
|
||||||
|
// I think this transmute is valid. It's going from an Id<Object> to an
|
||||||
|
// Id<NSObject>. From transmute's perspective, the only thing that matters is that
|
||||||
|
// they both have the same size (they do for now since the generic is phantom data).
|
||||||
|
// In both cases, the underlying pointer is an id (from `[NSString class]`), so
|
||||||
|
// again, this should be valid. There's just nothing implemented in objc_id or
|
||||||
|
// objc_foundation to do this "safely". By the way, the only reason this is
|
||||||
|
// necessary is because INSObject isn't implemented for Id<Object>.
|
||||||
|
//
|
||||||
|
// And if that argument isn't convincing, my final reasoning is that "it seems to
|
||||||
|
// work".
|
||||||
|
NSArray::from_vec(vec![mem::transmute(ns_string)])
|
||||||
|
};
|
||||||
|
|
||||||
|
// No options
|
||||||
|
//
|
||||||
|
// Apparently this doesn't compile without a type hint, so it maps objects to objects!
|
||||||
|
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
|
||||||
|
|
||||||
|
// call [pasteboard readObjectsForClasses:options:]
|
||||||
|
let copied_items = unsafe {
|
||||||
|
let copied_items: *mut NSArray<NSString> = msg_send![
|
||||||
|
self.0,
|
||||||
|
readObjectsForClasses:&*classes
|
||||||
|
options:&*options
|
||||||
|
];
|
||||||
|
|
||||||
|
if copied_items.is_null() {
|
||||||
|
return Err(ReadStringError::ReadObjectsForClasses);
|
||||||
|
} else {
|
||||||
|
Id::from_ptr(copied_items) as Id<NSArray<NSString>>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ok, this is great. We have an NSArray<NSString>, and these have decent bindings. Use
|
||||||
|
// the first item returned (if an item was returned) or just return an empty string
|
||||||
|
// XXX Should this return an error if no items were returned?
|
||||||
|
let contents = copied_items
|
||||||
|
.first_object()
|
||||||
|
.map(|ns_string| ns_string.as_str().to_owned())
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
|
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::error::Error for ReadStringError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
ReadStringError::GetStringClass => "NSString class not found",
|
||||||
|
ReadStringError::ReadObjectsForClasses => "readObjectsForClasses:options: failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Display for ReadStringError {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.write_str(::std::error::Error::description(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::error::Error for NewPasteboardError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
NewPasteboardError::GetPasteboardClass => {
|
||||||
|
"NSPasteboard class not found"
|
||||||
|
},
|
||||||
|
NewPasteboardError::LoadGeneralPasteboard => {
|
||||||
|
"[NSPasteboard generalPasteboard] failed"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Display for NewPasteboardError {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.write_str(::std::error::Error::description(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pasteboard {
|
||||||
|
pub fn new() -> Result<Pasteboard, NewPasteboardError> {
|
||||||
|
// NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
let ns_pasteboard_class = match Class::get("NSPasteboard") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Err(NewPasteboardError::GetPasteboardClass),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ptr = unsafe {
|
||||||
|
let ptr: *mut Object = msg_send![ns_pasteboard_class, generalPasteboard];
|
||||||
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
return Err(NewPasteboardError::LoadGeneralPasteboard);
|
||||||
|
} else {
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = unsafe {
|
||||||
|
Id::from_ptr(ptr)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Pasteboard(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
CreatePasteboard(ns::NewPasteboardError),
|
||||||
|
ReadString(ns::ReadStringError),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ::std::error::Error for Error {
|
||||||
|
fn cause(&self) -> Option<&::std::error::Error> {
|
||||||
|
match *self {
|
||||||
|
Error::CreatePasteboard(ref err) => Some(err),
|
||||||
|
Error::ReadString(ref err) => Some(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
Error::CreatePasteboard(ref _err) => "Failed to create pasteboard",
|
||||||
|
Error::ReadString(ref _err) => "Failed to read string from pasteboard",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Error::CreatePasteboard(ref err) => {
|
||||||
|
write!(f, "Failed to create pasteboard: {}", err)
|
||||||
|
},
|
||||||
|
Error::ReadString(ref err) => {
|
||||||
|
write!(f, "Failed to read string from pasteboard: {}", err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ns::NewPasteboardError> for Error {
|
||||||
|
fn from(val: ns::NewPasteboardError) -> Error {
|
||||||
|
Error::CreatePasteboard(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ns::ReadStringError> for Error {
|
||||||
|
fn from(val: ns::ReadStringError) -> Error {
|
||||||
|
Error::ReadString(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Clipboard(ns::Pasteboard);
|
||||||
|
|
||||||
|
impl super::Load for Clipboard {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
fn new() -> Result<Self, Error> {
|
fn new() -> Result<Self, Error> {
|
||||||
Ok(Clipboard)
|
Ok(Clipboard(try!(ns::Pasteboard::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_primary(&self) -> Result<String, Self::Err> {
|
fn load_primary(&self) -> Result<String, Self::Err> {
|
||||||
Ok(String::new())
|
Ok(try!(self::ns::PasteboardReadObject::<String>::read_object(&self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Clipboard;
|
||||||
|
use ::Load;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_clipboard_and_load_contents() {
|
||||||
|
let clipboard = Clipboard::new().unwrap();
|
||||||
|
println!("{:?}", clipboard.load_primary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue