protegeproject / protege

Protege Desktop
http://protege.stanford.edu
Other
1.01k stars 231 forks source link

Open with and double click does not work #1088

Closed matentzn closed 1 year ago

matentzn commented 1 year ago

This is just to keep a record for future work.

Using the terminal

open ontology.owl

Or double clicking on a file in Finder used to work with Protege 5.5.0, but now does not. Here is a summary of @gouttegd analysis about the issue, leaving out some of the ranting :P

@gouttegd says:

Since approximately the times of Brian Kernighan and Dennis Ritchie, the standard way of passing arguments (say, filenames) to a program was to pass them as arguments to the main function of said program. That is, if you call myprog myfile, the main function of myprog would be called with an array containing myfile (along with the name under which the program was called, but that’s irrelevant). That standard approach has since then been used by all operating systems (even Windows!) and almost all languages. But of course, that is too standard for Apple. [...censored rant...].

So, on macOS, when you call open ecto-edit.owl, [...censored..] finds an appropriate application for .owl file, and then does not call that application with ecto-edit.owl as an argument! Instead, it calls the application with an empty command line, and then sends the application an Apple-specific event that the application is supposed to react to (by using the Apple-specific Cocoa API). Now, Protégé contains some code specifically intended to deal with this kind of Apple [..censored..], but this is one of the area that has required some changes with the migration to Java11, and apparently the new code is not working…

Bottom line: this is not, contrary to what I thought, a problem with the new launcher. This is a Java11/macOS problem.

Nico: Maybe I can somehow write my own launcher and stick it into bash profile or zshrc

Damien: if you want your own launcher, you can make something like that:


#!/bin/sh
/Applications/Protégé-560-beta.app/Contents/run.sh "$PWD/$1"

> That is, bypass Apple’s open completely.

(Nico has confirmed, this works, but opens Protege in the _foreground_ rather than detached mode, which is great for him (better access to logs) but may not be ideal for normal users.)

@gouttegd says: 

> Damien: To be clear: ranting aside, and while the problem would not exist if not for Apple’s idiosyncrasies, this is still a bug in Protégé. The fact that you could open file.owl with Protégé 5.5.0 means that Protégé 5.5.0 reacted correctly to Apple desktop events; the fact that Protégé 5.6.0 does not is undoubtedly a regression.
Well, that’s very unlikely to be fixed anytime soon. As far as I understand, Protégé is doing The Right Thing by using the standard java.awt.Desktop API, instead of the (non-standard, deprecated, and simply not usable on Java >= 9) com.apple.eawt.Application class that we used before.
Except that the setFileOpenHandler of that API simply does not seem to work.
I tried building a very minimal Java application that basically does nothing except setting this handler, following exactly the API’s documentation… and the application never receives any “open file” event! The event handler is never called.
On the net people seem to have complained about various problems with that API for several years…

> So, I give up. I am now convinced that whatever the problem is, it is not in Protégé’s code. It’s either another Apple idiosyncrasy or a bug in the JRE, or maybe even both.

> For the record: It seems that a possible solution would be to start, at the same time as the Protégé application itself, another program in parallel, that would run in the background for as long as Protégé is running. That helper daemon would solely be tasked with listening to Apple events (and so would have to be written in something else than Java, since apparently we can’t get those events from a Java program; most likely it would have to be written in ObjC or in Swift), and upon receiving a “open file” event it would have to somehow communicate that to the main Protégé process.

> If anyone is willing to test that “solution”, be my guest. As for me, no way I am ever going to even consider that even further.
gouttegd commented 1 year ago

Nico has confirmed, this works, but opens Protege in the foreground rather than detached mode, which is great for him (better access to logs) but may not be ideal for normal users.

I could tweak the launcher so that when it is called with an argument (something that would normally never happen since, as explained, macOS always starts applications with an empty command line and uses events instead to pass filenames to the application), it detaches from the terminal and redirects stdout to nothing before exec’ing the Java virtual machine.

matentzn commented 1 year ago

For me this is not necessary. For normal users, the only thing that could matter is that the "double click" issue is fixed, but if its not its not.

gouttegd commented 1 year ago

It’s nothing we can fix. Protégé is already doing its bit correctly.

