diff options
author | Jonathan White <support@dmapps.us> | 2021-01-31 23:43:37 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-31 23:43:37 +0300 |
commit | 109671900bfdc21b9128020af46b5fb4c5d135a6 (patch) | |
tree | eda1c65245bdc526c4df4f96c87ce4db3cbaca46 | |
parent | 63df00a723c9ebd55f9138c319d8f567f48cd9a5 (diff) | |
parent | 2889341acdafbe919d56bca76e8b256690feed94 (diff) |
Merge pull request #6034 from keepassxreboot/hotfix/theme-switching-backport
-rw-r--r-- | .gitattributes | 3 | ||||
-rw-r--r-- | src/core/Resources.cpp | 185 | ||||
-rw-r--r-- | src/core/Resources.h | 4 | ||||
-rw-r--r-- | src/gui/Application.cpp | 21 | ||||
-rw-r--r-- | src/gui/ApplicationSettingsWidget.cpp | 4 | ||||
-rw-r--r-- | src/gui/EntryPreviewWidget.cpp | 9 | ||||
-rw-r--r-- | src/gui/MainWindow.cpp | 12 | ||||
-rw-r--r-- | src/gui/PasswordEdit.cpp | 4 | ||||
-rw-r--r-- | src/gui/osutils/OSUtilsBase.h | 16 | ||||
-rw-r--r-- | src/gui/osutils/macutils/AppKit.h | 4 | ||||
-rw-r--r-- | src/gui/osutils/macutils/AppKitImpl.h | 1 | ||||
-rw-r--r-- | src/gui/osutils/macutils/AppKitImpl.mm | 61 | ||||
-rw-r--r-- | src/gui/osutils/macutils/MacUtils.cpp | 14 | ||||
-rw-r--r-- | src/gui/osutils/macutils/MacUtils.h | 2 | ||||
-rw-r--r-- | src/gui/osutils/nixutils/NixUtils.cpp | 6 | ||||
-rw-r--r-- | src/gui/osutils/nixutils/NixUtils.h | 1 | ||||
-rw-r--r-- | src/gui/osutils/winutils/WinUtils.cpp | 23 | ||||
-rw-r--r-- | src/gui/osutils/winutils/WinUtils.h | 4 | ||||
-rw-r--r-- | src/gui/styles/base/BaseStyle.cpp | 33 | ||||
-rw-r--r-- | src/gui/styles/base/BaseStyle.h | 1 | ||||
-rw-r--r-- | src/gui/styles/dark/DarkStyle.cpp | 6 | ||||
-rw-r--r-- | src/gui/styles/light/LightStyle.cpp | 6 |
22 files changed, 291 insertions, 129 deletions
diff --git a/.gitattributes b/.gitattributes index 9df1af791..afc11a7bb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,6 @@ AppImage-Recipe.sh export-ignore # github-linguist language hints *.h linguist-language=C++ *.cpp linguist-language=C++ + +# binary files +*.ai binary diff --git a/src/core/Resources.cpp b/src/core/Resources.cpp index ae8c0d46a..df89a4f98 100644 --- a/src/core/Resources.cpp +++ b/src/core/Resources.cpp @@ -20,6 +20,7 @@ #include <QBitmap> #include <QDir> +#include <QIconEngine> #include <QLibrary> #include <QPainter> #include <QStyle> @@ -30,6 +31,18 @@ #include "gui/MainWindow.h" #include "gui/osutils/OSUtils.h" +class AdaptiveIconEngine : public QIconEngine +{ +public: + explicit AdaptiveIconEngine(QIcon baseIcon); + void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override; + QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override; + QIconEngine* clone() const override; + +private: + QIcon m_baseIcon; +}; + Resources* Resources::m_instance(nullptr); QString Resources::dataPath(const QString& name) const @@ -114,45 +127,105 @@ QString Resources::trayIconAppearance() const return iconAppearance; } -QIcon Resources::trayIcon() +QIcon Resources::trayIcon(QString style) { - return trayIconUnlocked(); -} + if (style == "unlocked") { + style.clear(); + } + if (!style.isEmpty()) { + style = "-" + style; + } -QIcon Resources::trayIconLocked() -{ auto iconApperance = trayIconAppearance(); - - if (iconApperance == "monochrome-light") { - return icon("keepassxc-monochrome-light-locked", false); + if (!iconApperance.startsWith("monochrome")) { + return icon(QString("keepassxc%1").arg(style), false); } - if (iconApperance == "monochrome-dark") { - return icon("keepassxc-monochrome-dark-locked", false); + + QIcon i; +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) + if (osUtils->isStatusBarDark()) { + i = icon(QString("keepassxc-monochrome-light%1").arg(style), false); + } else { + i = icon(QString("keepassxc-monochrome-dark%1").arg(style), false); } - return icon("keepassxc-locked", false); +#else + i = icon(QString("keepassxc-%1%2").arg(iconApperance, style), false); +#endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + // Set as mask to allow the operating system to recolour the tray icon. This may look weird + // if we failed to detect the status bar background colour correctly, but it is certainly + // better than a barely visible icon and even if we did guess correctly, it allows for better + // integration should the system's preferred colours not be 100% black or white. + i.setIsMask(true); +#endif + return i; +} + +QIcon Resources::trayIconLocked() +{ + return trayIcon("locked"); } QIcon Resources::trayIconUnlocked() { - auto iconApperance = trayIconAppearance(); + return trayIcon("unlocked"); +} - if (iconApperance == "monochrome-light") { - return icon("keepassxc-monochrome-light", false); - } - if (iconApperance == "monochrome-dark") { - return icon("keepassxc-monochrome-dark", false); +AdaptiveIconEngine::AdaptiveIconEngine(QIcon baseIcon) + : QIconEngine() + , m_baseIcon(std::move(baseIcon)) +{ +} + +void AdaptiveIconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) +{ + // Temporary image canvas to ensure that the background is transparent and alpha blending works. +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + auto scale = painter->device()->devicePixelRatioF(); +#else + auto scale = painter->device()->devicePixelRatio(); +#endif + QImage img(rect.size() * scale, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + QPainter p(&img); + + m_baseIcon.paint(&p, img.rect(), Qt::AlignCenter, mode, state); + + if (getMainWindow()) { + QPalette palette = getMainWindow()->palette(); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + + if (mode == QIcon::Active) { + p.fillRect(img.rect(), palette.color(QPalette::Active, QPalette::ButtonText)); + } else if (mode == QIcon::Selected) { + p.fillRect(img.rect(), palette.color(QPalette::Active, QPalette::HighlightedText)); + } else if (mode == QIcon::Disabled) { + p.fillRect(img.rect(), palette.color(QPalette::Disabled, QPalette::WindowText)); + } else { + p.fillRect(img.rect(), palette.color(QPalette::Normal, QPalette::WindowText)); + } } - return icon("keepassxc", false); + + painter->drawImage(rect, img); } -QIcon Resources::icon(const QString& name, bool recolor, const QColor& overrideColor) +QPixmap AdaptiveIconEngine::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) { - QIcon icon = m_iconCache.value(name); + QImage img(size, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + QPainter painter(&img); + paint(&painter, QRect(0, 0, size.width(), size.height()), mode, state); + return QPixmap::fromImage(img, Qt::ImageConversionFlag::NoFormatConversion); +} - if (!icon.isNull() && !overrideColor.isValid()) { - return icon; - } +QIconEngine* AdaptiveIconEngine::clone() const +{ + return new AdaptiveIconEngine(m_baseIcon); +} +QIcon Resources::icon(const QString& name, bool recolor, const QColor& overrideColor) +{ +#ifdef Q_OS_LINUX // Resetting the application theme name before calling QIcon::fromTheme() is required for hacky // QPA platform themes such as qt5ct, which randomly mess with the configured icon theme. // If we do not reset the theme name here, it will become empty at some point, causing @@ -161,71 +234,31 @@ QIcon Resources::icon(const QString& name, bool recolor, const QColor& overrideC // See issue #4963: https://github.com/keepassxreboot/keepassxc/issues/4963 // and qt5ct issue #80: https://sourceforge.net/p/qt5ct/tickets/80/ QIcon::setThemeName("application"); +#endif - icon = QIcon::fromTheme(name); - if (getMainWindow() && recolor) { - const QRect rect(0, 0, 48, 48); - QImage img = icon.pixmap(rect.width(), rect.height()).toImage(); - img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); - icon = {}; - - QPainter painter(&img); - painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); - - if (!overrideColor.isValid()) { - QPalette palette = getMainWindow()->palette(); - painter.fillRect(rect, palette.color(QPalette::Normal, QPalette::WindowText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); - - painter.fillRect(rect, palette.color(QPalette::Active, QPalette::ButtonText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Active); - - painter.fillRect(rect, palette.color(QPalette::Active, QPalette::HighlightedText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected); + QString cacheName = + QString("%1:%2:%3").arg(recolor ? "1" : "0", overrideColor.isValid() ? overrideColor.name() : "#", name); + QIcon icon = m_iconCache.value(cacheName); - painter.fillRect(rect, palette.color(QPalette::Disabled, QPalette::WindowText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Disabled); - } else { - painter.fillRect(rect, overrideColor); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); - } + if (!icon.isNull() && !overrideColor.isValid()) { + return icon; + } + icon = QIcon::fromTheme(name); + if (recolor) { + icon = QIcon(new AdaptiveIconEngine(icon)); #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) icon.setIsMask(true); #endif } - if (!overrideColor.isValid()) { - m_iconCache.insert(name, icon); - } - + m_iconCache.insert(cacheName, icon); return icon; } -QIcon Resources::onOffIcon(const QString& name, bool recolor) +QIcon Resources::onOffIcon(const QString& name, bool on, bool recolor) { - QString cacheName = "onoff/" + name; - - QIcon icon = m_iconCache.value(cacheName); - - if (!icon.isNull()) { - return icon; - } - - const QSize size(48, 48); - QIcon on = Resources::icon(name + "-on", recolor); - icon.addPixmap(on.pixmap(size, QIcon::Mode::Normal), QIcon::Mode::Normal, QIcon::On); - icon.addPixmap(on.pixmap(size, QIcon::Mode::Selected), QIcon::Mode::Selected, QIcon::On); - icon.addPixmap(on.pixmap(size, QIcon::Mode::Disabled), QIcon::Mode::Disabled, QIcon::On); - - QIcon off = Resources::icon(name + "-off", recolor); - icon.addPixmap(off.pixmap(size, QIcon::Mode::Normal), QIcon::Mode::Normal, QIcon::Off); - icon.addPixmap(off.pixmap(size, QIcon::Mode::Selected), QIcon::Mode::Selected, QIcon::Off); - icon.addPixmap(off.pixmap(size, QIcon::Mode::Disabled), QIcon::Mode::Disabled, QIcon::Off); - - m_iconCache.insert(cacheName, icon); - - return icon; + return icon(name + (on ? "-on" : "-off"), recolor); } Resources::Resources() diff --git a/src/core/Resources.h b/src/core/Resources.h index b76818150..1b9c9b91c 100644 --- a/src/core/Resources.h +++ b/src/core/Resources.h @@ -31,12 +31,12 @@ public: QString pluginPath(const QString& name) const; QString wordlistPath(const QString& name) const; QIcon applicationIcon(); - QIcon trayIcon(); + QIcon trayIcon(QString style = "unlocked"); QIcon trayIconLocked(); QIcon trayIconUnlocked(); QString trayIconAppearance() const; QIcon icon(const QString& name, bool recolor = true, const QColor& overrideColor = QColor::Invalid); - QIcon onOffIcon(const QString& name, bool recolor = true); + QIcon onOffIcon(const QString& name, bool on, bool recolor = true); static Resources* instance(); diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 0c389706f..784528294 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -22,6 +22,7 @@ #include "autotype/AutoType.h" #include "core/Config.h" #include "core/Global.h" +#include "core/Resources.h" #include "gui/MainWindow.h" #include "gui/osutils/OSUtils.h" #include "gui/styles/dark/DarkStyle.h" @@ -131,6 +132,12 @@ Application::Application(int& argc, char** argv) qWarning() << QObject::tr("The lock file could not be created. Single-instance mode disabled.").toUtf8().constData(); } + + connect(osUtils, &OSUtilsBase::interfaceThemeChanged, this, [this]() { + if (config()->get(Config::GUI_ApplicationTheme).toString() != "classic") { + applyTheme(); + } + }); } Application::~Application() @@ -153,15 +160,15 @@ void Application::applyTheme() } #endif } - + QPixmapCache::clear(); if (appTheme == "light") { - setStyle(new LightStyle); - // Workaround Qt 5.15+ bug - setPalette(style()->standardPalette()); + auto* s = new LightStyle; + setPalette(s->standardPalette()); + setStyle(s); } else if (appTheme == "dark") { - setStyle(new DarkStyle); - // Workaround Qt 5.15+ bug - setPalette(style()->standardPalette()); + auto* s = new DarkStyle; + setPalette(s->standardPalette()); + setStyle(s); m_darkTheme = true; } else { // Classic mode, don't check for dark theme on Windows diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index cf09df39b..8ec2d4730 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -250,8 +250,12 @@ void ApplicationSettingsWidget::loadSettings() } m_generalUi->trayIconAppearance->clear(); +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) + m_generalUi->trayIconAppearance->addItem(tr("Monochrome"), "monochrome"); +#else m_generalUi->trayIconAppearance->addItem(tr("Monochrome (light)"), "monochrome-light"); m_generalUi->trayIconAppearance->addItem(tr("Monochrome (dark)"), "monochrome-dark"); +#endif m_generalUi->trayIconAppearance->addItem(tr("Colorful"), "colorful"); int trayIconIndex = m_generalUi->trayIconAppearance->findData(resources()->trayIconAppearance()); if (trayIconIndex > 0) { diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index 45a71a348..e240f7ae4 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -50,9 +50,9 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent) // Entry m_ui->entryTotpButton->setIcon(resources()->icon("chronometer")); m_ui->entryCloseButton->setIcon(resources()->icon("dialog-close")); - m_ui->togglePasswordButton->setIcon(resources()->onOffIcon("password-show")); - m_ui->toggleEntryNotesButton->setIcon(resources()->onOffIcon("password-show")); - m_ui->toggleGroupNotesButton->setIcon(resources()->onOffIcon("password-show")); + m_ui->togglePasswordButton->setIcon(resources()->onOffIcon("password-show", true)); + m_ui->toggleEntryNotesButton->setIcon(resources()->onOffIcon("password-show", true)); + m_ui->toggleGroupNotesButton->setIcon(resources()->onOffIcon("password-show", true)); m_ui->entryAttachmentsWidget->setReadOnly(true); m_ui->entryAttachmentsWidget->setButtonsVisible(false); @@ -199,16 +199,19 @@ void EntryPreviewWidget::setPasswordVisible(bool state) } else { m_ui->entryPasswordLabel->setText(QString("\u25cf").repeated(6)); } + m_ui->togglePasswordButton->setIcon(resources()->onOffIcon("password-show", state)); } void EntryPreviewWidget::setEntryNotesVisible(bool state) { setNotesVisible(m_ui->entryNotesTextEdit, m_currentEntry->notes(), state); + m_ui->toggleEntryNotesButton->setIcon(resources()->onOffIcon("password-show", state)); } void EntryPreviewWidget::setGroupNotesVisible(bool state) { setNotesVisible(m_ui->groupNotesTextEdit, m_currentGroup->notes(), state); + m_ui->toggleGroupNotesButton->setIcon(resources()->onOffIcon("password-show", state)); } void EntryPreviewWidget::setNotesVisible(QTextEdit* notesWidget, const QString& notes, bool state) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 7f0f3bf6f..c08f6e677 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -39,6 +39,7 @@ #include "gui/DatabaseWidget.h" #include "gui/MessageBox.h" #include "gui/SearchWidget.h" +#include "gui/osutils/OSUtils.h" #include "keys/CompositeKey.h" #include "keys/FileKey.h" #include "keys/PasswordKey.h" @@ -496,6 +497,8 @@ MainWindow::MainWindow() connect(m_ui->actionOnlineHelp, SIGNAL(triggered()), SLOT(openOnlineHelp())); connect(m_ui->actionKeyboardShortcuts, SIGNAL(triggered()), SLOT(openKeyboardShortcuts())); + connect(osUtils, &OSUtilsBase::statusbarThemeChanged, this, &MainWindow::updateTrayIcon); + #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // Install event filter for empty-area drag auto* eventFilter = new MainWindowEventFilter(this); @@ -599,6 +602,11 @@ MainWindow::MainWindow() config()->set(Config::Messages_Qt55CompatibilityWarning, true); } #endif + + QObject::connect(qApp, SIGNAL(anotherInstanceStarted()), this, SLOT(bringToFront())); + QObject::connect(qApp, SIGNAL(applicationActivated()), this, SLOT(bringToFront())); + QObject::connect(qApp, SIGNAL(openFile(QString)), this, SLOT(openDatabase(QString))); + QObject::connect(qApp, SIGNAL(quitSignalReceived()), this, SLOT(appExit()), Qt::DirectConnection); } MainWindow::~MainWindow() @@ -1737,8 +1745,10 @@ void MainWindow::initViewMenu() connect(themeActions, &QActionGroup::triggered, this, [this, theme](QAction* action) { config()->set(Config::GUI_ApplicationTheme, action->data()); - if (action->data() != theme) { + if ((action->data() == "classic" || theme == "classic") && action->data() != theme) { restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?")); + } else { + kpxcApp->applyTheme(); } }); diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index b43f5c623..73f8dd0f9 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -58,7 +58,7 @@ PasswordEdit::PasswordEdit(QWidget* parent) #endif m_toggleVisibleAction = new QAction( - resources()->icon("password-show-off"), + resources()->onOffIcon("password-show", false), tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)), nullptr); m_toggleVisibleAction->setCheckable(true); @@ -113,7 +113,7 @@ void PasswordEdit::enablePasswordGenerator() void PasswordEdit::setShowPassword(bool show) { setEchoMode(show ? QLineEdit::Normal : QLineEdit::Password); - m_toggleVisibleAction->setIcon(resources()->icon(show ? "password-show-on" : "password-show-off")); + m_toggleVisibleAction->setIcon(resources()->onOffIcon("password-show", show)); m_toggleVisibleAction->setChecked(show); if (m_repeatPasswordEdit) { diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 340e9bf7e..0389b667c 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -36,6 +36,11 @@ public: virtual bool isDarkMode() const = 0; /** + * @return OS task / menu bar is dark. + */ + virtual bool isStatusBarDark() const = 0; + + /** * @return KeePassXC set to launch at system startup (autostart). */ virtual bool isLaunchAtStartupEnabled() const = 0; @@ -50,6 +55,17 @@ public: */ virtual bool isCapslockEnabled() = 0; +signals: + /** + * Indicates platform UI theme change (light mode to dark mode). + */ + void interfaceThemeChanged(); + + /* + * Indicates a change in the tray / statusbar theme. + */ + void statusbarThemeChanged(); + protected: explicit OSUtilsBase(QObject* parent = nullptr); virtual ~OSUtilsBase(); diff --git a/src/gui/osutils/macutils/AppKit.h b/src/gui/osutils/macutils/AppKit.h index 02121683d..9ce4c01db 100644 --- a/src/gui/osutils/macutils/AppKit.h +++ b/src/gui/osutils/macutils/AppKit.h @@ -20,6 +20,7 @@ #define KEEPASSX_APPKIT_H #include <QObject> +#include <QColor> #include <unistd.h> class AppKit : public QObject @@ -37,13 +38,14 @@ public: bool hideProcess(pid_t pid); bool isHidden(pid_t pid); bool isDarkMode(); - bool hasDarkMode(); + bool isStatusBarDark(); bool enableAccessibility(); bool enableScreenRecording(); void toggleForegroundApp(bool foreground); signals: void lockDatabases(); + void interfaceThemeChanged(); private: void* self; diff --git a/src/gui/osutils/macutils/AppKitImpl.h b/src/gui/osutils/macutils/AppKitImpl.h index 5dadc31dd..5e7a2fbae 100644 --- a/src/gui/osutils/macutils/AppKitImpl.h +++ b/src/gui/osutils/macutils/AppKitImpl.h @@ -35,6 +35,7 @@ - (bool) hideProcess:(pid_t) pid; - (bool) isHidden:(pid_t) pid; - (bool) isDarkMode; +- (bool) isStatusBarDark; - (void) userSwitchHandler:(NSNotification*) notification; - (bool) enableAccessibility; - (bool) enableScreenRecording; diff --git a/src/gui/osutils/macutils/AppKitImpl.mm b/src/gui/osutils/macutils/AppKitImpl.mm index 077dd71a6..faf061106 100644 --- a/src/gui/osutils/macutils/AppKitImpl.mm +++ b/src/gui/osutils/macutils/AppKitImpl.mm @@ -17,7 +17,11 @@ */ #import "AppKitImpl.h" +#include "AppKit.h" +#import <AppKit/NSStatusBar.h> +#import <AppKit/NSStatusItem.h> +#import <AppKit/NSStatusBarButton.h> #import <AppKit/NSWorkspace.h> #import <CoreVideo/CVPixelBuffer.h> @@ -26,17 +30,31 @@ - (id) initWithObject:(AppKit*)appkit { self = [super init]; + if (self) { m_appkit = appkit; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self) + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didDeactivateApplicationObserver:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil]; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self) + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(userSwitchHandler:) name:NSWorkspaceSessionDidResignActiveNotification object:nil]; + + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(interfaceThemeChanged:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; + + // Unfortunately, there is no notification for a wallpaper change, which affects + // the status bar colour on macOS Big Sur, but we can at least subscribe to this. + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(interfaceThemeChanged:) + name:@"AppleColorPreferencesChangedNotification" + object:nil]; + } return self; } @@ -55,6 +73,18 @@ } // +// Light / dark theme toggled +// +- (void) interfaceThemeChanged:(NSNotification*) notification +{ + Q_UNUSED(notification); + if (m_appkit) { + emit m_appkit->interfaceThemeChanged(); + } +} + + +// // Get process id of frontmost application (-> keyboard input) // - (pid_t) activeProcessId @@ -108,6 +138,23 @@ && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] ); } + +// +// Get global menu bar theme state +// +- (bool) isStatusBarDark +{ + if (@available(macOS 10.17, *)) { + // This is an ugly hack, but I couldn't find a way to access QTrayIcon's NSStatusItem. + NSStatusItem* dummy = [[NSStatusBar systemStatusBar] statusItemWithLength:0]; + NSString* appearance = [dummy.button.effectiveAppearance.name lowercaseString]; + [[NSStatusBar systemStatusBar] removeStatusItem:dummy]; + return [appearance containsString:@"dark"]; + } + + return [self isDarkMode]; +} + // // Notification for user switch // @@ -170,7 +217,8 @@ // ------------------------- C++ Trampolines ------------------------- // -AppKit::AppKit(QObject* parent) : QObject(parent) +AppKit::AppKit(QObject* parent) + : QObject(parent) { self = [[AppKitImpl alloc] initWithObject:this]; } @@ -178,6 +226,7 @@ AppKit::AppKit(QObject* parent) : QObject(parent) AppKit::~AppKit() { [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:static_cast<id>(self)]; [static_cast<id>(self) dealloc]; } @@ -216,6 +265,12 @@ bool AppKit::isDarkMode() return [static_cast<id>(self) isDarkMode]; } +bool AppKit::isStatusBarDark() +{ + return [static_cast<id>(self) isStatusBarDark]; +} + + bool AppKit::enableAccessibility() { return [static_cast<id>(self) enableAccessibility]; diff --git a/src/gui/osutils/macutils/MacUtils.cpp b/src/gui/osutils/macutils/MacUtils.cpp index d32a15612..ebcc627e0 100644 --- a/src/gui/osutils/macutils/MacUtils.cpp +++ b/src/gui/osutils/macutils/MacUtils.cpp @@ -22,6 +22,7 @@ #include <QFile> #include <QSettings> #include <QStandardPaths> +#include <QTimer> #include <CoreGraphics/CGEventSource.h> @@ -33,6 +34,14 @@ MacUtils::MacUtils(QObject* parent) , m_appkit(new AppKit()) { connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases())); + connect(m_appkit.data(), SIGNAL(interfaceThemeChanged()), SIGNAL(interfaceThemeChanged())); + connect(m_appkit.data(), &AppKit::interfaceThemeChanged, this, [this]() { + // Emit with delay, since isStatusBarDark() still returns the old value + // if we call it too fast after a theme change. + QTimer::singleShot(100, [this]() { + emit statusbarThemeChanged(); + }); + }); } MacUtils::~MacUtils() @@ -93,6 +102,11 @@ bool MacUtils::isDarkMode() const return m_appkit->isDarkMode(); } +bool MacUtils::isStatusBarDark() const +{ + return m_appkit->isStatusBarDark(); +} + QString MacUtils::getLaunchAgentFilename() const { auto launchAgentDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents")); diff --git a/src/gui/osutils/macutils/MacUtils.h b/src/gui/osutils/macutils/MacUtils.h index 281c5438c..52270f6a7 100644 --- a/src/gui/osutils/macutils/MacUtils.h +++ b/src/gui/osutils/macutils/MacUtils.h @@ -22,6 +22,7 @@ #include "gui/osutils/OSUtilsBase.h" #include "AppKit.h" +#include <QColor> #include <QPointer> #include <QScopedPointer> #include <qwindowdefs.h> @@ -34,6 +35,7 @@ public: static MacUtils* instance(); bool isDarkMode() const override; + bool isStatusBarDark() const override; bool isLaunchAtStartupEnabled() const override; void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp index d412aa813..eacb20060 100644 --- a/src/gui/osutils/nixutils/NixUtils.cpp +++ b/src/gui/osutils/nixutils/NixUtils.cpp @@ -62,6 +62,12 @@ bool NixUtils::isDarkMode() const return qApp->style()->standardPalette().color(QPalette::Window).toHsl().lightness() < 110; } +bool NixUtils::isStatusBarDark() const +{ + // TODO: implement + return isDarkMode(); +} + QString NixUtils::getAutostartDesktopFilename(bool createDirs) const { QDir autostartDir; diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h index c91580796..bc1be9975 100644 --- a/src/gui/osutils/nixutils/NixUtils.h +++ b/src/gui/osutils/nixutils/NixUtils.h @@ -29,6 +29,7 @@ public: static NixUtils* instance(); bool isDarkMode() const override; + bool isStatusBarDark() const override; bool isLaunchAtStartupEnabled() const override; void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp index 61e913c93..04216c54f 100644 --- a/src/gui/osutils/winutils/WinUtils.cpp +++ b/src/gui/osutils/winutils/WinUtils.cpp @@ -30,6 +30,8 @@ WinUtils* WinUtils::instance() { if (!m_instance) { m_instance = new WinUtils(qApp); + m_instance->m_darkAppThemeActive = m_instance->isDarkMode(); + m_instance->m_darkSystemThemeActive = m_instance->isStatusBarDark(); } return m_instance; @@ -66,14 +68,18 @@ bool WinUtils::DWMEventFilter::nativeEventFilter(const QByteArray& eventType, vo return false; } switch (msg->message) { - case WM_CREATE: - case WM_INITDIALOG: { - if (winUtils()->isDarkMode()) { - // TODO: indicate dark mode support for black title bar + case WM_SETTINGCHANGE: + if (m_instance->m_darkAppThemeActive != m_instance->isDarkMode()) { + m_instance->m_darkAppThemeActive = !m_instance->m_darkAppThemeActive; + emit m_instance->interfaceThemeChanged(); + } + + if (m_instance->m_darkSystemThemeActive != m_instance->isStatusBarDark()) { + m_instance->m_darkSystemThemeActive = !m_instance->m_darkSystemThemeActive; + emit m_instance->statusbarThemeChanged(); } break; } - } return false; } @@ -85,6 +91,13 @@ bool WinUtils::isDarkMode() const return settings.value("AppsUseLightTheme", 1).toInt() == 0; } +bool WinUtils::isStatusBarDark() const +{ + QSettings settings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)", + QSettings::NativeFormat); + return settings.value("SystemUsesLightTheme", 0).toInt() == 0; +} + bool WinUtils::isLaunchAtStartupEnabled() const { return QSettings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)", QSettings::NativeFormat) diff --git a/src/gui/osutils/winutils/WinUtils.h b/src/gui/osutils/winutils/WinUtils.h index c19040274..19f43fd16 100644 --- a/src/gui/osutils/winutils/WinUtils.h +++ b/src/gui/osutils/winutils/WinUtils.h @@ -33,6 +33,7 @@ public: static void registerEventFilters(); bool isDarkMode() const override; + bool isStatusBarDark() const override; bool isLaunchAtStartupEnabled() const override; void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; @@ -52,6 +53,9 @@ private: static QPointer<WinUtils> m_instance; static QScopedPointer<DWMEventFilter> m_eventFilter; + bool m_darkAppThemeActive; + bool m_darkSystemThemeActive; + Q_DISABLE_COPY(WinUtils) }; diff --git a/src/gui/styles/base/BaseStyle.cpp b/src/gui/styles/base/BaseStyle.cpp index 90c515871..26a251dc2 100644 --- a/src/gui/styles/base/BaseStyle.cpp +++ b/src/gui/styles/base/BaseStyle.cpp @@ -52,6 +52,10 @@ #include <QtMath> #include <qdrawutil.h> +#ifdef Q_OS_MACOS +#include <QOperatingSystemVersion> +#endif + #include <cmath> #include "core/Resources.h" @@ -288,10 +292,16 @@ namespace Phantom #ifdef Q_OS_MACOS QColor tabBarBase(const QPalette& pal) { - return hack_isLightPalette(pal) ? QRgb(0xD1D1D1) : QRgb(0x252525); + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur) { + return hack_isLightPalette(pal) ? QRgb(0xD4D4D4) : QRgb(0x2A2A2A); + } + return hack_isLightPalette(pal) ? QRgb(0xDD1D1D1) : QRgb(0x252525); } QColor tabBarBaseInactive(const QPalette& pal) { + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur) { + return hack_isLightPalette(pal) ? QRgb(0xF5F5F5) : QRgb(0x2D2D2D); + } return hack_isLightPalette(pal) ? QRgb(0xF4F4F4) : QRgb(0x282828); } #endif @@ -4571,27 +4581,6 @@ QStyle::SubControl BaseStyle::hitTestComplexControl(ComplexControl cc, return QCommonStyle::hitTestComplexControl(cc, opt, pt, w); } -QPixmap BaseStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const -{ - // Default icon highlight is way too subtle - if (iconMode == QIcon::Selected) { - QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); - QPainter painter(&img); - - painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); - - QColor color = - Phantom::DeriveColors::adjustLightness(opt->palette.color(QPalette::Normal, QPalette::Highlight), .25); - color.setAlphaF(0.25); - painter.fillRect(0, 0, img.width(), img.height(), color); - - painter.end(); - - return QPixmap::fromImage(img); - } - return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt); -} - int BaseStyle::styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, diff --git a/src/gui/styles/base/BaseStyle.h b/src/gui/styles/base/BaseStyle.h index d3c20915c..e9bdcbdc8 100644 --- a/src/gui/styles/base/BaseStyle.h +++ b/src/gui/styles/base/BaseStyle.h @@ -70,7 +70,6 @@ public: const QStyleOptionComplex* opt, SubControl sc, const QWidget* widget) const override; - QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const override; int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, diff --git a/src/gui/styles/dark/DarkStyle.cpp b/src/gui/styles/dark/DarkStyle.cpp index 25f75e5ab..491f18d1f 100644 --- a/src/gui/styles/dark/DarkStyle.cpp +++ b/src/gui/styles/dark/DarkStyle.cpp @@ -114,9 +114,9 @@ void DarkStyle::polish(QWidget* widget) auto palette = widget->palette(); #if defined(Q_OS_MACOS) if (!osUtils->isDarkMode()) { - palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x252525)); - palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0x282828)); - palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x252525)); + palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x2A2A2A)); + palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0x2D2D2D)); + palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x2D2D2D)); } #elif defined(Q_OS_WIN) // Register event filter for better dark mode support diff --git a/src/gui/styles/light/LightStyle.cpp b/src/gui/styles/light/LightStyle.cpp index f1f0cb997..483e1323b 100644 --- a/src/gui/styles/light/LightStyle.cpp +++ b/src/gui/styles/light/LightStyle.cpp @@ -115,9 +115,9 @@ void LightStyle::polish(QWidget* widget) auto palette = widget->palette(); #if defined(Q_OS_MACOS) if (osUtils->isDarkMode()) { - palette.setColor(QPalette::Active, QPalette::Window, QRgb(0xD1D1D1)); - palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0xF4F4F4)); - palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0xD1D1D1)); + palette.setColor(QPalette::Active, QPalette::Window, QRgb(0xD4D4D4)); + palette.setColor(QPalette::Inactive, QPalette::Window, QRgb(0xF5F5F5)); + palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0xF5F5F5)); } #elif defined(Q_OS_WIN) palette.setColor(QPalette::All, QPalette::Window, QRgb(0xFFFFFF)); |