use std::fmt;
use std::ptr::{self, NonNull};
use objc2_core_foundation::{CFRetained, CFString, CFType, CGPoint, CGRect, CGSize, Type};
use objc2_core_graphics::{CGContext, CGError};
use super::cg_ok;
use super::private::{
CGRegionCreateEmptyRegion, CGSNewRegionWithRect, CGSNewRegionWithRectList, G_CONNECTION,
SLSClearWindowTags, SLSFlushWindowContentRegion, SLSNewWindowWithOpaqueShapeAndContext,
SLSOrderWindow, SLSReleaseWindow, SLSSetWindowAlpha, SLSSetWindowBackgroundBlurRadius,
SLSSetWindowBackgroundBlurRadiusStyle, SLSSetWindowLevel, SLSSetWindowOpacity,
SLSSetWindowProperty, SLSSetWindowResolution, SLSSetWindowShape, SLSSetWindowSubLevel,
SLSSetWindowTags, SLWindowContextCreate, cid_t,
};
type WindowId = u32;
const TAG_BITSET_LEN: i32 = 64;
const DEFAULT_SUBLEVEL: i32 = 0;
const COMPOSITOR_WINDOW_OPTIONS: i32 = 13 | (1 << 18);
#[repr(transparent)]
pub(super) struct CFRegion(CFRetained<CFType>);
impl CFRegion {
fn from_rect(rect: &CGRect) -> Result<Self, CGError> {
let mut region: *mut CFType = ptr::null_mut();
cg_ok(unsafe { CGSNewRegionWithRect(rect, &mut region) })?;
let region = NonNull::new(region).ok_or(CGError(1000))?;
Ok(Self(unsafe { CFRetained::from_raw(region) }))
}
pub(super) fn from_rounded_rect(size: CGSize, radius: f64) -> Result<Self, CGError> {
let radius = radius.max(0.0).min(size.width.max(0.0) / 2.0).min(size.height.max(0.0) / 2.0);
if radius == 0.0 {
return Self::from_rect(&CGRect::new(CGPoint::new(0.0, 0.0), size));
}
const STEP: f64 = 0.5;
let mut rects = Vec::with_capacity((radius / STEP).ceil() as usize * 2 + 1);
let middle_height = (size.height - radius * 2.0).max(0.0);
if middle_height > 0.0 {
rects.push(CGRect::new(
CGPoint::new(0.0, radius),
CGSize::new(size.width, middle_height),
));
}
let mut y = 0.0;
while y < radius {
let height = STEP.min(radius - y);
let sample = y + height / 2.0;
let dy = radius - sample;
let inset = radius - (radius * radius - dy * dy).max(0.0).sqrt();
let width = (size.width - inset * 2.0).max(0.0);
rects.push(CGRect::new(CGPoint::new(inset, y), CGSize::new(width, height)));
rects.push(CGRect::new(
CGPoint::new(inset, size.height - y - height),
CGSize::new(width, height),
));
y += height;
}
let mut region = ptr::null_mut();
cg_ok(unsafe {
CGSNewRegionWithRectList(rects.as_ptr(), rects.len() as i32, &mut region)
})?;
NonNull::new(region)
.map(|region| Self(unsafe { CFRetained::from_raw(region) }))
.ok_or(CGError(1000))
}
fn empty() -> Result<Self, CGError> {
let region = NonNull::new(unsafe { CGRegionCreateEmptyRegion() }).ok_or(CGError(1000))?;
Ok(Self(unsafe { CFRetained::from_raw(region) }))
}
#[inline]
pub(super) fn as_ptr(&self) -> *mut CFType { CFRetained::<CFType>::as_ptr(&self.0).as_ptr() }
}
#[derive(Debug)]
pub enum CgsWindowError {
Region(CGError),
Window(CGError),
Resolution(CGError),
Alpha(CGError),
Blur(CGError),
Level(CGError),
Shape(CGError),
Surface(CGError),
Tags(CGError),
Release(CGError),
Property(CGError),
}
impl fmt::Display for CgsWindowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CgsWindowError::*;
match self {
Region(e) => write!(f, "CGS region error: {:?}", e),
Window(e) => write!(f, "CGS window create error: {:?}", e),
Resolution(e) => write!(f, "CGS window resolution error: {:?}", e),
Alpha(e) => write!(f, "CGS window alpha/opacity error: {:?}", e),
Blur(e) => write!(f, "CGS window blur error: {:?}", e),
Level(e) => write!(f, "CGS window level/order error: {:?}", e),
Shape(e) => write!(f, "CGS window shape error: {:?}", e),
Surface(e) => write!(f, "CGS window surface error: {:?}", e),
Tags(e) => write!(f, "CGS window tags error: {:?}", e),
Release(e) => write!(f, "CGS window release error: {:?}", e),
Property(e) => write!(f, "CGS window property error: {:?}", e),
}
}
}
impl std::error::Error for CgsWindowError {}
#[derive(Debug)]
pub struct CgsWindow {
id: WindowId,
connection: cid_t,
owned: bool,
}
impl CgsWindow {
pub fn new(frame: CGRect) -> Result<Self, CgsWindowError> {
unsafe {
let connection = *G_CONNECTION;
let frame_region = CFRegion::from_rect(&frame).map_err(CgsWindowError::Region)?;
let empty_region = CFRegion::empty().map_err(CgsWindowError::Region)?;
let mut tags: u64 = (1 << 1) | (1 << 9);
let mut wid: WindowId = 0;
cg_ok(SLSNewWindowWithOpaqueShapeAndContext(
connection,
2,
frame_region.as_ptr(),
empty_region.as_ptr(),
13,
&mut tags,
0.0,
0.0,
TAG_BITSET_LEN,
&mut wid,
ptr::null_mut(),
))
.map_err(CgsWindowError::Window)?;
let window = Self {
id: wid,
connection,
owned: true,
};
window.set_resolution(1.0)?;
Ok(window)
}
}
pub fn new_compositor(frame: CGRect, corner_radius: f64) -> Result<Self, CgsWindowError> {
unsafe {
let connection = *G_CONNECTION;
let frame_region = CFRegion::from_rounded_rect(frame.size, corner_radius)
.map_err(CgsWindowError::Region)?;
let empty_region = CFRegion::empty().map_err(CgsWindowError::Region)?;
let mut tags: u64 = (1 << 1) | (1 << 9) | (1 << 16);
let mut wid: WindowId = 0;
cg_ok(SLSNewWindowWithOpaqueShapeAndContext(
connection,
2,
frame_region.as_ptr(),
empty_region.as_ptr(),
COMPOSITOR_WINDOW_OPTIONS,
&mut tags,
frame.origin.x as f32,
frame.origin.y as f32,
TAG_BITSET_LEN,
&mut wid,
ptr::null_mut(),
))
.map_err(CgsWindowError::Window)?;
let window = Self {
id: wid,
connection,
owned: true,
};
window.set_resolution(2.0)?;
window.set_opacity(false)?;
window.clear_backing(frame.size)?;
Ok(window)
}
}
#[inline]
pub fn id(&self) -> WindowId { self.id }
#[inline]
pub fn into_unowned(mut self) -> Self {
self.owned = false;
self
}
#[inline]
pub fn from_existing(id: WindowId) -> Self {
Self {
id,
connection: *G_CONNECTION,
owned: false,
}
}
#[inline]
pub fn set_alpha(&self, alpha: f32) -> Result<(), CgsWindowError> {
unsafe { cg_ok(SLSSetWindowAlpha(self.connection, self.id, alpha)) }
.map_err(CgsWindowError::Alpha)
}
#[inline]
pub fn set_opacity(&self, opaque: bool) -> Result<(), CgsWindowError> {
unsafe { cg_ok(SLSSetWindowOpacity(self.connection, self.id, opaque)) }
.map_err(CgsWindowError::Alpha)
}
#[inline]
pub fn set_blur(&self, radius: i32, style: Option<i32>) -> Result<(), CgsWindowError> {
unsafe {
cg_ok(if let Some(style) = style {
SLSSetWindowBackgroundBlurRadiusStyle(self.connection, self.id, radius, style)
} else {
SLSSetWindowBackgroundBlurRadius(self.connection, self.id, radius)
})
}
.map_err(CgsWindowError::Blur)
}
#[inline]
pub fn set_level(&self, level: i32) -> Result<(), CgsWindowError> {
unsafe { cg_ok(SLSSetWindowLevel(self.connection, self.id, level)) }
.map_err(CgsWindowError::Level)?;
unsafe { cg_ok(SLSSetWindowSubLevel(self.connection, self.id, DEFAULT_SUBLEVEL)) }
.map_err(CgsWindowError::Level)
}
#[inline]
pub fn set_sublevel(&self, sublevel: i32) -> Result<(), CgsWindowError> {
unsafe { cg_ok(SLSSetWindowSubLevel(self.connection, self.id, sublevel)) }
.map_err(CgsWindowError::Level)
}
pub fn set_shape(&self, frame: CGRect) -> Result<(), CgsWindowError> {
unsafe {
let offset = frame.origin;
let size_rect = CGRect::new(CGPoint::new(0.0, 0.0), frame.size);
let region = CFRegion::from_rect(&size_rect).map_err(CgsWindowError::Region)?;
let result = cg_ok(SLSSetWindowShape(
self.connection,
self.id,
offset.x as f32,
offset.y as f32,
region.as_ptr(),
))
.map_err(CgsWindowError::Shape);
drop(region);
result
}
}
#[inline]
pub fn set_tags(&self, tags: u64) -> Result<(), CgsWindowError> {
unsafe {
let mut t = tags;
cg_ok(SLSSetWindowTags(
self.connection,
self.id,
&mut t,
TAG_BITSET_LEN,
))
.map_err(CgsWindowError::Tags)
}
}
#[inline]
pub fn clear_tags(&self, tags: u64) -> Result<(), CgsWindowError> {
unsafe {
let mut t = tags;
cg_ok(SLSClearWindowTags(
self.connection,
self.id,
&mut t,
TAG_BITSET_LEN,
))
.map_err(CgsWindowError::Tags)
}
}
#[inline]
pub fn order_above(&self, relative: Option<WindowId>) -> Result<(), CgsWindowError> {
let rel = relative.unwrap_or(0);
unsafe {
cg_ok(SLSOrderWindow(
self.connection,
self.id,
1, rel,
))
}
.map_err(CgsWindowError::Level)
}
#[inline]
pub fn order_below(&self, relative: Option<WindowId>) -> Result<(), CgsWindowError> {
let rel = relative.unwrap_or(0);
unsafe {
cg_ok(SLSOrderWindow(
self.connection,
self.id,
-1, rel,
))
}
.map_err(CgsWindowError::Level)
}
#[inline]
pub fn order_out(&self) -> Result<(), CgsWindowError> {
unsafe {
cg_ok(SLSOrderWindow(
self.connection,
self.id,
0, 0,
))
}
.map_err(CgsWindowError::Level)
}
#[inline]
pub fn set_property<T: Type>(
&self,
key: CFRetained<CFString>,
value: CFRetained<T>,
) -> Result<(), CgsWindowError> {
unsafe {
cg_ok(SLSSetWindowProperty(
self.connection,
self.id,
CFRetained::<CFString>::as_ptr(&key).as_ptr(),
CFRetained::<T>::as_ptr(&value).as_ptr() as *mut CFType,
))
.map_err(CgsWindowError::Property)
}
}
#[inline]
pub fn set_resolution(&self, scale: f64) -> Result<(), CgsWindowError> {
unsafe { cg_ok(SLSSetWindowResolution(self.connection, self.id, scale)) }
.map_err(CgsWindowError::Resolution)
}
pub fn clear_backing(&self, size: CGSize) -> Result<(), CgsWindowError> {
let context = unsafe { SLWindowContextCreate(self.connection, self.id, ptr::null_mut()) };
let context = NonNull::new(context).ok_or(CgsWindowError::Surface(CGError(1000)))?;
let context = unsafe { CFRetained::from_raw(context) };
CGContext::clear_rect(Some(&context), CGRect::new(CGPoint::new(0.0, 0.0), size));
CGContext::flush(Some(&context));
drop(context);
let _ = unsafe { SLSFlushWindowContentRegion(self.connection, self.id, ptr::null_mut()) };
Ok(())
}
}
impl Drop for CgsWindow {
fn drop(&mut self) {
if !self.owned {
return;
}
unsafe {
if let Err(err) = cg_ok(SLSReleaseWindow(self.connection, self.id)) {
tracing::warn!(error=?err, id=self.id, "failed to release CGS window");
}
}
}
}