April 3, 2025

Swoncord - Discord Rich Presence from Swinsian

I use a music player called Swinsian. You can think if Swinsian as if Apple never invented the iPod and didn’t make iTunes into a laggy mess many years ago, it’s a focused music player driven by a SQLite database that’s lightweight, fast and rock solid. The developer is very incognito and development seems to have stalled but beta versions for v3 are discoverable if you know where to look.

Discord has this cool feature called Rich Presence” which allows third party apps to display a short status of what you are currently doing. It’s a realtime ephemeral feed and the most common thing you’ll see is people listening to music or playing games. A few years ago I toyed around with the idea of displaying Swinsian as a Discord Rich Presence. I took a stab at it then and built something really janky. This approach used AppleScript to query Swinsian, manually formatted that AppleScript into JSON, parsed the JSON in Rust and posted the result to the Discord IPC pipe. It was a CLI tool that I quickly forgot about as it was tedious to turn it on and off and I eventually gave up on it.

al

Story could have ended there but as happened with the Slow Electric Vol.1 mix, someone randomly reached out to ask me how do I build this thing and make it work’ . Sadly the knowledge gap for this individual was too wide for me to bridge as I didn’t really feel in the mood of educating someone how to use a terminal, but the interaction reminded me that I at one point had this tool. A larger trend in my life lately has been to try to finish projects where I gave up or got distracted, for things I still would want to exist in my life. Separately, we played around with some ideas internally at Discord that surfaced rich presence in a more fun way and at this point it’s clear to me that I should give this thing another shot.

Digging the project up again, I quickly realized what the dead end was. Polling with AppleScript is honestly quite icky but seemed to me to be the only way to get the data. Looking deeper, Swinsian documents some developer’ keys that I could use but this seemed opauqe to me? I gave it a shot and implmented a simple NSDistributedNotificationCenter client in Rust that would listen to all the notification queues and just tell me Swinsian actually posted, not just what they document. Lo and behold the player posts way more data than what’s documented on the webpage, it’s basically firing of the entire data model as an event, which means we have artist, album, title, genre, composer, track_number, disc_number, year, length, current_time, track_uuid, art_path, thumbnail_path, file_path, file_type, bitrate, grouping, conductor & comment.

let block = ConcreteBlock::new(move |notification: id| {
    let name: id = msg_send![notification, name];
    let name_str = NSString::UTF8String(name);
    let name_rust = std::ffi::CStr::from_ptr(name_str).to_string_lossy();

    let user_info: id = msg_send![notification, userInfo];

    if user_info != nil {
        let track_info: TrackInfo = TrackInfo::from(user_info);
        sender.send((State::from(name_rust.as_ref()), track_info)).unwrap();
    }
});

let block = block.copy();

let _: () = msg_send![self.notifciation_center, 
    addObserverForName:track_playing
    object:nil
    queue:nil
    singBlock:&*block];

ewwwwwwwwwwwwwwwwwwwwwwwwwwww

Working on this makes me realize how true’ Cocoa & Objective-C tried to be to Smalltalk, it’s all basically message passing between the app and the operating system, where you package upp messages with structs and pointers and fire them of to Objective-C land. It seems reading about it that this is changing with Swift but it’s a very fundamentally different approach from the Win32 style programming. Yes, I’ve read about it 100 times and written Swift but interfacing against Cocoa from Rust strips away all of the syntactic sugar that Objective-C layered on top of this paradigm.

After writing some really jank Objective-C object parsing in Rust and got a stable path for extracting all values I had two more things to solve for: album art and minimal UI. I initially considered just uploading the album art every time I switched song to some serverless function on Cloudflare but ended up querying MusicBrainz, as they already have a good way to serve album art.

For the minimal UI, I really wanted the app to be a MacOS menu bar app. MacOS menu bar app are these small icons on the top of your desktop that either indicate status or serve as entrypoints to single-purpose apps. Initially I tried using tray-item, but MacOS expects there to be only one main thread recieving messages. Once I added this in conjunction with the message handlilng thread, MacOS yanked execution from underneath me. Since I already had the reference counting setup, I took a shot at wrangling more Rust around the NSStatusBar API myself. The hardest part ended up being figuring out the resource mapping between Rust and what an .app bundle expects for icons.

After one evening of wrangling, it’s up on github. It’s such a small nerdy feature but honestly sometimes we all have to take a break and make these features that benefits noone but yourself. It’s even debatable if benefits me LOL.

rich presence

Projects


Previous post
Garmin Varia Seatpost Extender I’m doing a longer bike ride (200 miles / 320 km) next week in an unsupported fashion. I’m expecting this to take a fair bit of time given I have to