From 124f474f8121bf1a20e68993133d1f2ddb2bbe5e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 10 Oct 2026 16:32:09 +0800 Subject: [PATCH] Media (macOS): don't spawn child process --- CMakeLists.txt | 49 ++++++++++++++- src/common/apple/Info.plist.in | 2 +- src/detection/media/media_apple.m | 101 +----------------------------- 3 files changed, 51 insertions(+), 101 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b3bcbecc..d2e4addda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,7 +454,29 @@ fastfetch_encode_c_string("${DATATEXT_JSON_HELP}" DATATEXT_JSON_HELP) configure_file(src/fastfetch_config.h.in fastfetch_config.h @ONLY) configure_file(src/fastfetch_datatext.h.in fastfetch_datatext.h @ONLY) if(APPLE) + # `mediaremoted` serves now-playing data only to clients whose bundle id starts + # with "com.apple.": -[MRDMediaRemoteClient isAllowedAccessToDataFromPlayerPath:] + # accepts the 0x200 entitlement bit either for the private + # com.apple.mediaremote.now-playing-read-access entitlement or for that prefix, + # and it derives the client's identity from the code signature / XPC audit token + # rather than from the process's own NSBundle. Both the embedded + # CFBundleIdentifier and the ad-hoc signing identifier below therefore have to + # carry the prefix; keep them in sync through these variables. + set(FASTFETCH_BUNDLE_ID "com.apple.fastfetch") + set(FLASHFETCH_BUNDLE_ID "com.apple.flashfetch") + # configure_file() inputs are not configure dependencies on their own, so + # without this an edit to Info.plist.in never even regenerates Info.plist. + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/common/apple/Info.plist.in") + # Each executable gets its own plist: the file is embedded with -sectcreate and read + # back by the system as the bundle id, so two targets sharing one would leave one of + # them advertising an identifier that its code signature does not match. + set(BUNDLE_ID "${FASTFETCH_BUNDLE_ID}") configure_file(src/common/apple/Info.plist.in Info.plist @ONLY) + if(BUILD_FLASHFETCH) + set(BUNDLE_ID "${FLASHFETCH_BUNDLE_ID}") + configure_file(src/common/apple/Info.plist.in Info.plist.flashfetch @ONLY) + endif() + unset(BUNDLE_ID) endif() #################### @@ -2244,6 +2266,15 @@ set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "") set_target_properties(fastfetch PROPERTIES LINKER_LANGUAGE C) if(WIN32) + # version.rc embeds manifest.xml (RT_MANIFEST) and logo.ico (ICON) as *resource + # references*, not as #includes, so the resource compiler never reports them and + # CMake would keep a stale .res when only the manifest or the icon changes. Both + # are plain files rather than generated ones, so declare them by hand; the source + # file property is per-path, which is why flashfetch below needs no repeat. + set_property(SOURCE src/common/windows/version.rc APPEND PROPERTY OBJECT_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/common/windows/manifest.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/src/common/windows/logo.ico" + ) target_sources(fastfetch PRIVATE src/common/windows/version.rc ) @@ -2251,6 +2282,15 @@ elseif(APPLE) target_link_options(fastfetch PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist ) + # `LINKER:` options are handed to ld verbatim and are not registered as build + # dependencies, so without this ninja never relinks when Info.plist changes. + set_property(TARGET fastfetch APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/Info.plist") + # ld derives the ad-hoc code-signing identifier from the output file name and + # ignores the embedded CFBundleIdentifier, so set it explicitly. + add_custom_command(TARGET fastfetch POST_BUILD + COMMAND codesign --force --sign - --identifier "${FASTFETCH_BUNDLE_ID}" "$" + VERBATIM + ) endif() if(BINARY_LINK_TYPE STREQUAL "static") @@ -2276,8 +2316,15 @@ if (BUILD_FLASHFETCH) PRIVATE src/common/windows/version.rc ) elseif(APPLE) + # flashfetch embeds its own plist so that its CFBundleIdentifier matches the + # identifier it is signed with below (see the APPLE block near the top). target_link_options(flashfetch - PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist + PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist.flashfetch + ) + set_property(TARGET flashfetch APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/Info.plist.flashfetch") + add_custom_command(TARGET flashfetch POST_BUILD + COMMAND codesign --force --sign - --identifier "${FLASHFETCH_BUNDLE_ID}" "$" + VERBATIM ) endif() diff --git a/src/common/apple/Info.plist.in b/src/common/apple/Info.plist.in index 52200538b..ab69b226d 100644 --- a/src/common/apple/Info.plist.in +++ b/src/common/apple/Info.plist.in @@ -3,7 +3,7 @@ CFBundleIdentifier - fastfetch + @BUNDLE_ID@ CFBundleName @PROJECT_NAME@ CFBundleShortVersionString diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m index 32f2c6166..a7acea467 100644 --- a/src/detection/media/media_apple.m +++ b/src/detection/media/media_apple.m @@ -110,106 +110,9 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) return error; } -#if !FF_MODULE_DISABLE_MEDIA -// Called from the Perl helper process through `DynaLoader::dl_install_xsub`. That mechanism installs a -// plain C function as an XSUB, so Perl calls it with the `CV*` in the first parameter slot and no Perl -// argument ever reaches a parameter — a `bool saveCover` parameter read as a pointer and was always -// truthy. The flag has to travel in the environment instead; see `getMediaByAuthorizedProcess`. -[[gnu::visibility("default"), gnu::used]] int ffPrintMediaByMediaRemote(void) { - const char* saveCoverEnv = getenv("FF_MEDIA_REMOTE_SAVE_COVER"); - bool saveCover = saveCoverEnv && saveCoverEnv[0] == '1'; - - FFMediaResult media = { - .status = ffStrbufCreate(), - .song = ffStrbufCreate(), - .artist = ffStrbufCreate(), - .album = ffStrbufCreate(), - .playerId = ffStrbufCreate(), - .player = ffStrbufCreate(), - .cover = ffStrbufCreate(), - }; - if (getMediaByMediaRemote(&media, saveCover) != nullptr) { - return 1; - } - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppend(&media.status, &media.song); - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppend(&media.status, &media.artist); - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppend(&media.status, &media.album); - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppend(&media.status, &media.playerId); - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppend(&media.status, &media.player); - ffStrbufAppendC(&media.status, '\n'); - if (saveCover) { - ffStrbufAppend(&media.status, &media.cover); - } - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppendUInt(&media.status, media.position); - ffStrbufAppendC(&media.status, '\n'); - ffStrbufAppendUInt(&media.status, media.length); - write(STDOUT_FILENO, media.status.chars, media.status.length); - ffStrbufDestroy(&media.status); - ffStrbufDestroy(&media.song); - ffStrbufDestroy(&media.artist); - ffStrbufDestroy(&media.album); - ffStrbufDestroy(&media.playerId); - ffStrbufDestroy(&media.player); - ffStrbufDestroy(&media.cover); - return 0; -} -#endif - -static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) { - // #1737 - // `DynaLoader::dl_install_xsub` hands the C function the `CV*` as its first parameter, so the flag - // cannot be passed as a Perl argument — `ffPrintMediaByMediaRemote` reads it from the environment, - // which the child process inherits. - FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("$ENV{'FF_MEDIA_REMOTE_SAVE_COVER'}='%c';\ -require DynaLoader;\ -my $so = DynaLoader::dl_load_file('%s', 0);\ -my $sym = DynaLoader::dl_find_symbol($so, 'ffPrintMediaByMediaRemote');\ -DynaLoader::dl_install_xsub('fn', $sym, __FILE__);\ -exit(fn())", - saveCover ? '1' : '0', - instance.state.platform.exePath.chars); - FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - - const char* error = ffProcessAppendStdOut( - &buffer, - (char* const[]) { "/usr/bin/perl", // Must be signed by Apple. Homebrew version doesn't work - "-e", - script.chars, - nil }); - if (error) { - return error; - } - if (buffer.length == 0) { - return "No media found"; - } - - // status\ntitle\nartist\nalbum\nbundleName\nappName\ncoverPath\nelapsedTimeMS\ndurationMS - FFstrbuf* const strList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover }; - char* line = nullptr; - size_t len = 0; - for (uint32_t i = 0; i < ARRAY_SIZE(strList) && ffStrbufGetline(&line, &len, &buffer); ++i) { - ffStrbufSetS(strList[i], line); - } - uint32_t* const numList[] = { &result->position, &result->length }; - for (uint32_t i = 0; i < ARRAY_SIZE(numList) && ffStrbufGetline(&line, &len, &buffer); ++i) { - *numList[i] = (uint32_t) strtoul(line, nullptr, 10); - } - return nullptr; -} - void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { - const char* error; - if (@available(macOS 15.4, *)) { - error = getMediaByAuthorizedProcess(media, saveCover); - } else { - error = getMediaByMediaRemote(media, saveCover); - } + const char* error = getMediaByMediaRemote(media, saveCover); + if (error) { ffStrbufAppendS(&media->error, error); } else {