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
CGSMainConnectionID
to 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
NSApplication
for 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.