/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosstatusicon.h" #include "leakdetector.h" #include "logger.h" #import #import #import /** * 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 * an indicator that can be displayed in color eventhough the icon is set as a * template. In that way we give the system control over it’s effective * appearance. */ @interface MacOSStatusIconDelegate : NSObject @property(assign) NSStatusItem* statusItem; @property(assign) NSView* statusIndicator; - (void)setIcon:(NSData*)imageData; - (void)setIndicator; - (void)setIndicatorColor:(NSColor*)color; - (void)setMenu:(NSMenu*)statusBarMenu; - (void)setToolTip:(NSString*)tooltip; @end @implementation MacOSStatusIconDelegate /** * Initializes and sets the status item and indicator objects. * * @return An instance of MacOSStatusIconDelegate. */ - (id)init { self = [super init]; // Create status item self.statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; self.statusItem.visible = true; // Add the indicator as a subview [self setIndicator]; return self; } /** * Sets the image for the status icon. * * @param iconPath The data for the icon image. */ - (void)setIcon:(NSData*)imageData { NSImage* image = [[NSImage alloc] initWithData:imageData]; [image setTemplate:true]; [self.statusItem.button setImage:image]; [image release]; } /** * Adds status indicator as a subview to the status item button. */ - (void)setIndicator { float viewHeight = NSHeight([self.statusItem.button bounds]); float dotSize = viewHeight * 0.35; float dotOrigin = (viewHeight - dotSize) * 0.8; NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)]; self.statusIndicator = dot; self.statusIndicator.wantsLayer = true; self.statusIndicator.layer.cornerRadius = dotSize * 0.5; [self.statusItem.button addSubview:self.statusIndicator]; [dot release]; } /** * Sets the color if the indicator. * * @param color The indicator background color. */ - (void)setIndicatorColor:(NSColor*)color { if (self.statusIndicator) { 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 { [self.statusItem.button setToolTip:tooltip]; } @end namespace { Logger logger("MacOSStatusIcon"); MacOSStatusIconDelegate* m_statusBarIcon = nullptr; } MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) { MZ_COUNT_CTOR(MacOSStatusIcon); logger.debug() << "Register delegate"; Q_ASSERT(!m_statusBarIcon); m_statusBarIcon = [[MacOSStatusIconDelegate alloc] init]; } MacOSStatusIcon::~MacOSStatusIcon() { MZ_COUNT_DTOR(MacOSStatusIcon); logger.debug() << "Remove delegate"; Q_ASSERT(m_statusBarIcon); [static_cast(m_statusBarIcon) dealloc]; m_statusBarIcon = nullptr; } void MacOSStatusIcon::setIcon(const QString& iconPath) { logger.debug() << "Set icon" << iconPath; QResource imageResource = QResource(iconPath); Q_ASSERT(imageResource.isValid()); [m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()]; } void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) { logger.debug() << "Set indicator color"; if (!indicatorColor.isValid()) { [m_statusBarIcon setIndicatorColor:[NSColor clearColor]]; return; } NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f green:indicatorColor.green() / 255.0f blue:indicatorColor.blue() / 255.0f alpha:indicatorColor.alpha() / 255.0f]; [m_statusBarIcon setIndicatorColor:color]; } void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) { logger.debug() << "Set menu"; [m_statusBarIcon setMenu:statusBarMenu]; } void MacOSStatusIcon::setToolTip(const QString& tooltip) { logger.debug() << "Set tooltip"; [m_statusBarIcon setToolTip:tooltip.toNSString()]; } void MacOSStatusIcon::showMessage(const QString& title, const QString& message) { logger.debug() << "Show message"; UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; // This is a no-op is authorization has been granted. [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError* _Nullable error) { if (error) { // Note: This error may happen if the application is not signed. NSLog(@"Error asking for permission to send notifications %@", error); return; } }]; UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; content.title = [title.toNSString() autorelease]; content.body = [message.toNSString() autorelease]; content.sound = [UNNotificationSound defaultSound]; UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn" content:content trigger:trigger]; [center addNotificationRequest:request withCompletionHandler:^(NSError* _Nullable error) { if (error) { logger.error() << "Local Notification failed" << error; } }]; }