one can now move windows between spaces programmatically in a nonhacky way without disabling SIP ```rust 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) } ```
Activity
jakubdonovan commentedon Sep 7, 2026
Is this still something you're planning to pursue, or more of a note to self for
now? Happy to test a branch
acsandmann commentedon Sep 7, 2026
Just an idea so far, it would be a significant change internally. once rift is a little more stable (we are pretty close) i will explore.