diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ad529ca..218a6ddd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,11 @@ file(GLOB SOURCE_FILES "plugins/*.h" ) +if(APPLE) + list(APPEND SOURCE_FILES + "utils/os/macos_sleep.mm") +endif() + get_cmake_property(_vars VARIABLES) set(PLUGIN_PREFIX "WITH_PLUGIN_") diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp index 5d6696e9..095036aa 100644 --- a/src/WindowManager.cpp +++ b/src/WindowManager.cpp @@ -8,6 +8,11 @@ #include #include +#if defined(Q_OS_MACOS) +#include "utils/os/macos_sleep.h" +#include +#endif + #include "Application.h" #include "constants.h" #include "MainWindow.h" @@ -41,6 +46,12 @@ WindowManager::WindowManager(QObject *parent) connect(qApp, SIGNAL(anotherInstanceStarted()), this, SLOT(raise())); connect(qApp, &QGuiApplication::lastWindowClosed, this, &WindowManager::quitAfterLastWindow); +#if defined(Q_OS_MACOS) + m_macSleepObserver = new MacSleepObserver(this); + connect(m_macSleepObserver, &MacSleepObserver::didWake, this, [this] { + QTimer::singleShot(0, this, &WindowManager::recoverFromSleep); + }); +#endif m_tray = new QSystemTrayIcon(icons()->icon("appicons/64x64.png")); m_tray->setToolTip("Feather Wallet"); @@ -621,6 +632,38 @@ void WindowManager::onWalletPassphraseNeeded(bool on_device) { m_walletManager->onPassphraseEntered(passphrase, false, false); } +void WindowManager::recoverFromSleep() { +#if defined(Q_OS_MACOS) + // Recreate tray icon to work around macOS Qt status item issues after sleep. + const bool trayVisible = conf()->get(Config::showTrayIcon).toBool(); + if (m_tray) { + m_tray->setVisible(false); + delete m_tray; + m_tray = nullptr; + } + + m_tray = new QSystemTrayIcon(icons()->icon("appicons/64x64.png")); + m_tray->setToolTip("Feather Wallet"); + this->buildTrayMenu(); + m_tray->setVisible(trayVisible); + + for (const auto &window : m_windows) { + if (!window) { + continue; + } + if (!window->isHidden()) { + window->bringToFront(); + } else { + window->update(); + } + } + if (m_wizard && !m_wizard->isHidden()) { + m_wizard->raise(); + m_wizard->activateWindow(); + } +#endif +} + // ######################## TRAY ######################## void WindowManager::buildTrayMenu() { @@ -818,4 +861,4 @@ WindowManager* WindowManager::instance() } return m_instance; -} \ No newline at end of file +} diff --git a/src/WindowManager.h b/src/WindowManager.h index d0f3e460..a55b35de 100644 --- a/src/WindowManager.h +++ b/src/WindowManager.h @@ -71,6 +71,7 @@ private slots: void onDeviceError(const QString &errorMessage, quint64 errorCode); void onWalletPassphraseNeeded(bool on_device); void onChangeTheme(const QString &themeName); + void recoverFromSleep(); private: void tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset, const QString &subaddressLookahead, bool newWallet); @@ -115,6 +116,9 @@ private slots: bool m_initialNetworkConfigured = false; QThread *m_cleanupThread; +#if defined(Q_OS_MACOS) + class MacSleepObserver *m_macSleepObserver = nullptr; +#endif }; inline WindowManager* windowManager() diff --git a/src/utils/os/macos_sleep.h b/src/utils/os/macos_sleep.h new file mode 100644 index 00000000..f786db0d --- /dev/null +++ b/src/utils/os/macos_sleep.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: The Monero Project + +#ifndef FEATHER_MACOS_SLEEP_H +#define FEATHER_MACOS_SLEEP_H + +#include + +class MacSleepObserver : public QObject { + Q_OBJECT + +public: + explicit MacSleepObserver(QObject *parent = nullptr); + ~MacSleepObserver() override; + + void notifyWillSleep(); + void notifyDidWake(); + +signals: + void willSleep(); + void didWake(); + +private: + void *m_observer = nullptr; +}; + +#endif // FEATHER_MACOS_SLEEP_H diff --git a/src/utils/os/macos_sleep.mm b/src/utils/os/macos_sleep.mm new file mode 100644 index 00000000..5fc79f7e --- /dev/null +++ b/src/utils/os/macos_sleep.mm @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: The Monero Project + +#include "macos_sleep.h" + +#if defined(Q_OS_MACOS) + +#import + +@interface FeatherSleepObserver : NSObject +@property (nonatomic, assign) MacSleepObserver *owner; +@end + +@implementation FeatherSleepObserver +- (instancetype)initWithOwner:(MacSleepObserver *)owner { + self = [super init]; + if (self) { + _owner = owner; + NSNotificationCenter *center = [[NSWorkspace sharedWorkspace] notificationCenter]; + [center addObserver:self selector:@selector(onWillSleep:) name:NSWorkspaceWillSleepNotification object:nil]; + [center addObserver:self selector:@selector(onDidWake:) name:NSWorkspaceDidWakeNotification object:nil]; + } + return self; +} + +- (void)dealloc { + NSNotificationCenter *center = [[NSWorkspace sharedWorkspace] notificationCenter]; + [center removeObserver:self]; +#if !__has_feature(objc_arc) + [super dealloc]; +#endif +} + +- (void)onWillSleep:(NSNotification *)__unused notification { + if (_owner) { + _owner->notifyWillSleep(); + } +} + +- (void)onDidWake:(NSNotification *)__unused notification { + if (_owner) { + _owner->notifyDidWake(); + } +} +@end + +MacSleepObserver::MacSleepObserver(QObject *parent) + : QObject(parent) +{ + FeatherSleepObserver *observer = [[FeatherSleepObserver alloc] initWithOwner:this]; +#if __has_feature(objc_arc) + m_observer = (__bridge_retained void *)observer; +#else + m_observer = observer; +#endif +} + +MacSleepObserver::~MacSleepObserver() { + if (m_observer) { +#if !__has_feature(objc_arc) + FeatherSleepObserver *observer = static_cast(m_observer); + [observer release]; +#else + CFBridgingRelease(m_observer); +#endif + m_observer = nullptr; + } +} + +void MacSleepObserver::notifyWillSleep() { + emit willSleep(); +} + +void MacSleepObserver::notifyDidWake() { + emit didWake(); +} + +#else + +MacSleepObserver::MacSleepObserver(QObject *parent) + : QObject(parent) +{} + +MacSleepObserver::~MacSleepObserver() = default; + +void MacSleepObserver::notifyWillSleep() {} + +void MacSleepObserver::notifyDidWake() {} + +#endif