From bccf4c61004feee49dc85071809c670a17f76a03 Mon Sep 17 00:00:00 2001 From: EinTim23 Date: Sat, 3 May 2025 22:59:52 +0200 Subject: [PATCH] add old mediaremote way of getting playback data, as osascript way only works on sonoma and newer. --- CMakeLists.txt | 8 +++ src/MediaRemote.hpp | 17 ++++++ src/backends/darwin.mm | 114 ++++++++++++++++++++++++++++++++--------- 3 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/MediaRemote.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index acf87cc..1cb2e3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,14 @@ if(WIN32) list(APPEND LIBRARIES WindowsApp) target_link_options(PlayerLink PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") elseif(APPLE) + set(MEDIAREMOTE_FRAMEWORK_PATH "/System/Library/PrivateFrameworks") + find_library(MEDIAREMOTE_LIBRARY MediaRemote PATHS ${MEDIAREMOTE_FRAMEWORK_PATH}) + if (MEDIAREMOTE_LIBRARY) + message(STATUS "Found MediaRemote: ${MEDIAREMOTE_LIBRARY}") + list(APPEND LIBRARIES ${MEDIAREMOTE_LIBRARY}) + else() + message(FATAL_ERROR "MediaRemote framework not found.") + endif() set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE TRUE) set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/MediaRemote.js PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") diff --git a/src/MediaRemote.hpp b/src/MediaRemote.hpp new file mode 100644 index 0000000..cfda3bb --- /dev/null +++ b/src/MediaRemote.hpp @@ -0,0 +1,17 @@ +#ifdef __APPLE__ +#import + +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoTitle; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoAlbum; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoArtist; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoDuration; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoElapsedTime; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoArtworkData; +FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoPlaybackRate; + +typedef void (^ MRMediaRemoteGetNowPlayingInfoCompletion)(CFDictionaryRef _Nullable information); +typedef void (^ MRMediaRemoteGetNowPlayingApplicationPIDCompletion)(int PID); + +FOUNDATION_EXPORT void MRMediaRemoteGetNowPlayingApplicationPID(dispatch_queue_t _Nullable queue, MRMediaRemoteGetNowPlayingApplicationPIDCompletion _Nullable completion); +FOUNDATION_EXPORT void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t _Nullable queue, MRMediaRemoteGetNowPlayingInfoCompletion _Nullable completion); +#endif \ No newline at end of file diff --git a/src/backends/darwin.mm b/src/backends/darwin.mm index e55b9d4..fd98060 100644 --- a/src/backends/darwin.mm +++ b/src/backends/darwin.mm @@ -8,6 +8,7 @@ #include #include +#include "../MediaRemote.hpp" #include "../backend.hpp" void hideDockIcon(bool shouldHide) { @@ -17,12 +18,12 @@ void hideDockIcon(bool shouldHide) { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; } -NSString* getFilePathFromBundle(NSString* fileName, NSString* fileType) { +NSString *getFilePathFromBundle(NSString *fileName, NSString *fileType) { NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType]; return filePath; } -NSString* executeCommand(NSString* command, NSArray* arguments) { +NSString *executeCommand(NSString *command, NSArray *arguments) { NSTask *task = [[NSTask alloc] init]; task.launchPath = command; task.arguments = arguments; @@ -34,39 +35,102 @@ NSString* executeCommand(NSString* command, NSArray* arguments) { NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile]; NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [task waitUntilExit]; - + return output; } std::shared_ptr backend::getMediaInformation() { - static NSString* script = getFilePathFromBundle(@"MediaRemote", @"js"); - NSString* output = executeCommand(@"/usr/bin/osascript", @[@"-l", @"JavaScript", script]); - nlohmann::json j = nlohmann::json::parse([output UTF8String]); + // apple decided to prevent apps not signed by them to use media remote, so we use an apple script instead. But that script only works on Sonoma or newer and the other one is arguably better, so keep the old method as well + if (@available(macOS 15.0, *)) { + static NSString *script = getFilePathFromBundle(@"MediaRemote", @"js"); + NSString *output = executeCommand(@"/usr/bin/osascript", @[ @"-l", @"JavaScript", script ]); + nlohmann::json j = nlohmann::json::parse([output UTF8String]); - std::string appName = j["player"].get(); - if (appName == "none") - return nullptr; + std::string appName = j["player"].get(); + if (appName == "none") + return nullptr; - bool paused = j["playbackStatus"].get() == 0; + bool paused = j["playbackStatus"].get() == 0; - std::string songTitle = j["title"].get(); + std::string songTitle = j["title"].get(); - std::string songAlbum = j["album"].get(); + std::string songAlbum = j["album"].get(); - std::string songArtist = j["artist"].get(); + std::string songArtist = j["artist"].get(); - int64_t elapsedTimeMs = 0; - int64_t durationMs = 0; - try { - double durationNumber = j["duration"].get(); - durationMs = static_cast(durationNumber * 1000); + int64_t elapsedTimeMs = 0; + int64_t durationMs = 0; + try { + double durationNumber = j["duration"].get(); + durationMs = static_cast(durationNumber * 1000); - double elapsedTimeNumber = j["elapsed"].get(); - elapsedTimeMs = static_cast(elapsedTimeNumber * 1000); - } catch (...) {} + double elapsedTimeNumber = j["elapsed"].get(); + elapsedTimeMs = static_cast(elapsedTimeNumber * 1000); + } catch (...) { + } - return std::make_shared(paused, songTitle, songArtist, songAlbum, appName, "", - durationMs, elapsedTimeMs); + return std::make_shared(paused, songTitle, songArtist, songAlbum, appName, "", durationMs, + elapsedTimeMs); + } else { + __block NSString *appName = nil; + __block NSDictionary *playingInfo = nil; + + dispatch_group_t group = dispatch_group_create(); + + dispatch_group_enter(group); + MRMediaRemoteGetNowPlayingApplicationPID(dispatch_get_main_queue(), ^(pid_t pid) { + if (pid > 0) { + NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + if (app) + appName = [[app.bundleIdentifier copy] retain]; + } + dispatch_group_leave(group); + }); + + dispatch_group_enter(group); + MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef result) { + if (result) + playingInfo = [[(__bridge NSDictionary *)result copy] retain]; + dispatch_group_leave(group); + }); + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + dispatch_release(group); + if (appName == nil || playingInfo == nil) + return nullptr; + + bool paused = [playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoPlaybackRate] intValue] == 0; + + NSString *title = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoTitle]; + std::string songTitle = title ? [title UTF8String] : ""; + + NSString *album = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoAlbum]; + std::string songAlbum = album ? [album UTF8String] : ""; + + NSString *artist = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtist]; + std::string songArtist = artist ? [artist UTF8String] : ""; + + NSData *artworkData = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData]; + + std::string thumbnailData; + if (artworkData) + thumbnailData = std::string((const char *)[artworkData bytes], [artworkData length]); + + NSNumber *elapsedTimeNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoElapsedTime]; + + NSNumber *durationNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoDuration]; + + int64_t elapsedTimeMs = elapsedTimeNumber ? static_cast([elapsedTimeNumber doubleValue] * 1000) : 0; + + int64_t durationMs = durationNumber ? static_cast([durationNumber doubleValue] * 1000) : 0; + + std::string appNameString = appName.UTF8String; + + [appName release]; + [playingInfo release]; + return std::make_shared(paused, songTitle, songArtist, songAlbum, appNameString, thumbnailData, + durationMs, elapsedTimeMs); + } } std::filesystem::path backend::getConfigDirectory() { @@ -80,14 +144,14 @@ bool backend::toggleAutostart(bool enabled) { launchAgentPath = launchAgentPath / "Library" / "LaunchAgents"; std::filesystem::create_directories(launchAgentPath); launchAgentPath = launchAgentPath / "PlayerLink.plist"; - + if (!enabled && std::filesystem::exists(launchAgentPath)) { std::filesystem::remove(launchAgentPath); return true; } NSString *binaryPath = [[[NSProcessInfo processInfo] arguments][0] stringByStandardizingPath]; - //I would also like to use std::format here, but well I also want to support older mac os versions. + // I would also like to use std::format here, but well I also want to support older mac os versions. std::string formattedPlist = "\n\n\n \n\n "