JBR-8422: A temporary workaround for crash in SystemHotkey setup on macOS 15.4 beta

This commit is contained in:
Nikita Tsarev
2025-03-24 18:57:26 +01:00
parent c5d734adcc
commit 1ab48b4b72
3 changed files with 78 additions and 82 deletions

View File

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

View File

@@ -1,34 +0,0 @@
// 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,4 +1,4 @@
#include "SystemHotkey.h"
#include <pthread.h>
#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>
@@ -15,6 +15,10 @@
extern JavaVM *jvm;
@interface SystemHotkey : NSObject
+ (void)subscribeToChanges;
@end
enum LOG_LEVEL {
LL_TRACE,
LL_DEBUG,
@@ -23,7 +27,7 @@ enum LOG_LEVEL {
LL_ERROR
};
void plog(int logLevel, const char *formatMsg, ...) {
static void plog(int logLevel, const char *formatMsg, ...) {
if (!jvm)
return;
if (logLevel < LL_TRACE || logLevel > LL_ERROR || formatMsg == NULL)
@@ -265,12 +269,20 @@ static const struct SymbolicHotKey defaultSymbolicHotKeys[] = {
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];
static struct SystemHotkeyState {
bool symbolicHotkeysFilled;
struct SymbolicHotKey currentSymbolicHotkeys[numSymbolicHotkeys];
// Should only be read and written inside a @synchronized([SystemHotkey class]) block
static bool subscribedToShortcutUpdates = false;
bool initialized;
bool enabled;
pthread_mutex_t mutex;
} state = {
.symbolicHotkeysFilled = false,
.initialized = false,
.enabled = false,
.mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER,
};
@interface DefaultParams: NSObject
@property (assign) BOOL enabled;
@@ -454,9 +466,10 @@ static void updateAppleSymbolicHotkeysCache() {
struct SymbolicHotKey hotkeys[numSymbolicHotkeys];
readAppleSymbolicHotkeys(hotkeys);
@synchronized ([SystemHotkey class]) {
memcpy(currentSymbolicHotkeys, hotkeys, numSymbolicHotkeys * sizeof(struct SymbolicHotKey));
}
pthread_mutex_lock(&state.mutex);
memcpy(state.currentSymbolicHotkeys, hotkeys, sizeof(hotkeys));
state.symbolicHotkeysFilled = true;
pthread_mutex_unlock(&state.mutex);
}
static void iterateAppleSymbolicHotkeys(struct SymbolicHotKey hotkeys[numSymbolicHotkeys], Visitor visitorBlock) {
@@ -556,19 +569,46 @@ 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 bool ensureInitializedAndEnabled() {
pthread_mutex_lock(&state.mutex);
if (state.initialized) {
bool enabled = state.enabled;
pthread_mutex_unlock(&state.mutex);
return enabled;
}
// JBR-8422
const NSOperatingSystemVersion macOSVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
if (macOSVersion.majorVersion > 15 || (macOSVersion.majorVersion == 15 && macOSVersion.minorVersion >= 4)) {
state.initialized = true;
state.enabled = false;
pthread_mutex_unlock(&state.mutex);
return false;
}
state.initialized = true;
state.enabled = true;
[SystemHotkey subscribeToChanges];
pthread_mutex_unlock(&state.mutex);
updateAppleSymbolicHotkeysCache();
return true;
}
static void readAppleSymbolicHotkeysCached(struct SymbolicHotKey hotkeys[numSymbolicHotkeys]) {
memset(hotkeys, 0, sizeof(struct SymbolicHotKey) * numSymbolicHotkeys);
pthread_mutex_lock(&state.mutex);
if (state.symbolicHotkeysFilled) {
memcpy(hotkeys, state.currentSymbolicHotkeys, sizeof(struct SymbolicHotKey) * numSymbolicHotkeys);
}
pthread_mutex_unlock(&state.mutex);
}
static void readSystemHotkeysImpl(Visitor visitorBlock) {
// Normally, SystemHotkey would get initialized in LWCToolkit initialization.
// But since we can (theoretically) use this API from headless, let's check again.
if (!ensureInitializedAndEnabled()) {
return;
}
struct SymbolicHotKey hotkeys[numSymbolicHotkeys];
readAppleSymbolicHotkeysCached(hotkeys);
@@ -578,40 +618,32 @@ static void readSystemHotkeysImpl(Visitor visitorBlock) {
}
@implementation SystemHotkey
+ (void)setUp {
// This should be called on LWCToolkit initialization.
+ (void)subscribeToChanges {
// Subscribe to changes
NSUserDefaults *symbolicHotKeys = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
[symbolicHotKeys addObserver:self forKeyPath:@"AppleSymbolicHotKeys" options:NSKeyValueObservingOptionNew
context:nil];
@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];
NSUserDefaults *pbsHotKeys = [[NSUserDefaults alloc] initWithSuiteName:@"pbs"];
[pbsHotKeys addObserver:self forKeyPath:@"NSServicesStatus" options:NSKeyValueObservingOptionNew context:nil];
subscribedToShortcutUpdates = true;
}
NSUserDefaults *pbsHotKeys = [[NSUserDefaults alloc] initWithSuiteName:@"pbs"];
[pbsHotKeys addObserver:self forKeyPath:@"NSServicesStatus" options:NSKeyValueObservingOptionNew context:nil];
}
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
// Called after AppleSymbolicHotKeys or pbs hotkeys change.
// This method can be called from any thread.
if ([keyPath isEqualToString:@"AppleSymbolicHotKeys"]) {
updateAppleSymbolicHotkeysCache();
}
// This method should only be called from the main thread, but let's check anyway
if (pthread_main_np() == 0) {
return;
}
// Since this notification is sent *after* the configuration was updated,
// the user can safely re-read the hotkeys info after receiving this callback.
// On the Java side, this simply enqueues the change handler to run on the EDT later.
JNIEnv* env = [ThreadUtilities getJNIEnv];
DECLARE_CLASS(jc_SystemHotkey, "java/awt/desktop/SystemHotkey");
DECLARE_STATIC_METHOD(jsm_onChange, jc_SystemHotkey, "onChange", "()V");
@@ -621,12 +653,13 @@ static void readSystemHotkeysImpl(Visitor visitorBlock) {
@end
bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, int keyCode, NSString *chars) {
struct SymbolicHotKey shortcut;
@synchronized ([SystemHotkey class]) {
if (!subscribedToShortcutUpdates) {
[SystemHotkey setUp];
struct SymbolicHotKey shortcut = defaultSymbolicHotKeys[Shortcut_FocusNextApplicationWindow];
if (ensureInitializedAndEnabled()) {
pthread_mutex_lock(&state.mutex);
if (state.symbolicHotkeysFilled) {
shortcut = state.currentSymbolicHotkeys[Shortcut_FocusNextApplicationWindow];
}
shortcut = currentSymbolicHotkeys[Shortcut_FocusNextApplicationWindow];
pthread_mutex_unlock(&state.mutex);
}
int ignoredModifiers = NSAlphaShiftKeyMask | NSFunctionKeyMask | NSNumericPadKeyMask | NSHelpKeyMask;