screencapturekit/shareable_content/
window.rs

1use crate::cg::CGRect;
2use crate::utils::ffi_string::{ffi_string_from_buffer, DEFAULT_BUFFER_SIZE};
3use core::fmt;
4use std::ffi::c_void;
5
6use super::SCRunningApplication;
7
8/// Wrapper around `SCWindow` from `ScreenCaptureKit`
9///
10/// Represents a window that can be captured.
11///
12/// # Examples
13///
14/// ```no_run
15/// use screencapturekit::shareable_content::SCShareableContent;
16///
17/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
18/// let content = SCShareableContent::get()?;
19/// for window in content.windows() {
20///     if let Some(title) = window.title() {
21///         println!("Window: {} (ID: {})", title, window.window_id());
22///     }
23/// }
24/// # Ok(())
25/// # }
26/// ```
27#[repr(transparent)]
28pub struct SCWindow(*const c_void);
29
30impl PartialEq for SCWindow {
31    fn eq(&self, other: &Self) -> bool {
32        self.0 == other.0
33    }
34}
35
36impl Eq for SCWindow {}
37
38impl std::hash::Hash for SCWindow {
39    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40        self.0.hash(state);
41    }
42}
43
44/// Raw pointer type for `SCWindow` (for FFI compatibility)
45pub type SCWindowRef = *const c_void;
46
47impl SCWindow {
48    /// Create from raw pointer (used internally by shareable content)
49    pub(crate) unsafe fn from_ptr(ptr: *const c_void) -> Self {
50        Self(ptr)
51    }
52
53    /// Get the raw pointer (used internally)
54    pub(crate) fn as_ptr(&self) -> *const c_void {
55        self.0
56    }
57
58    /// Get the owning application
59    pub fn owning_application(&self) -> Option<SCRunningApplication> {
60        unsafe {
61            let app_ptr = crate::ffi::sc_window_get_owning_application(self.0);
62            if app_ptr.is_null() {
63                None
64            } else {
65                Some(SCRunningApplication::from_ptr(app_ptr))
66            }
67        }
68    }
69
70    /// Get the window ID
71    pub fn window_id(&self) -> u32 {
72        unsafe { crate::ffi::sc_window_get_window_id(self.0) }
73    }
74
75    /// Get the window frame (position and size)
76    pub fn frame(&self) -> CGRect {
77        unsafe {
78            let mut x = 0.0;
79            let mut y = 0.0;
80            let mut width = 0.0;
81            let mut height = 0.0;
82            crate::ffi::sc_window_get_frame(self.0, &mut x, &mut y, &mut width, &mut height);
83            CGRect::new(x, y, width, height)
84        }
85    }
86
87    /// Get the window title (if available)
88    pub fn title(&self) -> Option<String> {
89        unsafe {
90            ffi_string_from_buffer(DEFAULT_BUFFER_SIZE, |buf, len| {
91                crate::ffi::sc_window_get_title(self.0, buf, len)
92            })
93        }
94    }
95
96    /// Get window layer
97    pub fn window_layer(&self) -> i32 {
98        // FFI returns isize but window layer fits in i32
99        #[allow(clippy::cast_possible_truncation)]
100        unsafe {
101            crate::ffi::sc_window_get_window_layer(self.0) as i32
102        }
103    }
104
105    /// Check if window is on screen
106    pub fn is_on_screen(&self) -> bool {
107        unsafe { crate::ffi::sc_window_is_on_screen(self.0) }
108    }
109
110    /// Check if window is active (macOS 14.0+)
111    pub fn is_active(&self) -> bool {
112        unsafe { crate::ffi::sc_window_is_active(self.0) }
113    }
114}
115
116impl Drop for SCWindow {
117    fn drop(&mut self) {
118        if !self.0.is_null() {
119            unsafe {
120                crate::ffi::sc_window_release(self.0);
121            }
122        }
123    }
124}
125
126impl Clone for SCWindow {
127    fn clone(&self) -> Self {
128        unsafe { Self(crate::ffi::sc_window_retain(self.0)) }
129    }
130}
131
132impl fmt::Debug for SCWindow {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.debug_struct("SCWindow")
135            .field("window_id", &self.window_id())
136            .field("title", &self.title())
137            .field("frame", &self.frame())
138            .field("window_layer", &self.window_layer())
139            .field("is_on_screen", &self.is_on_screen())
140            .field("is_active", &self.is_active())
141            .finish()
142    }
143}
144
145impl fmt::Display for SCWindow {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(
148            f,
149            "Window {} \"{}\" ({})",
150            self.window_id(),
151            self.title().unwrap_or_else(|| String::from("<untitled>")),
152            self.frame()
153        )
154    }
155}
156
157unsafe impl Send for SCWindow {}
158unsafe impl Sync for SCWindow {}