6

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?

JasonGenX
  • 4,952
  • 27
  • 106
  • 198

4 Answers4

9

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.

Tanuva
  • 151
  • 12
exscape
  • 2,185
  • 4
  • 21
  • 25
  • 2
    It's a nice answer but, as an aside, I don't think that it's necessary to avoid the use of objective C. qmake handles it, and Qt headers are valid when included from an `.mm` file. This could simply be written in obj-C++ in an `.mm` file and would be a bit more readable without explicit calls to objc runtime. Qt projects on mac can include .m and .mm files, and you can use Qt from an .mm file. – Kuba hasn't forgotten Monica Jul 06 '15 at 12:44
  • error: no matching function for call to 'objc_msgSend' note: candidate function not viable: requires 0 arguments, but 2 were provided – Hhry Mar 01 '20 at 03:07
6

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.

JasonGenX
  • 4,952
  • 27
  • 106
  • 198
  • Thank you for this! Apparently, this code stopped working in Qt 4.8.7, but I managed to get it working (on Qt 5.4.1), so I posted an answer with an updated version. – exscape Mar 02 '15 at 20:19
  • Thanks Jason ! And yeah calling msgSend seems to be the only way to interact with Objective-C abilities in Cpp. Thanks a lot for your work! – Romain Cendre Apr 01 '21 at 08:21
0

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

Avalon
  • 31
  • 2
0

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);
}
kmedv
  • 109
  • 2