We set up the OpenFileHandler :

Desktop application = Desktop.getDesktop();
application.setPreferencesHandler(event -> handlePreferencesRequest());
application.setAboutHandler(event -> handleAboutRequest());
application.setOpenFileHandler(event -> {
    File file = event.getFiles().get(0);
    try {
        editFile(file.getAbsolutePath());
    } catch (Exception e) {
        logger.error("invalid file: {}", file);
    }
});
application.setQuitHandler((event, response) -> handleQuitRequest());

The Preferences, About, and Quit handlers are installed and work as expected (this is how for example Protégé can display its own ”About Protégé” dialog when users click on About Protégé in the main menu – if the handler didn’t work we would get a generic dialog instead).

Then we declare the file associations in Protégé’s Info.plist file:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeExtensions</key>
        <array>
             <string>owl</string>
        </array>
        <key>CFBundleTypeName</key>
        <string>OWL Ontology Document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
    </dict>
</array>

This is how macOS knows that when users double-click on a .owl file (or a more advanced user does open file.owl, which under the hood is basically the same thing), it should sends a “Open file” event to the Protégé application.

The fact that Protégé is brought to the forefront (or started, if it was not already running) upon running open file.owl indicates that the Info.plist is correct: macOS does indeed know that .owl files are associated with the Protégé application.

So, we’re doing everything that we should. But still, the file event handler is never called…

Basically, the java.awt.Desktop::setOpenFileHandler interface is not doing what it says on the can. And that interface is the correct way to do this, as per the JDK’s documentation.

gouttegd commented 1 year ago

Also, this is not a problem caused by the OSGI stuff in any way. I observe exactly the same problem in a much simpler Java application that makes no use of OSGI.

gouttegd commented 1 year ago

OH MY FUDGING GOD IT WORKS!

So…

There is no way to make this thing work when you start your Java application by calling the java binary, as my current launcher is doing.

Instead, what you need to do is to “manually” create a Java virtual machine directly from the launcher, using the Java Native Interface (JNI_CreateJavaVM).

AND the main method of the Java application must be started in a separate thread, while the initial thread of the launcher is used to run a dummy loop.

Then, and only then, will the application receive the “open file” events through the java.awt.Desktop interface.

SOMETHING THAT IS NOT DOCUMENTED ANYWHERE IN THE DOCUMENTATION OF SAID INTERFACE!

I have a draft of a new launcher that does what’s described above and that works-for-me. It still needs a lot of polishing (and a whole lot more of testing), but it WILL work in the end.

Bottom line:

Huge, huge THANK YOU to the developers of ImageJ, whose launching code was crucial in helping me understand what needed to be done.

Huge, huge FUCK YOU to Apple and Oracle.

matentzn commented 1 year ago

:D That sounds awesome! :)!!! Thank you so much for burning so many hours on this issue!

gouttegd commented 1 year ago

The one thing that may not work correctly is the case when you want to open a OWL file while no instance of Protégé is already running.

What’s supposed to happen in that case is that macOS will start a new Protégé instance and send it the “open file” event. However, if the application takes too long to start before it is ready to receive events, it may “miss” the “open file” event. What will happen then is that an instance of Protégé will appear, but no file will be opened. Trying to open the same file again will this time send the “open file” event to the already running Protégé, which will then be ready and will open the file as expected.

Fixing that will be quite tricky. One approach would be to rely on the launcher to catch the events arriving in the first milliseconds of the application’s lifecycle, cache them until Protégé is fully started, and then somehow pass them to the Java code like “hey, here’s what arrived for you while you were busy starting up”.

Tricky and quite frankly, not my priority right now.

gouttegd commented 1 year ago

Closing here as the main issue is fixed. The secondary issue (the fact that it does not work reliably if an instance of Protégé is not already running) will be tracked in the new issue #1102.

teras commented 8 months ago

Huge, huge FUCK YOU to Apple and Oracle.

A huge THANK YOU, from my site too. I had similar complaints for my own Java apps, why in the past double click worked and now it doesn't. Simple debugging didn't work, as you said. But with your findings (small thank you to Google too), now I believe I am on the right track...

gouttegd commented 8 months ago

Ha, glad this issue may be of help to someone else! Good luck with your apps!