Space Monitor is a Rust API for subscribing to real-time changes on Mac OS X to obtain the current active space (virtual desktop) index.
Heavily inspired by the great work of George Christou and his Swift project - WhichSpace.
Check usage in the examples directory
use std::thread;
use macos_space_monitor::{MonitorEvent, SpaceMonitor};
fn main() {
let (monitor, rx) = SpaceMonitor::new();
let _monitoring_thread = thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
MonitorEvent::SpaceChange(space) => {
println!("Space change detected! Active space is: {}", space);
}
}
}
});
monitor.start_listening();
}use macos_space_monitor::SpaceMonitor;
fn main() {
let space = SpaceMonitor::get_current_space_number();
println!("Current space: {}", space);
}This library was motivated by a fun project I am working on that deals with managing spaces in a more custom way on Mac OS X for more efficient space navigation. One of the core requirements when building space/window management tooling is to understand where you are within your display. This is a key crate I rely on to enable real-time lookups to map a virtual display ID to a space index.
If you don't know Rust or aren't using Rust and simply just want a binary you can invoke from your own code, you can build the example directly and embed the binary or add it to your $PATH.
-
Event Listener Version:
- Build:
cargo build --release --example monitor - Run:
./target/release/examples/monitor
- Build:
-
Adhoc Version:
- Build:
cargo build --release --example adhoc - Run:
./target/release/examples/adhoc
- Build:
Surprisingly, obtaining the active virtual desktop index is a non-trivial task on Mac OS X and attempts in doing so have been breaking release after release as the method relies on undocumented Mac OS native APIs.
This method relies on a few key ingredients:
-
Core Graphics (CG)
- We use
CGSMainConnectionIDto get a connection to the main window server - The CGS (core graphics services) API is exploited to obtain this information
- We use
-
FFI (Foreign function interface)
- Bridge for us to call the C APIs from Rust
-
Cocoa
- Apple's native API for Mac OS apps
NSApplicationfor background app- Handle system notifications
-
Objective-C
- Some message-passing invocations (
msg_send!) - Used for receiving event notifications
- Some message-passing invocations (
Space monitor is essentially a Rust binding to access lower-level mac OS internal APIs in an easy and efficient way.
While you can occassionally deciper some esoteric plist files to derive the active screen via defaults read com.apple.spaces SpacesDisplayConfiguration, the contents are almost always incorrect and out of date, which makes it a non-starter for realtime change detection.
When I designed this crate, I wanted a minimal example I could iterate off of in Swift to simplify the migration into Rust since I'm not a Swift developer. Mostly just committing this for posterity, but you can find a much simpler implementation of this lib in Swift underneath the ./swift directory. Once again, this is heavily inspired by WhichSpace, but wanted to remove all the boilerplate.
You can compile it via either of the following:
- ./swift/compile.sh
swiftc -o SpaceMonitor CurrentSpace-types.swift CurrentSpace-main.swift CurrentSpace-delegate.swift
Then just run:
./SpaceMonitor
As this crate relies on private, undocumented native Mac OS APIs internally, I believe your app would be rejected from the Apple app store if this crate is used within your application. However, users can still install the application externally.