From cdde2ec49e00b6f950f8150e5ff948b4a2a0d63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 18 Sep 2026 20:47:22 -0300 Subject: [PATCH] Fix file associations in macOS. --- premake4.lua | 7 ++++- premake5.lua | 6 ++++- src/eepp/system/fileassociation.cpp | 28 ++++++++++++++------ src/eepp/system/fileassociation_macos.mm | 33 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/eepp/system/fileassociation_macos.mm diff --git a/premake4.lua b/premake4.lua index 3b74bcbb4..e73acffec 100644 --- a/premake4.lua +++ b/premake4.lua @@ -593,6 +593,10 @@ function build_link_configuration( package_name, use_ee_icon ) end end + if os.is_real("macosx") then + linkoptions { "-weak_framework UniformTypeIdentifiers" } + end + if _OPTIONS["with-mold-linker"] then if _OPTIONS.platform == "clang" or _OPTIONS.platform == "clang-analyzer" then linkoptions { "-fuse-ld=mold" } @@ -1644,7 +1648,8 @@ solution "eepp" set_targetdir("libs/" .. os.get_real() .. "/") includedirs { "include", "src" } files { "src/eepp/ui/platform/macos/macosmenubar.mm", - "src/eepp/window/platform/macos/platformhelper.mm" } + "src/eepp/window/platform/macos/platformhelper.mm", + "src/eepp/system/fileassociation_macos.mm" } buildoptions { "-x objective-c++" } if not is_vs() then buildoptions{ "-std=c++20" } diff --git a/premake5.lua b/premake5.lua index a83ffea23..28a88c687 100644 --- a/premake5.lua +++ b/premake5.lua @@ -538,6 +538,9 @@ function build_link_configuration( package_name, use_ee_icon ) linkoptions { "-Wl,-rpath,'$$ORIGIN'" } end + filter "system:macosx" + linkoptions { "-weak_framework UniformTypeIdentifiers" } + filter { "system:bsd" } if package_name ~= "eepp" and package_name ~= "eepp-static" then if type(userelativelinks) == "function" then @@ -1675,7 +1678,8 @@ workspace "eepp" cppdialect "C++20" incdirs { "include", "src" } files { "src/eepp/ui/platform/macos/macosmenubar.mm", - "src/eepp/window/platform/macos/platformhelper.mm" } + "src/eepp/window/platform/macos/platformhelper.mm", + "src/eepp/system/fileassociation_macos.mm" } buildoptions { "-x objective-c++" } build_base_cpp_configuration( "eepp-macos-helper" ) target_dir_lib( "" ) diff --git a/src/eepp/system/fileassociation.cpp b/src/eepp/system/fileassociation.cpp index eb0426606..b0b6c4d9b 100644 --- a/src/eepp/system/fileassociation.cpp +++ b/src/eepp/system/fileassociation.cpp @@ -21,6 +21,10 @@ #include #endif +#if EE_PLATFORM == EE_PLATFORM_MACOS +extern "C" CFStringRef eeppFileAssociationTypeIdentifierForExtension( const char* extension ); +#endif + namespace EE::System { namespace { @@ -557,11 +561,19 @@ static CFStringRef cfString( const std::string& value ) { } static CFStringRef typeForExtension( const std::string& extension ) { - CFRef extensionString( cfString( extension ) ); - if ( !extensionString.get() ) - return nullptr; - return UTTypeCreatePreferredIdentifierForTag( - kUTTagClassFilenameExtension, static_cast( extensionString.get() ), nullptr ); + return eeppFileAssociationTypeIdentifierForExtension( extension.c_str() ); +} + +static CFStringRef applicationIdentifier( const FileAssociationApplication& application ) { + /* Launch Services uses the bundle identifier from the registered bundle. The cross-platform + * application id is not necessarily that identifier (ecode, for example, uses ensoft.dev in + * its macOS Info.plist). Prefer the bundle metadata when this process is running from a bundle, + * and retain the supplied id as a fallback for non-bundled callers. */ + if ( auto* bundle = CFBundleGetMainBundle() ) { + if ( auto identifier = CFBundleGetIdentifier( bundle ) ) + return static_cast( CFRetain( identifier ) ); + } + return cfString( application.id ); } #endif @@ -621,7 +633,7 @@ std::vector FileAssociation::getRegisteredExtensions( registered.emplace_back( extension ); } #elif EE_PLATFORM == EE_PLATFORM_MACOS - CFRef applicationId( cfString( mApplication.id ) ); + CFRef applicationId( applicationIdentifier( mApplication ) ); if ( !applicationId.get() ) { mLastError = "The application identifier is not valid UTF-8."; return {}; @@ -656,7 +668,7 @@ bool FileAssociation::setRegisteredExtensions( const std::vector& r const auto supported = normalizeExtensions( supportedExtensions ); const auto requested = normalizeExtensions( registeredExtensions ); std::vector selected; - selected.reserve( (std::min)( requested.size(), supported.size() ) ); + selected.reserve( ( std::min )( requested.size(), supported.size() ) ); std::set_intersection( requested.begin(), requested.end(), supported.begin(), supported.end(), std::back_inserter( selected ) ); #if EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_BSD @@ -712,7 +724,7 @@ bool FileAssociation::setRegisteredExtensions( const std::vector& r mLastError = "Launch Services could not register the application bundle."; return false; } - CFRef applicationId( cfString( mApplication.id ) ); + CFRef applicationId( applicationIdentifier( mApplication ) ); if ( !applicationId.get() ) { mLastError = "The application identifier is not valid UTF-8."; return false; diff --git a/src/eepp/system/fileassociation_macos.mm b/src/eepp/system/fileassociation_macos.mm new file mode 100644 index 000000000..dd449ae15 --- /dev/null +++ b/src/eepp/system/fileassociation_macos.mm @@ -0,0 +1,33 @@ +#include + +#if EE_PLATFORM == EE_PLATFORM_MACOS + +#import +#import +#import + +extern "C" CFStringRef eeppFileAssociationTypeIdentifierForExtension( const char* extension ) { + if ( extension == nullptr ) + return nullptr; + + NSString* extensionString = [NSString stringWithUTF8String:extension]; + if ( extensionString == nil ) + return nullptr; + + if ( @available( macOS 11.0, * ) ) { + UTType* type = [UTType typeWithFilenameExtension:extensionString]; + if ( type == nil || type.identifier == nil ) + return nullptr; + return CFStringCreateWithCString( kCFAllocatorDefault, type.identifier.UTF8String, + kCFStringEncodingUTF8 ); + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + CFStringRef type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, static_cast( extensionString ), nullptr ); +#pragma clang diagnostic pop + return type; +} + +#endif