use crate::stream::content_filter::SCContentFilter;
use std::ffi::c_void;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SCPickedSource {
Window(String),
Display(u32),
Application(String),
Unknown,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCContentSharingPickerMode {
#[default]
SingleWindow = 0,
MultipleWindows = 1,
SingleDisplay = 2,
SingleApplication = 3,
MultipleApplications = 4,
}
pub struct SCContentSharingPickerConfiguration {
ptr: *const c_void,
}
impl SCContentSharingPickerConfiguration {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { crate::ffi::sc_content_sharing_picker_configuration_create() };
Self { ptr }
}
pub fn set_allowed_picker_modes(&mut self, modes: &[SCContentSharingPickerMode]) {
let mode_values: Vec<i32> = modes.iter().map(|m| *m as i32).collect();
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_set_allowed_picker_modes(
self.ptr,
mode_values.as_ptr(),
mode_values.len(),
);
}
}
pub fn set_allows_changing_selected_content(&mut self, allows: bool) {
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_set_allows_changing_selected_content(
self.ptr,
allows,
);
}
}
pub fn allows_changing_selected_content(&self) -> bool {
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_get_allows_changing_selected_content(
self.ptr,
)
}
}
pub fn set_excluded_bundle_ids(&mut self, bundle_ids: &[&str]) {
let c_strings: Vec<std::ffi::CString> = bundle_ids
.iter()
.filter_map(|s| std::ffi::CString::new(*s).ok())
.collect();
let ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_set_excluded_bundle_ids(
self.ptr,
ptrs.as_ptr(),
ptrs.len(),
);
}
}
pub fn excluded_bundle_ids(&self) -> Vec<String> {
let count = unsafe {
crate::ffi::sc_content_sharing_picker_configuration_get_excluded_bundle_ids_count(
self.ptr,
)
};
let mut result = Vec::with_capacity(count);
for i in 0..count {
let mut buffer = vec![0i8; 256];
let success = unsafe {
crate::ffi::sc_content_sharing_picker_configuration_get_excluded_bundle_id_at(
self.ptr,
i,
buffer.as_mut_ptr(),
buffer.len(),
)
};
if success {
let c_str = unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) };
if let Ok(s) = c_str.to_str() {
result.push(s.to_string());
}
}
}
result
}
pub fn set_excluded_window_ids(&mut self, window_ids: &[u32]) {
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_set_excluded_window_ids(
self.ptr,
window_ids.as_ptr(),
window_ids.len(),
);
}
}
pub fn excluded_window_ids(&self) -> Vec<u32> {
let count = unsafe {
crate::ffi::sc_content_sharing_picker_configuration_get_excluded_window_ids_count(
self.ptr,
)
};
let mut result = Vec::with_capacity(count);
for i in 0..count {
let id = unsafe {
crate::ffi::sc_content_sharing_picker_configuration_get_excluded_window_id_at(
self.ptr, i,
)
};
result.push(id);
}
result
}
#[must_use]
pub const fn as_ptr(&self) -> *const c_void {
self.ptr
}
}
impl Default for SCContentSharingPickerConfiguration {
fn default() -> Self {
Self::new()
}
}
impl Clone for SCContentSharingPickerConfiguration {
fn clone(&self) -> Self {
unsafe {
Self {
ptr: crate::ffi::sc_content_sharing_picker_configuration_retain(self.ptr),
}
}
}
}
impl Drop for SCContentSharingPickerConfiguration {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
crate::ffi::sc_content_sharing_picker_configuration_release(self.ptr);
}
}
}
}
impl std::fmt::Debug for SCContentSharingPickerConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SCContentSharingPickerConfiguration")
.field("ptr", &self.ptr)
.finish()
}
}
#[derive(Debug)]
pub enum SCPickerFilterOutcome {
Filter(SCContentFilter),
Cancelled,
Error(String),
}
pub struct SCPickerResult {
ptr: *const c_void,
}
impl SCPickerResult {
#[cfg(feature = "async")]
#[must_use]
pub(crate) fn from_ptr(ptr: *const c_void) -> Self {
Self { ptr }
}
#[must_use]
pub fn filter(&self) -> SCContentFilter {
let filter_ptr = unsafe { crate::ffi::sc_picker_result_get_filter(self.ptr) };
SCContentFilter::from_picker_ptr(filter_ptr)
}
#[must_use]
pub fn size(&self) -> (f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
let mut width = 0.0;
let mut height = 0.0;
unsafe {
crate::ffi::sc_picker_result_get_content_rect(
self.ptr,
&mut x,
&mut y,
&mut width,
&mut height,
);
}
(width, height)
}
#[must_use]
pub fn rect(&self) -> (f64, f64, f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
let mut width = 0.0;
let mut height = 0.0;
unsafe {
crate::ffi::sc_picker_result_get_content_rect(
self.ptr,
&mut x,
&mut y,
&mut width,
&mut height,
);
}
(x, y, width, height)
}
#[must_use]
pub fn scale(&self) -> f64 {
unsafe { crate::ffi::sc_picker_result_get_scale(self.ptr) }
}
#[must_use]
pub fn pixel_size(&self) -> (u32, u32) {
let (w, h) = self.size();
let scale = self.scale();
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let width = (w * scale) as u32;
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let height = (h * scale) as u32;
(width, height)
}
#[must_use]
pub fn windows(&self) -> Vec<crate::shareable_content::SCWindow> {
let count = unsafe { crate::ffi::sc_picker_result_get_windows_count(self.ptr) };
(0..count)
.filter_map(|i| {
let ptr = unsafe { crate::ffi::sc_picker_result_get_window_at(self.ptr, i) };
if ptr.is_null() {
None
} else {
Some(crate::shareable_content::SCWindow::from_ffi_owned(ptr))
}
})
.collect()
}
#[must_use]
pub fn displays(&self) -> Vec<crate::shareable_content::SCDisplay> {
let count = unsafe { crate::ffi::sc_picker_result_get_displays_count(self.ptr) };
(0..count)
.filter_map(|i| {
let ptr = unsafe { crate::ffi::sc_picker_result_get_display_at(self.ptr, i) };
if ptr.is_null() {
None
} else {
Some(crate::shareable_content::SCDisplay::from_ffi_owned(ptr))
}
})
.collect()
}
#[must_use]
pub fn applications(&self) -> Vec<crate::shareable_content::SCRunningApplication> {
let count = unsafe { crate::ffi::sc_picker_result_get_applications_count(self.ptr) };
(0..count)
.filter_map(|i| {
let ptr = unsafe { crate::ffi::sc_picker_result_get_application_at(self.ptr, i) };
if ptr.is_null() {
None
} else {
Some(crate::shareable_content::SCRunningApplication::from_ffi_owned(ptr))
}
})
.collect()
}
#[must_use]
#[allow(clippy::option_if_let_else)]
pub fn source(&self) -> SCPickedSource {
if let Some(window) = self.windows().first() {
SCPickedSource::Window(window.title().unwrap_or_else(|| "Untitled".to_string()))
} else if let Some(display) = self.displays().first() {
SCPickedSource::Display(display.display_id())
} else if let Some(app) = self.applications().first() {
SCPickedSource::Application(app.application_name())
} else {
SCPickedSource::Unknown
}
}
}
impl Drop for SCPickerResult {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
crate::ffi::sc_picker_result_release(self.ptr);
}
}
}
}
impl std::fmt::Debug for SCPickerResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (w, h) = self.size();
let scale = self.scale();
f.debug_struct("SCPickerResult")
.field("size", &(w, h))
.field("scale", &scale)
.field("pixel_size", &self.pixel_size())
.finish()
}
}
#[derive(Debug)]
pub enum SCPickerOutcome {
Picked(SCPickerResult),
Cancelled,
Error(String),
}
pub struct SCContentSharingPicker;
impl SCContentSharingPicker {
pub fn show<F>(config: &SCContentSharingPickerConfiguration, callback: F)
where
F: FnOnce(SCPickerOutcome) + Send + 'static,
{
let callback = Box::new(callback);
let context = Box::into_raw(callback).cast::<std::ffi::c_void>();
unsafe {
crate::ffi::sc_content_sharing_picker_show_with_result(
config.as_ptr(),
picker_callback_boxed::<F>,
context,
);
}
}
pub fn show_for_stream<F>(
config: &SCContentSharingPickerConfiguration,
stream: &crate::stream::SCStream,
callback: F,
) where
F: FnOnce(SCPickerOutcome) + Send + 'static,
{
let callback = Box::new(callback);
let context = Box::into_raw(callback).cast::<std::ffi::c_void>();
unsafe {
crate::ffi::sc_content_sharing_picker_show_for_stream(
config.as_ptr(),
stream.as_ptr(),
picker_callback_boxed::<F>,
context,
);
}
}
pub fn show_filter<F>(config: &SCContentSharingPickerConfiguration, callback: F)
where
F: FnOnce(SCPickerFilterOutcome) + Send + 'static,
{
let callback = Box::new(callback);
let context = Box::into_raw(callback).cast::<std::ffi::c_void>();
unsafe {
crate::ffi::sc_content_sharing_picker_show(
config.as_ptr(),
picker_filter_callback_boxed::<F>,
context,
);
}
}
pub fn show_using_style<F>(
config: &SCContentSharingPickerConfiguration,
style: crate::stream::content_filter::SCShareableContentStyle,
callback: F,
) where
F: FnOnce(SCPickerOutcome) + Send + 'static,
{
let callback = Box::new(callback);
let context = Box::into_raw(callback).cast::<std::ffi::c_void>();
unsafe {
crate::ffi::sc_content_sharing_picker_show_using_style(
config.as_ptr(),
style as i32,
picker_callback_boxed::<F>,
context,
);
}
}
pub fn show_for_stream_using_style<F>(
config: &SCContentSharingPickerConfiguration,
stream: &crate::stream::SCStream,
style: crate::stream::content_filter::SCShareableContentStyle,
callback: F,
) where
F: FnOnce(SCPickerOutcome) + Send + 'static,
{
let callback = Box::new(callback);
let context = Box::into_raw(callback).cast::<std::ffi::c_void>();
unsafe {
crate::ffi::sc_content_sharing_picker_show_for_stream_using_style(
config.as_ptr(),
stream.as_ptr(),
style as i32,
picker_callback_boxed::<F>,
context,
);
}
}
pub fn set_maximum_stream_count(count: usize) {
unsafe {
crate::ffi::sc_content_sharing_picker_set_maximum_stream_count(count);
}
}
pub fn maximum_stream_count() -> usize {
unsafe { crate::ffi::sc_content_sharing_picker_get_maximum_stream_count() }
}
}
extern "C" fn picker_callback_boxed<F>(
code: i32,
ptr: *const std::ffi::c_void,
context: *mut std::ffi::c_void,
) where
F: FnOnce(SCPickerOutcome) + Send + 'static,
{
let callback = unsafe { Box::from_raw(context.cast::<F>()) };
let outcome = match code {
1 if !ptr.is_null() => SCPickerOutcome::Picked(SCPickerResult { ptr }),
0 => SCPickerOutcome::Cancelled,
_ => SCPickerOutcome::Error("Picker failed".to_string()),
};
callback(outcome);
}
extern "C" fn picker_filter_callback_boxed<F>(
code: i32,
ptr: *const std::ffi::c_void,
context: *mut std::ffi::c_void,
) where
F: FnOnce(SCPickerFilterOutcome) + Send + 'static,
{
let callback = unsafe { Box::from_raw(context.cast::<F>()) };
let outcome = match code {
1 if !ptr.is_null() => SCPickerFilterOutcome::Filter(SCContentFilter::from_picker_ptr(ptr)),
0 => SCPickerFilterOutcome::Cancelled,
_ => SCPickerFilterOutcome::Error("Picker failed".to_string()),
};
callback(outcome);
}
unsafe impl Send for SCContentSharingPickerConfiguration {}
unsafe impl Sync for SCContentSharingPickerConfiguration {}
unsafe impl Send for SCPickerResult {}
unsafe impl Sync for SCPickerResult {}