mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
fixed icon conn/disc macos
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
#include "embedded_agw_public_keys.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
#ifndef MACOSSTATUSICON_H
|
#ifndef MACOSSTATUSICON_H
|
||||||
#define MACOSSTATUSICON_H
|
#define MACOSSTATUSICON_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QColor>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
class MacOSStatusIcon final : public QObject {
|
class MacOSStatusIcon final : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -16,12 +20,16 @@ class MacOSStatusIcon final : public QObject {
|
|||||||
explicit MacOSStatusIcon(QObject* parent);
|
explicit MacOSStatusIcon(QObject* parent);
|
||||||
~MacOSStatusIcon();
|
~MacOSStatusIcon();
|
||||||
|
|
||||||
public:
|
|
||||||
void setIcon(const QString& iconUrl);
|
void setIcon(const QString& iconUrl);
|
||||||
|
void setIconFromData(const QByteArray& imageData);
|
||||||
void setIndicatorColor(const QColor& indicatorColor);
|
void setIndicatorColor(const QColor& indicatorColor);
|
||||||
void setMenu(NSMenu* statusBarMenu);
|
void setMenu(QMenu* menu);
|
||||||
|
void rebuildNativeMenu();
|
||||||
void setToolTip(const QString& tooltip);
|
void setToolTip(const QString& tooltip);
|
||||||
void showMessage(const QString& title, const QString& message);
|
void showMessage(const QString& title, const QString& message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<QMenu> m_qtMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MACOSSTATUSICON_H
|
#endif // MACOSSTATUSICON_H
|
||||||
|
|||||||
@@ -10,6 +10,24 @@
|
|||||||
#import <UserNotifications/UserNotifications.h>
|
#import <UserNotifications/UserNotifications.h>
|
||||||
#import <QResource>
|
#import <QResource>
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
|
||||||
|
@interface MacOSStatusIconMenuTarget : NSObject {
|
||||||
|
@public
|
||||||
|
QAction* action;
|
||||||
|
}
|
||||||
|
- (void)triggerAction:(id)sender;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MacOSStatusIconMenuTarget
|
||||||
|
- (void)triggerAction:(id)sender {
|
||||||
|
Q_UNUSED(sender);
|
||||||
|
if (action) {
|
||||||
|
action->trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
|
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
|
||||||
* set as a subview to the button item of the status item. The view serves as
|
* set as a subview to the button item of the status item. The view serves as
|
||||||
@@ -20,38 +38,35 @@
|
|||||||
@interface MacOSStatusIconDelegate : NSObject
|
@interface MacOSStatusIconDelegate : NSObject
|
||||||
@property(assign) NSStatusItem* statusItem;
|
@property(assign) NSStatusItem* statusItem;
|
||||||
@property(assign) NSView* statusIndicator;
|
@property(assign) NSView* statusIndicator;
|
||||||
|
@property(retain) NSMenu* nativeMenu;
|
||||||
|
@property(retain) NSMutableArray* menuActionTargets;
|
||||||
|
|
||||||
- (void)setIcon:(NSData*)imageData;
|
- (void)setIcon:(NSData*)imageData;
|
||||||
- (void)setIndicator;
|
- (void)setIndicator;
|
||||||
- (void)setIndicatorColor:(NSColor*)color;
|
- (void)setIndicatorColor:(NSColor*)color;
|
||||||
- (void)setMenu:(NSMenu*)statusBarMenu;
|
|
||||||
- (void)setToolTip:(NSString*)tooltip;
|
- (void)setToolTip:(NSString*)tooltip;
|
||||||
|
- (void)rebuildMenuFromQMenu:(QMenu*)menu;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MacOSStatusIconDelegate
|
@implementation MacOSStatusIconDelegate
|
||||||
/**
|
|
||||||
* Initializes and sets the status item and indicator objects.
|
|
||||||
*
|
|
||||||
* @return An instance of MacOSStatusIconDelegate.
|
|
||||||
*/
|
|
||||||
- (id)init {
|
- (id)init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
self.menuActionTargets = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
// Create status item
|
|
||||||
self.statusItem =
|
self.statusItem =
|
||||||
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
||||||
self.statusItem.visible = true;
|
self.statusItem.visible = true;
|
||||||
// Add the indicator as a subview
|
|
||||||
[self setIndicator];
|
[self setIndicator];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
- (void)dealloc {
|
||||||
* Sets the image for the status icon.
|
self.nativeMenu = nil;
|
||||||
*
|
self.menuActionTargets = nil;
|
||||||
* @param iconPath The data for the icon image.
|
[super dealloc];
|
||||||
*/
|
}
|
||||||
|
|
||||||
- (void)setIcon:(NSData*)imageData {
|
- (void)setIcon:(NSData*)imageData {
|
||||||
NSImage* image = [[NSImage alloc] initWithData:imageData];
|
NSImage* image = [[NSImage alloc] initWithData:imageData];
|
||||||
[image setTemplate:true];
|
[image setTemplate:true];
|
||||||
@@ -60,9 +75,6 @@
|
|||||||
[image release];
|
[image release];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds status indicator as a subview to the status item button.
|
|
||||||
*/
|
|
||||||
- (void)setIndicator {
|
- (void)setIndicator {
|
||||||
float viewHeight = NSHeight([self.statusItem.button bounds]);
|
float viewHeight = NSHeight([self.statusItem.button bounds]);
|
||||||
float dotSize = viewHeight * 0.35;
|
float dotSize = viewHeight * 0.35;
|
||||||
@@ -77,41 +89,60 @@
|
|||||||
[dot release];
|
[dot release];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the color if the indicator.
|
|
||||||
*
|
|
||||||
* @param color The indicator background color.
|
|
||||||
*/
|
|
||||||
- (void)setIndicatorColor:(NSColor*)color {
|
- (void)setIndicatorColor:(NSColor*)color {
|
||||||
if (self.statusIndicator) {
|
if (self.statusIndicator) {
|
||||||
self.statusIndicator.layer.backgroundColor = color.CGColor;
|
self.statusIndicator.layer.backgroundColor = color.CGColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the status bar menu to the status item.
|
|
||||||
*
|
|
||||||
* @param statusBarMenu The menu object that is passed from QT.
|
|
||||||
*/
|
|
||||||
- (void)setMenu:(NSMenu*)statusBarMenu {
|
|
||||||
[self.statusItem setMenu:statusBarMenu];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the tooltip string for the status item.
|
|
||||||
*
|
|
||||||
* @param tooltip The tooltip string.
|
|
||||||
*/
|
|
||||||
- (void)setToolTip:(NSString*)tooltip {
|
- (void)setToolTip:(NSString*)tooltip {
|
||||||
[self.statusItem.button setToolTip:tooltip];
|
[self.statusItem.button setToolTip:tooltip];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)rebuildMenuFromQMenu:(QMenu*)menu {
|
||||||
|
[self.menuActionTargets removeAllObjects];
|
||||||
|
|
||||||
|
if (self.nativeMenu) {
|
||||||
|
[self.statusItem setMenu:nil];
|
||||||
|
self.nativeMenu = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMenu* nsMenu = [[NSMenu alloc] initWithTitle:@""];
|
||||||
|
for (QAction* action : menu->actions()) {
|
||||||
|
if (action->isSeparator()) {
|
||||||
|
[nsMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MacOSStatusIconMenuTarget* target = [[MacOSStatusIconMenuTarget alloc] init];
|
||||||
|
target->action = action;
|
||||||
|
[self.menuActionTargets addObject:target];
|
||||||
|
[target release];
|
||||||
|
|
||||||
|
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:action->text().toNSString()
|
||||||
|
action:@selector(triggerAction:)
|
||||||
|
keyEquivalent:@""];
|
||||||
|
[item setTarget:target];
|
||||||
|
[item setEnabled:action->isEnabled()];
|
||||||
|
[item setHidden:!action->isVisible()];
|
||||||
|
[nsMenu addItem:item];
|
||||||
|
[item release];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nativeMenu = nsMenu;
|
||||||
|
[self.statusItem setMenu:nsMenu];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("MacOSStatusIcon");
|
Logger logger("MacOSStatusIcon");
|
||||||
|
|
||||||
MacOSStatusIconDelegate* m_statusBarIcon = nullptr;
|
MacOSStatusIconDelegate* m_statusBarIcon = nullptr;
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) {
|
MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) {
|
||||||
MZ_COUNT_CTOR(MacOSStatusIcon);
|
MZ_COUNT_CTOR(MacOSStatusIcon);
|
||||||
@@ -141,6 +172,17 @@ void MacOSStatusIcon::setIcon(const QString& iconPath) {
|
|||||||
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
|
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacOSStatusIcon::setIconFromData(const QByteArray& imageData) {
|
||||||
|
logger.debug() << "Set icon from rendered data";
|
||||||
|
|
||||||
|
if (imageData.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData* data = [NSData dataWithBytes:imageData.constData() length:imageData.size()];
|
||||||
|
[m_statusBarIcon setIcon:data];
|
||||||
|
}
|
||||||
|
|
||||||
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
|
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
|
||||||
logger.debug() << "Set indicator color";
|
logger.debug() << "Set indicator color";
|
||||||
|
|
||||||
@@ -156,9 +198,17 @@ void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
|
|||||||
[m_statusBarIcon setIndicatorColor:color];
|
[m_statusBarIcon setIndicatorColor:color];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
|
void MacOSStatusIcon::setMenu(QMenu* menu) {
|
||||||
logger.debug() << "Set menu";
|
m_qtMenu = menu;
|
||||||
[m_statusBarIcon setMenu:statusBarMenu];
|
rebuildNativeMenu();
|
||||||
|
|
||||||
|
if (menu) {
|
||||||
|
connect(menu, &QMenu::aboutToShow, this, [this]() { rebuildNativeMenu(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacOSStatusIcon::rebuildNativeMenu() {
|
||||||
|
[m_statusBarIcon rebuildMenuFromQMenu:m_qtMenu.data()];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
|
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
|
||||||
@@ -171,14 +221,11 @@ void MacOSStatusIcon::showMessage(const QString& title, const QString& message)
|
|||||||
|
|
||||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||||
|
|
||||||
// This is a no-op is authorization has been granted.
|
|
||||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||||
UNAuthorizationOptionBadge)
|
UNAuthorizationOptionBadge)
|
||||||
completionHandler:^(BOOL granted, NSError* _Nullable error) {
|
completionHandler:^(__unused BOOL granted, NSError* _Nullable error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
// Note: This error may happen if the application is not signed.
|
|
||||||
NSLog(@"Error asking for permission to send notifications %@", error);
|
NSLog(@"Error asking for permission to send notifications %@", error);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class MacOSUtils final {
|
class MacOSUtils final {
|
||||||
public:
|
public:
|
||||||
static NSString* appId();
|
static NSString* appId();
|
||||||
@@ -23,6 +25,9 @@ class MacOSUtils final {
|
|||||||
static void showDockIcon();
|
static void showDockIcon();
|
||||||
|
|
||||||
static void patchNSStatusBarSetImageForBigSur();
|
static void patchNSStatusBarSetImageForBigSur();
|
||||||
|
|
||||||
|
static bool isDarkTheme();
|
||||||
|
static void installInterfaceThemeObserver(std::function<void()> callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MACOSUTILS_H
|
#endif // MACOSUTILS_H
|
||||||
|
|||||||
@@ -137,6 +137,32 @@ void MacOSUtils::showDockIcon() {
|
|||||||
* Original bug (and sample implementation):
|
* Original bug (and sample implementation):
|
||||||
* https://bugreports.qt.io/browse/QTBUG-88600
|
* https://bugreports.qt.io/browse/QTBUG-88600
|
||||||
*/
|
*/
|
||||||
|
bool MacOSUtils::isDarkTheme() {
|
||||||
|
if (@available(macOS 10.14, *)) {
|
||||||
|
NSAppearanceName appearanceName = [[NSApp effectiveAppearance] name];
|
||||||
|
return appearanceName && [appearanceName isEqualToString:NSAppearanceNameDarkAqua];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacOSUtils::installInterfaceThemeObserver(std::function<void()> callback) {
|
||||||
|
if (!callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy into block storage: capturing the parameter by reference would dangle
|
||||||
|
// once this function returns.
|
||||||
|
const std::function<void()> themeCallback = std::move(callback);
|
||||||
|
|
||||||
|
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
|
||||||
|
[center addObserverForName:@"AppleInterfaceThemeChangedNotification"
|
||||||
|
object:nil
|
||||||
|
queue:[NSOperationQueue mainQueue]
|
||||||
|
usingBlock:^(__unused NSNotification *notification) {
|
||||||
|
themeCallback();
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
|
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
|
||||||
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
|
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
|
||||||
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
|
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
|
||||||
|
|||||||
@@ -60,3 +60,17 @@ QString WindowsUtils::windowsVersion() {
|
|||||||
void WindowsUtils::forceCrash() {
|
void WindowsUtils::forceCrash() {
|
||||||
RaiseException(0x0000DEAD, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
RaiseException(0x0000DEAD, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool WindowsUtils::isDarkTheme() {
|
||||||
|
QSettings settings(
|
||||||
|
QStringLiteral("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
|
||||||
|
QSettings::NativeFormat);
|
||||||
|
|
||||||
|
if (settings.contains(QStringLiteral("AppsUseLightTheme"))) {
|
||||||
|
return settings.value(QStringLiteral("AppsUseLightTheme")).toInt() != 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warning() << "AppsUseLightTheme registry key is unavailable; assuming dark theme";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class WindowsUtils final {
|
|||||||
|
|
||||||
// Force an application crash for testing
|
// Force an application crash for testing
|
||||||
static void forceCrash();
|
static void forceCrash();
|
||||||
|
|
||||||
|
static bool isDarkTheme();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WINDOWSUTILS_H
|
#endif // WINDOWSUTILS_H
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include "platformTheme.h"
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QStyleHints>
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
# include "platforms/macos/macosutils.h"
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
# include "platforms/windows/windowsutils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool platformIsDarkTheme()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
return MacOSUtils::isDarkTheme();
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
return WindowsUtils::isDarkTheme();
|
||||||
|
#else
|
||||||
|
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
||||||
|
return styleHints->colorScheme() == Qt::ColorScheme::Dark;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef PLATFORMTHEME_H
|
||||||
|
#define PLATFORMTHEME_H
|
||||||
|
|
||||||
|
bool platformIsDarkTheme();
|
||||||
|
|
||||||
|
#endif // PLATFORMTHEME_H
|
||||||
@@ -5,14 +5,23 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "systemTrayNotificationHandler.h"
|
#include "systemTrayNotificationHandler.h"
|
||||||
|
|
||||||
|
#include "platformTheme.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QColor>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyleHints>
|
#include <QStyleHints>
|
||||||
#include <QSvgRenderer>
|
#include <QSvgRenderer>
|
||||||
#include <QWindow>
|
#include <QEvent>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
# include "platforms/macos/macosstatusicon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -21,6 +30,30 @@ namespace {
|
|||||||
constexpr int kTrayIconSize = 128;
|
constexpr int kTrayIconSize = 128;
|
||||||
constexpr char kTrayTemplateIconPath[] = ":/images/tray/icon.svg";
|
constexpr char kTrayTemplateIconPath[] = ":/images/tray/icon.svg";
|
||||||
|
|
||||||
|
class TrayThemeChangeFilter final : public QObject {
|
||||||
|
public:
|
||||||
|
explicit TrayThemeChangeFilter(std::function<void()> onThemeChanged, QObject *parent = nullptr)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_onThemeChanged(std::move(onThemeChanged))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override
|
||||||
|
{
|
||||||
|
Q_UNUSED(watched);
|
||||||
|
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange) {
|
||||||
|
if (m_onThemeChanged) {
|
||||||
|
m_onThemeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QObject::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void()> m_onThemeChanged;
|
||||||
|
};
|
||||||
|
|
||||||
QPixmap renderTrayTemplate(const QString &resourcePath, qreal opacity)
|
QPixmap renderTrayTemplate(const QString &resourcePath, qreal opacity)
|
||||||
{
|
{
|
||||||
QSvgRenderer renderer(resourcePath);
|
QSvgRenderer renderer(resourcePath);
|
||||||
@@ -38,6 +71,17 @@ QPixmap renderTrayTemplate(const QString &resourcePath, qreal opacity)
|
|||||||
return pixmap;
|
return pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray renderTrayTemplatePng(qreal opacity)
|
||||||
|
{
|
||||||
|
const QPixmap pixmap = renderTrayTemplate(QString::fromLatin1(kTrayTemplateIconPath), opacity);
|
||||||
|
|
||||||
|
QByteArray bytes;
|
||||||
|
QBuffer buffer(&bytes);
|
||||||
|
buffer.open(QIODevice::WriteOnly);
|
||||||
|
pixmap.save(&buffer, "PNG");
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
QIcon buildTrayIcon(qreal opacity)
|
QIcon buildTrayIcon(qreal opacity)
|
||||||
{
|
{
|
||||||
const QPixmap pixmap = renderTrayTemplate(QString::fromLatin1(kTrayTemplateIconPath), opacity);
|
const QPixmap pixmap = renderTrayTemplate(QString::fromLatin1(kTrayTemplateIconPath), opacity);
|
||||||
@@ -51,12 +95,21 @@ QIcon buildTrayIcon(qreal opacity)
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
|
SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
|
||||||
NotificationHandler(parent),
|
NotificationHandler(parent)
|
||||||
m_systemTrayIcon(parent)
|
#if !defined(Q_OS_MAC) || defined(MACOS_NE)
|
||||||
|
, m_systemTrayIcon(parent)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
m_macStatusIcon = new MacOSStatusIcon(this);
|
||||||
|
m_macStatusIcon->setMenu(&m_menu);
|
||||||
|
m_macStatusIcon->setToolTip(APPLICATION_NAME);
|
||||||
|
// Template NSStatusItem icons follow the menu bar appearance automatically.
|
||||||
|
#else
|
||||||
m_systemTrayIcon.show();
|
m_systemTrayIcon.show();
|
||||||
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated);
|
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated);
|
||||||
|
m_systemTrayIcon.setContextMenu(&m_menu);
|
||||||
|
#endif
|
||||||
|
|
||||||
m_trayActionShow = m_menu.addAction(tr("Show") + " " + APPLICATION_NAME, this, [this](){
|
m_trayActionShow = m_menu.addAction(tr("Show") + " " + APPLICATION_NAME, this, [this](){
|
||||||
emit raiseRequested();
|
emit raiseRequested();
|
||||||
@@ -75,14 +128,19 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
|
|||||||
this,
|
this,
|
||||||
[&](){ qApp->quit(); });
|
[&](){ qApp->quit(); });
|
||||||
|
|
||||||
m_systemTrayIcon.setContextMenu(&m_menu);
|
#if !defined(Q_OS_MAC) || defined(MACOS_NE)
|
||||||
|
|
||||||
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
||||||
connect(styleHints, &QStyleHints::colorSchemeChanged, this, [this]() {
|
connect(styleHints, &QStyleHints::colorSchemeChanged, this, [this]() {
|
||||||
updateTrayIcon();
|
refreshTheme();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qApp->installEventFilter(new TrayThemeChangeFilter([this]() {
|
||||||
|
refreshTheme();
|
||||||
|
}, this));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
refreshTheme();
|
||||||
setTrayState(Vpn::ConnectionState::Disconnected);
|
setTrayState(Vpn::ConnectionState::Disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +160,12 @@ void SystemTrayNotificationHandler::onTranslationsUpdated()
|
|||||||
m_trayActionDisconnect->setText(tr("Disconnect"));
|
m_trayActionDisconnect->setText(tr("Disconnect"));
|
||||||
m_trayActionVisitWebSite->setText(tr("Visit Website"));
|
m_trayActionVisitWebSite->setText(tr("Visit Website"));
|
||||||
m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME);
|
m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME);
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
if (m_macStatusIcon) {
|
||||||
|
m_macStatusIcon->rebuildNativeMenu();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUrl) {
|
void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUrl) {
|
||||||
@@ -109,6 +173,15 @@ void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUr
|
|||||||
websiteUrl = newWebsiteUrl;
|
websiteUrl = newWebsiteUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::refreshTheme()
|
||||||
|
{
|
||||||
|
m_isDarkTheme = platformIsDarkTheme();
|
||||||
|
|
||||||
|
#if !defined(Q_OS_MAC) || defined(MACOS_NE)
|
||||||
|
updateTrayIcon();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
qreal SystemTrayNotificationHandler::trayIconOpacityForState(Vpn::ConnectionState state) const
|
qreal SystemTrayNotificationHandler::trayIconOpacityForState(Vpn::ConnectionState state) const
|
||||||
{
|
{
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@@ -126,10 +199,29 @@ qreal SystemTrayNotificationHandler::trayIconOpacityForState(Vpn::ConnectionStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor SystemTrayNotificationHandler::trayIndicatorColorForState(Vpn::ConnectionState state) const
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case Vpn::ConnectionState::Connected:
|
||||||
|
return QColor(52, 199, 89);
|
||||||
|
case Vpn::ConnectionState::Error:
|
||||||
|
return QColor(235, 87, 87);
|
||||||
|
default:
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SystemTrayNotificationHandler::updateTrayIcon()
|
void SystemTrayNotificationHandler::updateTrayIcon()
|
||||||
{
|
{
|
||||||
const qreal opacity = trayIconOpacityForState(m_trayState);
|
const qreal opacity = trayIconOpacityForState(m_trayState);
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
Q_ASSERT(m_macStatusIcon);
|
||||||
|
m_macStatusIcon->setIconFromData(renderTrayTemplatePng(opacity));
|
||||||
|
m_macStatusIcon->setIndicatorColor(trayIndicatorColorForState(m_trayState));
|
||||||
|
#else
|
||||||
m_systemTrayIcon.setIcon(buildTrayIcon(opacity));
|
m_systemTrayIcon.setIcon(buildTrayIcon(opacity));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
||||||
@@ -182,8 +274,13 @@ void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTrayIcon();
|
updateTrayIcon();
|
||||||
}
|
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
if (m_macStatusIcon) {
|
||||||
|
m_macStatusIcon->rebuildNativeMenu();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
||||||
const QString& title,
|
const QString& title,
|
||||||
@@ -191,7 +288,12 @@ void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
|||||||
int timerMsec) {
|
int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
Q_ASSERT(m_macStatusIcon);
|
||||||
|
m_macStatusIcon->showMessage(title, message);
|
||||||
|
#else
|
||||||
m_systemTrayIcon.showMessage(title, message, buildTrayIcon(kConnectedTrayOpacity), timerMsec);
|
m_systemTrayIcon.showMessage(title, message, buildTrayIcon(kConnectedTrayOpacity), timerMsec);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemTrayNotificationHandler::showHideWindow() {
|
void SystemTrayNotificationHandler::showHideWindow() {
|
||||||
|
|||||||
@@ -7,9 +7,14 @@
|
|||||||
|
|
||||||
#include "notificationHandler.h"
|
#include "notificationHandler.h"
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
class MacOSStatusIcon;
|
||||||
|
#endif
|
||||||
|
|
||||||
class SystemTrayNotificationHandler : public NotificationHandler {
|
class SystemTrayNotificationHandler : public NotificationHandler {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -34,12 +39,19 @@ private:
|
|||||||
void setTrayState(Vpn::ConnectionState state);
|
void setTrayState(Vpn::ConnectionState state);
|
||||||
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||||
|
|
||||||
|
void refreshTheme();
|
||||||
void updateTrayIcon();
|
void updateTrayIcon();
|
||||||
qreal trayIconOpacityForState(Vpn::ConnectionState state) const;
|
qreal trayIconOpacityForState(Vpn::ConnectionState state) const;
|
||||||
|
QColor trayIndicatorColorForState(Vpn::ConnectionState state) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMenu m_menu;
|
QMenu m_menu;
|
||||||
|
|
||||||
|
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||||
|
MacOSStatusIcon *m_macStatusIcon = nullptr;
|
||||||
|
#else
|
||||||
QSystemTrayIcon m_systemTrayIcon;
|
QSystemTrayIcon m_systemTrayIcon;
|
||||||
|
#endif
|
||||||
|
|
||||||
QAction* m_trayActionShow = nullptr;
|
QAction* m_trayActionShow = nullptr;
|
||||||
QAction* m_trayActionConnect = nullptr;
|
QAction* m_trayActionConnect = nullptr;
|
||||||
@@ -50,6 +62,7 @@ private:
|
|||||||
QAction* m_separator = nullptr;
|
QAction* m_separator = nullptr;
|
||||||
|
|
||||||
Vpn::ConnectionState m_trayState = Vpn::ConnectionState::Unknown;
|
Vpn::ConnectionState m_trayState = Vpn::ConnectionState::Unknown;
|
||||||
|
bool m_isDarkTheme = false;
|
||||||
|
|
||||||
static constexpr qreal kDisconnectedTrayOpacity = 0.5;
|
static constexpr qreal kDisconnectedTrayOpacity = 0.5;
|
||||||
static constexpr qreal kConnectedTrayOpacity = 1.0;
|
static constexpr qreal kConnectedTrayOpacity = 1.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user