我有一个开放的 Qt Mac 应用程序。我正在单击应用程序图标
有没有办法在应用程序中获得通知?
由于弃用警告(OS X 10.5 后)和类型错误,我无法获得正确编译的原始答案;我更改了一些类型名称并对其进行编译,但代码仍然无法正常工作。
事实证明,较新版本的 Qt(4.8.7+,包括 5.x;我使用 5.4.1)实现了我们要添加的方法,class_addMethod
如果该方法已经存在,则会失败。看到这个 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
另请参阅有关 applicationShouldHandleReopen:hasVisibleWindows: 方法的 Apple 文档。
为了编译它,您还需要链接一些额外的框架。
使用 qmake,我将以下内容添加到我的 .pro 文件中:
LIBS += -framework CoreFoundation -framework Carbon -lobjc
如果您手动编译,这些标志当然正是您应该添加到 c++ 或 clang++ 命令行的内容。
这应该是所需要的一切。
这很疯狂,但我明白了,而且没有任何 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...
}
像魅力一样工作。
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);
}
从 Qt5.4.0 开始,您可以处理与单击 Dock 相关的 QEvent:QEvent::ApplicationActivate。