Tuesday, 15 July 2008

Adding a Url Scheme to a Qt Application Running on Mac Os X and Win32

Introduction

What is a URL Scheme or Plugin Protocol?

When you write a web address it starts with "http:" or "https:". An FTP connection (within a browser) starts "ftp:" that part of the address is known as the "scheme".

Many applications create their own schemes so that data can be passed from the browser via an <a href="anewscheme:this_data_is_sent_to_the_native_application"> style link.

I went through the mill getting this to work (especially on the Mac). By comparison Windows is very simple to setup - just a few registry hacks. So, let's start there.

Windows

All one needs to do to in Windows is set the appropriate registry keys. Let's assume that we've installed our application at "C:\Program Files\Acme\MyAcmeProgram.exe"

We need to add the following keys to the registry, all in HKEY Class Root. I instruct NSIS to do this at installation time as it knows where the application is to be installed.

"HKCR\anewscheme" = "URL:BODiBEAT Protocol"
"HKCR\anewscheme\URL Protocol" = ""
"HKCR\anewscheme\Shell\Open\Command" = "C:\Program Files\Acme\MyAcmeProgram.exe "%1$""

NOTE, the "URL Protocol" key really is empty. It's simply a place marker

NOTE, the extra quotes around the %1 are essential especially if your URL contains commas, spaces or any other character that specifies a parameter separator on the command line.

Operation

When the user accesses a link with like "anewscheme:datagoeshere" the application is executed and the link itself is sent to the application as argv[1]. It's that simple. You may want to make your application single instance and pipe the given URL to the first instance of the application.


Mac Os X

For the Mac we have much more work to do. The first thing we will need to do is to install an AppleEvent handler in our "main" function like this...


OSErr err = AEInstallEventHandler(kInternetEventClass,
kAEGetURL,
NewAEEventHandlerUPP((AEEventHandlerProcPtr)macCallbackGetUrl),
(long) (&app),
false);

We also need to provide the function "macCallbackGetUrl"


// AppleEvent callback
static pascal OSErr macCallbackGetUrl(const AppleEvent* inEvent,
AppleEvent* outEvent,
long refCon)
{
OSErr err = noErr;
Size actualSize = 0;
DescType descType = typeChar;

if ((err = AESizeOfParam(inEvent, keyDirectObject, &descType, &actualSize)) == noErr)
{
if (0 != actualSize)
{
// make a buffer (Qt style)
QByteArray bUrl;
bUrl.resize(actualSize + 1);

err = AEGetParamPtr(inEvent,
keyDirectObject,
typeChar,
0,
bUrl.data(),
actualSize,
&actualSize);
// Grufty C cast... meh.
(CMyApplication*)(app)->appReceivedUrl(bUrl);
}
}

return noErr;
}

NOTE:"app" is whatever you want. Possibly the most convenient object to pass to the event handler is your QApplication derived application class.

We're NEARLY there! The very last thing we need to do is to invoke the Carbon event loop. However, Qt does this for you when you create a QApplication object (which you did in main just after setting up the Apple event handler right?)

To make this all compile and link properly we need to link against the Carbon framework. TO do this we need to add one extra line to our qmake .pro file when building for the Mac

mac:LIBS += -framework Carbon

To finish off we need to add some XML to Info.plist which is inside the app bundle. Use finder to open the package and in the "Contents" folder you'll find "Info.plist" - you can use the property edit to edit this - just double click.

However! There's a little trick you can use in your qmake .pro (or .pri) file. Qt installs a very minimal Info.plist in your app bundle. To override this and provide your own add the line

mac:QMAKE_INFO_PLIST = ./Info.plist

Another way to do this is to use "install" type "man install" for more info. This lets you add/delete files from a package at will and is very powerful.

Anyhoo. These are the keys that need adding...

 <key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>A New Scheme</string>
<key>CFBundleURLSchemes</key>
<array>
<string>anewscheme</string>
</array>
</dict>
</array>
<key>NSAppleScriptEnabled</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
Operation

When the user access a "anewscheme:" URL an Apple Event is received in the callback function and we can pick out that data and process it anyway we like.

Epilogue

And that's all! Honest! L8rz, burnttoy

3 comments:

Çelik Kapı said...

Sorry my english firstly, i read well but i cant write :), So its useful post for me and i bookmarked your blog. | yemek tarifi | konut | çelik kapı | çelik kapı | şöförlü araç kiralama | tercüme | seo |

Buğra Şilte said...

great thanks admin shares
Çelik Kapı şilte

Hakan Lager said...

Isn't QT's own url-scheme-handler usable for this?

/ Digiloo