use crate::{Result, SyphonError};
#[derive(Debug, Clone, Default)]
pub struct ServerOptions {
pub is_private: bool,
}
#[cfg(target_os = "macos")]
use objc::runtime::{Class, Object};
#[cfg(target_os = "macos")]
use objc::{msg_send, sel, sel_impl};
#[cfg(target_os = "macos")]
use objc_id::ShareId;
pub struct SyphonServer {
#[cfg(target_os = "macos")]
inner: ShareId<Object>,
name: String,
width: u32,
height: u32,
}
#[cfg(target_os = "macos")]
unsafe impl Send for SyphonServer {}
#[cfg(target_os = "macos")]
unsafe impl Sync for SyphonServer {}
impl SyphonServer {
pub fn new(name: &str, width: u32, height: u32) -> Result<Self> {
Self::new_with_options(name, width, height, ServerOptions::default())
}
pub fn new_with_options(
name: &str,
width: u32,
height: u32,
options: ServerOptions,
) -> Result<Self> {
#[cfg(target_os = "macos")]
{
let device = Self::create_default_metal_device()
.ok_or_else(|| SyphonError::CreateFailed(
"Failed to create Metal device".to_string()
))?;
Self::new_macos(name, device, width, height, options)
}
#[cfg(not(target_os = "macos"))]
{ Err(SyphonError::NotAvailable) }
}
pub fn new_with_name_and_device(
name: &str,
metal_device: *mut Object,
width: u32,
height: u32,
) -> Result<Self> {
Self::new_with_name_and_device_and_options(
name, metal_device, width, height, ServerOptions::default()
)
}
pub fn new_with_name_and_device_and_options(
name: &str,
metal_device: *mut Object,
width: u32,
height: u32,
options: ServerOptions,
) -> Result<Self> {
#[cfg(target_os = "macos")]
{ Self::new_macos(name, metal_device, width, height, options) }
#[cfg(not(target_os = "macos"))]
{ Err(SyphonError::NotAvailable) }
}
#[cfg(target_os = "macos")]
fn create_default_metal_device() -> Option<*mut Object> {
unsafe {
extern "C" {
fn MTLCreateSystemDefaultDevice() -> *mut Object;
}
let device = MTLCreateSystemDefaultDevice();
if device.is_null() {
None
} else {
Some(device)
}
}
}
#[cfg(target_os = "macos")]
fn new_macos(
name: &str,
metal_device: *mut Object,
width: u32,
height: u32,
options: ServerOptions,
) -> Result<Self> {
use crate::utils::to_nsstring;
unsafe {
objc::rc::autoreleasepool(|| {
let cls = Class::get("SyphonMetalServer")
.or_else(|| Class::get("SyphonServer"))
.ok_or_else(|| SyphonError::FrameworkNotFound(
"SyphonMetalServer class not found".to_string()
))?;
let ns_name = to_nsstring(name)?;
let options_dict: *mut Object = if options.is_private {
Self::build_options_dict(&options)
} else {
std::ptr::null_mut()
};
let obj: *mut Object = msg_send![cls, alloc];
let obj: *mut Object = msg_send![
obj,
initWithName: ns_name
device: metal_device
options: options_dict
];
if !options_dict.is_null() {
let _: () = msg_send![options_dict, release];
}
if obj.is_null() {
return Err(SyphonError::CreateFailed("Failed to create SyphonServer".to_string()));
}
Ok(Self { inner: ShareId::from_ptr(obj), name: name.to_string(), width, height })
})
}
}
#[cfg(target_os = "macos")]
unsafe fn build_options_dict(options: &ServerOptions) -> *mut Object {
use crate::utils::to_nsstring;
let dict_cls = Class::get("NSMutableDictionary").unwrap();
let dict: *mut Object = msg_send![dict_cls, dictionary];
let _: () = msg_send![dict, retain];
if options.is_private {
let key = to_nsstring("SyphonServerOptionIsPrivate").unwrap();
let num_cls = Class::get("NSNumber").unwrap();
let val: *mut Object = msg_send![num_cls, numberWithBool: true];
let _: () = msg_send![dict, setObject: val forKey: key];
}
dict
}
pub fn name(&self) -> &str {
&self.name
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[cfg(target_os = "macos")]
pub unsafe fn publish_metal_texture(
&self,
texture: *mut Object, command_buffer: *mut Object, ) {
use cocoa::foundation::{NSRect, NSPoint, NSSize};
use objc::rc::autoreleasepool;
autoreleasepool(|| {
let region = NSRect {
origin: NSPoint::new(0.0, 0.0),
size: NSSize::new(self.width as f64, self.height as f64),
};
let _: () = msg_send![
&*self.inner,
publishFrameTexture: texture
onCommandBuffer: command_buffer
imageRegion: region
flipped: false
];
});
}
#[cfg(target_os = "macos")]
pub fn client_count(&self) -> usize {
unsafe {
objc::rc::autoreleasepool(|| {
let has: bool = msg_send![&*self.inner, hasClients];
if has { 1 } else { 0 }
})
}
}
pub fn has_clients(&self) -> bool {
self.client_count() > 0
}
pub fn stop(&self) {
#[cfg(target_os = "macos")]
unsafe {
objc::rc::autoreleasepool(|| { let _: () = msg_send![&*self.inner, stop]; });
}
}
}
impl Drop for SyphonServer {
fn drop(&mut self) {
self.stop();
log::debug!("[SyphonServer] '{}' dropped", self.name);
}
}