use core_foundation::base::{CFType, TCFType};
use core_foundation::boolean::CFBoolean;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_graphics::window::{
copy_window_info, kCGNullWindowID, kCGWindowBounds, kCGWindowIsOnscreen, kCGWindowLayer,
kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly, kCGWindowName,
kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID,
};
use objc2::rc::Retained;
use objc2_app_kit::{NSApplicationActivationOptions, NSRunningApplication};
use crate::errors::{CarDesktopError, Result};
use crate::models::{WindowFilter, WindowFrame, WindowHandle, WindowInfo};
pub fn list_windows_impl(filter: &WindowFilter) -> Result<Vec<WindowInfo>> {
let options = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
let list = copy_window_info(options, kCGNullWindowID).ok_or_else(|| {
CarDesktopError::OsApi {
detail: "CGWindowListCopyWindowInfo returned null".into(),
source: None,
}
})?;
let count = list.len();
let mut out = Vec::with_capacity(count as usize);
for i in 0..count {
let dict_ref = match list.get(i) {
Some(item) => item,
None => continue,
};
let dict_type_ref = *dict_ref as core_foundation::base::CFTypeRef;
let dict: CFDictionary<CFString, CFType> = unsafe {
CFDictionary::wrap_under_get_rule(dict_type_ref as _)
};
let Some(info) = window_info_from_dict(&dict) else {
continue;
};
if filter.matches(&info) {
out.push(info);
}
}
Ok(out)
}
pub fn focus_window_impl(handle: WindowHandle) -> Result<()> {
let pid = handle.pid as libc::pid_t;
let app: Option<Retained<NSRunningApplication>> =
unsafe { NSRunningApplication::runningApplicationWithProcessIdentifier(pid) };
let Some(app) = app else {
return Err(CarDesktopError::WindowNotFound {
detail: format!("no running application for pid {}", handle.pid),
});
};
let success: bool = unsafe {
app.activateWithOptions(NSApplicationActivationOptions::NSApplicationActivateIgnoringOtherApps)
};
if !success {
return Err(CarDesktopError::OsApi {
detail: format!(
"NSRunningApplication.activateWithOptions returned false for pid {}",
handle.pid
),
source: None,
});
}
Ok(())
}
fn window_info_from_dict(dict: &CFDictionary<CFString, CFType>) -> Option<WindowInfo> {
let pid = cfnumber_value(dict, unsafe { kCGWindowOwnerPID })? as u32;
let window_id = cfnumber_value(dict, unsafe { kCGWindowNumber })? as u64;
let layer = cfnumber_value(dict, unsafe { kCGWindowLayer }).unwrap_or(0.0) as i32;
let title = cfstring_value(dict, unsafe { kCGWindowName }).unwrap_or_default();
let owner_name =
cfstring_value(dict, unsafe { kCGWindowOwnerName }).unwrap_or_default();
let frame = window_frame_from_bounds(dict).unwrap_or(WindowFrame {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
});
let on_screen = cfboolean_value(dict, unsafe { kCGWindowIsOnscreen }).unwrap_or(true);
let bundle_id = bundle_id_for_pid(pid);
Some(WindowInfo {
handle: WindowHandle::new(pid, window_id),
title,
bundle_id,
owner_name,
frame,
layer,
on_screen,
})
}
fn bundle_id_for_pid(pid: u32) -> Option<String> {
let app: Option<Retained<NSRunningApplication>> =
unsafe { NSRunningApplication::runningApplicationWithProcessIdentifier(pid as libc::pid_t) };
let app = app?;
let ns_str = unsafe { app.bundleIdentifier() }?;
Some(unsafe { ns_str.to_string() })
}
fn window_frame_from_bounds(dict: &CFDictionary<CFString, CFType>) -> Option<WindowFrame> {
let key_bounds = unsafe { CFString::wrap_under_get_rule(kCGWindowBounds) };
let value = dict.find(&key_bounds)?;
let bounds_untyped: CFDictionary = value.downcast::<CFDictionary>()?;
let bounds: CFDictionary<CFString, CFType> =
unsafe { CFDictionary::wrap_under_get_rule(bounds_untyped.as_concrete_TypeRef()) };
let x = cfnumber_value_str(&bounds, "X")?;
let y = cfnumber_value_str(&bounds, "Y")?;
let width = cfnumber_value_str(&bounds, "Width")?;
let height = cfnumber_value_str(&bounds, "Height")?;
Some(WindowFrame {
x,
y,
width,
height,
})
}
fn cfnumber_value(
dict: &CFDictionary<CFString, CFType>,
key_ref: core_foundation::string::CFStringRef,
) -> Option<f64> {
let key = unsafe { CFString::wrap_under_get_rule(key_ref) };
let value = dict.find(&key)?;
let number: CFNumber = value.downcast::<CFNumber>()?;
number.to_f64().or_else(|| number.to_i64().map(|i| i as f64))
}
fn cfnumber_value_str(dict: &CFDictionary<CFString, CFType>, key_str: &str) -> Option<f64> {
let key = CFString::new(key_str);
let value = dict.find(&key)?;
let number: CFNumber = value.downcast::<CFNumber>()?;
number.to_f64().or_else(|| number.to_i64().map(|i| i as f64))
}
fn cfstring_value(
dict: &CFDictionary<CFString, CFType>,
key_ref: core_foundation::string::CFStringRef,
) -> Option<String> {
let key = unsafe { CFString::wrap_under_get_rule(key_ref) };
let value = dict.find(&key)?;
let s: CFString = value.downcast::<CFString>()?;
Some(s.to_string())
}
fn cfboolean_value(
dict: &CFDictionary<CFString, CFType>,
key_ref: core_foundation::string::CFStringRef,
) -> Option<bool> {
let key = unsafe { CFString::wrap_under_get_rule(key_ref) };
let value = dict.find(&key)?;
let boolean: CFBoolean = value.downcast::<CFBoolean>()?;
Some(boolean.into())
}