I’m trying to develop a script here that would list all the applications recommended for opening a file. As the Finder does when opening the Open With context menu.
On another site, I came across an interesting solution to this problem in Python. Here it is:
[format]import sys
from AppKit import NSURL
from LaunchServices import LSCopyApplicationURLsForURL, kLSRolesAll
url = NSURL.fileURLWithPath_(sys.argv[1])
for url in LSCopyApplicationURLsForURL(url, kLSRolesAll):
print url.path()[/format]
As you can see, it uses function LSCopyApplicationURLsForURL from Core Services framework. I tried to translate (to bridge) it to AsObjC, but can’t. Please help me to solve this interesting problem. If this bridging is possible, of course.
I also quote here my failed attempt for clarity:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "CoreServices"
use scripting additions
set posixPath to POSIX path of (choose file)
set theNSURL to current application's NSURL's fileURLWithPath:posixPath
set kLSRolesAll to a reference to current application's kLSRolesAll
set recommendedApplications to LSCopyApplicationURLsForURL(theNSURL, kLSRolesAll)
The use of the Launch Services in the CoreServices framework requires you to handle C data types.
And that means being able to work with ‘CF’ Core Foundation data type references and pointers.
Which you can’t do directly with AppleScript or AppleScriptObjC.
So you would have to write a command line tool with Python or Swift or any other language.
In order to access the ‘LSCopyApplicationURLsForURL’ function, as in the python code snippet.
And then call the command line tool from AppleScript with a “do shell script” command.
The other option open to you is to use the NSWorkspace class’s URLForApplicationToOpenURL: function, to retrieve the file type’s default application’s URL, something like this.
use framework "Foundation"
use scripting additions
property myApp : a reference to current application
set workspace to myApp's NSWorkspace's sharedWorkspace()
try
set filePath to POSIX path of (choose file with prompt "Select a file type to open")
on error
return -- User canceled selecting a file type to open
end try
set fileURL to myApp's NSURL's fileURLWithPath:filePath
set appURL to workspace's URLForApplicationToOpenURL:fileURL
if appURL ≠ missing value then
set appName to appURL's lastPathComponent() as text
-- Optional opening of the file in the default application
set fileName to fileURL's lastPathComponent() as text
set openFile to button returned of (display alert "Do you want to open the " & fileName & " file in the default application?" buttons {"NO", "YES"})
if openFile = "YES" then
set fileOpened to workspace's openFile:filePath withApplication:appName
if fileOpened then
return "SUCCESS Opening the file"
else
return "FAILURE Opening the file"
end if
else if openFile = "NO" then
return -- User chose not to open the file in the default application
-- You could use the Standard Additions "choose application" as alternative
end if
else
return -- No default application was found for the selected file type
end if
The NSWorkspace class has many useful functions for opening files and other applications, and you could always use the “choose application” command from Standard Additions, to select an alternative application to open the file type.
If I can think of another way to use ‘LSCopyApplicationURLsForURL’ function after more thought, I will let you know.
Thank you very much for trying to help resolve the issue. Unfortunately, the script you provided is not what I need. I need to get not default application, but a list of all recommended applications. To understand me, select the file, then right-click and hover the mouse over the Open With menu item. The list I want appears in the pop-up window.
No, about working the AppleScript or AppleScriptObjC with ‘CF’ Core Foundation data type references and pointers. I have heard before that this is impossible. But, one day, I myself was still able to make this “impossible” possible. Here is a concrete example of how I did it for other task:
-- Convert MacRoman text to IBM866 text (russian encoding)
use AppleScript version "2.4"
use framework "Foundation"
use framework "CoreFoundation"
use scripting additions
property NSMacRomanStringEncoding : a reference to 30
set str to "熂†´™† èÆ´‚†¢™† (à.䆢†´•‡®§ß•, 1936).srt"
-- get appropriate NSStringEncoding from known kCFStringEncoding
set theEncoding to current application's CFString's CFStringConvertEncodingToNSStringEncoding(current application's kCFStringEncodingDOSRussian)
--> 2.147484699E+9
set theData to ((current application's NSString)'s stringWithString:str)'s dataUsingEncoding:NSMacRomanStringEncoding
-- convert to IBM866 encoding
return (current application's NSString's alloc()'s initWithData:theData encoding:theEncoding) as text
--> "Наталка Полтавка (И.Кавалеридзе, 1936).srt"
So I thought there might be a way to do something like this with a new task.
Your example doesn’t use any CF types – it just uses an integer (via a constant) as argument, and then returns another integer. It’s only when CF types are used that it fails, and unfortunately that’s most of the time.
However, Monterey has exposed several of the Launch Services functions as NSWorkspace methods. So you can get what you want like this:
use AppleScript version "2.8" -- Monterey (12.0) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions
set theFile to (choose file)
set theURLs to (current application's NSWorkspace's sharedWorkspace()'s URLsForApplicationsToOpenURL:theFile) as list
You can also use URLsForApplicationsWithBundleIdentifier:, and there are methods for setting the default application.
Shanes solution is a cut down version of the one I posted using NSWorkspace’s URLForApplicationToOpenURL: function, which is available on all MacOS’s, and not just “Monterey”.
I also read somewhere that the Launch Services ‘LSCopyApplicationURLsForURL’ function has also been deprecated on the latest MacOS’s, I’m not sure what version of OS that it was deprecated on, but it may be worth checking if it’s still available on your “Catalina” system anyway.
If it is still there on “Catalina” I can give you a basic Swift shell script code, which you could use to get what you want, or as you’ve already stated a python script would be better, as python is installed as standard on MacOS’s, where as Swift would have to be installed to run a swift shell script.
I have knocked up a quick dirty python script and swift script as a starting point for you if your interested.
And also that the forum moderators don’t mind non AppleScript code.
It carries the deprecation notice in Monterey: “Use -[NSWorkspace URLsForApplicationsToOpenURL:] instead.” Which is what I used above. The deprecation version is listed as API_TO_BE_DEPRECATED, which I guess means we’re on notice that it will be hard deprecated in some future release.
It doesn’t look like there’s going to be much left in Launch Services.
I created an executable utility launchHandler from your Swift code and saved it to the /usr/local/bin directory. Works great. I only had to patch kLSRolesEditor to kLSRolesAll to list all recommended applications, not just editors.
Thank you too, @Mark FX and @Shane Stanley. The problem was solved perfectly by the joint efforts of experienced users.
Example using created executable:
do shell script "/usr/local/bin/launchHandler " & quoted form of POSIX path of (choose file)
-- Result:
-- BBEdit.app
-- Google Chrome.app
-- LibreOffice.app
-- Microsoft Excel.app
-- Microsoft Word.app
-- Notes.app
-- Pages.app
-- Sublime Text.app
-- System Information.app
-- TextEdit.app
Hey! the solution was found for the OP, and that’s the main thing.
But a lot of smart people on various developer sites give their time and expertise for free.
They do it to help others in an unselfish way, the least we can all do in return is to give them some appreciation and credit for their efforts.
I also found that code when researching the ‘LSCopyApplicationURLsForURL’ function, and decided the code was convoluted, and did not need two for loops, but it is still a good and valid solution, if not a bit old fashioned in style.
But for me it’s simple, I would simply link to the web page, because that’s the honourable thing to do.
The OP would have their solution, and the code author can receive the gratitude.
And you would have still been helping someone yourself, and so everyone would be a winner.