/* * Copyright (C) 2016 Lennart Glauer * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 or (at your option) * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #import "AppKitImpl.h" #include "AppKit.h" #import #import #import #import #import @implementation AppKitImpl - (id) initWithObject:(AppKit*)appkit { self = [super init]; if (self) { m_appkit = appkit; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didDeactivateApplicationObserver:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil]; [[[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; } // // Update last active application property // - (void) didDeactivateApplicationObserver:(NSNotification*) notification { NSDictionary* userInfo = notification.userInfo; NSRunningApplication* app = userInfo[NSWorkspaceApplicationKey]; if (app.processIdentifier != [self ownProcessId]) { self.lastActiveApplication = app; } } // // 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 { return [NSWorkspace sharedWorkspace].frontmostApplication.processIdentifier; } // // Get process id of own process // - (pid_t) ownProcessId { return [NSProcessInfo processInfo].processIdentifier; } // // Activate application by process id // - (bool) activateProcess:(pid_t) pid { NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; } // // Hide application by process id // - (bool) hideProcess:(pid_t) pid { NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app hide]; } // // Get application hidden state by process id // - (bool) isHidden:(pid_t) pid { NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app isHidden]; } // // Get state of macOS Dark Mode color scheme // - (bool) isDarkMode { NSDictionary* dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain]; id style = [dict objectForKey:@"AppleInterfaceStyle"]; return ( style && [style isKindOfClass:[NSString class]] && 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 // - (void) userSwitchHandler:(NSNotification*) notification { if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit) { emit m_appkit->lockDatabases(); } } // // Check if accessibility is enabled, may show an popup asking for permissions // - (bool) enableAccessibility { // Request accessibility permissions for Auto-Type type on behalf of the user NSDictionary* opts = @{static_cast(kAXTrustedCheckOptionPrompt): @YES}; return AXIsProcessTrustedWithOptions(static_cast(opts)); } // // Check if screen recording is enabled, may show an popup asking for permissions // - (bool) enableScreenRecording { if (@available(macOS 10.15, *)) { // Request screen recording permission on macOS 10.15+ // This is necessary to get the current window title CGDisplayStreamRef stream = CGDisplayStreamCreate(CGMainDisplayID(), 1, 1, kCVPixelFormatType_32BGRA, nil, ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) { Q_UNUSED(status); Q_UNUSED(displayTime); Q_UNUSED(frameSurface); Q_UNUSED(updateRef); }); if (stream) { CFRelease(stream); } else { return NO; } } return YES; } - (void) toggleForegroundApp:(bool) foreground { ProcessSerialNumber psn = {0, kCurrentProcess}; if (foreground) { TransformProcessType(&psn, kProcessTransformToForegroundApplication); } else { TransformProcessType(&psn, kProcessTransformToUIElementApplication); } } @end // // ------------------------- C++ Trampolines ------------------------- // AppKit::AppKit(QObject* parent) : QObject(parent) { self = [[AppKitImpl alloc] initWithObject:this]; } AppKit::~AppKit() { [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast(self)]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:static_cast(self)]; [static_cast(self) dealloc]; } pid_t AppKit::lastActiveProcessId() { return [static_cast(self) lastActiveApplication].processIdentifier; } pid_t AppKit::activeProcessId() { return [static_cast(self) activeProcessId]; } pid_t AppKit::ownProcessId() { return [static_cast(self) ownProcessId]; } bool AppKit::activateProcess(pid_t pid) { return [static_cast(self) activateProcess:pid]; } bool AppKit::hideProcess(pid_t pid) { return [static_cast(self) hideProcess:pid]; } bool AppKit::isHidden(pid_t pid) { return [static_cast(self) isHidden:pid]; } bool AppKit::isDarkMode() { return [static_cast(self) isDarkMode]; } bool AppKit::isStatusBarDark() { return [static_cast(self) isStatusBarDark]; } bool AppKit::enableAccessibility() { return [static_cast(self) enableAccessibility]; } bool AppKit::enableScreenRecording() { return [static_cast(self) enableScreenRecording]; } void AppKit::toggleForegroundApp(bool foreground) { [static_cast(self) toggleForegroundApp:foreground]; }