JBR-5690: Reload next window shortcut when it changes

(cherry picked from commit be0d27983c)
This commit is contained in:
Nikita Tsarev
2024-10-10 13:24:11 +02:00
committed by jbrbot
parent 9f76a53307
commit fdf3824ec0
3 changed files with 156 additions and 61 deletions

View File

@@ -36,6 +36,7 @@
#import "NSApplicationAWT.h"
#import "PropertiesUtilities.h"
#import "ApplicationDelegate.h"
#import "SystemHotkey.h"
#import "sun_lwawt_macosx_LWCToolkit.h"
@@ -249,6 +250,8 @@ static void setUpAWTAppKit(BOOL installObservers)
CFRelease(notBusyObserver);
setBusy(YES);
[SystemHotkey setUp];
}
JNIEnv* env = [ThreadUtilities getJNIEnv];

View File

@@ -0,0 +1,34 @@
// Copyright 2024 JetBrains s.r.o.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
//
// This code is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License version 2 only, as
// published by the Free Software Foundation. Oracle designates this
// particular file as subject to the "Classpath" exception as provided
// by Oracle in the LICENSE file that accompanied this code.
//
// This code 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
// version 2 for more details (a copy is included in the LICENSE file that
// accompanied this code).
//
// You should have received a copy of the GNU General Public License version
// 2 along with this work; if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
// or visit www.oracle.com if you need additional information or have any
// questions.
#ifndef JBR_SYSTEMHOTKEY_H
#define JBR_SYSTEMHOTKEY_H
#import <objc/NSObject.h>
@interface SystemHotkey : NSObject
+ (void)setUp;
@end
#endif //JBR_SYSTEMHOTKEY_H

View File

