JBR-6324: JBR API for System Shortcuts (macOS)

This commit is contained in:
Nikita Tsarev
2024-10-10 14:20:45 +02:00
parent 8cf2476301
commit 82999dc986
3 changed files with 179 additions and 17 deletions

View File

@@ -0,0 +1,114 @@
/*
* 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.
*/
package sun.lwawt.macosx;
import com.jetbrains.exported.JBRApi;
import java.awt.*;
import java.awt.desktop.SystemHotkey;
import java.awt.event.InputEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@JBRApi.Service
@JBRApi.Provides("SystemShortcuts")
public class JBRSystemShortcutsMacOS {
@JBRApi.Provides("SystemShortcuts.Shortcut")
public static class Shortcut {
private final int keyCode;
private final char keyChar;
private final int modifiers;
private final String id;
private final String description;
public Shortcut(int keyCode, char keyChar, int modifiers, String id, String description) {
this.keyCode = keyCode;
this.keyChar = keyChar;
this.modifiers = modifiers;
this.id = id;
this.description = description;
}
public int getKeyCode() {
return keyCode;
}
public char getKeyChar() {
return keyChar;
}
public int getModifiers() {
return modifiers;
}
public String getId() {
return id;
}
public String getDescription() {
return description;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Shortcut shortcut)) return false;
return keyCode == shortcut.keyCode && keyChar == shortcut.keyChar && modifiers == shortcut.modifiers && Objects.equals(id, shortcut.id) && Objects.equals(description, shortcut.description);
}
@Override
public int hashCode() {
return Objects.hash(keyCode, keyChar, modifiers, id, description);
}
}
@JBRApi.Provided("SystemShortcuts.ChangeEventListener")
public interface ChangeEventListener {
void handleSystemShortcutsChangeEvent();
}
public Shortcut[] querySystemShortcuts() {
var hotkeys = SystemHotkey.readSystemHotkeys();
var result = new Shortcut[hotkeys.size()];
for (int i = 0; i < hotkeys.size(); ++i) {
var hotkey = hotkeys.get(i);
result[i] = new Shortcut(
hotkey.getKeyCode(),
hotkey.getKeyChar(),
hotkey.getModifiers(),
hotkey.getDescription(),
hotkey.getDescription()
);
}
return result;
}
public void setChangeListener(ChangeEventListener listener) {
SystemHotkey.setChangeEventHandler(listener::handleSystemShortcutsChangeEvent);
}
}

View File

