| Author | DaveJarvis <email> |
|---|---|
| Date | 2020-08-03 18:01:33 GMT-0700 |
| Commit | 6d53434084967b7aac127bccc2f9fabd03361bae |
| Parent | d961a97 |
| ``` | ||
| +To see the configuration options, run the program as follows: | ||
| + | ||
| +``` bash | ||
| +java -jar kmcaster.jar -h | ||
| +``` | ||
| + | ||
| +To quit the application: | ||
| + | ||
| +1. Click the application to give it focus. | ||
| +1. Press `Alt+F4` to exit. | ||
| + | ||
| ## Error Messages | ||
| import java.util.Map; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareState.SWITCH_PRESSED; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareState.SWITCH_RELEASED; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareSwitch.KEY_REGULAR; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareSwitch.MOUSE_UNDEFINED; | ||
| +import static com.whitemagicsoftware.kmcaster.HardwareState.*; | ||
| +import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*; | ||
| import static com.whitemagicsoftware.kmcaster.LabelConfig.*; | ||
| import static com.whitemagicsoftware.kmcaster.ui.Constants.*; | ||
| */ | ||
| private static final HardwareSwitchState MOUSE_RELEASED = | ||
| - new HardwareSwitchState( MOUSE_UNDEFINED, SWITCH_RELEASED ); | ||
| + new HardwareSwitchState( MOUSE_EXTRA, SWITCH_RELEASED ); | ||
| private final HardwareImages mHardwareImages; | ||
| private final AutofitLabel[] mLabels = new AutofitLabel[ LabelConfig.size() ]; | ||
| private final Map<HardwareSwitch, ResetTimer> mTimers = new HashMap<>(); | ||
| public EventHandler( | ||
| - final HardwareImages hardwareImages, final UserSettings userSettings ) { | ||
| + final HardwareImages hardwareImages, final Settings userSettings ) { | ||
| mHardwareImages = hardwareImages; | ||
| mLabels[ config.ordinal() ] = label; | ||
| - config.getHardwareSwitch().ifPresentOrElse( | ||
| + final var hwSwitch = config.getHardwareSwitch(); | ||
| + | ||
| + hwSwitch.ifPresentOrElse( | ||
| s -> mHardwareImages.get( s ).add( label ), | ||
| () -> mHardwareImages.get( KEY_REGULAR ).add( label ) | ||
| ); | ||
| - } | ||
| - | ||
| - final var delayModifier = userSettings.getDelayKeyModifier(); | ||
| - final var delayRegular = userSettings.getDelayKeyRegular(); | ||
| - final var delayButton = userSettings.getDelayMouseButton(); | ||
| - | ||
| - for( final var key : HardwareSwitch.keyboardSwitches() ) { | ||
| - final var delay = key.isModifier() ? delayModifier : delayRegular; | ||
| - | ||
| - mTimers.put( key, new ResetTimer( delay ) ); | ||
| } | ||
| - for( final var key : HardwareSwitch.mouseSwitches() ) { | ||
| - mTimers.put( key, new ResetTimer( delayButton ) ); | ||
| - } | ||
| + putTimers( modifierSwitches(), userSettings.getDelayKeyModifier() ); | ||
| + putTimers( regularSwitches(), userSettings.getDelayKeyRegular() ); | ||
| + putTimers( mouseSwitches(), userSettings.getDelayMouseButton() ); | ||
| + putTimers( scrollSwitches(), userSettings.getDelayMouseScroll() ); | ||
| } | ||
| } | ||
| - private final Deque<HardwareSwitch> mMousePressed = new LinkedList<>(); | ||
| + private final Deque<HardwareSwitch> mMouseActions = new LinkedList<>(); | ||
| /** | ||
| else { | ||
| if( hwState == SWITCH_RELEASED ) { | ||
| - mMousePressed.remove( hwSwitch ); | ||
| + mMouseActions.remove( hwSwitch ); | ||
| timer.addActionListener( | ||
| ( event ) -> updateMouseStatus( switchState ) | ||
| ); | ||
| } | ||
| else { | ||
| timer.stop(); | ||
| - mMousePressed.add( hwSwitch ); | ||
| + mMouseActions.add( hwSwitch ); | ||
| updateMouseStatus( switchState ); | ||
| + | ||
| + if( hwSwitch.isScroll() ) { | ||
| + timer.addActionListener( | ||
| + ( action ) -> { | ||
| + final var sauce = e.getSource(); | ||
| + final var name = e.getPropertyName(); | ||
| + final var event = new PropertyChangeEvent( | ||
| + sauce, name, true, false ); | ||
| + | ||
| + update( event ); | ||
| + } | ||
| + ); | ||
| + } | ||
| } | ||
| } | ||
| private void updateMouseStatus( final HardwareSwitchState switchState ) { | ||
| - final var container = getHardwareComponent( MOUSE_RELEASED ); | ||
| - final var rm = currentManager( container ); | ||
| - final var button = getLabel( LABEL_MOUSE_UNDEFINED ); | ||
| final var hwSwitch = switchState.getHardwareSwitch(); | ||
| final var hwState = switchState.getHardwareState(); | ||
| - if( hwSwitch == MOUSE_UNDEFINED ) { | ||
| + if( hwSwitch == MOUSE_EXTRA ) { | ||
| + final var config = LabelConfig.valueFrom( hwSwitch ); | ||
| + final var button = getLabel( config ); | ||
| + | ||
| if( hwState == SWITCH_PRESSED ) { | ||
| button.setText( switchState.getValue() ); | ||
| } | ||
| - container.setState( new HardwareSwitchState( hwSwitch, SWITCH_RELEASED ) ); | ||
| - rm.paintDirtyRegions(); | ||
| + final var component = getHardwareComponent( MOUSE_RELEASED ); | ||
| + final var rm = currentManager( component ); | ||
| - for( final var mouseSwitch : mMousePressed ) { | ||
| - final var buttonState = new HardwareSwitchState( | ||
| - mouseSwitch, SWITCH_PRESSED ); | ||
| + component.setState( new HardwareSwitchState( hwSwitch, SWITCH_RELEASED ) ); | ||
| + rm.paintDirtyRegions(); | ||
| - container.setState( buttonState ); | ||
| + for( final var action : mMouseActions ) { | ||
| + component.setState( new HardwareSwitchState( action, SWITCH_PRESSED ) ); | ||
| rm.paintDirtyRegions(); | ||
| } | ||
| final HardwareSwitchState state ) { | ||
| return mHardwareImages.get( state.getHardwareSwitch() ); | ||
| + } | ||
| + | ||
| + private void putTimers( final HardwareSwitch[] hwSwitches, final int delay ) { | ||
| + for( final var hwSwitch : hwSwitches ) { | ||
| + mTimers.put( hwSwitch, new ResetTimer( delay ) ); | ||
| + } | ||
| } | ||
| KEY_SHIFT, new Insets( 10, 50, 12, 11 ), | ||
| KEY_REGULAR, new Insets( 3, 7, 6, 7 ), | ||
| - MOUSE_UNDEFINED, new Insets( 27, 5, 11, 5 ) | ||
| + MOUSE_EXTRA, new Insets( 27, 5, 11, 5 ) | ||
| ); | ||
| mSwitches = new HashMap<>(); | ||
| - public HardwareImages( final UserSettings userSettings ) { | ||
| + public HardwareImages( final Settings userSettings ) { | ||
| mAppDimensions = userSettings.createAppDimensions(); | ||
| final var mouseReleased = mouseImage( "0" ); | ||
| final var mouseScale = mouseReleased.getValue(); | ||
| final var mouseStates = | ||
| - createHardwareComponent( MOUSE_UNDEFINED, mouseScale ); | ||
| + createHardwareComponent( MOUSE_EXTRA, mouseScale ); | ||
| - for( final var key : HardwareSwitch.mouseSwitches() ) { | ||
| - final var stateOn = state( key, SWITCH_PRESSED ); | ||
| - final var stateOff = state( key, SWITCH_RELEASED ); | ||
| + for( final var hwSwitch : mouseSwitches() ) { | ||
| + final var stateOn = state( hwSwitch, SWITCH_PRESSED ); | ||
| + final var stateOff = state( hwSwitch, SWITCH_RELEASED ); | ||
| + final var imageDn = mouseImage( hwSwitch.toString() ); | ||
| - final var imageDn = mouseImage( key.toString() ); | ||
| mouseStates.put( stateOn, imageDn.getKey() ); | ||
| mouseStates.put( stateOff, mouseReleased.getKey() ); | ||
| - mSwitches.put( key, mouseStates ); | ||
| + mSwitches.put( hwSwitch, mouseStates ); | ||
| } | ||
| - for( final var key : HardwareSwitch.keyboardSwitches() ) { | ||
| + for( final var key : keyboardSwitches() ) { | ||
| final var stateOn = state( key, SWITCH_PRESSED ); | ||
| final var stateOff = state( key, SWITCH_RELEASED ); | ||
| final var imageDn = keyDnImage( FILE_NAME_PREFIXES.get( key ) ); | ||
| final var imageUp = keyUpImage( FILE_NAME_PREFIXES.get( key ) ); | ||
| final var scale = imageDn.getValue(); | ||
| final var keyStates = createHardwareComponent( key, scale ); | ||
| keyStates.put( stateOn, imageDn.getKey() ); | ||
| keyStates.put( stateOff, imageUp.getKey() ); | ||
| - | ||
| mSwitches.put( key, keyStates ); | ||
| } | ||
| package com.whitemagicsoftware.kmcaster; | ||
| -import static java.lang.Boolean.*; | ||
| +import static java.lang.Boolean.FALSE; | ||
| /** | ||
| * Responsible for defining hardware switch states. | ||
| */ | ||
| public enum HardwareState { | ||
| /** | ||
| - * Defines when a hardware switch is down. | ||
| + * Indicates a hardware switch is down. | ||
| */ | ||
| SWITCH_PRESSED, | ||
| /** | ||
| - * Defines when a hardware switch is up. | ||
| + * Indicates a hardware switch is up. | ||
| */ | ||
| SWITCH_RELEASED; |
| MOUSE_MIDDLE( "2" ), | ||
| MOUSE_RIGHT( "3" ), | ||
| - MOUSE_UNDEFINED( "undefined" ), | ||
| + MOUSE_EXTRA( "extra" ), | ||
| + MOUSE_SCROLL_U( "u" ), | ||
| + MOUSE_SCROLL_D( "d" ), | ||
| + MOUSE_SCROLL_L( "l" ), | ||
| + MOUSE_SCROLL_R( "r" ), | ||
| KEY_SHIFT( "shift", SHIFT_MASK ), | ||
| KEY_CTRL( "ctrl", CTRL_MASK ), | ||
| KEY_ALT( "alt", ALT_MASK ), | ||
| KEY_REGULAR( "regular" ); | ||
| + | ||
| + private final static HardwareSwitch[] mMouseSwitches = { | ||
| + MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, MOUSE_EXTRA, | ||
| + MOUSE_SCROLL_U, MOUSE_SCROLL_D, MOUSE_SCROLL_L, MOUSE_SCROLL_R | ||
| + }; | ||
| + | ||
| + private final static HardwareSwitch[] mScrollSwitches = { | ||
| + MOUSE_SCROLL_U, MOUSE_SCROLL_D, MOUSE_SCROLL_L, MOUSE_SCROLL_R | ||
| + }; | ||
| + | ||
| + private final static HardwareSwitch[] mKeyboardSwitches = | ||
| + {KEY_SHIFT, KEY_CTRL, KEY_ALT, KEY_REGULAR}; | ||
| + | ||
| + private final static HardwareSwitch[] mModifierSwitches = | ||
| + {KEY_SHIFT, KEY_CTRL, KEY_ALT}; | ||
| + | ||
| + private final static HardwareSwitch[] mRegularSwitches = | ||
| + {KEY_REGULAR}; | ||
| /** | ||
| */ | ||
| public boolean isKeyboard() { | ||
| - return name().startsWith( "KEY" ); | ||
| + return isPrefix( "KEY" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether this hardware switch represents a scroll "button". | ||
| + * | ||
| + * @return {@code true} to indicate a scrolling action occurred. | ||
| + */ | ||
| + public boolean isScroll() { | ||
| + return isPrefix( "MOUSE_SCROLL" ); | ||
| + } | ||
| + | ||
| + private boolean isPrefix( final String key ) { | ||
| + return name().startsWith( key ); | ||
| } | ||
| */ | ||
| public static HardwareSwitch[] keyboardSwitches() { | ||
| - return new HardwareSwitch[]{KEY_SHIFT, KEY_CTRL, KEY_ALT, KEY_REGULAR}; | ||
| + return mKeyboardSwitches; | ||
| } | ||
| public static HardwareSwitch[] modifierSwitches() { | ||
| - return new HardwareSwitch[]{KEY_SHIFT, KEY_CTRL, KEY_ALT}; | ||
| + return mModifierSwitches; | ||
| + } | ||
| + | ||
| + public static HardwareSwitch[] regularSwitches() { | ||
| + return mRegularSwitches; | ||
| } | ||
| /** | ||
| * Returns a list of mouse buttons. | ||
| * | ||
| - * @return The complete list of mouse buttons. | ||
| + * @return The complete list of mouse and scroll buttons. | ||
| */ | ||
| public static HardwareSwitch[] mouseSwitches() { | ||
| - return new HardwareSwitch[]{ | ||
| - MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, MOUSE_UNDEFINED | ||
| - }; | ||
| + return mMouseSwitches; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of scrolling direction indicators. | ||
| + * | ||
| + * @return The complete list of scroll direction indicators. | ||
| + */ | ||
| + public static HardwareSwitch[] scrollSwitches() { | ||
| + return mScrollSwitches; | ||
| } | ||
| public final class KmCaster extends JFrame { | ||
| - private final UserSettings mUserSettings = new UserSettings( this ); | ||
| + private final Settings mUserSettings = new Settings( this ); | ||
| /** | ||
| } | ||
| - private UserSettings getUserSettings() { | ||
| + private Settings getUserSettings() { | ||
| return mUserSettings; | ||
| } | ||
| import javax.swing.*; | ||
| +import java.util.NoSuchElementException; | ||
| import java.util.Optional; | ||
| LABEL_REGULAR_NUM_SUPERSCRIPT( TOP, LEFT ), | ||
| LABEL_REGULAR_COUNTER( TOP, RIGHT ), | ||
| - LABEL_MOUSE_UNDEFINED( MOUSE_UNDEFINED ); | ||
| + LABEL_MOUSE_OTHER( MOUSE_EXTRA ); | ||
| /** | ||
| mVerticalAlign = vAlign; | ||
| mHorizontalAlign = hAlign; | ||
| + } | ||
| + | ||
| + static LabelConfig valueFrom( final HardwareSwitch hwSwitch ) { | ||
| + for( final var lc : values() ) { | ||
| + if( lc.isHardwareSwitch( hwSwitch ) ) { | ||
| + return lc; | ||
| + } | ||
| + } | ||
| + | ||
| + throw new NoSuchElementException( hwSwitch.toTitleCase() ); | ||
| } | ||
| String toTitleCase() { | ||
| return mHardwareSwitch == null ? " " : mHardwareSwitch.toTitleCase(); | ||
| + } | ||
| + | ||
| + private boolean isHardwareSwitch( final HardwareSwitch hwSwitch ) { | ||
| + final var os = getHardwareSwitch(); | ||
| + return os.isPresent() && os.get() == hwSwitch; | ||
| } | ||
| +/* | ||
| + * Copyright 2020 White Magic Software, Ltd. | ||
| + * | ||
| + * All rights reserved. | ||
| + * | ||
| + * Redistribution and use in source and binary forms, with or without | ||
| + * modification, are permitted provided that the following conditions are met: | ||
| + * | ||
| + * o Redistributions of source code must retain the above copyright | ||
| + * notice, this list of conditions and the following disclaimer. | ||
| + * | ||
| + * o Redistributions in binary form must reproduce the above copyright | ||
| + * notice, this list of conditions and the following disclaimer in the | ||
| + * documentation and/or other materials provided with the distribution. | ||
| + * | ||
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| + */ | ||
| +package com.whitemagicsoftware.kmcaster; | ||
| + | ||
| +import picocli.CommandLine; | ||
| + | ||
| +import java.awt.*; | ||
| +import java.util.concurrent.Callable; | ||
| + | ||
| +@CommandLine.Command( | ||
| + name = "KmCaster", | ||
| + mixinStandardHelpOptions = true, | ||
| + description = "Displays key presses and mouse clicks on the screen." | ||
| +) | ||
| +@SuppressWarnings("FieldMayBeFinal") | ||
| +public final class Settings implements Callable<Integer> { | ||
| + /** | ||
| + * Minimum application height, in pixels. | ||
| + */ | ||
| + private static final int MIN_HEIGHT_PX = 20; | ||
| + | ||
| + /** | ||
| + * Executable class. | ||
| + */ | ||
| + private final KmCaster mKmCaster; | ||
| + | ||
| + /** | ||
| + * Application height in pixels. Images are scaled to this height, maintaining | ||
| + * aspect ratio. The height constrains the width, so as long as the width | ||
| + * is large enough, the application's window will adjust to fit. | ||
| + */ | ||
| + @CommandLine.Option( | ||
| + names = {"-d", "--dimension"}, | ||
| + description = | ||
| + "Application height (${DEFAULT-VALUE} pixels)", | ||
| + paramLabel = "height", | ||
| + defaultValue = "100" | ||
| + ) | ||
| + private int mHeight = 100; | ||
| + | ||
| + /** | ||
| + * Milliseconds to wait before releasing (clearing) a regular key. | ||
| + */ | ||
| + @CommandLine.Option( | ||
| + names = {"-a", "--delay-alphanum"}, | ||
| + description = | ||
| + "Regular key release delay (${DEFAULT-VALUE} milliseconds)", | ||
| + paramLabel = "delay", | ||
| + defaultValue = "250" | ||
| + ) | ||
| + private int mDelayKeyRegular = 250; | ||
| + | ||
| + /** | ||
| + * Milliseconds to wait before releasing (clearing) any modifier key. | ||
| + */ | ||
| + @CommandLine.Option( | ||
| + names = {"-m", "--delay-modifier"}, | ||
| + description = | ||
| + "Modifier key release delay (${DEFAULT-VALUE} milliseconds)", | ||
| + paramLabel = "delay", | ||
| + defaultValue = "150" | ||
| + ) | ||
| + private int mDelayKeyModifier = 150; | ||
| + | ||
| + /** | ||
| + * Milliseconds to wait before releasing (clearing) a mouse button. | ||
| + */ | ||
| + @CommandLine.Option( | ||
| + names = {"-b", "--delay-button"}, | ||
| + description = | ||
| + "Mouse button release delay (${DEFAULT-VALUE} milliseconds)", | ||
| + paramLabel = "delay", | ||
| + defaultValue = "100" | ||
| + ) | ||
| + private int mDelayMouseButton = 100; | ||
| + | ||
| + /** | ||
| + * Milliseconds to wait before releasing (clearing) a mouse scroll event. | ||
| + */ | ||
| + @CommandLine.Option( | ||
| + names = {"-s", "--delay-scroll"}, | ||
| + description = | ||
| + "Mouse scroll release delay (${DEFAULT-VALUE} milliseconds)", | ||
| + paramLabel = "delay", | ||
| + defaultValue = "300" | ||
| + ) | ||
| + private int mDelayMouseScroll = 300; | ||
| + | ||
| + public Settings( final KmCaster kmCaster ) { | ||
| + assert kmCaster != null; | ||
| + | ||
| + mKmCaster = kmCaster; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Invoked after the command-line arguments are parsed to launch the | ||
| + * application. | ||
| + * | ||
| + * @return Exit level zero. | ||
| + */ | ||
| + @Override | ||
| + public Integer call() { | ||
| + mKmCaster.init(); | ||
| + return 0; | ||
| + } | ||
| + | ||
| + public int getDelayKeyRegular() { | ||
| + return mDelayKeyRegular; | ||
| + } | ||
| + | ||
| + public int getDelayKeyModifier() { | ||
| + return mDelayKeyModifier; | ||
| + } | ||
| + | ||
| + public int getDelayMouseButton() { | ||
| + return mDelayMouseButton; | ||
| + } | ||
| + | ||
| + public int getDelayMouseScroll() { | ||
| + return mDelayMouseScroll; | ||
| + } | ||
| + | ||
| + public Dimension createAppDimensions() { | ||
| + return new Dimension( 1024 + getHeight(), getHeight() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * This will return the user-specified height | ||
| + * | ||
| + * @return The application height, in pixels. | ||
| + */ | ||
| + private int getHeight() { | ||
| + return mHeight < MIN_HEIGHT_PX ? MIN_HEIGHT_PX : mHeight; | ||
| + } | ||
| +} | ||
| -/* | ||
| - * Copyright 2020 White Magic Software, Ltd. | ||
| - * | ||
| - * All rights reserved. | ||
| - * | ||
| - * Redistribution and use in source and binary forms, with or without | ||
| - * modification, are permitted provided that the following conditions are met: | ||
| - * | ||
| - * o Redistributions of source code must retain the above copyright | ||
| - * notice, this list of conditions and the following disclaimer. | ||
| - * | ||
| - * o Redistributions in binary form must reproduce the above copyright | ||
| - * notice, this list of conditions and the following disclaimer in the | ||
| - * documentation and/or other materials provided with the distribution. | ||
| - * | ||
| - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| - */ | ||
| -package com.whitemagicsoftware.kmcaster; | ||
| - | ||
| -import picocli.CommandLine; | ||
| - | ||
| -import java.awt.*; | ||
| -import java.util.concurrent.Callable; | ||
| - | ||
| -@CommandLine.Command( | ||
| - name = "KmCaster", | ||
| - mixinStandardHelpOptions = true, | ||
| - description = "Displays key presses and mouse clicks on the screen." | ||
| -) | ||
| -@SuppressWarnings("FieldMayBeFinal") | ||
| -public final class UserSettings implements Callable<Integer> { | ||
| - /** | ||
| - * Minimum application height, in pixels. | ||
| - */ | ||
| - private static final int MIN_HEIGHT_PX = 20; | ||
| - | ||
| - /** | ||
| - * Executable class. | ||
| - */ | ||
| - private final KmCaster mKmCaster; | ||
| - | ||
| - /** | ||
| - * Application height in pixels. Images are scaled to this height, maintaining | ||
| - * aspect ratio. The height constrains the width, so as long as the width | ||
| - * is large enough, the application's window will adjust to fit. | ||
| - */ | ||
| - @CommandLine.Option( | ||
| - names = {"-s", "--size"}, | ||
| - description = | ||
| - "Application size (${DEFAULT-VALUE} pixels)", | ||
| - paramLabel = "height", | ||
| - defaultValue = "100" | ||
| - ) | ||
| - private int mHeight = 100; | ||
| - | ||
| - /** | ||
| - * Milliseconds to wait before releasing (clearing) a regular key. | ||
| - */ | ||
| - @CommandLine.Option( | ||
| - names = {"-a", "--delay-alphanum"}, | ||
| - description = | ||
| - "Regular key release delay (${DEFAULT-VALUE} milliseconds)", | ||
| - paramLabel = "delay", | ||
| - defaultValue = "250" | ||
| - ) | ||
| - private int mDelayKeyRegular = 250; | ||
| - | ||
| - /** | ||
| - * Milliseconds to wait before releasing (clearing) any modifier key. | ||
| - */ | ||
| - @CommandLine.Option( | ||
| - names = {"-m", "--delay-modifier"}, | ||
| - description = | ||
| - "Modifier key release delay (${DEFAULT-VALUE} milliseconds)", | ||
| - paramLabel = "delay", | ||
| - defaultValue = "150" | ||
| - ) | ||
| - private int mDelayKeyModifier = 150; | ||
| - | ||
| - /** | ||
| - * Milliseconds to wait before releasing (clearing) a mouse button. | ||
| - */ | ||
| - @CommandLine.Option( | ||
| - names = {"-b", "--delay-button"}, | ||
| - description = | ||
| - "Mouse button release delay (${DEFAULT-VALUE} milliseconds)", | ||
| - paramLabel = "delay", | ||
| - defaultValue = "100" | ||
| - ) | ||
| - private int mDelayMouseButton = 100; | ||
| - | ||
| - public UserSettings( final KmCaster kmCaster ) { | ||
| - assert kmCaster != null; | ||
| - | ||
| - mKmCaster = kmCaster; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Invoked after the command-line arguments are parsed to launch the | ||
| - * application. | ||
| - * | ||
| - * @return Exit level zero. | ||
| - */ | ||
| - @Override | ||
| - public Integer call() { | ||
| - mKmCaster.init(); | ||
| - return 0; | ||
| - } | ||
| - | ||
| - public int getDelayKeyRegular() { | ||
| - return mDelayKeyRegular; | ||
| - } | ||
| - | ||
| - public int getDelayKeyModifier() { | ||
| - return mDelayKeyModifier; | ||
| - } | ||
| - | ||
| - public int getDelayMouseButton() { | ||
| - return mDelayMouseButton; | ||
| - } | ||
| - | ||
| - public Dimension createAppDimensions() { | ||
| - return new Dimension( 1024 + getHeight(), getHeight() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * This will return the user-specified height | ||
| - * | ||
| - * @return The application height, in pixels. | ||
| - */ | ||
| - private int getHeight() { | ||
| - return mHeight < MIN_HEIGHT_PX ? MIN_HEIGHT_PX : mHeight; | ||
| - } | ||
| -} | ||
| import com.whitemagicsoftware.kmcaster.HardwareSwitch; | ||
| +import com.whitemagicsoftware.kmcaster.util.Pair; | ||
| import org.jnativehook.mouse.NativeMouseEvent; | ||
| import org.jnativehook.mouse.NativeMouseInputListener; | ||
| import org.jnativehook.mouse.NativeMouseWheelEvent; | ||
| import org.jnativehook.mouse.NativeMouseWheelListener; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareSwitch.MOUSE_UNDEFINED; | ||
| -import static com.whitemagicsoftware.kmcaster.HardwareSwitch.mouseSwitches; | ||
| +import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*; | ||
| import static java.util.Map.entry; | ||
| +import static org.jnativehook.mouse.NativeMouseWheelEvent.WHEEL_HORIZONTAL_DIRECTION; | ||
| +import static org.jnativehook.mouse.NativeMouseWheelEvent.WHEEL_VERTICAL_DIRECTION; | ||
| /** | ||
| * Listens for all mouse events: clicks and mouse wheel scrolls. | ||
| */ | ||
| public final class MouseListener | ||
| extends PropertyDispatcher<HardwareSwitch> | ||
| implements NativeMouseInputListener, NativeMouseWheelListener { | ||
| - private final static Map<Integer, String> SCROLL_CODES = | ||
| - Map.ofEntries( | ||
| - entry( 1, "↑" ), | ||
| - entry( -1, "↓" ) | ||
| - ); | ||
| + private final static Map<Pair<Integer, Integer>, HardwareSwitch> | ||
| + SCROLL_CODES = Map.ofEntries( | ||
| + entry( new Pair<>( WHEEL_VERTICAL_DIRECTION, -1 ), MOUSE_SCROLL_U ), | ||
| + entry( new Pair<>( WHEEL_VERTICAL_DIRECTION, 1 ), MOUSE_SCROLL_D ), | ||
| + entry( new Pair<>( WHEEL_HORIZONTAL_DIRECTION, -1 ), MOUSE_SCROLL_L ), | ||
| + entry( new Pair<>( WHEEL_HORIZONTAL_DIRECTION, 1 ), MOUSE_SCROLL_R ) | ||
| + ); | ||
| + | ||
| + /** | ||
| + * Most recently pressed non-mapped button value, empty signifies release. | ||
| + */ | ||
| + private String mExtra = ""; | ||
| /** | ||
| * Stores the state of button presses. The contents of the map reflect the | ||
| * state of each switch, so the reference can be final but not its contents. | ||
| */ | ||
| private final Map<HardwareSwitch, Boolean> mSwitches = new HashMap<>(); | ||
| + /** | ||
| + * Initializes the mouse switches to a released state. | ||
| + */ | ||
| public MouseListener() { | ||
| for( final var key : mouseSwitches() ) { | ||
| mSwitches.put( key, false ); | ||
| } | ||
| } | ||
| public void nativeMousePressed( final NativeMouseEvent e ) { | ||
| - dispatchMouseEvent( e, true ); | ||
| + dispatchButtonEvent( e, true ); | ||
| } | ||
| public void nativeMouseReleased( final NativeMouseEvent e ) { | ||
| - dispatchMouseEvent( e, false ); | ||
| + dispatchButtonEvent( e, false ); | ||
| } | ||
| public void nativeMouseWheelMoved( final NativeMouseWheelEvent e ) { | ||
| - //dispatchMouseEvent( e, e.getWheelRotation() ); | ||
| - } | ||
| + final var pair = new Pair<>( e.getWheelDirection(), e.getWheelRotation() ); | ||
| + final var scrollSwitch = SCROLL_CODES.get( pair ); | ||
| - /** | ||
| - * Most recently pressed non-mapped button value, empty signifies release. | ||
| - */ | ||
| - private String mUndefined = ""; | ||
| + for( final var hwSwitch : scrollSwitches() ) { | ||
| + if( mSwitches.get( hwSwitch ) ) { | ||
| + tryFire( hwSwitch, true, false ); | ||
| + mSwitches.put( hwSwitch, false ); | ||
| + } | ||
| + } | ||
| + | ||
| + tryFire( scrollSwitch, mSwitches.get( scrollSwitch ), true ); | ||
| + mSwitches.put( scrollSwitch, true ); | ||
| + } | ||
| /** | ||
| * Called to send a mouse event to all listeners. | ||
| * | ||
| * @param e The mouse event that was most recently triggered. | ||
| * @param pressed {@code true} means pressed, {@code false} means released. | ||
| */ | ||
| - private void dispatchMouseEvent( | ||
| + private void dispatchButtonEvent( | ||
| final NativeMouseEvent e, final boolean pressed ) { | ||
| final var hwSwitch = getMouseSwitch( e ); | ||
| // Percolate the button number as a string for any undefined (unmapped) | ||
| // mouse buttons that are clicked. This enables additional mouse | ||
| // buttons beyond two to appear, without an image representation. | ||
| - if( hwSwitch == MOUSE_UNDEFINED ) { | ||
| + if( hwSwitch == MOUSE_EXTRA ) { | ||
| final var button = Integer.toString( e.getButton() ); | ||
| final var n = pressed ? button : ""; | ||
| - final var o = pressed ? mUndefined : button; | ||
| + final var o = pressed ? mExtra : button; | ||
| fire( hwSwitch, o, n ); | ||
| - mUndefined = n; | ||
| + mExtra = n; | ||
| } | ||
| else { | ||
| return switch( button ) { | ||
| case 1, 2, 3 -> HardwareSwitch.valueFrom( Integer.toString( button ) ); | ||
| - default -> MOUSE_UNDEFINED; | ||
| + default -> MOUSE_EXTRA; | ||
| }; | ||
| } | ||
| -<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><radialGradient id="a" cx="193.4" cy="181.62" gradientTransform="matrix(.13524 0 0 .34162 8.5772 -48.069)" gradientUnits="userSpaceOnUse" r="68.141" spreadMethod="reflect" xlink:href="#b"/><linearGradient id="b"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#666"/></linearGradient><radialGradient id="c" cx="85.528" cy="170.14" gradientTransform="matrix(.14849 0 0 .25527 8.1707 -36.461)" gradientUnits="userSpaceOnUse" r="252.08" spreadMethod="reflect" xlink:href="#b"/><linearGradient id="d" gradientTransform="matrix(.13405 0 0 .2832 8.3022 -37.262)" gradientUnits="userSpaceOnUse" spreadMethod="reflect" x1="193.63" x2="193.63" y1="161.65" y2="162.42"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#fff"/></linearGradient><radialGradient id="e" cx="158.03" cy="169.2" gradientTransform="matrix(.13493 0 0 .28136 8.3022 -37.262)" gradientUnits="userSpaceOnUse" r="31.163" spreadMethod="reflect"><stop offset="0" stop-color="#b3b3eb" stop-opacity="0"/><stop offset="1"/></radialGradient><g transform="translate(-14.6 .6541)"><path d="m32.103 59.075c-14.905 0-17.159-12.11-17.004-15.947.22946-3.6188.42034-18.176-.23806-30.434.16776-13.396 11.35-13.076 17.24-13.076 5.8896 0 17.072-.32021 17.24 13.076-.65838 12.258-.46751 26.815-.23806 30.433.1542 3.8367-2.0993 15.947-17.004 15.947" style="fill-rule:evenodd;stroke:#000;stroke-linejoin:round;stroke-opacity:.19772;stroke-width:.53116;fill:url(#c)"/><ellipse cx="32.083" cy="10.963" fill="url(#a)" fill-rule="evenodd" rx="3.1799" ry="8.0323"/><g fill="none" stroke="#484848"><path d="m32.105 21.772c-8.1608 0-12.278.18287-14.374.37127-2.0963.1884-2.0388.39852-2.2514.40444" stroke-width=".53125"/><path d="m32.104 21.772c8.1608 0 12.278.18287 14.374.37127 2.0963.1884 2.0388.39852 2.2514.40444" stroke-width=".53125"/><path d="m32.098-.10793v21.716" stroke-width=".53116"/></g><g fill-rule="evenodd"><path d="m34.434 11.065a2.3354 4.9339 0 1 1 -4.6708 0 2.3354 4.9339 0 1 1 4.6708 0z" fill="url(#d)"/><path d="m34.434 11.065a2.3354 4.9339 0 1 1 -4.6708 0 2.3354 4.9339 0 1 1 4.6708 0z" fill="url(#e)" stroke="#000" stroke-width=".10716"/></g></g></svg> | ||
| +<?xml version="1.0" encoding="UTF-8"?> | ||
| +<svg version="1.1" viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||
| + <radialGradient id="a" cx="193.4" cy="181.62" r="68.141" gradientTransform="matrix(.13524 0 0 .34162 -6.0078 -47.415)" gradientUnits="userSpaceOnUse" spreadMethod="reflect" xlink:href="#b"/> | ||
| + <linearGradient id="b"> | ||
| + <stop stop-color="#fff" offset="0"/> | ||
| + <stop stop-color="#666" offset="1"/> | ||
| + </linearGradient> | ||
| + <radialGradient id="c" cx="85.528" cy="170.14" r="252.08" gradientTransform="matrix(.14849 0 0 .25527 -6.4293 -35.807)" gradientUnits="userSpaceOnUse" spreadMethod="reflect" xlink:href="#b"/> | ||
| + <linearGradient id="d" x1="193.63" x2="193.63" y1="161.65" y2="162.42" gradientTransform="matrix(.13405 0 0 .2832 -6.2984 -36.608)" gradientUnits="userSpaceOnUse" spreadMethod="reflect"> | ||
| + <stop stop-color="#333" offset="0"/> | ||
| + <stop stop-color="#fff" offset="1"/> | ||
| + </linearGradient> | ||
| + <radialGradient id="e" cx="158.03" cy="169.2" r="31.163" gradientTransform="matrix(.13493 0 0 .28136 -6.2984 -36.608)" gradientUnits="userSpaceOnUse" spreadMethod="reflect"> | ||
| + <stop stop-color="#b3b3eb" stop-opacity="0" offset="0"/> | ||
| + <stop offset="1"/> | ||
| + </radialGradient> | ||
| + <path d="m17.503 59.729c-14.905 0-17.159-12.11-17.004-15.947 0.22946-3.6188 0.42034-18.176-0.23806-30.434 0.16776-13.396 11.35-13.076 17.24-13.076 5.8896 0 17.072-0.32021 17.24 13.076-0.65838 12.258-0.46751 26.815-0.23806 30.433 0.1542 3.8367-2.0993 15.947-17.004 15.947" fill="url(#c)" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-opacity=".19772" stroke-width=".53116"/> | ||
| + <ellipse cx="17.498" cy="11.617" rx="3.1799" ry="8.0323" fill="url(#a)" fill-rule="evenodd"/> | ||
| + <g fill="none" stroke="#484848"> | ||
| + <path d="m17.505 22.426c-8.1608 0-12.278 0.18287-14.374 0.37127-2.0963 0.1884-2.0388 0.39852-2.2514 0.40444" stroke-width=".53125"/> | ||
| + <path d="m17.504 22.426c8.1608 0 12.278 0.18287 14.374 0.37127 2.0963 0.1884 2.0388 0.39852 2.2514 0.40444" stroke-width=".53125"/> | ||
| + <path d="m17.498 0.54617v21.716" stroke-width=".53116"/> | ||
| + </g> | ||
| + <path d="m19.833 11.719a2.3354 4.9339 0 1 1-4.6708 0 2.3354 4.9339 0 1 1 4.6708 0z" fill="url(#d)" fill-rule="evenodd"/> | ||
| + <path d="m19.833 11.719a2.3354 4.9339 0 1 1-4.6708 0 2.3354 4.9339 0 1 1 4.6708 0z" fill="url(#e)" fill-rule="evenodd" stroke="#000" stroke-width=".10716"/> | ||
| +</svg> | ||
| + |
| +<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m9.4655 10.991h4.9429v-5.6695h6.1788v5.6743h4.9432l-8.0327 6.9166z" fill="#333" fill-opacity=".8"/></svg> | ||
| - | ||
| +<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m17.502 59.388c-14.667 0-16.886-11.917-16.733-15.693.2258-3.5611.06598-20.108.06598-20.108s-.03406-.98114 16.665-.98698c16.626 0 16.665.98698 16.665.98698s-.15981 16.547.06598 20.107c.15174 3.7756-2.0658 15.693-16.733 15.693" fill="#feff00" fill-opacity=".25098" fill-rule="evenodd"/></svg> |
| +<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m18.124 19.65v-4.9429h5.6695v-6.1788h-5.6743v-4.9432l-6.9166 8.0327z" fill="#333" fill-opacity=".8"/></svg> | ||
| +<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m16.872 19.65v-4.9429h-5.6695v-6.1788h5.6743v-4.9432l6.9166 8.0327z" fill="#333" fill-opacity=".8"/></svg> | ||
| +<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m9.4655 12.243h4.9429v5.6695h6.1788v-5.6743h4.9432l-8.0327-6.9166z" fill="#333" fill-opacity=".8"/></svg> | ||
| -<svg viewBox="0 0 35 60" xmlns="http://www.w3.org/2000/svg"><path d="m17.502 59.388c-14.667 0-16.886-11.917-16.733-15.693.2258-3.5611.06598-20.108.06598-20.108s-.03406-.98114 16.665-.98698c16.626 0 16.665.98698 16.665.98698s-.15981 16.547.06598 20.107c.15174 3.7756-2.0658 15.693-16.733 15.693" fill="#feff00" fill-opacity=".25098" fill-rule="evenodd"/></svg> | ||
| + |
| Delta | 370 lines added, 224 lines removed, 146-line increase |
|---|