In context of built application are the the following equivalent?

macOS: Ventura 13.4 / Xojo 2023.1

I want to get a reference to the folder that contains the compiled Xojo app that is running.

Are these equivalent?

Var f As FolderItem
f = App.ExecutableFile.Parent

versus

Var f As FolderItem
f = SpecialFolder.CurrentWorkingDirectory.Parent

According to the docs, you can also do:

Var f As New FolderItem ("")

The current working directory can change and doesn’t need to be the app folder.

4 Likes

2 Likes

Just some follow-up for anyone who might be confused by or interested in this topic.
MacOS Ventura
Xojo 2023 v1.1

My test built app is FindRAA and it is in the Applications folder.

Christian writes:

The current working directory can change and doesn’t need to be the app folder.

Documentation says:
“Current working directory (depends on the location of the application). When run from Xojo, it is the path to Xojo. In a built app, it is the path to the built app.”

When I try this,

Var f As New FolderItem
f = SpecialFolder.CurrentWorkingDirectory.Parent

I just get Nil because f has no parent.

When I try this

Var f As New FolderItem
f = SpecialFolder.CurrentWorkingDirectory

The NativePath is: /
The Name is: Macintosh HD

So I do not understand the Documentation or it is wrong. I do not see how “it is the path to the built app.”


Tim writes that this should give you the Parent folder of the app:

Var f As New FolderItem ("")

In my testing on my machine this is true. I put the compiled application in the Applications folder. (I am having problems with the whole thing because some user reports a different outcome)

As Tim says, the documentation implies this in the discussion of Constructors under FolderItem.

The NativePath is: /Applications.
The Name is: Applications.

In my experiments this also works {you do not need the (“”)}, i.e. the default of a New FolderItem is the Parent folder of the application although I do not see this documented.

Var f As New FolderItem

The NativePath is: /Applications.
The Name is: Applications.


The documentation for App.ExecutableFile says:
“Returns a FolderItem for the actual executable Application even if it is in a bundle.”

Var f As New FolderItem
f = App.ExecutableFile.Parent

In my testing with an application called FindRAA sitting in the Applications folder this returned
The NativePath is: /Applications/FindRAA.app/Contents/MacOS
The Name is: MacOS.

This is not what I expected but whatever. Perhaps the Documentation could be clearer.


All of the above is perhaps irrelevant to my true life issue.

My actual problem is that my code works to make sure that the user has, as instructed, put my application in the Applications folder. I just get the Name using the Tim Streater technique and compare it with the string “Applications”. Most people do not seem to have a problem related to this, but a user from South Africa who is German has the application quit because it does not meet this criteria. His application folder seems to be “Programme” Is this something that non-English systems are likely to show? — some alternate name for the Applications folder. Perhaps I should not use the string “Applications” in my code but rather somehow get the name of the Applications folder on that particular machine. Perhaps on his machine, SpecialFolder.Applications would return as its Name — Programme.

Is anybody familiar with the names of the Application folder on non-English machines? I just assumed that it was “Applications” but perhaps there is some localization thing that changes this.

It’s too late right now, but there is a sanctioned way to get the Applications folder that user applications are in from macOS itself. It’s fairly easy to do with declares, I just don’t remember off the top of my head. If you bug me tomorrow, I can look it up.

Actually, I just reviewed my code and I was NOT actually comparing with the simple string “Applications”. I was just comparing two folder items to see if they were the same (had the same NativePath). I was insisting that f1 and f2 were the same.

Var f1 As New FolderItem ("")

Var f2 As New FolderItem = SpecialFolder.Applications

But I am still curious about this “Programme” business that my user reports.

This can be the French translation for Application, but the Applications folder is called… Applications in France. But I do not know how it is called there:
Belgium (Belgique)
Québec,
Swiss (French),
any other French speaking country…

From where your user who report an error live ?

Sometimes people want to do best or better job and fall into a trap. This can be a case.

Well those two may not be the same anyway. Remember, there is an “Applications” folder in each User directory as well which is where Standard (non-admin) users are allowed to put applications without an admin password. Unfortunately, Xojo’s SpecialFolder module does not have a way to consider User vs Local(All Users) vs Network vs System, all of which could return a different path. If you give me a few minutes, I can throw together a few declares that will give you access to all of those.

