Thursday, February 16, 2012

How to handle click on app icon in Mac OS X dock in Qt application.

Couple of days ago I faced with task when I need to handle a click on application icon in Dock application.
I had very good recommendations how to do it using Objective-C here (unfortunately in Russian). Big thanks for list of such beautiful tricks to its author. But there was one inconvenience.  I did not want to add Objective-C++ file (.mm) in my project.
And I found other very good article suite from Robin Mills.(See ObjCandCandC++.pdf)

As result I got the following working bunch of code WITHOUT usage Objective-C++ and it has allowed me to reach what I wanted.
The prototype in Objective-C was very short.

Main idea was as usual to write separate Objective-C class. It should be separate compilation unit and it can be available for the code in Qt/C++.

This is callback that should be declared and registered in order to handle a click on dock's icon .
 
void dockClickHandler(id self, SEL _cmd)
{
    Q_UNUSED(self)
    Q_UNUSED(_cmd)
    MyPrivate::instance()->emitClick();
}



and registration of this callback in Objective-C. 
 
MyPrivate::MyPrivate() :

    QObject(NULL)
{
    Class cls = [[[NSApplication sharedApplication] delegate] class];
    if (!class_addMethod(cls, 
        @selector(applicationShouldHandleReopen:hasVisibleWindows:), 
        (IMP) dockClickHandler, "v@:"))
       NSLog(@"MyPrivate::MyPrivate() : class_addMethod failed!");
}
void MyPrivate::emitClick()
{
    emit dockClicked();
}
 
But as you remember I did not want to use Objective-C at all. I did not want to add new file in my project.

as result I got the following:

The callback was declared in the top of my .cpp file there I declared my descendant from QApplication:


#ifdef Q_OS_MAC
#include <objc/objc.h>
#include <objc/message.h>

bool dockClickHandler(id self,SEL _cmd,...)
{
    Q_UNUSED(self)
    Q_UNUSED(_cmd)
   ((Application*)qApp)->onClickOnDock();
     return true;
}
#endif

where Application is my descendant from QApplication.

and most interesting thing, we can register in runtime this function as new method for delegate class (NSApplicationDelegate):


#ifdef Q_OS_MAC

objc_object* cls = objc_getClass("NSApplication");
SEL sharedApplication = sel_registerName("sharedApplication");
objc_object* appInst = objc_msgSend(cls,sharedApplication);

if(appInst != NULL)
{
     objc_object* delegate = objc_msgSend(appInst,
        sel_registerName("delegate")); 
     objc_object* delegateClass = objc_msgSend(delegateClass,
        sel_registerName("class"));
     constchar* tst = class_getName(delegateClass->isa);
     bool test = class_addMethod((objc_class*) delegateClass,
      sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"),
     (IMP)dockClickHandler,"B@:"); 
// "B@:" is quite tricky thing called Method Signature which allows to define C function to selector "applicationShouldHandleReopen:hasVisibleWindows:" and dockClickHandler function will be called as if it was written inside objective-c code.

if(test)
     {
     qDebug("Registration was successful");
     }
}
#endif
 
Now, I was able to do what I want in my onClickOnDock() when a user clicks on application Menu in the dock.