Skip to content

Commit 30f3827

Browse files
authored
Address segmentation faults in the Log message handlers on macOS. (#766)
* Remove obj_c for macOS Dock icon setting Qt `setWindowIcon()` does this work. * Use Qt signal for macOS Dock icon click event This moves the Dock icon click reaction code to the common place and allows some cleanup in obj_c code. According to the Apple's docs `class_replaceMethod` behaves as `class_addMethod`, if the method identified by name does not yet exist; or as `method_setImplementation`, if it does exist. * Remove obj_c for macOS Dock icon menu Qt `setAsDockMenu()` does this work. * qt: Replace objc_msgSend with native syntax * Do not use Qt's debugging information in the log handler. * This information is not always available and caused crashes in some builds. Authored-by: Hennadii Stepanov <[email protected]>
1 parent d4cbfea commit 30f3827

File tree

6 files changed

+64
-115
lines changed

6 files changed

+64
-115
lines changed

configure.ac

+1-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ case $host in
567567
fi
568568

569569
AX_CHECK_LINK_FLAG([[-Wl,-headerpad_max_install_names]], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"])
570-
CPPFLAGS="$CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0"
570+
CPPFLAGS="$CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0"
571571
OBJCXXFLAGS="$CXXFLAGS"
572572
;;
573573
*android*)

src/qt/bitcoingui.cpp

+19-13
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
105105
windowTitle += tr("Node");
106106
}
107107
windowTitle += " " + networkStyle->getTitleAddText();
108-
#ifndef Q_OS_MAC
109108
QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon());
110109
setWindowIcon(networkStyle->getTrayAndWindowIcon());
111-
#else
112-
MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon());
113-
#endif
114110
setWindowTitle(windowTitle);
115111

116112
rpcConsole = new RPCConsole(node, _platformStyle, 0);
@@ -685,7 +681,7 @@ void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle)
685681
void BitcoinGUI::createTrayIconMenu()
686682
{
687683
#ifndef Q_OS_MAC
688-
// return if trayIcon is unset (only on non-Mac OSes)
684+
// return if trayIcon is unset (only on non-macOSes)
689685
if (!trayIcon)
690686
return;
691687

@@ -695,15 +691,20 @@ void BitcoinGUI::createTrayIconMenu()
695691
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
696692
this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
697693
#else
698-
// Note: On Mac, the dock icon is used to provide the tray's functionality.
694+
// Note: On macOS, the Dock icon is used to provide the tray's functionality.
699695
MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
700-
dockIconHandler->setMainWindow(static_cast<QMainWindow*>(this));
701-
trayIconMenu = dockIconHandler->dockMenu();
696+
connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated);
697+
698+
trayIconMenu = new QMenu(this);
699+
trayIconMenu->setAsDockMenu();
702700
#endif
703701

704702
// Configuration of the tray icon (or dock icon) icon menu
703+
#ifndef Q_OS_MAC
704+
// Note: On macOS, the Dock icon's menu already has Show / Hide action.
705705
trayIconMenu->addAction(toggleHideAction);
706-
#ifdef ENABLE_WALLET
706+
trayIconMenu->addSeparator();
707+
#endif
707708
if(enableWallet) {
708709
trayIconMenu->addSeparator();
709710
trayIconMenu->addAction(sendCoinsMenuAction);
@@ -712,11 +713,10 @@ void BitcoinGUI::createTrayIconMenu()
712713
trayIconMenu->addAction(signMessageAction);
713714
trayIconMenu->addAction(verifyMessageAction);
714715
trayIconMenu->addSeparator();
715-
trayIconMenu->addAction(optionsAction);
716-
trayIconMenu->addAction(openRPCConsoleAction);
717716
}
718-
#endif
719-
#ifndef Q_OS_MAC // This is built-in on Mac
717+
trayIconMenu->addAction(optionsAction);
718+
trayIconMenu->addAction(openRPCConsoleAction);
719+
#ifndef Q_OS_MAC // This is built-in on macOS
720720
trayIconMenu->addSeparator();
721721
trayIconMenu->addAction(quitAction);
722722
#endif
@@ -731,6 +731,12 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
731731
toggleHidden();
732732
}
733733
}
734+
#else
735+
void BitcoinGUI::macosDockIconActivated()
736+
{
737+
show();
738+
activateWindow();
739+
}
734740
#endif
735741

