Skip to main content

screencapturekit/shareable_content/
display.rs

1use crate::cg::CGRect;
2use core::fmt;
3use std::ffi::c_void;
4
5/// Opaque wrapper around `SCDisplay` from `ScreenCaptureKit`
6///
7/// Represents a physical or virtual display that can be captured.
8///
9/// # Examples
10///
11/// ```no_run
12/// use screencapturekit::shareable_content::SCShareableContent;
13///
14/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
15/// let content = SCShareableContent::get()?;
16/// for display in content.displays() {
17///     println!("Display {}: {}x{}",
18///         display.display_id(),
19///         display.width(),
20///         display.height()
21///     );
22/// }
23/// # Ok(())
24/// # }
25/// ```
26#[repr(transparent)]
27pub struct SCDisplay(*const c_void);
28
29impl PartialEq for SCDisplay {
30    fn eq(&self, other: &Self) -> bool {
31        self.0 == other.0
32    }
33}
34
35impl Eq for SCDisplay {}
36
37impl std::hash::Hash for SCDisplay {
38    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39        self.0.hash(state);
40    }
41}
42
43impl SCDisplay {
44    /// Create from raw pointer (used internally by shareable content)
45    pub(crate) unsafe fn from_ptr(ptr: *const c_void) -> Self {
46        Self(ptr)
47    }
48
49    /// Create from an FFI-owned (retained) pointer, returning `None` if null.
50    ///
51    /// # Safety
52    /// `ptr` must be null or a valid retained `SCDisplay` pointer transferred
53    /// from the Swift FFI bridge (ownership moves into the returned wrapper).
54    pub(crate) unsafe fn from_retained_ptr(ptr: *const c_void) -> Option<Self> {
55        if ptr.is_null() {
56            None
57        } else {
58            Some(unsafe { Self::from_ptr(ptr) })
59        }
60    }
61
62    /// Get the raw pointer (used internally)
63    pub(crate) fn as_ptr(&self) -> *const c_void {
64        self.0
65    }
66
67    /// Get display ID
68    ///
69    /// # Examples
70    ///
71    /// ```no_run
72    /// # use screencapturekit::shareable_content::SCShareableContent;
73    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
74    /// let content = SCShareableContent::get()?;
75    /// if let Some(display) = content.displays().first() {
76    ///     println!("Display ID: {}", display.display_id());
77    /// }
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub fn display_id(&self) -> u32 {
82        unsafe { crate::ffi::sc_display_get_display_id(self.0) }
83    }
84
85    /// Get display frame (position and size)
86    pub fn frame(&self) -> CGRect {
87        let mut x = 0.0;
88        let mut y = 0.0;
89        let mut width = 0.0;
90        let mut height = 0.0;
91        unsafe {
92            crate::ffi::sc_display_get_frame_packed(
93                self.0,
94                &mut x,
95                &mut y,
96                &mut width,
97                &mut height,
98            );
99        }
100        CGRect::new(x, y, width, height)
101    }
102
103    /// Get display height in pixels
104    ///
105    /// # Examples
106    ///
107    /// ```no_run
108    /// # use screencapturekit::shareable_content::SCShareableContent;
109    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
110    /// let content = SCShareableContent::get()?;
111    /// if let Some(display) = content.displays().first() {
112    ///     println!("Display resolution: {}x{}", display.width(), display.height());
113    /// }
114    /// # Ok(())
115    /// # }
116    /// ```
117    pub fn height(&self) -> u32 {
118        // FFI returns isize but display dimensions are always positive and fit in u32
119        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
120        unsafe {
121            crate::ffi::sc_display_get_height(self.0) as u32
122        }
123    }
124
125    /// Get display width in pixels
126    pub fn width(&self) -> u32 {
127        // FFI returns isize but display dimensions are always positive and fit in u32
128        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
129        unsafe {
130            crate::ffi::sc_display_get_width(self.0) as u32
131        }
132    }
133}
134
135crate::utils::retained::sc_retained!(
136    SCDisplay,
137    retain = crate::ffi::sc_display_retain,
138    release = crate::ffi::sc_display_release,
139);
140
141// SAFETY: `SCDisplay` wraps an immutable Objective-C ScreenCaptureKit object.
142// ObjC reference counting is atomic and these accessor-only objects are safe to
143// send between and share across threads.
144unsafe impl Send for SCDisplay {}
145unsafe impl Sync for SCDisplay {}
146
147impl fmt::Debug for SCDisplay {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.debug_struct("SCDisplay")
150            .field("display_id", &self.display_id())
151            .field("width", &self.width())
152            .field("height", &self.height())
153            .finish()
154    }
155}
156
157impl fmt::Display for SCDisplay {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(
160            f,
161            "Display {} ({}x{})",
162            self.display_id(),
163            self.width(),
164            self.height()
165        )
166    }
167}