Comparing the NativePath of f1 and f2 ought to be enough, and is better than what I do at the moment to achieve the same end as you - ensure that under macOS the user has installed the app into /Applications. But I may need to beef up my approach to that.

Something to keep in mind…

There are a lot of users out there now that are running macOS on managed systems, that is, controlled by an IT department somewhere in the world and the user’s computer isn’t completely in control of everything. It’s entirely possible that some ambitious IT person decided that Applications should be renamed to “Programme” (seems most likely that it’s French) and that it’s what the user sees. macOS will also allow you to change the name that appears on a folder without changing the name that’s used by the system, so you can’t entirely rely on the user’s experience with the system.

Here’s an example project of what I was talking about above where the type of directory matters:

https://www.dropbox.com/scl/fi/f6jcs2lto2hlv95pl5h7o/searchpaths-macos.xojo_binary_project?rlkey=etoutdnt113qasbo7u3zicgqb&dl=0

For instance, on my machine, when I ask for Applications, I get this:

It also matters in some cases if you pass a “relative to” folderitem. For instance, the Trash folder changes depending if the folder is relative to an iCloud folder or not.

// User specific
User: /Users/gregolon/Applications

// All Users
Local: /Applications

// Network
Network: /Network/Applications

// System (Apple apps)
System: /System/Applications

NOTE: The method throws exceptions if there’s a problem, so be prepared for that.

2 Likes

That is the expected result and consistent with the documentation. Your executable is inside the app bundle, inside Contents, inside a folder called MacOS. So

App.ExecutableFile.Parent

is a folder called “MacOS”.

2 Likes

If you don’t want to muck around with traversing the folder items, you can get to it using declares:

#If TargetMacOS
  // @property(class, readonly, strong) NSBundle *mainBundle;
  Declare Function getMainBundle Lib "Foundation" Selector "mainBundle" (cls As ptr) As Ptr
  Declare Function NSClassFromString Lib "Foundation" (name As cfstringref) As ptr
  // @property(readonly, copy) NSString *bundlePath;
  Declare Function getBundlePath Lib "Foundation" Selector "bundlePath" (obj As ptr) As CFStringRef
  
  Dim bundle As ptr = getMainBundle(NSClassFromString("NSBundle"))
  Dim path As String = getBundlePath(bundle)
  
  Dim f As New FolderItem(path, FolderItem.PathModes.Native)
  
  Break
#EndIf

Thanks for this project. The code is over my head but I am capable of copying it. I do get an error here whenever the call does not return a path. For example if you select User.

If urlPath = "" Then
  Raise New ObjCException("The call did not return a path")
End If

Perhaps this is what you are referring to when you say:

NOTE: The method throws exceptions if there’s a problem, so be prepared for that.

But this is a great tool that you have provided.

Not every directory is available in all of the domains, so yeah, there will be times when it throws exceptions. If that’s a problem, you could just make them return nil. I just like exceptions because they can tell you why it is failing.

Ah… so “User” in that app, is for selecting the path to the Users directory. While that’s relevant for the user and local domains, network and system don’t have the concept, so they’ll return an empty string. In any case, it’s a good sample project for seeing what the different combinations are.

If you’re running it in the IDE and you hit an exception like that, you should be able to just continue and it’ll write that message into the listbox as well.

If you use FolderItem.DisplayName, you’d indeed get “Programme” (i.e. the localised version), but with FolderItem.Name, you should still get “Applications”, unlocalised.

Like others have said, it’s better to compare the NativePaths than the parent’s folder names alone. E.g. I could fairly well install your app in “/Users/me/desktop/Applications/your app”.

By the way: how did you add horizontal separators in your post? Seems a great way of organising them.

More probable to be German, actually, where this would be the exact spelling (that’s the native word for “Applications”). In French, even if someone was to prefer the word “Programme”, they’d put a leading “s” (“Programmes”) for the plural form.

2 Likes

The documentation for FolderItem.DisplayName is a little bit terse.

The name of the FolderItem as it should be seen by the user.
This property is read-only.
It is usually the same as the Name property.

This localization issue is an interesting case where FolderItem.DisplayName and FolderItem.Name are actually different. I had no idea.


Three or more consecutive underscores result in the appearance of a separation line.

2 Likes