Media (macOS): don't spawn child process

This commit is contained in:
Carter Li
2026-10-10 16:32:09 +08:00
parent 068d86ef13
commit 124f474f81
3 changed files with 51 additions and 101 deletions
+48 -1
View File
@@ -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}" "$<TARGET_FILE:fastfetch>"
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}" "$<TARGET_FILE:flashfetch>"
VERBATIM
)
endif()
+1 -1
View File
@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>fastfetch</string>
<string>@BUNDLE_ID@</string>
<key>CFBundleName</key>
<string>@PROJECT_NAME@</string>
<key>CFBundleShortVersionString</key>
+2 -99
View File
@@ -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 {