JasonGenX
JasonGenX

Reputation: 5444

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

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?

Upvotes: 6

Views: 4590

Answers (4)

kmedv
kmedv

Reputation: 109

The problem with QEvent::ApplicationActivate is that it will be emitted for every activation - eg., even if you switch to the app on Application Switcher. The native behavior is to show the app only on Dock icon click, not when you are switching by cmd+tab.

But, there is a hack that works at least for Qt 5.9.1. The Dock icon click produces 2 sequential QEvent::ApplicationStateChangeEvent events, meanwhile cmd+tab - only one. So, this code will emit Dock click signal quite accurately. App class is the application class inherited from QApplication and also an event filter for itself.

bool App::eventFilter(QObject* watched, QEvent* event)
{
#ifdef Q_OS_MACOS
    if (watched == this && event->type() == QEvent::ApplicationStateChange) {
        auto ev = static_cast<QApplicationStateChangeEvent*>(event);
        if (_prevAppState == Qt::ApplicationActive
                && ev->applicationState() == Qt::ApplicationActive) {
            emit clickedOnDock();
        }
        _prevAppState = ev->applicationState();
    }
#endif // Q_OS_MACOS
    return QApplication::eventFilter(watched, event);
}

Upvotes: 0

Avalon
Avalon

Reputation: 31

Starting from Qt5.4.0 you can handle QEvent, that related to click on dock: QEvent::ApplicationActivate.

https://bugreports.qt.io/browse/QTBUG-10899

https://doc.qt.io/qt-5/qevent.html

Upvotes: 0

exscape
exscape

Reputation: 2225

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.

Upvotes: 9

JasonGenX
JasonGenX

Reputation: 5444

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.

Upvotes: 6

Related Questions