【问题标题】:QT on OS X, how to detect clicking the app Dock IconOS X 上的 QT,如何检测单击应用程序 Dock 图标
【发布时间】:2017-09-29 20:08:34
【问题描述】:

我有一个打开的 Qt Mac 应用程序。 我正在点击应用图标

有没有办法在应用程序中获得通知?

【问题讨论】:

    标签: qt


    【解决方案1】:

    由于弃用警告(OS X 10.5 后)和类型错误,我无法获得正确编译的原始答案;我更改了一些类型名称并进行了编译,但代码仍然无法正常工作。

    事实证明,较新版本的 Qt(4.8.7+,包括 5.x;我使用 5.4.1)实现了我们要添加的方法,如果该方法已经存在,class_addMethod 会失败。见this QTBUG
    注意:上面的错误报告包含一个稍微不同的解决方案(我自己修复问题后发现的)。

    对我有用的一个解决方案是检查该方法是否存在。如果是,我们将其替换。如果没有,我们只需添加它。
    我没有在旧的 Qt 版本上测试过这段代码,但是它使用了 OPs 逻辑,所以它应该可以工作。

    这是我的代码。与 OP 一样,所有代码都在 QApplication 子类的 .cpp 文件中。

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

    我的 QApplication 子类构造函数包含

    #ifdef Q_OS_MAC
        setupDockClickHandler();
    #endif
    

    最后,在同一个文件的某处(在我的例子中,在底部):

    #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
    

    另见Apple documentation on the applicationShouldHandleReopen:hasVisibleWindows: method

    为了编译它,您还需要链接一些额外的框架。
    使用 qmake,我将以下内容添加到我的 .pro 文件中:

    LIBS += -framework CoreFoundation -framework Carbon -lobjc
    

    如果您手动编译,这些标志当然正是您应该添加到 c++ 或 clang++ 命令行的内容。
    这应该是所需的一切。

    【讨论】:

    • 这是一个不错的答案,但顺便说一句,我认为没有必要避免使用目标 C。qmake 处理它,并且 Qt 标头在包含在 .mm 中时是有效的文件。这可以简单地在 .mm 文件中用 obj-C++ 编写,并且在不显式调用 objc 运行时的情况下更具可读性。 Mac 上的 Qt 项目可以包含 .m 和 .mm 文件,您可以从 .mm 文件中使用 Qt。
    • 错误:没有用于调用“objc_msgSend”的匹配函数注意:候选函数不可行:需要 0 个参数,但提供了 2 个
    【解决方案2】:

    这很疯狂,但我明白了,而且没有任何 Objective-C 编码:

    我派生了 QApplication。在我的派生类的 *.cpp 部分中,我放了:

    #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
    

    在我的派生应用程序类构造函数中:

    #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
    

    将这个简单的方法添加到我的应用程序类中(注意它是从我的答案顶部的处理程序中引用的)

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

    像魅力一样工作。

    【讨论】:

    • 谢谢你!显然,这段代码在 Qt 4.8.7 中停止工作,但我设法让它工作(在 Qt 5.4.1 上),所以我发布了一个更新版本的答案。
    • 谢谢杰森!是的,调用 msgSend 似乎是与 Cpp 中的 Objective-C 功能交互的唯一方法。非常感谢您的工作!
    【解决方案3】:

    QEvent::ApplicationActivate 的问题在于它会在每次激活时发出 - 例如,即使您在 Application Switcher 上切换到应用程序也是如此。本机行为是仅在 Dock 图标单击时显示应用程序,而不是在您通过 cmd+tab 切换时显示应用程序。

    但是,有一个至少适用于 Qt 5.9.1 的 hack。 Dock 图标单击会产生 2 个连续的 QEvent::ApplicationStateChangeEvent 事件,同时 cmd+tab - 只有一个。 因此,此代码将非常准确地发出 Dock click 信号。 App类是继承自QApplication的应用类,也是自身的事件过滤器。

    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);
    }
    

    【讨论】:

      【解决方案4】:

      从Qt5.4.0开始你可以处理QEvent,与点击dock有关:QEvent::ApplicationActivate。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多