use super::attachment::Attachment;
pub(crate) const UNSUPPORTED: &str = "Reading copied files or images from the clipboard is not available on this platform. Drag the file onto the chat instead.";
#[cfg(target_os = "macos")]
pub(crate) fn can_read() -> bool {
objc2::MainThreadMarker::new().is_some()
}
#[cfg(not(target_os = "macos"))]
pub(crate) const fn can_read() -> bool {
false
}
#[cfg(target_os = "macos")]
pub(crate) fn read_files() -> (Vec<Attachment>, Vec<String>) {
if !can_read() {
return (Vec::new(), Vec::new());
}
files(&objc2_app_kit::NSPasteboard::generalPasteboard())
}
#[cfg(not(target_os = "macos"))]
pub(crate) const fn read_files() -> (Vec<Attachment>, Vec<String>) {
(Vec::new(), Vec::new())
}
#[cfg(target_os = "macos")]
pub(crate) fn read_image() -> Result<Option<Attachment>, String> {
if !can_read() {
return Ok(None);
}
image(&objc2_app_kit::NSPasteboard::generalPasteboard())
}
#[cfg(not(target_os = "macos"))]
#[expect(clippy::unnecessary_wraps)] pub(crate) const fn read_image() -> Result<Option<Attachment>, String> {
Ok(None)
}
#[cfg(target_os = "macos")]
fn files(pasteboard: &objc2_app_kit::NSPasteboard) -> (Vec<Attachment>, Vec<String>) {
use objc2::ClassType;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_app_kit::NSPasteboardURLReadingFileURLsOnlyKey;
use objc2_foundation::{NSArray, NSDictionary, NSNumber, NSURL};
let classes = NSArray::from_slice(&[NSURL::class()]);
let only_files = unsafe { Retained::cast_unchecked::<AnyObject>(NSNumber::new_bool(true)) };
let only_file_urls_key = unsafe { NSPasteboardURLReadingFileURLsOnlyKey };
let options = NSDictionary::from_retained_objects(&[only_file_urls_key], &[only_files]);
let Some(objects) =
(unsafe { pasteboard.readObjectsForClasses_options(&classes, Some(&options)) })
else {
return (Vec::new(), Vec::new());
};
let paths = objects.to_vec().into_iter().filter_map(|object| {
let url = object.downcast_ref::<NSURL>()?;
let path = url.path()?;
Some(std::path::PathBuf::from(path.to_string()))
});
super::attachment::intake(paths)
}
#[cfg(target_os = "macos")]
fn image(pasteboard: &objc2_app_kit::NSPasteboard) -> Result<Option<Attachment>, String> {
use objc2::AnyThread;
use objc2_app_kit::{NSImage, NSPasteboardTypePNG, NSPasteboardTypeTIFF};
let (png_type, tiff_type) = unsafe { (NSPasteboardTypePNG, NSPasteboardTypeTIFF) };
let png = pasteboard
.dataForType(png_type)
.map(|data| data.to_vec())
.or_else(|| {
let tiff = pasteboard.dataForType(tiff_type)?;
png_from_tiff(&tiff)
})
.or_else(|| {
let image = NSImage::initWithPasteboard(NSImage::alloc(), pasteboard)?;
let tiff = image.TIFFRepresentation()?;
png_from_tiff(&tiff)
});
let Some(png) = png.filter(|png| !png.is_empty()) else {
return Ok(None);
};
Attachment::from_image_bytes(format!("clipboard-{}.png", crate::util::unix_millis()), png)
.map(Some)
}
#[cfg(target_os = "macos")]
fn png_from_tiff(data: &objc2_foundation::NSData) -> Option<Vec<u8>> {
use objc2::AnyThread;
use objc2_app_kit::{NSBitmapImageFileType, NSBitmapImageRep};
use objc2_foundation::NSDictionary;
let rep = NSBitmapImageRep::initWithData(NSBitmapImageRep::alloc(), data)?;
let properties = NSDictionary::new();
let png =
unsafe { rep.representationUsingType_properties(NSBitmapImageFileType::PNG, &properties) }?;
Some(png.to_vec())
}