Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/kmcaster.git
src/main/com/whitemagicsoftware/kmcaster/listeners/KeyboardListener.java
import org.jnativehook.keyboard.NativeKeyListener;
+import javax.swing.*;
+import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
+import java.util.Stack;
import static com.whitemagicsoftware.kmcaster.HardwareState.BOOLEAN_FALSE;
entry( 65514, KEY_ALT )
);
+
+ /**
+ * Most recently pressed non-modifier key value, empty signifies release.
+ */
+ private String mRegularHeld = "";
/**
* Stores the state of modifier keys. The contents of the map reflect the
* state of each switch, so the reference can be final but not its contents.
+ * An integer is used because keyboards usually have two separate keys for
+ * each modifier, both can be pressed and released independently.
*/
private final Map<HardwareSwitch, Integer> mModifiers = new HashMap<>();
- private String mRegularHeld = "";
+ /**
+ * Informing the application of a key release is delayed so that the user
+ * interface will give the end user a momentary glance of what key was
+ * pressed before it is released. Without this delay the keys disappear
+ * as fast as a typist can type, which can be too quick to read as individual
+ * keystrokes.
+ * <p>
+ * Track the number of key release timers are running so that they can
+ * all be stopped to prevent releasing the key when another key has been
+ * pressed in the mean time.
+ * </p>
+ */
+ private final Stack<Timer> mTimerStack = new Stack<>();
+ /**
+ * Creates a keyboard listener that publishes events when keys are either
+ * pressed or released. The constructor initializes all modifier keys to
+ * the released state because the native keyboard hook API does not offer
+ * a way to query what keys are currently pressed.
+ */
public KeyboardListener() {
- for( final var key : HardwareSwitch.modifierSwitches() ) {
+ for( final var key : modifierSwitches() ) {
mModifiers.put( key, 0 );
}
}
@Override
public void nativeKeyPressed( final NativeKeyEvent e ) {
- updateRegular( mRegularHeld, getDisplayText( e ) );
- updateModifier( e, 1 );
+ final var key = getKey( e );
+
+ if( key == null ) {
+ while( !mTimerStack.isEmpty() ) {
+ final var timer = mTimerStack.pop();
+ timer.stop();
+ }
+
+ updateRegular( mRegularHeld, getDisplayText( e ) );
+ }
+ else {
+ updateModifier( key, 1 );
+ }
}
@Override
public void nativeKeyReleased( final NativeKeyEvent e ) {
- updateRegular( getDisplayText( e ), BOOLEAN_FALSE );
- updateModifier( e, -1 );
+ final var regularDelay = 250;
+ final var modifierDelay = 100;
+
+ final var key = getKey( e );
+
+ if( key == null ) {
+ final var timer = delayedAction( regularDelay, ( action ) ->
+ updateRegular( getDisplayText( e ), BOOLEAN_FALSE )
+ );
+
+ mTimerStack.push( timer );
+ }
+ else {
+ delayedAction( modifierDelay, ( action ) ->
+ updateModifier( key, -1 ) );
+ }
+ }
+
+ /**
+ * Convenience method to start a one-time action at a relative time in
+ * the future.
+ *
+ * @param delay When to perform the action.
+ * @param listener The listener that will perform some future action.
+ * @return The {@link Timer} that will perform a one-time action.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private Timer delayedAction(
+ final int delay, final ActionListener listener ) {
+ final var timer = new Timer( delay, listener );
+
+ timer.setRepeats( false );
+ timer.start();
+
+ return timer;
}
// happens before the frame is set to visible.
tryFire( key, state == 0, state == 1 );
- }
- }
-
- /**
- * State for a regular (non-modifier) key has changed.
- *
- * @param o Previous key value.
- * @param n Current key value.
- */
- private void updateRegular( final String o, final String n ) {
- assert o != null;
- assert n != null;
-
- boolean isModifier = false;
-
- // The key is regular iff its name does not match any modifier name.
- for( final var key : mModifiers.keySet() ) {
- isModifier |= (key.isName( n ) || key.isName( o ));
- }
-
- // If it's not a modifier key, broadcast the regular value.
- if( !isModifier ) {
- tryFire( KEY_REGULAR, o, n );
- mRegularHeld = n;
}
}
* fails to call this method.
*
- * @param e The keyboard event that was most recently triggered.
+ * @param key A modifier key.
+ * @param increment {@code -1} means released, {@code 1} means pressed.
*/
- private void updateModifier( final NativeKeyEvent e, final int increment ) {
- final var key = mModifierCodes.get( e.getRawCode() );
+ private void updateModifier(
+ final HardwareSwitch key, final int increment ) {
+ final var oldCount = mModifiers.get( key );
+ final var newCount = max( oldCount + increment, 0 );
- if( key != null ) {
- final var oldCount = mModifiers.get( key );
- final var newCount = max( oldCount + increment, 0 );
+ tryFire( key, oldCount > 0, newCount > 0 );
+ mModifiers.put( key, newCount );
+ }
- tryFire( key, oldCount > 0, newCount > 0 );
- mModifiers.put( key, newCount );
- }
+ /**
+ * State for a regular (non-modifier) key has changed.
+ *
+ * @param o Previous key value.
+ * @param n Current key value.
+ */
+ private void updateRegular( final String o, final String n ) {
+ assert o != null;
+ assert n != null;
+
+ tryFire( KEY_REGULAR, o, n );
+ mRegularHeld = n;
}
e.getRawCode(), getKeyText( e.getKeyCode() )
);
+ }
+
+ /**
+ * Returns the modifier key that corresponds to the raw key code from
+ * the given event. This is necessary to ensure that both left and right
+ * modifier keys return the same {@link HardwareSwitch} value.
+ *
+ * @param e The event containing a raw key code to look up.
+ * @return The switch matching the raw key code, or {@code null} if the
+ * raw key code does not represent a modifier.
+ */
+ private HardwareSwitch getKey( final NativeKeyEvent e ) {
+ return mModifierCodes.get( e.getRawCode() );
+ }
+
+ @SuppressWarnings("unused")
+ private void log( final String s, final NativeKeyEvent e ) {
+ System.out.printf( "%s: %d %s%n", s, e.getRawCode(), getDisplayText( e ) );
}
}
src/main/com/whitemagicsoftware/kmcaster/ui/Constants.java
* the width is large enough, the application's window will adjust to fit.
*/
- public static final Dimension APP_DIMENSIONS = new Dimension( 1024, 256 );
+ public static final Dimension APP_DIMENSIONS = new Dimension( 1024, 100 );
/**

Add delay for keyboard release

Author DaveJarvis <email>
Date 2020-07-25 14:50:27 GMT-0700
Commit 1f594f9f80c39074b4f447b67ff707b169b7bfab
Parent 4b6efe6
Delta 118 lines added, 40 lines removed, 78-line increase