#![cfg(target_os = "macos")]
use super::{Context, Icon, IconError, IconInterface, DEFAULT_ICON_SIZE};
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
use core_graphics::image::CGImageRef;
use objc::rc::StrongPtr;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use serde;
use url::Url;
#[derive(serde::Deserialize, Debug, Clone, PartialEq, Default)]
pub struct Os;
impl IconInterface for Os {
fn get_default_icon(&self) -> Result<Icon, IconError> {
let ident: NSString = "public.item".into();
unsafe {
let typ: *mut Object = msg_send![class!(UTType), typeWithIdentifier: ident];
let workspace = get_workspace()?;
let icon: *mut Object = msg_send![workspace, iconForContentType: typ];
let icon = icon.as_mut().context("iconForContentType was null")?;
image2icon(icon).context("Could not get default icon")
}
}
fn get_icon_for_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Icon, IconError> {
unsafe { icon_for_file(path.as_ref().as_os_str().into()) }
}
fn get_icon_for_url(&self, url: &str) -> Result<Icon, IconError> {
Url::parse(url).context("url is not valid")?;
unsafe {
let webstr: NSString = url.into();
let nsurl = class!(NSURL);
let url: *mut Object = msg_send![nsurl, URLWithString: webstr];
if url.is_null() {
return Err("Could not make NSURL".into());
}
let workspace = get_workspace()?;
let appurl: *mut Object = msg_send![workspace, URLForApplicationToOpenURL: url];
if appurl.is_null() {
return Err("Could not get url of app for opening url".into());
}
let path: *mut Object = msg_send![appurl, path];
if path.is_null() {
return Err("Could not get path of app url".into());
}
icon_for_file(path.try_into()?)
}
}
}
unsafe fn get_workspace() -> Result<&'static mut Object, IconError> {
let workspace: *mut Object = msg_send![class!(NSWorkspace), sharedWorkspace];
workspace
.as_mut()
.context("Could not get sharedWorkspace: Null")
}
unsafe fn image2icon(image: &mut Object) -> Result<Icon, IconError> {
let rect = CGRect {
origin: CGPoint::new(0.0, 0.0),
size: CGSize::new(DEFAULT_ICON_SIZE as f64, DEFAULT_ICON_SIZE as f64),
};
let cgicon: *mut CGImageRef = msg_send![image, CGImageForProposedRect:&rect context:0 hints:0];
let cgicon = cgicon.as_ref().ok_or("Cannot get CGImage")?;
let bpc = cgicon.bits_per_component();
let bpp = cgicon.bits_per_pixel();
if bpc != 8 || bpp != 32 {
return Err(format!("CGImage does not have 32bit depth: bpc: {bpc} bpp: {bpp}").into());
}
let h = cgicon.height() as u32;
let w = cgicon.width() as u32;
let pixels = Vec::from(cgicon.data().bytes());
Ok(Icon::from_pixels(h, w, pixels.leak()))
}
unsafe fn icon_for_file(path: NSString) -> Result<Icon, IconError> {
let workspace = get_workspace()?;
let icon: *mut Object = msg_send![workspace, iconForFile: path];
image2icon(icon.as_mut().context("Could not get iconForFile: null")?)
}
struct NSString(StrongPtr);
impl NSString {
unsafe fn from_raw(b: *const u8, len: usize) -> Self {
let nsstring = class!(NSString);
let obj = StrongPtr::new(msg_send![nsstring, alloc]);
if obj.is_null() {
panic!("failed to alloc NSString")
}
let outstr = StrongPtr::new(msg_send![*obj, initWithBytes:b length:len encoding:4]);
outstr.as_ref().expect("Could not init NSString");
Self(outstr)
}
}
use std::ffi::OsStr;
impl From<&OsStr> for NSString {
fn from(s: &OsStr) -> NSString {
use std::os::unix::ffi::OsStrExt;
unsafe {
let b = s.as_bytes();
NSString::from_raw(b.as_ptr(), b.len())
}
}
}
impl std::ops::Deref for NSString {
type Target = *mut Object;
fn deref(&self) -> &*mut Object {
self.0.deref()
}
}
use std::fmt::{Formatter, Pointer};
impl Pointer for NSString {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<&str> for NSString {
fn from(s: &str) -> NSString {
let b = s.as_bytes();
unsafe { NSString::from_raw(b.as_ptr(), b.len()) }
}
}
impl TryFrom<*mut Object> for NSString {
type Error = IconError;
fn try_from(s: *mut Object) -> Result<Self, Self::Error> {
use objc::runtime::{BOOL, NO};
unsafe {
let p = s.as_ref().context("Null Ptr While converting NSString")?;
let result: BOOL = msg_send![p, isKindOfClass: class!(NSString)];
if result == NO {
return Err("Conversion error: Object is not isKindOfClass NSString".into());
}
Ok(NSString(StrongPtr::new(s)))
}
}
}