mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-6324: JBR API for System Shortcuts (macOS)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user