How to find the applications folder?

In former, simpler times a specialfolder.applications would have worked. But with beloved Catalina everything became so much more interesting.

Since Xojo 2019r1 I have a wrapper for getting the applications folder:

[code]Public Function getApplicationsFolder() as FolderItem

dim ApplicationsFolder as FolderItem = SpecialFolder.Applications

if ApplicationsFolder <> Nil and ApplicationsFolder.Exists then Return ApplicationsFolder

try
dim AppPath as String = “/System/Volumes/Data/Applications”
ApplicationsFolder = GetFolderItem(AppPath, FolderItem.PathTypeShell)
Return ApplicationsFolder
Catch err as UnsupportedFormatException
'catalina is really screwed up
Return Nil
end try

End Function[/code]

In Xojo >= 2019r2.1 this is useless because SpecialFolder.Applications returns the wrong directory. It’s the system applications folder and not the user one. The shellpath is “/System/Applications” and not “/System/Volumes/Data/Applications”.

I found a feature request to change the behaviour (feedback://showreport?report_id=57672). Additionally, I found https://forum.xojo.com/56114-specialfolder-returns-wrong-path-in-10-15 with a Xojo project to get all applications folders. That doesn’t include the one I need.

Does anyone have a better idea than hardcoding the path for Catalina?

If I am not mistaken, the Specialfolder.application does return the correct path, unless you need access to the default Apple .apps like Mail.app etc…

“/System/Applications” is not the droid that I’m looking for.

1 Like

Hmm - which one isn’t included in the output that you need?
I think the mentioned example project gets all the possible application directories from the OS…

You probably don’t want that one. If I remember correct, this is the same as just “/Applications”, as the Data Volume is mounted at “/System/Volumes/Data”.

But yes… Catalina needs different thinking of “SpecialFolder.Applications”. There are two of them. A system one, and a user one. Safari is in the user one (as far as I remember), Mail in the System one. The Xojo Framework should reflect this somehow. Currently it’s both correct and wrong - depending on which one you’re looking for :slight_smile:

So that’s why I suggest to use the example project for now, and try if you find the desired “AppName.app” as a .Child in any of these possible Application directories. That seems safer to me than hardcoded paths.

Even better would be to use the Declare to find the app by BundleID. As far as I remember, that’s included in the example project, too.

@Jürg Otter: I checked the folderitem array in the debugger and must have missed “/Applications”. Checking each folder for the file I need is rather idiotic. Gr…

Using the bundle id in all cases will not be possible.

There is also the question if I still need the workaround with the “/System/Volumes/Data/Applications” which I had to add for 2019r1.

Almost need a SpecialFolder.UserApplications and SpecialFolder.SystemApplications or something like that since they are two completely different locations

That’s what the Feedback request I mentioned is about.

And don’t forget: (user-home)/Applications :wink:
So it’s more a SystemApplications, AllUsersApplications, UserApplications.
And let’s just ignore all those users that have many of their Applications in Downloads, on Desktop, where-ever.

So to me, an array of all default application directories would fit. Similar story on Windows (x32, x64 programs).
And sure: a shared helper method to find an .app (in those directories; or by BundleID for macOS only) would be nice to have.
Then again… quite easy to write your own (for now). The example project (in Feedback and in the other Thread) has all one needs.

[quote=466678:@Jürg Otter]And don’t forget: (user-home)/Applications :wink:
So it’s more a SystemApplications, AllUsersApplications, UserApplications.
[/quote]
ah right forgot about the Applications dir in the users home folder

yes because these special folder dirs arent about “locating all applications”
they are about “what is the location for this particular thing” and so I dont expect them to track down ALL applications that could ever possible be installed or are installed
There are other means to do that

Indeed, now Catalina has the notion of user level applications, like it has existed in Windows for over a decade. So each user has it’s own Applications folder.

But as far as I can see, it is simply SpecialFolder.UserHome.Child("Applications")

Besides, there are not many applications yet that install in the user Applications folder. None I encountered so far.

none by default
often you can change the install location and you might put them there

Oh, that’s not something new in Catalina - the “Applications” in the “user home” exists for quite some time.
And it does make sense on multi-user systems. Or on managed machines where you don’t have an account that allows to “manage this computer”.

You are right. I did not notice it, but an empty Applications folder exists in my High Sierra install.

I know the use of user installs. As I mentioned, they have been part of Windows for a very long time.

I simply never encountered them in the Mac world so far.

DMG installs are usually geared at the system wide Applications folder, although it would be possible to make them user local. At any rate, as noted by Norman, the ultimately user can do what he wants.

Under Windows, where installs are much more common, the installer will ask if the install should be limited to this user, or to all users. Perhaps this will justify using installers more frequently in the future.

Have you tried the spotlight control? It should help you find the application you want, regardless of where it is. Until Apple removes Spotlight for 3rd party developers, you know for “Security”.

Thomas Tempelmann had the best idea: fix the declare from Jürg to find the correct folder:

[code]#If TargetMacOS Then
//https://developer.apple.com/documentation/foundation/nsfilemanager/1407726-urlsfordirectory?language=objc
//- (NSArray<NSURL *> *)URLsForDirectory:(NSSearchPathDirectory)directory inDomains:(NSSearchPathDomainMask)domainMask;

Declare Function NSClassFromString Lib “Foundation” (className As CFStringRef) As Ptr
Declare Function defaultManager Lib “Foundation” selector “defaultManager” (ptrNSFileManagerClass As Ptr) As Ptr
Declare Function path Lib “Foundation” selector “path” (ptrNSURLInstance As Ptr) As CFStringRef
Declare Function fileExistsAtPathAndIsDirectory Lib “Foundation” selector “fileExistsAtPath:isDirectory:” (ptrNSFileManagerInstance As Ptr, path As CFStringRef, ByRef isDirectory As Boolean) As Boolean

Dim ptrNSFileManagerClass As Ptr = NSClassFromString(“NSFileManager”)
if (ptrNSFileManagerClass = nil) then return nil

Dim ptrToNSFileManagerDefaultInstance As Ptr = defaultManager(ptrNSFileManagerClass)
if (ptrToNSFileManagerDefaultInstance = nil) then return nil

Declare Function URLsForDirectory Lib “Foundation” selector “URLsForDirectory:inDomains:” (ptrToNSFileManagerInstance As Ptr, NSSearchPathDirectory As UInteger, NSSearchPathDomainMask As UInteger) As Ptr
const kNSAllApplicationsDirectory = 100
const kNSApplicationDirectory = 1
const kNSAllDomainsMask = &h0ffff
const kNSLocalDomainMask = 2

Declare Function NSArrayCount Lib “Foundation” selector “count” (ptrToNSArray As Ptr) As UInteger
Declare Function NSArrayObjectAtIndex Lib “Foundation” selector “objectAtIndex:” (ptrToNSArray As Ptr, index As UInteger) As Ptr
Declare Function NSArrayObjectAtIndex_IsA_CFStringRef Lib “Foundation” selector “objectAtIndex:” (ptrToNSArray As Ptr, index As UInteger) As CFStringRef
Declare Function CFURLCopyFileSystemPath Lib “Foundation” (anURL As Ptr, pathStyle As Int32) As CFStringRef

Const kCFURLPOSIXPathStyle = 0
Const kCFURLHFSPathStyle = 1

Dim ptrToArray As Ptr = URLsForDirectory(ptrToNSFileManagerDefaultInstance, kNSApplicationDirectory, kNSLocalDomainMask)
If (ptrToArray = Nil) Then Return Nil

Dim iResultCount As UInteger = NSArrayCount(ptrToArray)
If (iResultCount < 1) Then Return Nil

Dim oResults() As FolderItem
For i As Integer = 0 To iResultCount - 1
Dim ptrToNSURL As Ptr = NSArrayObjectAtIndex(ptrToArray, i)
If (ptrToNSURL = Nil) Then Continue

Dim bIsDirectory As Boolean = false
if (not fileExistsAtPathAndIsDirectory(ptrToNSFileManagerDefaultInstance, path(ptrToNSURL), bIsDirectory)) or (not bIsDirectory) then continue

Dim sNativePath As String = CFURLCopyFileSystemPath(ptrToNSURL, kCFURLPOSIXPathStyle)

Try
  Dim oResult As New FolderItem(sNativePath, FolderItem.PathTypeNative)
  oResults.Append(oResult)
Catch UnsupportedFormatException
  'ignore
End Try

Next

If (oResults.Ubound >= 0) Then Return oResults
Return Nil
#EndIf

Return Nil
[/code]

I found one in my install, and it contains ‘apps’ used by Chrome, in a folder called ‘Chrome Apps’

Why “fix”?
The example was to get all possible application directories (using kNSAllApplicationsDirectory, kNSAllDomainsMask).
The “fix” is just shortening the list to only get the “local application directory” (using kNSApplicationDirectory, kNSLocalDomainMask).
And yes - the example has been intended to show all possible ones (well, all those that the OS defines).
But sure - use this “fix” if you are only interested in the “local application directory”. Sorry that I haven’t included all constants that would be useful in the example - it had been obvious to me that one could/would do that if only that single directory is of interest :wink:
Just: if you do that, then I’d modify it further to only return 1 Folder, not an Array :wink:

And fyi: Here is the documentation for NSSearchPathDirectory and NSSearchPathDomainMask.
That’s helpful if you want to get the NSTrashDirectory, NSDownloadsDirectory, and much more :wink:
Again I think it’s easy to add/modify the example to get the desired directory from the OS.

Edit: Updated the example project with links to the Documentation, so that you can more quickly find the values/directories you’re looking for.

I have moved the Example Project: macOS: App Directories to my bits and pieces for Xojo Developers.

The example shows how to find Applications by BundleID (the preferred way), by AppName (this will try to find it in all possible system defined application directories), and it allows to get an Array of Folders defined by Apple (see: NSSearchPathDirectory). You no longer need to look up the Enum-values, as they are provided in the Module that’s in the example project.

So you can easily get all Application directories (including the per-user one, if it exists). Or just query the OS to return the “local application directory” (using NSApplicationDirectory, NSLocalDomainMask).