portlight 0.0.2

Cross-platform window management for audio plugins
Documentation
use std::{ptr, slice};

use objc2_core_foundation::{CFDictionary, CFNumber, CFRetained, CFString};
use objc2_core_graphics::kCGColorSpaceSRGB;
use objc2_core_video::kCVPixelFormatType_32BGRA;
use objc2_io_surface::{
    kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow, kIOSurfaceColorSpace, kIOSurfaceHeight,
    kIOSurfacePixelFormat, kIOSurfaceWidth, IOSurfaceLockOptions, IOSurfaceRef,
};

use libc::kern_return_t;

use super::OsError;
use crate::{Bitmap, Error, Result};

#[allow(non_upper_case_globals)]
const kIOSurfaceSuccess: kern_return_t = 0;

const BYTES_PER_ELEMENT: usize = 4;

pub struct Surface {
    surface: CFRetained<IOSurfaceRef>,
    width: usize,
    height: usize,
    stride: usize,
}

impl Surface {
    pub fn new(width: usize, height: usize) -> Result<Surface> {
        let bytes_per_row = IOSurfaceRef::align_property(
            unsafe { kIOSurfaceBytesPerRow },
            width * BYTES_PER_ELEMENT,
        );
        let stride = bytes_per_row / BYTES_PER_ELEMENT;

        let properties = CFDictionary::<CFString, CFNumber>::from_slices(
            &[
                unsafe { kIOSurfaceWidth },
                unsafe { kIOSurfaceHeight },
                unsafe { kIOSurfaceBytesPerElement },
                unsafe { kIOSurfaceBytesPerRow },
                unsafe { kIOSurfacePixelFormat },
            ],
            &[
                &CFNumber::new_i32(width as i32),
                &CFNumber::new_i32(height as i32),
                &CFNumber::new_i32(BYTES_PER_ELEMENT as i32),
                &CFNumber::new_i32(bytes_per_row as i32),
                &CFNumber::new_i32(kCVPixelFormatType_32BGRA as i32),
            ],
        );

        let Some(surface) = (unsafe { IOSurfaceRef::new(properties.as_opaque()) }) else {
            return Err(Error::Os(OsError::Other("could not create IOSurface")));
        };

        unsafe {
            surface.set_value(kIOSurfaceColorSpace, kCGColorSpaceSRGB);
        }

        Ok(Surface {
            surface,
            width,
            height,
            stride,
        })
    }

    pub fn as_ptr(&self) -> *const IOSurfaceRef {
        CFRetained::as_ptr(&self.surface).as_ptr()
    }

    pub fn width(&self) -> usize {
        self.width
    }

    pub fn height(&self) -> usize {
        self.height
    }

    pub fn update(&self, bitmap: Bitmap) {
        let ret = unsafe { self.surface.lock(IOSurfaceLockOptions::empty(), ptr::null_mut()) };
        if ret != kIOSurfaceSuccess {
            return;
        }

        let addr = self.surface.base_address().as_ptr();
        let len = self.stride * self.height;
        let buffer = unsafe { slice::from_raw_parts_mut(addr as *mut u32, len) };

        let copy_width = bitmap.width().min(self.width);
        let copy_height = bitmap.height().min(self.height);

        for row in 0..copy_height {
            let src = &bitmap.data()[row * bitmap.width()..row * bitmap.width() + copy_width];
            let dst = &mut buffer[row * self.stride..row * self.stride + copy_width];
            dst.copy_from_slice(src);
        }

        unsafe {
            self.surface.unlock(IOSurfaceLockOptions::empty(), ptr::null_mut());
        }
    }
}