Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QT on OS X, how to detect clicking the app Dock Icon

Tags:

qt

I have an open Qt Mac app. I am clicking the app Icon

Is there a way to get a notification for this in the app?

like image 948
JasonGenX Avatar asked Feb 28 '13 18:02

JasonGenX


2 Answers

I couldn't get the original answer to compile properly due to deprecation warnings (post-OS X 10.5) and type errors; I changed a few type names and got it to compile, but the code still didn't work.

It turns out that newer versions of Qt (4.8.7+, including 5.x; I use 5.4.1) implement the method we want to add, and class_addMethod fails if the method already exists. See this QTBUG.
Note: the above bug report contains a slightly different solution (I found it after fixing the issue myself).

One solution, that works for me, is to check if the method exists. If it does, we replace it. If not, we simply add it.
I have not tested this code on older Qt versions, but it uses OPs logic, so it should work.

Here's my code. As in OPs case, all code is in the .cpp file of a QApplication subclass.

#ifdef Q_OS_MAC
#include <objc/objc.h>
#include <objc/message.h>
void setupDockClickHandler();
bool dockClickHandler(id self,SEL _cmd,...);
#endif

My QApplication subclass constructor contains

#ifdef Q_OS_MAC
    setupDockClickHandler();
#endif

And finally, somewhere in the same file (at the bottom, in my case):

#ifdef Q_OS_MAC
void setupDockClickHandler() {
    Class cls = objc_getClass("NSApplication");
    objc_object *appInst = objc_msgSend((objc_object*)cls, sel_registerName("sharedApplication"));

    if(appInst != NULL) {
        objc_object* delegate = objc_msgSend(appInst, sel_registerName("delegate"));
        Class delClass = (Class)objc_msgSend(delegate,  sel_registerName("class"));
        SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
        if (class_getInstanceMethod(delClass, shouldHandle)) {
            if (class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"))
                qDebug() << "Registered dock click handler (replaced original method)";
            else
                qWarning() << "Failed to replace method for dock click handler";
        }
        else {
            if (class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"))
                qDebug() << "Registered dock click handler";
            else
                qWarning() << "Failed to register dock click handler";
        }
    }
}

bool dockClickHandler(id self,SEL _cmd,...) {
    Q_UNUSED(self)
    Q_UNUSED(_cmd)
    // Do something fun here!
    qDebug() << "Dock icon clicked!";

    // Return NO (false) to suppress the default OS X actions
    return false;
}
#endif

Also see the Apple documentation on the applicationShouldHandleReopen:hasVisibleWindows: method.

In order for this to compile, you also need to link with some extra frameworks.
Using qmake, I added the following to my .pro file:

LIBS += -framework CoreFoundation -framework Carbon -lobjc

Those flags are of course exactly what you should add to the c++ or clang++ command line, if you compile manually.
That should be everything that's required.

like image 101
exscape Avatar answered Nov 19 '22 23:11

exscape


It's crazy, but i got it, and without any Objective-C coding:

I derived QApplication. In the *.cpp portion of my derived class i put:

#ifdef Q_OS_MAC

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

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

#endif

in my derived application class constructor I put:

#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* delClass = objc_msgSend(delegate,  sel_registerName("class"));
        const char* tst = class_getName(delClass->isa);
        bool test = class_addMethod((objc_class*)delClass, sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"), (IMP)dockClickHandler,"B@:");

        if (!test)
        {
            // failed to register handler...
        }
    }

#endif

Added this simple method to my application class (note it's referred to from the handler at the top of my answer)

void MyApplictionClass::onClickOnDock()
{
  // do something... 
}

Works like charm.

like image 6
JasonGenX Avatar answered Nov 19 '22 22:11

JasonGenX