@@ -8,6 +8,8 @@
#import "java_awt_event_KeyEvent.h"
#include <jni.h>
#import <ThreadUtilities.h>
#import <JNIUtilities.h>
#include "jni_util.h"
@@ -312,7 +314,7 @@ static int javaModifiers2NS(int jmask) {
return result;
}
typedef void (^ Visitor)(int, const char *, int, const char *, int);
typedef void (^ Visitor)(int, const char *, int, const char *, int, const char*);
static void visitServicesShortcut(Visitor visitorBlock, NSString * key_equivalent, NSString * desc) {
// @ - command
@@ -336,7 +338,7 @@ static void visitServicesShortcut(Visitor visitorBlock, NSString * key_equivalen
NSCharacterSet * excludeSet = [NSCharacterSet characterSetWithCharactersInString:@"@$^~"];
NSString * keyChar = [key_equivalent stringByTrimmingCharactersInSet:excludeSet];
visitorBlock(-1, keyChar.UTF8String, modifiers, desc.UTF8String, -1);
visitorBlock(-1, keyChar.UTF8String, modifiers, desc.UTF8String, -1, NULL);
}
static void readAppleSymbolicHotkeys(struct SymbolicHotKey hotkeys[numSymbolicHotkeys]) {
@@ -472,12 +474,13 @@ static void iterateAppleSymbolicHotkeys(struct SymbolicHotKey hotkeys[numSymboli
keyCharStr = NULL;
}
int modifiers = symbolicHotKeysModifiers2java(hotkey->modifiers);
visitorBlock(hotkey->key, keyCharStr, modifiers, hotkey->description, uid);
visitorBlock(hotkey->key, keyCharStr, modifiers, hotkey->description, uid, hotkey->id);
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);
visitorBlock(hotkey->key, keyCharStr, modifiers | AWT_SHIFT_DOWN_MASK,
"Move focus to the previous window in application", -1, "FocusPreviousApplicationWindow");
}
}
}
@@ -590,17 +593,29 @@ static void readSystemHotkeysImpl(Visitor visitorBlock) {
[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;
}
}
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
// Called when AppleSymbolicHotKeys change.
// Called after AppleSymbolicHotKeys or pbs hotkeys change.
// This method can be called from any thread.
if ([keyPath isEqualToString:@"AppleSymbolicHotKeys"]) {
updateAppleSymbolicHotkeysCache();
}
// 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");
(*env)->CallStaticVoidMethod(env, jc_SystemHotkey, jsm_onChange);
CHECK_EXCEPTION();
}
@end
@@ -637,14 +652,15 @@ bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, int keyC
JNIEXPORT void JNICALL Java_java_awt_desktop_SystemHotkeyReader_readSystemHotkeys(JNIEnv* env, jobject reader) {
jclass clsReader = (*env)->GetObjectClass(env, reader);
jmethodID methodAdd = (*env)->GetMethodID(env, clsReader, "add", "(ILjava/lang/String;ILjava/lang/String;)V");
jmethodID methodAdd = (*env)->GetMethodID(env, clsReader, "add", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
readSystemHotkeysImpl(
^(int vkeyCode, const char * keyCharStr, int jmodifiers, const char * descriptionStr, int hotkeyUid){
^(int vkeyCode, const char * keyCharStr, int jmodifiers, const char * descriptionStr, int hotkeyUid, const char * idStr) {
jstring jkeyChar = keyCharStr == NULL ? NULL : (*env)->NewStringUTF(env, keyCharStr);
jstring jdesc = descriptionStr == NULL ? NULL : (*env)->NewStringUTF(env, descriptionStr);
jstring jid = idStr == NULL ? NULL : (*env)->NewStringUTF(env, idStr);
(*env)->CallVoidMethod(
env, reader, methodAdd, (jint)vkeyCode, jkeyChar, (jint)jmodifiers, jdesc
env, reader, methodAdd, (jint)vkeyCode, jkeyChar, (jint)jmodifiers, jdesc, jid
);
}
);

View File

@@ -14,28 +14,44 @@ import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
/**
* Provides info about system hotkeys
* Provides info about system hotkeys.
* Deprecated, use JBR SystemShortcuts API instead.
*/
public class SystemHotkey extends AWTKeyStroke {
private static final long serialVersionUID = -6593119259058157651L;
private static final long serialVersionUID = 4340163519946285280L;
private static final PlatformLogger ourLog = PlatformLogger.getLogger(java.awt.desktop.SystemHotkey.class.getName());
private static final Map<Integer, String> ourCodeDescriptionCache = new HashMap<>();
private static volatile Runnable changeEventHandler = null;
private final int myNativeKeyCode;
private final String myId;
private final String myDescription;
SystemHotkey(char keyChar, int javaKeyCode, int javaModifiers, String description, int nativeKeyCode) {
super(keyChar, javaKeyCode, javaModifiers, true);
this.myNativeKeyCode = nativeKeyCode;
this.myDescription = description;
this(keyChar, javaKeyCode, javaModifiers, description, nativeKeyCode, description);
}
SystemHotkey(char keyChar, int javaKeyCode, int javaModifiers, String description, int nativeKeyCode, String id) {
super(keyChar, javaKeyCode, javaModifiers, true);
this.myNativeKeyCode = nativeKeyCode;
this.myId = id;
this.myDescription = description;
}
public String toString() {
return String.format("desc='%s' char=%s mod='%s' nativeKeyCode=0x%X ['%s'] javaKeyCode=0x%X",
String.valueOf(myDescription), String.valueOf(getKeyChar()), InputEvent.getModifiersExText(getModifiers()),
return String.format("id='%s' desc='%s' char=%s mod='%s' nativeKeyCode=0x%X ['%s'] javaKeyCode=0x%X",
String.valueOf(myId), String.valueOf(myDescription), String.valueOf(getKeyChar()), InputEvent.getModifiersExText(getModifiers()),
myNativeKeyCode, getOsxKeyCodeDescription(myNativeKeyCode), getKeyCode());
}
/**
* Gets unique hotkey identifier
* @return hotkey identifier
*/
public String getId() {
return myId;
}
/**
* Gets hotkey description
* @return hotkey description
@@ -67,16 +83,32 @@ public class SystemHotkey extends AWTKeyStroke {
}
private static native String osxKeyCodeDescription(int osxCode);
/**
* Set the event handler, which is fired when the user changes a system shortcut.
* @param changeEventHandler event handler
*/
public static void setChangeEventHandler(Runnable changeEventHandler) {
SystemHotkey.changeEventHandler = changeEventHandler; // store to volatile
}
private static void onChange() {
// called from native code from any thread
var handler = changeEventHandler; // load from volatile
if (handler != null) {
EventQueue.invokeLater(handler);
}
}
}
class SystemHotkeyReader {
private final List<SystemHotkey> myResult = new ArrayList<>();
void add(int keyCode, String keyChar, int modifiers, String desc) {
void add(int keyCode, String keyChar, int modifiers, String desc, String id) {
myResult.add(new SystemHotkey(
keyChar == null || keyChar.isEmpty() ? KeyEvent.CHAR_UNDEFINED : keyChar.charAt(0),
keyCode == -1 ? KeyEvent.VK_UNDEFINED : osx2java(keyCode),
modifiers, desc, keyCode
modifiers, desc, keyCode, (id == null) ? desc : id
));
}