Skip to content

Commit

Permalink
macOS: set image to clipboard without core-graphics
Browse files Browse the repository at this point in the history
  • Loading branch information
Gae24 committed Oct 17, 2024
1 parent 397d169 commit 3203d92
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 197 deletions.
83 changes: 2 additions & 81 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ rust-version = "1.67.1"

[features]
default = ["image-data"]
image-data = ["core-graphics", "image", "windows-sys"]
image-data = ["image", "windows-sys"]
wayland-data-control = ["wl-clipboard-rs"]

[dependencies]
Expand All @@ -37,7 +37,6 @@ log = "0.4"
objc2 = { version = "0.5.1", features = ["relax-void-encoding"] }
objc2-foundation = { version = "0.2.0", features = ["NSArray", "NSString", "NSEnumerator", "NSGeometry"] }
objc2-app-kit = { version = "0.2.0", features = ["NSPasteboard", "NSPasteboardItem", "NSImage"] }
core-graphics = { version = "0.23", optional = true }

[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies]
log = "0.4"
Expand Down
22 changes: 22 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,28 @@ impl<'a> ImageData<'a> {
}
}

#[cfg(feature = "image-data")]
pub(crate) fn encode_as_png(image: &ImageData) -> Result<Vec<u8>, Error> {
use image::ImageEncoder as _;

if image.bytes.is_empty() || image.width == 0 || image.height == 0 {
return Err(Error::ConversionFailure);
}

let mut png_bytes = Vec::new();
let encoder = image::codecs::png::PngEncoder::new(&mut png_bytes);
encoder
.write_image(
image.bytes.as_ref(),
image.width as u32,
image.height as u32,
image::ExtendedColorType::Rgba8,
)
.map_err(|_| Error::ConversionFailure)?;

Ok(png_bytes)
}