@@ -1,3 +1,5 @@
#include "SystemHotkey.h"
#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
@@ -261,6 +263,15 @@ static const struct SymbolicHotKey defaultSymbolicHotKeys[] = {
[Shortcut_ToggleTypingFeedback] = { "ToggleTypingFeedback", "Turn typing feedback on or off", YES, 65535, 65535, 0, 14 },
};
static const int numSymbolicHotkeys = sizeof(defaultSymbolicHotKeys) / sizeof(defaultSymbolicHotKeys[0]);
// Current state of system shortcuts.
// Should only be read and written inside a @synchronized([SystemHotkey class]) block
static struct SymbolicHotKey currentSymbolicHotkeys[numSymbolicHotkeys];
// Should only be read and written inside a @synchronized([SystemHotkey class]) block
static bool subscribedToShortcutUpdates = false;
@interface DefaultParams: NSObject
@property (assign) BOOL enabled;
@property (strong) NSString *key_equivalent;
@@ -304,7 +315,7 @@ static int javaModifiers2NS(int jmask) {
return result;
}
typedef bool (^ Visitor)(int, const char *, int, const char *, int);
typedef void (^ Visitor)(int, const char *, int, const char *, int);
static void visitServicesShortcut(Visitor visitorBlock, NSString * key_equivalent, NSString * desc) {
// @ - command
@@ -331,9 +342,8 @@ static void visitServicesShortcut(Visitor visitorBlock, NSString * key_equivalen
visitorBlock(-1, keyChar.UTF8String, modifiers, desc.UTF8String, -1);
}
static void readAppleSymbolicHotkeys(Visitor visitorBlock) {
static const int numIds = sizeof(defaultSymbolicHotKeys) / sizeof(defaultSymbolicHotKeys[0]);
const NSOperatingSystemVersion macOSVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
static void readAppleSymbolicHotkeys(struct SymbolicHotKey hotkeys[numSymbolicHotkeys]) {
// Called from the main thread
@try {
NSDictionary<NSString *, id> *shk = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"];
@@ -359,8 +369,7 @@ static void readAppleSymbolicHotkeys(Visitor visitorBlock) {
return;
}
struct SymbolicHotKey hotkeys[numIds];
memcpy(hotkeys, defaultSymbolicHotKeys, sizeof(hotkeys));
memcpy(hotkeys, defaultSymbolicHotKeys, numSymbolicHotkeys * sizeof(struct SymbolicHotKey));
for (id keyObj in hkObj) {
if (![keyObj isKindOfClass:[NSString class]]) {
@@ -435,33 +444,45 @@ static void readAppleSymbolicHotkeys(Visitor visitorBlock) {
hotkeys[uid].key = p1 == nil ? 0xFFFF : [p1 intValue];
hotkeys[uid].modifiers = p2 == nil ? 0 : [p2 intValue];
}
for (int uid = 0; uid < numIds; ++uid) {
struct SymbolicHotKey* hotkey = &hotkeys[uid];
if (!hotkey->enabled) continue;
if (hotkey->macOSVersion > macOSVersion.majorVersion) continue;
char keyCharBuf[64];
const char *keyCharStr = keyCharBuf;
if (hotkey->character >= 0 && hotkey->character <= 0xFF) {
sprintf(keyCharBuf, "%c", hotkey->character);
} else {
keyCharStr = NULL;
}
int modifiers = symbolicHotKeysModifiers2java(hotkey->modifiers);
visitorBlock(hotkey->key, keyCharStr, modifiers, hotkey->description, uid);
if (uid == Shortcut_FocusNextApplicationWindow) {
// Derive the "Move focus to the previous window in application" shortcut
if (!(modifiers & AWT_SHIFT_DOWN_MASK)) {
visitorBlock(hotkey->key, keyCharStr, modifiers | AWT_SHIFT_DOWN_MASK, "Move focus to the previous window in application", -1);
}
}
}
}
@catch (NSException *exception) {
NSLog(@"readAppleSymbolicHotkeys: catched exception, reason '%@'", exception.reason);
NSLog(@"readCachedAppleSymbolicHotkeys: catched exception, reason '%@'", exception.reason);
}
}
static void updateAppleSymbolicHotkeysCache() {
struct SymbolicHotKey hotkeys[numSymbolicHotkeys];
readAppleSymbolicHotkeys(hotkeys);
@synchronized ([SystemHotkey class]) {
memcpy(currentSymbolicHotkeys, hotkeys, numSymbolicHotkeys * sizeof(struct SymbolicHotKey));
}
}
static void iterateAppleSymbolicHotkeys(struct SymbolicHotKey hotkeys[numSymbolicHotkeys], Visitor visitorBlock) {
const NSOperatingSystemVersion macOSVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
for (int uid = 0; uid < numSymbolicHotkeys; ++uid) {
struct SymbolicHotKey* hotkey = &hotkeys[uid];
if (!hotkey->enabled) continue;
if (hotkey->macOSVersion > macOSVersion.majorVersion) continue;
char keyCharBuf[64];
const char *keyCharStr = keyCharBuf;
if (hotkey->character >= 0 && hotkey->character <= 0xFF) {
sprintf(keyCharBuf, "%c", hotkey->character);
} else {
keyCharStr = NULL;
}
int modifiers = symbolicHotKeysModifiers2java(hotkey->modifiers);
visitorBlock(hotkey->key, keyCharStr, modifiers, hotkey->description, uid);
if (uid == Shortcut_FocusNextApplicationWindow) {
// Derive the "Move focus to the previous window in application" shortcut
if (!(modifiers & AWT_SHIFT_DOWN_MASK)) {
visitorBlock(hotkey->key, keyCharStr, modifiers | AWT_SHIFT_DOWN_MASK, "Move focus to the previous window in application", -1);
}
}
}
}
@@ -534,46 +555,84 @@ static void readPbsHotkeys(Visitor visitorBlock) {
}
}
static void readAppleSymbolicHotkeysCached(struct SymbolicHotKey hotkeys[numSymbolicHotkeys]) {
@synchronized ([SystemHotkey class]) {
if (!subscribedToShortcutUpdates) {
[SystemHotkey setUp];
}
memcpy(hotkeys, currentSymbolicHotkeys, numSymbolicHotkeys * sizeof(struct SymbolicHotKey));
}
}
static void readSystemHotkeysImpl(Visitor visitorBlock) {
readAppleSymbolicHotkeys(visitorBlock);
// Normally, SystemHotkey would get initialized in LWCToolkit initialization.
// But since we can (theoretically) use this API from headless, let's check again.
struct SymbolicHotKey hotkeys[numSymbolicHotkeys];
readAppleSymbolicHotkeysCached(hotkeys);
iterateAppleSymbolicHotkeys(hotkeys, visitorBlock);
readPbsHotkeys(visitorBlock);
}
bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, int keyCode, NSString *chars) {
static NSString * shortcutCharacter = nil;
static int shortcutMask = 0;
static int shortcutKeyCode = -1;
if (shortcutCharacter == nil && shortcutKeyCode == -1) {
readSystemHotkeysImpl(
^bool(int vkeyCode, const char * keyCharStr, int jmodifiers, const char * descriptionStr, int hotkeyUid) {
if (hotkeyUid != Shortcut_FocusNextApplicationWindow)
return true;
@implementation SystemHotkey
+ (void)setUp {
// This should be called on LWCToolkit initialization.
if (keyCharStr != NULL) {
shortcutCharacter = [[NSString stringWithFormat:@"%s", keyCharStr] retain];
}
if (vkeyCode != -1) {
shortcutKeyCode = vkeyCode;
}
shortcutMask = javaModifiers2NS(jmodifiers);
return false;
}
);
if (shortcutCharacter == nil && shortcutKeyCode == -1) {
shortcutCharacter = @"`";
shortcutMask = NSCommandKeyMask;
@synchronized (self) {
if (subscribedToShortcutUpdates) {
return;
}
// Update cached values
updateAppleSymbolicHotkeysCache();
// Subscribe to changes
NSUserDefaults *symbolicHotKeys = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
[symbolicHotKeys addObserver:self forKeyPath:@"AppleSymbolicHotKeys" options:NSKeyValueObservingOptionNew
context:nil];
subscribedToShortcutUpdates = true;
}
}
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
// Called when AppleSymbolicHotKeys change.
// This method can be called from any thread.
if ([keyPath isEqualToString:@"AppleSymbolicHotKeys"]) {
updateAppleSymbolicHotkeysCache();
}
}
@end
bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, int keyCode, NSString *chars) {
struct SymbolicHotKey shortcut;
@synchronized ([SystemHotkey class]) {
if (!subscribedToShortcutUpdates) {
[SystemHotkey setUp];
}
shortcut = currentSymbolicHotkeys[Shortcut_FocusNextApplicationWindow];
}
int ignoredModifiers = NSAlphaShiftKeyMask | NSFunctionKeyMask | NSNumericPadKeyMask | NSHelpKeyMask;
// Ignore Shift because of JBR-4899.
if (!(shortcutMask & NSShiftKeyMask)) {
if (!(shortcut.modifiers & NSShiftKeyMask)) {
ignoredModifiers |= NSShiftKeyMask;
}
if ((modifiersMask & ~ignoredModifiers) == shortcutMask) {
return shortcutKeyCode == keyCode || [chars isEqualToString:shortcutCharacter];
if ((modifiersMask & ~ignoredModifiers) != shortcut.modifiers) {
return false;
}
if (shortcut.key == keyCode) {
return true;
}
if (shortcut.character > 0 && shortcut.character < 0xFFFF) {
unichar ch = shortcut.character;
return [chars isEqualToString:[NSString stringWithCharacters:&ch length:1]];
}
return false;
@@ -584,13 +643,12 @@ JNIEXPORT void JNICALL Java_java_awt_desktop_SystemHotkeyReader_readSystemHotkey
jmethodID methodAdd = (*env)->GetMethodID(env, clsReader, "add", "(ILjava/lang/String;ILjava/lang/String;)V");
readSystemHotkeysImpl(
^bool(int vkeyCode, const char * keyCharStr, int jmodifiers, const char * descriptionStr, int hotkeyUid){
^(int vkeyCode, const char * keyCharStr, int jmodifiers, const char * descriptionStr, int hotkeyUid){
jstring jkeyChar = keyCharStr == NULL ? NULL : (*env)->NewStringUTF(env, keyCharStr);
jstring jdesc = descriptionStr == NULL ? NULL : (*env)->NewStringUTF(env, descriptionStr);
(*env)->CallVoidMethod(
env, reader, methodAdd, (jint)vkeyCode, jkeyChar, (jint)jmodifiers, jdesc
);
return true;
}
);
}