Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan White <support@dmapps.us>2021-01-31 23:43:37 +0300
committerGitHub <noreply@github.com>2021-01-31 23:43:37 +0300
commit109671900bfdc21b9128020af46b5fb4c5d135a6 (patch)
treeeda1c65245bdc526c4df4f96c87ce4db3cbaca46
parent63df00a723c9ebd55f9138c319d8f567f48cd9a5 (diff)
parent2889341acdafbe919d56bca76e8b256690feed94 (diff)
Merge pull request #6034 from keepassxreboot/hotfix/theme-switching-backport
-rw-r--r--.gitattributes3
-rw-r--r--src/core/Resources.cpp185
-rw-r--r--src/core/Resources.h4
-rw-r--r--src/gui/Application.cpp21
-rw-r--r--src/gui/ApplicationSettingsWidget.cpp4
-rw-r--r--src/gui/EntryPreviewWidget.cpp9
-rw-r--r--src/gui/MainWindow.cpp12
-rw-r--r--src/gui/PasswordEdit.cpp4
-rw-r--r--src/gui/osutils/OSUtilsBase.h16
-rw-r--r--src/gui/osutils/macutils/AppKit.h4
-rw-r--r--src/gui/osutils/macutils/AppKitImpl.h1
-rw-r--r--src/gui/osutils/macutils/AppKitImpl.mm61
-rw-r--r--src/gui/osutils/macutils/MacUtils.cpp14
-rw-r--r--src/gui/osutils/macutils/MacUtils.h2
-rw-r--r--src/gui/osutils/nixutils/NixUtils.cpp6
-rw-r--r--src/gui/osutils/nixutils/NixUtils.h1
-rw-r--r--src/gui/osutils/winutils/WinUtils.cpp23
-rw-r--r--src/gui/osutils/winutils/WinUtils.h4
-rw-r--r--src/gui/styles/base/BaseStyle.cpp33
-rw-r--r--src/gui/styles/base/BaseStyle.h1
-rw-r--r--src/gui/styles/dark/DarkStyle.cpp6
-rw-r--r--src/gui/styles/light/LightStyle.cpp6
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));