amnezia-client/client/platforms/macos/macosutils.mm
Mykola Baibuz 07c38e9b6c
WireGuard rework for MacOS and Windows (#314)
WireGuard rework for MacOS and Windows
2023-09-14 17:44:17 +01:00

201 lines
5.9 KiB
Text

/* 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 "macosutils.h"
#include "logger.h"
#include <objc/message.h>
#include <objc/objc.h>
#import <Cocoa/Cocoa.h>
#import <ServiceManagement/ServiceManagement.h>
namespace {
Logger logger("MacOSUtils");
}
// static
NSString* MacOSUtils::appId() {
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
if (!appId) {
// Fallback. When an unsigned/un-notarized app is executed in
// command-line mode, it could fail the fetching of its own bundle id.
appId = @"org.amnezia.AmneziaVPN";
}
return appId;
}
// static
QString MacOSUtils::computerName() {
NSString* name = [[NSHost currentHost] localizedName];
return QString::fromNSString(name);
}
// static
void MacOSUtils::enableLoginItem(bool startAtBoot) {
logger.debug() << "Enabling login-item";
NSString* appId = MacOSUtils::appId();
Q_ASSERT(appId);
NSString* loginItemAppId =
QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString();
CFStringRef cfs = (__bridge CFStringRef)loginItemAppId;
Boolean ok = SMLoginItemSetEnabled(cfs, startAtBoot ? YES : NO);
logger.debug() << "Result: " << ok;
}
namespace {
bool dockClickHandler(id self, SEL cmd, ...) {
Q_UNUSED(self);
Q_UNUSED(cmd);
logger.debug() << "Dock icon clicked.";
//TODO IMPL FOR AMNEZIA
//QmlEngineHolder::instance()->showWindow();
return FALSE;
}
} // namespace
// static
void MacOSUtils::setDockClickHandler() {
NSApplication* app = [NSApplication sharedApplication];
if (!app) {
logger.debug() << "No sharedApplication";
return;
}
id delegate = [app delegate];
if (!delegate) {
logger.debug() << "No delegate";
return;
}
Class delegateClass = [delegate class];
if (!delegateClass) {
logger.debug() << "No delegate class";
return;
}
SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
if (class_getInstanceMethod(delegateClass, shouldHandle)) {
if (!class_replaceMethod(delegateClass, shouldHandle, (IMP)dockClickHandler, "B@:")) {
logger.error() << "Failed to replace the dock click handler";
}
} else if (!class_addMethod(delegateClass, shouldHandle, (IMP)dockClickHandler, "B@:")) {
logger.error() << "Failed to register the dock click handler";
}
}
void MacOSUtils::hideDockIcon() {
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
}
void MacOSUtils::showDockIcon() {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}
/**
* Replace the setImage method on NSStatusBarButton with a method that scales
* images proportionally before setting.
*
* The reason for this is that there is a bug in Qt 5.15 that causes status bar
* icons to be displayed larger than UI recommendations, and out of proportion
* on displays with a device pixel ratio greater than 1 (MacOS Big Sur only).
* This bug will not be fixed in Qt open source versions, so we have to resort
* to a hack that exchanges the implementation of a method on NSStatusBarButton
* with one that correctly scales the icon.
*
* Original bug (and sample implementation):
* https://bugreports.qt.io/browse/QTBUG-88600
*/
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
method_exchangeImplementations(original, patched);
}
@interface NSImageScalingHelper : NSObject
/**
* Create a proportionally scaled image according to the given target size.
*
* @param sourceImage The original image to be scaled.
* @param targetSize The required size of the image.
* @return A scaled image.
*/
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize;
@end
@implementation NSImageScalingHelper
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize {
NSImage* newImage = nil;
if ([sourceImage isValid]) {
NSSize sourceSize = [sourceImage size];
if (sourceSize.width != 0.0 && sourceSize.height != 0.0) {
float scaleFactor = 0.0;
float scaledWidth = targetSize.width;
float scaledHeight = targetSize.height;
NSPoint thumbnailPoint = NSZeroPoint;
if (NSEqualSizes(sourceSize, targetSize) == NO) {
float widthFactor = targetSize.width / sourceSize.width;
float heightFactor = targetSize.height / sourceSize.height;
if (widthFactor < heightFactor) {
scaleFactor = widthFactor;
} else {
scaleFactor = heightFactor;
}
scaledWidth = sourceSize.width * scaleFactor;
scaledHeight = sourceSize.height * scaleFactor;
if (widthFactor < heightFactor) {
thumbnailPoint.y = (targetSize.height - scaledHeight) * 0.5;
} else {
thumbnailPoint.x = (targetSize.width - scaledWidth) * 0.5;
}
}
newImage = [[NSImage alloc] initWithSize:targetSize];
[newImage lockFocus];
NSRect thumbnailRect;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect
fromRect:NSZeroRect
operation:NSCompositingOperationSourceOver
fraction:1.0];
[newImage unlockFocus];
[newImage setTemplate:[sourceImage isTemplate]];
}
}
return [newImage autorelease];
}
@end
@implementation NSStatusBarButton (Swizzle)
- (void)setImagePatched:(NSImage*)image {
NSImage* img = image;
if (image != nil) {
int thickness = [[NSStatusBar systemStatusBar] thickness];
img = [NSImageScalingHelper imageByScaling:image size:NSMakeSize(thickness, thickness)];
}
[self setImagePatched:img];
}
@end