add toggle for global/non-global hotkeys. add hook registration retry
kind of a hacky implementation, but it works. ideally i would like to not depend on JNativeHook at all if global hotkeys are disabled and provide some sort of "dual" event processing using both the built in Swing key press events and the native key press events, using input from whichever is appropriate based on the user settings. however, JNativeHook and Swing use different key codes. it would be possible to write some code to translate between the two, but i don't have a bunch of different keyboards and OS installs to test this properly and feel that the odds of me writing some key code translation function that doesn't work 100% of the time is a bit too high. so, we just use JNativeHook for global and non-global and test for window focus depending on which is enabled. this has the unfortunate consequence of requiring that the key event hook registration was successful regardless of if the user wants to use global or non-global hotkeys. kind of annoying, but can't be helped for now!
This commit is contained in:
parent
3e460f4a12
commit
20bd866eba
|
@ -42,6 +42,7 @@ public enum Language {
|
|||
setting_color_separators,
|
||||
|
||||
// Settings > Hotkey
|
||||
setting_useGlobalHotkeys,
|
||||
setting_hotkey_split,
|
||||
setting_hotkey_unsplit,
|
||||
setting_hotkey_skip,
|
||||
|
@ -241,7 +242,13 @@ public enum Language {
|
|||
* 1.4
|
||||
*/
|
||||
INCREMENT,
|
||||
START_VALUE;
|
||||
START_VALUE,
|
||||
|
||||
|
||||
/* */
|
||||
GLOBAL_HOTKEYS_WARNING,
|
||||
GLOBAL_HOTKEYS_HOOK_RETRY,
|
||||
GLOBAL_HOTKEYS_HOOK_ERROR;
|
||||
|
||||
public static final Locale[] LANGUAGES = new Locale[] {
|
||||
Locale.ENGLISH,
|
||||
|
|
|
@ -284,15 +284,17 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
|
|||
* main thread.
|
||||
*/
|
||||
@Override public void nativeKeyPressed( final NativeKeyEvent event ) {
|
||||
int keyCode = event.getKeyCode();
|
||||
boolean hotkeysEnabler = ( keyCode == Settings.hotkeyLock.get() );
|
||||
if (Settings.useGlobalHotkeys.get() || this.hasFocus()) {
|
||||
int keyCode = event.getKeyCode();
|
||||
boolean hotkeysEnabler = ( keyCode == Settings.hotkeyLock.get() );
|
||||
|
||||
if ( !ignoresNativeInputs() || hotkeysEnabler ) {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override public void run() {
|
||||
actions.process( event );
|
||||
}
|
||||
} );
|
||||
if ( !ignoresNativeInputs() || hotkeysEnabler ) {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override public void run() {
|
||||
actions.process( event );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,15 +450,7 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
|
|||
* @throws IllegalStateException if JNativeHook cannot be registered.
|
||||
*/
|
||||
private void setBehavior() {
|
||||
try {
|
||||
GlobalScreen.registerNativeHook();
|
||||
} catch (NativeHookException e) {
|
||||
// NOTE: commenting this out as the latest version of JNativeHook has at least some ability to
|
||||
// pop up an OS-specific dialog asking about accessibility permissions (at least on OS X)
|
||||
// and afterwards the application recovered fine from the user's perspective. throwing an
|
||||
// exception here causes Llanfair to just close immediately after the dialog has opened.
|
||||
//throw new IllegalStateException("cannot register native hook");
|
||||
}
|
||||
registerNativeKeyHook();
|
||||
setAlwaysOnTop(Settings.alwaysOnTop.get());
|
||||
addWindowListener(this);
|
||||
addMouseWheelListener(this);
|
||||
|
@ -473,4 +467,21 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
|
|||
MenuItem.populateRecentlyOpened();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register a hook to capture system-wide (global) key events.
|
||||
* @return true if the hook was registered, false if not
|
||||
*/
|
||||
public static boolean registerNativeKeyHook() {
|
||||
try {
|
||||
GlobalScreen.registerNativeHook();
|
||||
return true;
|
||||
} catch (NativeHookException e) {
|
||||
// NOTE: in the event of a failure, JNativeHook now has some ability (on some OS's at least)
|
||||
// to pop up an OS-specific dialog or other action that allows the user to rectify the
|
||||
// problem. e.g. on OS X, if an exception is thrown a dialog telling the user that the
|
||||
// application has requested some accessibility-related access shows up.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public class Settings {
|
|||
|
||||
/* HOTKEY properties */
|
||||
|
||||
public static final Property<Boolean> useGlobalHotkeys = new Property<>("useGlobalHotkeys");
|
||||
public static final Property<Integer> hotkeySplit = new Property<>( "hotkey.split" );
|
||||
public static final Property<Integer> hotkeyUnsplit = new Property<>( "hotkey.unsplit" );
|
||||
public static final Property<Integer> hotkeySkip = new Property<>( "hotkey.skip" );
|
||||
|
@ -213,6 +214,7 @@ public class Settings {
|
|||
global.put( colorHighlight.key, Color.decode( "0xffffff" ) );
|
||||
global.put( colorSeparators.key, Color.decode( "0x666666" ) );
|
||||
|
||||
global.put( useGlobalHotkeys.key, false );
|
||||
global.put( hotkeySplit.key, -1 );
|
||||
global.put( hotkeyUnsplit.key, -1 );
|
||||
global.put( hotkeySkip.key, -1 );
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.fenix.llanfair.dialog;
|
||||
|
||||
import org.fenix.llanfair.Language;
|
||||
import org.fenix.llanfair.Llanfair;
|
||||
import org.fenix.llanfair.config.Settings;
|
||||
import org.fenix.utils.gui.GBC;
|
||||
import org.jnativehook.GlobalScreen;
|
||||
|
@ -9,8 +10,7 @@ import org.jnativehook.keyboard.NativeKeyListener;
|
|||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -22,6 +22,11 @@ class TabHotkeys extends SettingsTab {
|
|||
|
||||
// ------------------------------------------------------------- ATTRIBUTES
|
||||
|
||||
private JCheckBox globalHotKeys;
|
||||
|
||||
private JLabel globalHotKeysHookWarning;
|
||||
private JButton globalHotKeysHookRetryButton;
|
||||
|
||||
/**
|
||||
* List of all key fields customizable by the user.
|
||||
*/
|
||||
|
@ -38,6 +43,31 @@ class TabHotkeys extends SettingsTab {
|
|||
* Creates the "Hotkeys" settings tab. Only called by {@link EditSettings}.
|
||||
*/
|
||||
TabHotkeys() {
|
||||
final Component that = this;
|
||||
|
||||
globalHotKeys = new JCheckBox("" + Language.setting_useGlobalHotkeys);
|
||||
globalHotKeys.setSelected(Settings.useGlobalHotkeys.get());
|
||||
globalHotKeys.addActionListener(new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) { Settings.useGlobalHotkeys.set(globalHotKeys.isSelected()); }
|
||||
});
|
||||
|
||||
globalHotKeysHookWarning = new JLabel("" + Language.GLOBAL_HOTKEYS_WARNING);
|
||||
globalHotKeysHookWarning.setForeground(Color.RED);
|
||||
globalHotKeysHookRetryButton = new JButton("" + Language.GLOBAL_HOTKEYS_HOOK_RETRY);
|
||||
globalHotKeysHookRetryButton.addActionListener(new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean isRegistered = Llanfair.registerNativeKeyHook();
|
||||
if (isRegistered) {
|
||||
globalHotKeysHookWarning.setVisible(false);
|
||||
globalHotKeysHookRetryButton.setVisible(false);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(that, Language.GLOBAL_HOTKEYS_HOOK_ERROR, Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
keyFields = new ArrayList<KeyField>();
|
||||
keyLabels = new ArrayList<JLabel>();
|
||||
|
||||
|
@ -72,13 +102,21 @@ class TabHotkeys extends SettingsTab {
|
|||
private void place() {
|
||||
setLayout(new GridBagLayout());
|
||||
|
||||
for (int row = 0; row < keyFields.size(); row++) {
|
||||
int row;
|
||||
for (row = 0; row < keyFields.size(); row++) {
|
||||
add(
|
||||
keyLabels.get(row),
|
||||
GBC.grid(0, row).insets(10, 0, 10, 10).anchor(GBC.LE)
|
||||
);
|
||||
add(keyFields.get(row), GBC.grid(1, row));
|
||||
}
|
||||
|
||||
add(globalHotKeys, GBC.grid(2, 3).insets(0, 50, 0, 0).anchor(GBC.LS));
|
||||
|
||||
if (!GlobalScreen.isNativeHookRegistered()) {
|
||||
add(globalHotKeysHookWarning, GBC.grid(0, row + 1, 3, 1).insets(10, 0, 10, 0));
|
||||
add(globalHotKeysHookRetryButton, GBC.grid(0, row + 2, 3, 1).insets(0, 0, 10, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------- INTERNAL TYPES
|
||||
|
@ -91,7 +129,9 @@ class TabHotkeys extends SettingsTab {
|
|||
* @author Xavier "Xunkar" Sencert
|
||||
*/
|
||||
static class KeyField extends JTextField
|
||||
implements MouseListener, NativeKeyListener {
|
||||
implements MouseListener, FocusListener, NativeKeyListener {
|
||||
|
||||
private Color originalBgColor;
|
||||
|
||||
// ----------------------------------------------------- CONSTANTS
|
||||
|
||||
|
@ -134,7 +174,10 @@ class TabHotkeys extends SettingsTab {
|
|||
String text = NativeKeyEvent.getKeyText(setting.get());
|
||||
setText(setting.get() == -1 ? "" + Language.DISABLED : text);
|
||||
|
||||
originalBgColor = getBackground();
|
||||
|
||||
addMouseListener(this);
|
||||
addFocusListener(this);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------- GETTERS
|
||||
|
@ -146,6 +189,33 @@ class TabHotkeys extends SettingsTab {
|
|||
return setting;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------- CORE KEY EVENT HANDLING
|
||||
|
||||
private void enableKeyListening(boolean enable) {
|
||||
if (enable) {
|
||||
if (!isEditing) {
|
||||
setBackground(Color.YELLOW);
|
||||
GlobalScreen.addNativeKeyListener(this);
|
||||
isEditing = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (isEditing) {
|
||||
setBackground(originalBgColor);
|
||||
GlobalScreen.removeNativeKeyListener(this);
|
||||
isEditing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setKey(int code, String keyText) {
|
||||
setting.set(code);
|
||||
if (code == -1)
|
||||
setText("" + Language.DISABLED);
|
||||
else
|
||||
setText(keyText);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------- CALLBACKS
|
||||
|
||||
/**
|
||||
|
@ -154,11 +224,7 @@ class TabHotkeys extends SettingsTab {
|
|||
* new color to signify that this field is now listening.
|
||||
*/
|
||||
public void mouseClicked(MouseEvent event) {
|
||||
if (!isEditing) {
|
||||
setBackground(Color.YELLOW);
|
||||
GlobalScreen.addNativeKeyListener(this);
|
||||
isEditing = true;
|
||||
}
|
||||
enableKeyListening(true);
|
||||
}
|
||||
|
||||
// $UNUSED$
|
||||
|
@ -183,18 +249,13 @@ class TabHotkeys extends SettingsTab {
|
|||
int code = event.getKeyCode();
|
||||
String text = null;
|
||||
|
||||
if (code == NativeKeyEvent.VC_ESCAPE) {
|
||||
if (code == NativeKeyEvent.VC_ESCAPE)
|
||||
code = -1;
|
||||
text = "" + Language.DISABLED;
|
||||
} else {
|
||||
else
|
||||
text = NativeKeyEvent.getKeyText(code);
|
||||
}
|
||||
setText(text);
|
||||
setting.set(code);
|
||||
|
||||
setBackground(Color.GREEN);
|
||||
GlobalScreen.removeNativeKeyListener(this);
|
||||
isEditing = false;
|
||||
setKey(code, text);
|
||||
enableKeyListening(false);
|
||||
}
|
||||
|
||||
// $UNUSED$
|
||||
|
@ -203,5 +264,10 @@ class TabHotkeys extends SettingsTab {
|
|||
// $UNUSED$
|
||||
public void nativeKeyTyped(NativeKeyEvent event) {}
|
||||
|
||||
public void focusLost(FocusEvent event) {
|
||||
enableKeyListening(false);
|
||||
}
|
||||
|
||||
public void focusGained(FocusEvent event) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ setting_color_newRecord = New Record
|
|||
setting_color_title = Title
|
||||
setting_color_highlight = Highlight
|
||||
setting_color_separators = Separators
|
||||
setting_useGlobalHotkeys = Global Hotkeys
|
||||
setting_hotkey_split = Start / Split
|
||||
setting_hotkey_unsplit = Unsplit
|
||||
setting_hotkey_skip = Skip
|
||||
|
@ -180,3 +181,6 @@ UNTITLED = <untitled>
|
|||
WARNING = Warning
|
||||
INCREMENT =
|
||||
START_VALUE =
|
||||
GLOBAL_HOTKEYS_WARNING = <html><div style="width: 300px;">Key event hook registration failed. <strong>You will not be able to set or use any of your hotkeys until this is fixed!</strong> Click 'Retry' below to attempt to register it again.</div></html>
|
||||
GLOBAL_HOTKEYS_HOOK_RETRY = Retry
|
||||
GLOBAL_HOTKEYS_HOOK_ERROR = Key event hook registration failed. You will need to grant extra accessibility permissions to Llanfair.
|
Reference in a new issue