From 48230b94ce87c398481f0a214c3d041b635e8139 Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Thu, 2 Apr 2026 22:25:09 +0200 Subject: [PATCH] [Cocoa] Replace deprecated LSGetApplicationForInfo in Program.findProgram LSGetApplicationForInfo (deprecated macOS 10.10) and CFURLCreateFromFSRef (deprecated macOS 10.9) are replaced by NSWorkspace APIs: - On macOS 12.0+: UTType.typeWithFilenameExtension: combined with NSWorkspace.urlForApplicationToOpenContentType: for reliable content type-based lookup, including third-party file types like .xlsx - Fallback for older macOS: NSWorkspace.urlForApplicationToOpenURL: with a dummy file URL carrying the extension Adds a test asserting that findProgram(".txt") returns a non-null program with a name on Windows and macOS. The test is skipped on Linux because applications for MIME types may not be registered in Linux test environments. --- .../Eclipse SWT PI/cocoa/library/os.c | 37 +------------------ .../Eclipse SWT PI/cocoa/library/os_stats.h | 4 +- .../swt/internal/cocoa/NSWorkspace.java | 12 +++++- .../org/eclipse/swt/internal/cocoa/OS.java | 22 ++--------- .../eclipse/swt/internal/cocoa/Selector.java | 5 ++- .../org/eclipse/swt/program/Program.java | 36 +++++++++++++----- .../Test_org_eclipse_swt_program_Program.java | 14 ++++++- 7 files changed, 60 insertions(+), 70 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c index b29812ac75..face3d4229 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -1091,22 +1091,6 @@ JNIEXPORT void JNICALL OS_NATIVE(CFRunLoopObserverInvalidate) } #endif -#ifndef NO_CFURLCreateFromFSRef -JNIEXPORT jlong JNICALL OS_NATIVE(CFURLCreateFromFSRef) - (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) -{ - jbyte *lparg1=NULL; - jlong rc = 0; - OS_NATIVE_ENTER(env, that, CFURLCreateFromFSRef_FUNC); - if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail; - rc = (jlong)CFURLCreateFromFSRef((CFAllocatorRef)arg0, (FSRef*)lparg1); -fail: - if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0); - OS_NATIVE_EXIT(env, that, CFURLCreateFromFSRef_FUNC); - return rc; -} -#endif - #ifndef NO_CFURLCreateStringByAddingPercentEscapes JNIEXPORT jlong JNICALL OS_NATIVE(CFURLCreateStringByAddingPercentEscapes) (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlong arg2, jlong arg3, jint arg4) @@ -2106,25 +2090,6 @@ JNIEXPORT jbyte JNICALL OS_NATIVE(LMGetKbdType) } #endif -#ifndef NO_LSGetApplicationForInfo -JNIEXPORT jlong JNICALL OS_NATIVE(LSGetApplicationForInfo) - (JNIEnv *env, jclass that, jint arg0, jint arg1, jlong arg2, jint arg3, jbyteArray arg4, jintArray arg5) -{ - jbyte *lparg4=NULL; - jint *lparg5=NULL; - jlong rc = 0; - OS_NATIVE_ENTER(env, that, LSGetApplicationForInfo_FUNC); - if (arg4) if ((lparg4 = (*env)->GetByteArrayElements(env, arg4, NULL)) == NULL) goto fail; - if (arg5) if ((lparg5 = (*env)->GetIntArrayElements(env, arg5, NULL)) == NULL) goto fail; - rc = (jlong)LSGetApplicationForInfo((OSType)arg0, (OSType)arg1, (CFStringRef)arg2, (LSRolesMask)arg3, (FSRef *)lparg4, (CFURLRef *)lparg5); -fail: - if (arg5 && lparg5) (*env)->ReleaseIntArrayElements(env, arg5, lparg5, 0); - if (arg4 && lparg4) (*env)->ReleaseByteArrayElements(env, arg4, lparg4, 0); - OS_NATIVE_EXIT(env, that, LSGetApplicationForInfo_FUNC); - return rc; -} -#endif - #ifndef NO_LineTo JNIEXPORT void JNICALL OS_NATIVE(LineTo) (JNIEnv *env, jclass that, jshort arg0, jshort arg1) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h index f574e737cb..be2c525007 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -70,7 +70,6 @@ typedef enum { CFRunLoopGetCurrent_FUNC, CFRunLoopObserverCreate_FUNC, CFRunLoopObserverInvalidate_FUNC, - CFURLCreateFromFSRef_FUNC, CFURLCreateStringByAddingPercentEscapes_FUNC, CGAffineTransform_1sizeof_FUNC, CGBitmapContextCreate_FUNC, @@ -149,7 +148,6 @@ typedef enum { JSStringCreateWithUTF8CString_FUNC, JSStringRelease_FUNC, LMGetKbdType_FUNC, - LSGetApplicationForInfo_FUNC, LineTo_FUNC, MoveTo_FUNC, NSAccessibilityAttributedStringForRangeParameterizedAttribute_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java index 46b9d320f1..9bfa02eae8 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -45,6 +45,16 @@ public boolean openURL(NSURL url) { return OS.objc_msgSend_bool(this.id, OS.sel_openURL_, url != null ? url.id : 0); } +public NSURL urlForApplicationToOpenURL(NSURL url) { + long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenURL_, url != null ? url.id : 0); + return result != 0 ? new NSURL(result) : null; +} + +public NSURL urlForApplicationToOpenContentType(long contentType) { + long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenContentType_, contentType); + return result != 0 ? new NSURL(result) : null; +} + public boolean openURLs(NSArray urls, NSString bundleIdentifier, long options, NSAppleEventDescriptor descriptor, long identifiers) { return OS.objc_msgSend_bool(this.id, OS.sel_openURLs_withAppBundleIdentifier_options_additionalEventParamDescriptor_launchIdentifiers_, urls != null ? urls.id : 0, bundleIdentifier != null ? bundleIdentifier.id : 0, options, descriptor != null ? descriptor.id : 0, identifiers); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java index fffa4837ed..53e6876a2d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2022 IBM Corporation and others. + * Copyright (c) 2007, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -65,9 +65,6 @@ public static int VERSION (int major, int minor, int bugfix) { public static final int kUIModeNormal = 0; public static final int kUIModeContentHidden = 2; public static final int kUIModeAllHidden = 3; - public static final int kLSUnknownType = 0; - public static final int kLSUnknownCreator = 0; - public static final int kLSRolesAll = 0xFFFFFFFF; public static final int kAXUnderlineStyleNone = 0x0; public static final int kAXUnderlineStyleSingle = 0x1; public static final int kAXUnderlineStyleThick = 0x2; @@ -313,15 +310,6 @@ public static boolean isBigSurOrLater () { public static final native long AcquireRootMenu (); /** @method flags=dynamic */ public static final native int CancelMenuTracking (long inRootMenu, boolean inImmediate, int inDismissalReason); -/** - * @param inType cast=(OSType) - * @param inCreator cast=(OSType) - * @param inExtension cast=(CFStringRef) - * @param inRoleMask cast=(LSRolesMask) - * @param outAppRef cast=(FSRef *) - * @param outAppURL cast=(CFURLRef *) - */ -public static final native long LSGetApplicationForInfo(int inType, int inCreator,long inExtension, int inRoleMask, byte[] outAppRef, int[] outAppURL); /** @method flags=dynamic * @param pmSessionInfo cast=(PMPrintSession) * @param outPMPrinter cast=(PMPrinter *) @@ -854,6 +842,8 @@ public static Selector getSelector (long value) { public static final long sel_TIFFRepresentation = Selector.sel_TIFFRepresentation.value; public static final long sel_URL = Selector.sel_URL.value; public static final long sel_URLFromPasteboard_ = Selector.sel_URLFromPasteboard_.value; +public static final long sel_URLForApplicationToOpenURL_ = Selector.sel_URLForApplicationToOpenURL_.value; +public static final long sel_URLForApplicationToOpenContentType_ = Selector.sel_URLForApplicationToOpenContentType_.value; public static final long sel_URLWithString_ = Selector.sel_URLWithString_.value; public static final long sel_UTF8String = Selector.sel_UTF8String.value; public static final long sel_abortEditing = Selector.sel_abortEditing.value; @@ -2061,6 +2051,7 @@ public static Selector getSelector (long value) { public static final long sel_type = Selector.sel_type.value; public static final long sel_type_conformsToType_ = Selector.sel_type_conformsToType_.value; public static final long sel_typeOfFile_error_ = Selector.sel_typeOfFile_error_.value; +public static final long sel_typeWithFilenameExtension_ = Selector.sel_typeWithFilenameExtension_.value; public static final long sel_types = Selector.sel_types.value; public static final long sel_typesetter = Selector.sel_typesetter.value; public static final long sel_unarchiveObjectWithData_ = Selector.sel_unarchiveObjectWithData_.value; @@ -2975,11 +2966,6 @@ public static Selector getSelector (long value) { * @param observer cast=(CFRunLoopObserverRef) */ public static final native void CFRunLoopObserverInvalidate(long observer); -/** - * @param allocator cast=(CFAllocatorRef) - * @param fsRef cast=(FSRef*) - */ -public static final native long CFURLCreateFromFSRef(long allocator, byte[] fsRef); /** * @param allocator cast=(CFAllocatorRef) * @param originalString cast=(CFStringRef) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java index d400229e1a..589d736607 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 itemis AG and others. + * Copyright (c) 2017, 2026 itemis AG and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -97,6 +97,8 @@ public enum Selector { , sel_TIFFRepresentation("TIFFRepresentation") , sel_URL("URL") , sel_URLFromPasteboard_("URLFromPasteboard:") + , sel_URLForApplicationToOpenURL_("URLForApplicationToOpenURL:") + , sel_URLForApplicationToOpenContentType_("URLForApplicationToOpenContentType:") , sel_URLWithString_("URLWithString:") , sel_UTF8String("UTF8String") , sel_abortEditing("abortEditing") @@ -1304,6 +1306,7 @@ public enum Selector { , sel_type("type") , sel_type_conformsToType_("type:conformsToType:") , sel_typeOfFile_error_("typeOfFile:error:") + , sel_typeWithFilenameExtension_("typeWithFilenameExtension:") , sel_types("types") , sel_typesetter("typesetter") , sel_unarchiveObjectWithData_("unarchiveObjectWithData:") diff --git a/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java b/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java index cbf63d8fc5..465abcc118 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -73,15 +73,10 @@ public static Program findProgram (String extension) { } NSString ext = NSString.stringWithCharacters(chars, chars.length); if (ext != null) { - byte[] fsRef = new byte[80]; - if (OS.LSGetApplicationForInfo(OS.kLSUnknownType, OS.kLSUnknownCreator, ext.id, OS.kLSRolesAll, fsRef, null) == OS.noErr) { - long url = OS.CFURLCreateFromFSRef(OS.kCFAllocatorDefault(), fsRef); - if (url != 0) { - NSString bundlePath = new NSURL(url).path(); - NSBundle bundle = NSBundle.bundleWithPath(bundlePath); - if (bundle != null) program = getProgram(bundle); - OS.CFRelease(url); - } + NSURL appURL = findAppURLForExtension(ext); + if (appURL != null) { + NSBundle bundle = NSBundle.bundleWithPath(appURL.path()); + if (bundle != null) program = getProgram(bundle); } } return program; @@ -153,6 +148,27 @@ public static Program findProgram (String extension) { } } +private static NSURL findAppURLForExtension(NSString ext) { + NSWorkspace workspace = NSWorkspace.sharedWorkspace(); + // On macOS 12.0+, use the content type-based API which works reliably + // for all file types including third-party ones. + if (OS.VERSION >= OS.VERSION(12, 0, 0)) { + long UTTypeClass = OS.objc_getClass("UTType"); + if (UTTypeClass != 0) { + long utType = OS.objc_msgSend(UTTypeClass, OS.sel_typeWithFilenameExtension_, ext.id); + if (utType != 0) { + NSURL appURL = workspace.urlForApplicationToOpenContentType(utType); + if (appURL != null) { + return appURL; + } + } + } + } + // Fallback: URL-based lookup (available since macOS 10.6, deprecated in macOS 12.0) + NSURL fileURL = NSURL.fileURLWithPath(NSString.stringWith("/tmp/dummy." + ext.getString())); + return workspace.urlForApplicationToOpenURL(fileURL); +} + static Program getProgram(NSBundle bundle) { NSString CFBundleName = NSString.stringWith("CFBundleName"); NSString CFBundleDisplayName = NSString.stringWith("CFBundleDisplayName"); diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_program_Program.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_program_Program.java index 9b90605614..aae53a6499 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_program_Program.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_program_Program.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.util.HashSet; import java.util.Set; @@ -90,6 +91,17 @@ public void test_findProgramLjava_lang_String() { assertSWTProblem("Failed to throw ERROR_NULL_ARGUMENT", SWT.ERROR_NULL_ARGUMENT, e); } +@Test +public void test_findProgramForTxt() { + // Applications for MIME types may not be registered in Linux test environments + assumeFalse(SwtTestUtil.isGTK, "Applications for MIME types may not be registered in Linux test environments"); + // .txt files are always associated with a text editor on Windows and macOS + Program program = Program.findProgram(".txt"); + assertNotNull(program, "Expected a program for .txt files"); + assertNotNull(program.getName(), "Expected program to have a non-null name"); + assertTrue(program.getName().length() > 0, "Expected program to have a non-empty name"); +} + @Test public void test_getExtensions() { String[] extensions = Program.getExtensions();