736742
#ifdef ENABLE_WALLET

src/qt/bitcoingui.h

+3
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ private Q_SLOTS:
314314
#ifndef Q_OS_MAC
315315
/** Handle tray icon clicked */
316316
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
317+
#else
318+
/** Handle macOS Dock icon clicked */
319+
void macosDockIconActivated();
317320
#endif
318321

319322
/** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */

src/qt/guiutil.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@
5656
#include <QFontDatabase>
5757
#endif
5858

59+
#if defined(Q_OS_MAC)
60+
#include <CoreServices/CoreServices.h>
61+
#include <QProcess>
62+
63+
void ForceActivation();
64+
#endif
65+
5966
static fs::detail::utf8_codecvt_facet utf8;
6067

6168
namespace GUIUtil {
@@ -359,6 +366,24 @@ bool isObscured(QWidget *w)
359366
&& checkPoint(QPoint(w->width() / 2, w->height() / 2), w));
360367
}
361368

369+
void bringToFront(QWidget* w)
370+
{
371+
#ifdef Q_OS_MAC
372+
ForceActivation();
373+
#endif
374+
375+
if (w) {
376+
// activateWindow() (sometimes) helps with keyboard focus on Windows
377+
if (w->isMinimized()) {
378+
w->showNormal();
379+
} else {
380+
w->show();
381+
}
382+
w->activateWindow();
383+
w->raise();
384+
}
385+
}
386+
362387
void openDebugLogfile()
363388
{
364389
fs::path pathDebug = GetDataDir() / "debug.log";

src/qt/macdockiconhandler.h

+1-18
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,23 @@
55
#ifndef BITCOIN_QT_MACDOCKICONHANDLER_H
66
#define BITCOIN_QT_MACDOCKICONHANDLER_H
77

8-
#include <QMainWindow>
98
#include <QObject>
109

11-
QT_BEGIN_NAMESPACE
12-
class QIcon;
13-
class QMenu;
14-
class QWidget;
15-
QT_END_NAMESPACE
16-
17-
/** Macintosh-specific dock icon handler.
10+
/** macOS-specific Dock icon handler.
1811
*/
1912
class MacDockIconHandler : public QObject
2013
{
2114
Q_OBJECT
2215

2316
public:
24-
~MacDockIconHandler();
25-
26-
QMenu *dockMenu();
27-
void setIcon(const QIcon &icon);
28-
void setMainWindow(QMainWindow *window);
2917
static MacDockIconHandler *instance();
3018
static void cleanup();
31-
void handleDockIconClickEvent();
3219

3320
Q_SIGNALS:
3421
void dockIconClicked();
3522

3623
private:
3724
MacDockIconHandler();
38-
39-
QWidget *m_dummyWidget;
40-
QMenu *m_dockMenu;
41-
QMainWindow *mainWindow;
4225
};
4326

4427
#endif // BITCOIN_QT_MACDOCKICONHANDLER_H

src/qt/macdockiconhandler.mm

+15-83
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,30 @@
44

55
#include "macdockiconhandler.h"
66

7-
#include <QImageWriter>
8-
#include <QMenu>
9-
#include <QBuffer>
10-
#include <QWidget>
11-
12-
#undef slots
13-
#include <Cocoa/Cocoa.h>
14-
#include <objc/objc.h>
15-
#include <objc/message.h>
7+
#include <AppKit/AppKit.h>
8+
#include <objc/runtime.h>
169

1710
static MacDockIconHandler *s_instance = nullptr;
1811

19-
bool dockClickHandler(id self,SEL _cmd,...) {
12+
bool dockClickHandler(id self, SEL _cmd, ...) {
2013
Q_UNUSED(self)
2114
Q_UNUSED(_cmd)
2215

23-
s_instance->handleDockIconClickEvent();
16+
Q_EMIT s_instance->dockIconClicked();
2417

25-
// Return NO (false) to suppress the default OS X actions
18+
// Return NO (false) to suppress the default macOS actions
2619
return false;
2720
}
2821

2922
void setupDockClickHandler() {
30-
31-
Class delClass = (Class)[[[NSApplication sharedApplication] delegate] class];
32-
33-
SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
34-
class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:");
23+
Class delClass = (Class)[[[NSApplication sharedApplication] delegate] class];
24+
SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
25+
class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:");
3526
}
3627

37-
3828
MacDockIconHandler::MacDockIconHandler() : QObject()
3929
{
40-
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
41-
4230
setupDockClickHandler();
43-
this->m_dummyWidget = new QWidget();
44-
this->m_dockMenu = new QMenu(this->m_dummyWidget);
45-
this->setMainWindow(nullptr);
46-
#if QT_VERSION >= 0x050200
47-
this->m_dockMenu->setAsDockMenu();
48-
#endif
49-
[pool release];
50-
}
51-
52-
void MacDockIconHandler::setMainWindow(QMainWindow *window) {
53-
this->mainWindow = window;
54-
}
55-
56-
MacDockIconHandler::~MacDockIconHandler()
57-
{
58-
delete this->m_dummyWidget;
59-
this->setMainWindow(nullptr);
60-
}
61-
62-
QMenu *MacDockIconHandler::dockMenu()
63-
{
64-
return this->m_dockMenu;
65-
}
66-
67-
void MacDockIconHandler::setIcon(const QIcon &icon)
68-
{
69-
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
70-
NSImage *image = nil;
71-
if (icon.isNull())
72-
image = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
73-
else {
74-
// generate NSImage from QIcon and use this as dock icon.
75-
QSize size = icon.actualSize(QSize(128, 128));
76-
QPixmap pixmap = icon.pixmap(size);
77-
78-
// Write image into a R/W buffer from raw pixmap, then save the image.
79-
QBuffer notificationBuffer;
80-
if (!pixmap.isNull() && notificationBuffer.open(QIODevice::ReadWrite)) {
81-
QImageWriter writer(&notificationBuffer, "PNG");
82-
if (writer.write(pixmap.toImage())) {
83-
NSData* macImgData = [NSData dataWithBytes:notificationBuffer.buffer().data()
84-
length:notificationBuffer.buffer().size()];
85-
image = [[NSImage alloc] initWithData:macImgData];
86-
}
87-
}
88-
89-
if(!image) {
90-
// if testnet image could not be created, load std. app icon
91-
image = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
92-
}
93-
}
94-
95-
[NSApp setApplicationIconImage:image];
96-
[image release];
97-
[pool release];
9831
}
9932

10033
MacDockIconHandler *MacDockIconHandler::instance()
@@ -109,13 +42,12 @@ void setupDockClickHandler() {
10942
delete s_instance;
11043
}
11144

112-
void MacDockIconHandler::handleDockIconClickEvent()
45+
/**
46+
* Force application activation on macOS. With Qt 5.5.1 this is required when
47+
* an action in the Dock menu is triggered.
48+
* TODO: Define a Qt version where it's no-longer necessary.
49+
*/
50+
void ForceActivation()
11351
{
114-
if (this->mainWindow)
115-
{
116-
this->mainWindow->activateWindow();
117-
this->mainWindow->show();
118-
}
119-
120-
Q_EMIT this->dockIconClicked();
52+
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
12153
}

0 commit comments

Comments
 (0)