#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
pub(crate) struct ScopeGuard<F: FnOnce()> {
callback: Option<F>,
Expand Down
22 changes: 0 additions & 22 deletions src/platform/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,6 @@ fn into_unknown<E: std::fmt::Display>(error: E) -> Error {
Error::Unknown { description: error.to_string() }
}

#[cfg(feature = "image-data")]
fn encode_as_png(image: &ImageData) -> Result<Vec<u8>, Error> {
use image::ImageEncoder as _;

if image.bytes.is_empty() || image.width == 0 || image.height == 0 {
return Err(Error::ConversionFailure);
}

let mut png_bytes = Vec::new();
let encoder = image::codecs::png::PngEncoder::new(&mut png_bytes);
encoder
.write_image(
image.bytes.as_ref(),
image.width as u32,
image.height as u32,
image::ExtendedColorType::Rgba8,
)
.map_err(|_| Error::ConversionFailure)?;

Ok(png_bytes)
}

/// Clipboard selection
///
/// Linux has a concept of clipboard "selections" which tend to be used in different contexts. This
Expand Down
4 changes: 1 addition & 3 deletions src/platform/linux/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ use wl_clipboard_rs::{
utils::is_primary_selection_supported,
};

#[cfg(feature = "image-data")]
use super::encode_as_png;
use super::{into_unknown, LinuxClipboardKind, WaitConfig};
use crate::common::Error;
#[cfg(feature = "image-data")]
use crate::common::ImageData;
use crate::{common::encode_as_png, ImageData};

#[cfg(feature = "image-data")]
const MIME_PNG: &str = "image/png";
Expand Down
4 changes: 1 addition & 3 deletions src/platform/linux/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ use x11rb::{
COPY_DEPTH_FROM_PARENT, COPY_FROM_PARENT, NONE,
};

#[cfg(feature = "image-data")]
use super::encode_as_png;
use super::{into_unknown, LinuxClipboardKind, WaitConfig};
#[cfg(feature = "image-data")]
use crate::ImageData;
use crate::{common::encode_as_png, ImageData};
use crate::{common::ScopeGuard, Error};

type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down
87 changes: 17 additions & 70 deletions src/platform/osx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ the Apache 2.0 or the MIT license at the licensee's choice. The terms
and conditions of the chosen license apply to this file.
*/

#[cfg(feature = "image-data")]
use crate::common::ImageData;
use crate::common::{private, Error};
use objc2::{
msg_send_id,
Expand All @@ -23,69 +21,11 @@ use std::{
borrow::Cow,
panic::{RefUnwindSafe, UnwindSafe},
};

/// Returns an NSImage object on success.
#[cfg(feature = "image-data")]
fn image_from_pixels(
pixels: Vec<u8>,
width: usize,
height: usize,
) -> Result<Id<objc2_app_kit::NSImage>, Box<dyn std::error::Error>> {
use core_graphics::{
base::{kCGBitmapByteOrderDefault, kCGImageAlphaLast, kCGRenderingIntentDefault, CGFloat},
color_space::CGColorSpace,
data_provider::{CGDataProvider, CustomData},
image::{CGImage, CGImageRef},
};
use objc2_app_kit::NSImage;
use objc2_foundation::NSSize;
use std::ffi::c_void;

#[derive(Debug)]
struct PixelArray {
data: Vec<u8>,
}

impl CustomData for PixelArray {
unsafe fn ptr(&self) -> *const u8 {
self.data.as_ptr()
}
unsafe fn len(&self) -> usize {
self.data.len()
}
}

let colorspace = CGColorSpace::create_device_rgb();
let pixel_data: Box<Box<dyn CustomData>> = Box::new(Box::new(PixelArray { data: pixels }));
let provider = unsafe { CGDataProvider::from_custom_data(pixel_data) };

let cg_image = CGImage::new(
width,
height,
8,
32,
4 * width,
&colorspace,
kCGBitmapByteOrderDefault | kCGImageAlphaLast,
&provider,
false,
kCGRenderingIntentDefault,
);

// Convert the owned `CGImage` into a reference `&CGImageRef`, and pass
// that as `*const c_void`, since `CGImageRef` does not implement
// `RefEncode`.
let cg_image: *const CGImageRef = &*cg_image;
let cg_image: *const c_void = cg_image.cast();

let size = NSSize { width: width as CGFloat, height: height as CGFloat };
// XXX: Use `NSImage::initWithCGImage_size` once `objc2-app-kit` supports
// CoreGraphics.
let image: Id<NSImage> =
unsafe { msg_send_id![NSImage::alloc(), initWithCGImage: cg_image, size:size] };

Ok(image)
}
use {
crate::{common::encode_as_png, ImageData},
objc2_app_kit::NSPasteboardTypePNG,
};

pub(crate) struct Clipboard {
pasteboard: Id<NSPasteboard>,
Expand Down Expand Up @@ -207,7 +147,6 @@ impl<'clipboard> Get<'clipboard> {

#[cfg(feature = "image-data")]
pub(crate) fn image(self) -> Result<ImageData<'static>, Error> {
use objc2_app_kit::NSPasteboardTypePNG;
use std::io::Cursor;

// XXX: There does not appear to be an alternative for obtaining images without the need for
Expand Down Expand Up @@ -295,14 +234,22 @@ impl<'clipboard> Set<'clipboard> {

#[cfg(feature = "image-data")]
pub(crate) fn image(self, data: ImageData) -> Result<(), Error> {
let pixels = data.bytes.into();
let image = image_from_pixels(pixels, data.width, data.height)
.map_err(|_| Error::ConversionFailure)?;
use objc2_foundation::NSData;
use std::ffi::c_void;

self.clipboard.clear();

let image_array = NSArray::from_vec(vec![ProtocolObject::from_id(image)]);
let success = unsafe { self.clipboard.pasteboard.writeObjects(&image_array) };
let encoded = encode_as_png(&data)?;
let success = unsafe {
let ns_data = {
NSData::initWithBytes_length(
NSData::alloc(),
encoded.as_ptr() as *mut c_void,
encoded.len(),
)
};
self.clipboard.pasteboard.setData_forType(Some(&ns_data), NSPasteboardTypePNG)
};

add_clipboard_exclusions(self.clipboard, self.exclude_from_history);

Expand Down
18 changes: 2 additions & 16 deletions src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ use std::{borrow::Cow, marker::PhantomData, thread, time::Duration};
#[cfg(feature = "image-data")]
mod image_data {
use super::*;
use crate::common::ScopeGuard;
use image::codecs::png::PngEncoder;
use image::ExtendedColorType;
use image::ImageEncoder;
use crate::common::{encode_as_png, ScopeGuard};
use std::{convert::TryInto, ffi::c_void, io, mem::size_of, ptr::copy_nonoverlapping};
use windows_sys::Win32::{
Foundation::HGLOBAL,
Expand Down Expand Up @@ -128,18 +125,7 @@ mod image_data {
}

pub(super) fn add_png_file(image: &ImageData) -> Result<(), Error> {
// Try encoding the image as PNG.
let mut buf = Vec::new();
let encoder = PngEncoder::new(&mut buf);

encoder
.write_image(
&image.bytes,
image.width as u32,
image.height as u32,
ExtendedColorType::Rgba8,
)
.map_err(|_| Error::ConversionFailure)?;
let buf = encode_as_png(image)?;

// Register PNG format.
let format_id = match clipboard_win::register_format("PNG") {
Expand Down

0 comments on commit 3203d92

Please sign in to comment.