- Allow switching between themes without restart (except classic) - Rework icon loading and recolouring logic to react to theme changes - Automatically react to light/dark theme change - Remove explicit selection of monochrome tray icon variant (selected automatically now) - Update theme background colours for Big Sur - Update application icon to match Big Sur HIG The tray icon doesn't respond perfectly to theme changes yet on Big Sur, since we need different icons for dark and light theme and cannot simply let the OS recolour the icon for us (we do that, too, but only as an additional fallback). At the moment, there is no signal to listen to that would allow this. This patch adds a few generic methods to OSUtils for detecting and communicating theme changes, which are only stubs for Windows and Linux at the moment and need to be implemented in future commits. Fixes #4933 Fixes #5349
288 lines
8.6 KiB
Plaintext
288 lines
8.6 KiB
Plaintext
/*
|
|
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
|
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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>
|
|
|
|
@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<id>(kAXTrustedCheckOptionPrompt): @YES};
|
|
return AXIsProcessTrustedWithOptions(static_cast<CFDictionaryRef>(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<id>(self)];
|
|
[[NSDistributedNotificationCenter defaultCenter] removeObserver:static_cast<id>(self)];
|
|
[static_cast<id>(self) dealloc];
|
|
}
|
|
|
|
pid_t AppKit::lastActiveProcessId()
|
|
{
|
|
return [static_cast<id>(self) lastActiveApplication].processIdentifier;
|
|
}
|
|
|
|
pid_t AppKit::activeProcessId()
|
|
{
|
|
return [static_cast<id>(self) activeProcessId];
|
|
}
|
|
|
|
pid_t AppKit::ownProcessId()
|
|
{
|
|
return [static_cast<id>(self) ownProcessId];
|
|
}
|
|
|
|
bool AppKit::activateProcess(pid_t pid)
|
|
{
|
|
return [static_cast<id>(self) activateProcess:pid];
|
|
}
|
|
|
|
bool AppKit::hideProcess(pid_t pid)
|
|
{
|
|
return [static_cast<id>(self) hideProcess:pid];
|
|
}
|
|
|
|
bool AppKit::isHidden(pid_t pid)
|
|
{
|
|
return [static_cast<id>(self) isHidden:pid];
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
bool AppKit::enableScreenRecording()
|
|
{
|
|
return [static_cast<id>(self) enableScreenRecording];
|
|
}
|
|
|
|
void AppKit::toggleForegroundApp(bool foreground)
|
|
{
|
|
[static_cast<id>(self) toggleForegroundApp:foreground];
|
|
}
|