Implement a Custom Protocol on Mac
-
@stefanwoe can you share a minimal compilable sample application so we can investigate ?
-
@stefanwoe said in Implement a Custom Protocol on Mac:
@SGaist if i had this i would not have asked this question.
The goal is not that you provide a working solution. Having a minimal project will allow us to check your issue and help you debug it.
-
@SGaist This can be done with any sample application. i.e. the MDI Example https://doc.qt.io/qt-6.2/qtwidgets-mainwindows-mdi-example.html.
If we us a URL in a browser such usmayprotocol://mydata
A handler within this application shall be called.
AND: if the application is not running it shall be started. That works fine on windows (no programming in the required - its just called with a command line parameter) see the link in my question), but i dont get it to work on the mac. -
After spending several days in total with this I now had success and the custom protocol works as expected.
Basically its correct, that only a CFBundleURLTypes entry in Info.plist is needed but there are some caveats that were so hard to find out:
- Updating Info.plist in the QCreator project will not immediately update the Info.plist file in the created application boundle. This remains unchanged until a full rebuild is made.
- But even if we modify the file inside the Application boundle (MyApp.app/Contents/Info.plist) a modification in this file will not always be noticed by the macOS after you once start the application. I have not found the details of this. I created a sample project in XCode and there all changes were registered immediately. Also if you download a dmg file this is registered immediately.
- The Key to get all this to work during development is the lsregister utility described here:
https://eclecticlight.co/2019/03/25/lsregister-a-valuable-undocumented-command-for-launchservices/
Command to remove all custom protocols:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -domain u -domain s -domain l -v
If you do that, as soon as you start your application, the custom protocol defined in MyApp.app/Contents/Info.plist will be registered
To find if a protocol is registered:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep myprotocol
Where myprotocol is the name of the protocol searched. You may pipe the dump to less or a file to see which application is registered for the protocol. The dump returns a huge amount of data.
Also you can tell lsregister to add the protocol(s) from a boundle:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f pathname
The linked article states "You shouldn’t ever have to do this" - but this seems invalid to me.
Or tell lsregister to forget about a application:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f -u pathname code_text
where pathname is the path and name of the app.
So the whole process is:
- Add CFBundleURLTypes to your Info.plist
<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>CFBundleURLName</key> <string>com.mydomain</string> <key>CFBundleURLSchemes</key> <array> <string>myProtocol</string> </array> </dict> </array>
And then take care about the registering as described above.
If the protocol IS registered on the Mac, then you'll get a nice clean QEvent::FileOpen in the event callback of your application class derived from QApplication:
class CMyApplication : public QApplication { public: CMyApplication(int &argc, char **argv) : QApplication(argc, argv) { } bool event(QEvent *event) override { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event); QString urlString = openEvent->url().toString(); // Processs urlString ... } return QApplication::event(event); } };
Now you can open a browser and type in the address bar:
myProtocol://?param1=value1¶m2=value2
And the browser will ask you if you want to start the registered application:
If you accept this, openEvent->url().toString() will return the complete string you typed into your browser. If your Application is not running it will be started by the macOS.
-
Thanks for the thorough feedback !