one can now move windows between spaces programmatically in a nonhacky way without disabling SIP
use objc2::{
msg_send, sel,
rc::Retained,
runtime::{AnyClass, AnyObject, Bool},
};
use objc2_foundation::{ns_string, NSArray, NSNumber};
use std::{ffi::c_char, sync::OnceLock};
pub type CGWindowID = u32;
pub type SLSSpaceMask = i32;
pub fn move_window_to_space(window: CGWindowID, space: u64) -> bool {
op_move(&[window], space)
.and_then(|op| perform(&op))
.is_some()
}
pub fn copy_spaces_for_windows(windows: &[CGWindowID], options: SLSSpaceMask) -> Option<Vec<u64>> {
let result = perform(&op_copy_spaces(windows, options)?)?;
let nums: Retained<AnyObject> =
unsafe { msg_send![&*result, valueForKey: ns_string!("numbers")] };
Some((0..unsafe { msg_send![&*nums, count] })
.map(|i: usize| unsafe {
let n: *mut AnyObject = msg_send![&*nums, objectAtIndex: i];
msg_send![n, unsignedLongLongValue]
})
.collect())
}
pub fn copy_managed_display_spaces() -> Option<Retained<AnyObject>> {
let obj: Retained<AnyObject> = unsafe { msg_send![class(c"SLSBridgedCopyManagedDisplaySpacesOperation")?, new] };
let result = perform(&obj)?;
Some(unsafe { msg_send![&*result, valueForKey: ns_string!("propertyListArray")] })
}
fn op_move(windows: &[CGWindowID], space: u64) -> Option<Retained<AnyObject>> {
let cls = class(c"SLSBridgedMoveWindowsToManagedSpaceOperation")?;
let arr = nsnums(windows);
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
Some(unsafe { msg_send![obj, initWithWindows: &*arr, spaceID: space] })
}
fn op_copy_spaces(windows: &[CGWindowID], options: SLSSpaceMask) -> Option<Retained<AnyObject>> {
let cls = class(c"SLSBridgedCopySpacesForWindowsOperation")?;
let arr = nsnums(windows);
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
Some(unsafe { msg_send![obj, initWithOptions: options, windows: &*arr] })
}
fn perform(op: &AnyObject) -> Option<Retained<AnyObject>> {
let ok: Bool = unsafe { msg_send![op, respondsToSelector: sel!(performWithWMBridgeDelegate)] };
ok.as_bool()
.then(|| unsafe { msg_send![op, performWithWMBridgeDelegate] })
.flatten()
}
fn nsnums(xs: &[CGWindowID]) -> Retained<NSArray<NSNumber>> {
NSArray::from_vec(xs.iter().copied().map(NSNumber::new_u32).collect())
}
fn class(name: &'static std::ffi::CStr) -> Option<&'static AnyClass> {
static LOAD: OnceLock<()> = OnceLock::new();
LOAD.get_or_init(|| unsafe {
libc::dlopen(
c"/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight"
.as_ptr()
.cast::<c_char>(),
libc::RTLD_LAZY | libc::RTLD_LOCAL,
);
});
AnyClass::get(name)
}
one can now move windows between spaces programmatically in a nonhacky way without disabling SIP