From a09abe5da573f99484cb3100f8227681c3205d55 Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Mon, 18 May 2026 11:28:06 +0200 Subject: [PATCH 1/6] Abstracting platform-specific code --- src/main/java/mousemaster/ComboWatcher.java | 1 + src/main/java/mousemaster/GridManager.java | 13 +- src/main/java/mousemaster/Hint.java | 2 +- src/main/java/mousemaster/HintManager.java | 44 ++++--- .../java/mousemaster/IndicatorManager.java | 13 +- src/main/java/mousemaster/KeyboardLayout.java | 1 + .../java/mousemaster/KeyboardManager.java | 1 + src/main/java/mousemaster/MacroPlayer.java | 21 +-- .../java/mousemaster/MouseController.java | 63 ++++----- src/main/java/mousemaster/Mousemaster.java | 40 +++--- .../mousemaster/MousemasterApplication.java | 24 +--- src/main/java/mousemaster/Platform.java | 28 +++- src/main/java/mousemaster/ScreenManager.java | 9 +- src/main/java/mousemaster/ZoomManager.java | 25 ++-- .../mousemaster/platform/ActiveAppFinder.java | 8 ++ .../java/mousemaster/platform/Console.java | 8 ++ .../mousemaster/platform/KeyRegurgitator.java | 9 ++ .../java/mousemaster/platform/Keyboard.java | 23 ++++ .../platform/KeyboardLayoutProvider.java | 9 ++ src/main/java/mousemaster/platform/Mouse.java | 33 +++++ .../java/mousemaster/platform/Overlay.java | 61 +++++++++ .../java/mousemaster/platform/Screens.java | 11 ++ .../mousemaster/platform/UiAutomation.java | 13 ++ .../{ => platform/windows}/Dwmapi.java | 4 +- .../windows}/ExtendedAdvapi32.java | 4 +- .../{ => platform/windows}/ExtendedGDI32.java | 4 +- .../windows}/ExtendedKernel32.java | 4 +- .../{ => platform/windows}/ExtendedPsapi.java | 4 +- .../windows}/ExtendedUser32.java | 4 +- .../{ => platform/windows}/FadeAnimator.java | 3 +- .../{ => platform/windows}/Gdiplus.java | 4 +- .../windows}/KbdlayoutinfoParser.java | 4 +- .../{ => platform/windows}/Magnification.java | 4 +- .../{ => platform/windows}/Shcore.java | 4 +- .../windows/WindowsActiveAppFinder.java} | 10 +- .../{ => platform/windows}/WindowsClock.java | 4 +- .../platform/windows/WindowsConsole.java | 25 ++++ .../windows/WindowsKeyRegurgitator.java} | 21 ++- .../windows}/WindowsKeyboard.java | 4 +- .../windows/WindowsKeyboardAdapter.java | 46 +++++++ .../{ => platform/windows}/WindowsMouse.java | 4 +- .../platform/windows/WindowsMouseAdapter.java | 79 ++++++++++++ .../windows}/WindowsOverlay.java | 8 +- .../windows/WindowsOverlayAdapter.java | 122 ++++++++++++++++++ .../windows}/WindowsPlatform.java | 72 +++++++++-- .../{ => platform/windows}/WindowsScreen.java | 4 +- .../platform/windows/WindowsScreens.java | 16 +++ .../windows}/WindowsUiAccess.java | 4 +- .../windows}/WindowsUiAutomation.java | 8 +- .../windows/WindowsUiAutomationAdapter.java | 17 +++ .../windows}/WindowsVirtualKey.java | 4 +- .../{ => platform/windows}/Winmm.java | 4 +- 52 files changed, 792 insertions(+), 163 deletions(-) create mode 100644 src/main/java/mousemaster/platform/ActiveAppFinder.java create mode 100644 src/main/java/mousemaster/platform/Console.java create mode 100644 src/main/java/mousemaster/platform/KeyRegurgitator.java create mode 100644 src/main/java/mousemaster/platform/Keyboard.java create mode 100644 src/main/java/mousemaster/platform/KeyboardLayoutProvider.java create mode 100644 src/main/java/mousemaster/platform/Mouse.java create mode 100644 src/main/java/mousemaster/platform/Overlay.java create mode 100644 src/main/java/mousemaster/platform/Screens.java create mode 100644 src/main/java/mousemaster/platform/UiAutomation.java rename src/main/java/mousemaster/{ => platform/windows}/Dwmapi.java (86%) rename src/main/java/mousemaster/{ => platform/windows}/ExtendedAdvapi32.java (94%) rename src/main/java/mousemaster/{ => platform/windows}/ExtendedGDI32.java (98%) rename src/main/java/mousemaster/{ => platform/windows}/ExtendedKernel32.java (87%) rename src/main/java/mousemaster/{ => platform/windows}/ExtendedPsapi.java (86%) rename src/main/java/mousemaster/{ => platform/windows}/ExtendedUser32.java (98%) rename src/main/java/mousemaster/{ => platform/windows}/FadeAnimator.java (98%) rename src/main/java/mousemaster/{ => platform/windows}/Gdiplus.java (99%) rename src/main/java/mousemaster/{ => platform/windows}/KbdlayoutinfoParser.java (99%) rename src/main/java/mousemaster/{ => platform/windows}/Magnification.java (95%) rename src/main/java/mousemaster/{ => platform/windows}/Shcore.java (94%) rename src/main/java/mousemaster/{ActiveAppFinder.java => platform/windows/WindowsActiveAppFinder.java} (94%) rename src/main/java/mousemaster/{ => platform/windows}/WindowsClock.java (94%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsConsole.java rename src/main/java/mousemaster/{KeyRegurgitator.java => platform/windows/WindowsKeyRegurgitator.java} (75%) rename src/main/java/mousemaster/{ => platform/windows}/WindowsKeyboard.java (99%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java rename src/main/java/mousemaster/{ => platform/windows}/WindowsMouse.java (99%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java rename src/main/java/mousemaster/{ => platform/windows}/WindowsOverlay.java (99%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java rename src/main/java/mousemaster/{ => platform/windows}/WindowsPlatform.java (94%) rename src/main/java/mousemaster/{ => platform/windows}/WindowsScreen.java (98%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsScreens.java rename src/main/java/mousemaster/{ => platform/windows}/WindowsUiAccess.java (99%) rename src/main/java/mousemaster/{ => platform/windows}/WindowsUiAutomation.java (99%) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java rename src/main/java/mousemaster/{ => platform/windows}/WindowsVirtualKey.java (99%) rename src/main/java/mousemaster/{ => platform/windows}/Winmm.java (78%) diff --git a/src/main/java/mousemaster/ComboWatcher.java b/src/main/java/mousemaster/ComboWatcher.java index 37546c0c..075cc135 100644 --- a/src/main/java/mousemaster/ComboWatcher.java +++ b/src/main/java/mousemaster/ComboWatcher.java @@ -2,6 +2,7 @@ import mousemaster.ComboMove.WaitComboMove; import mousemaster.ResolvedKeyComboMove.ResolvedPressComboMove; +import mousemaster.platform.ActiveAppFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/mousemaster/GridManager.java b/src/main/java/mousemaster/GridManager.java index d8a9eea6..8b5d41c3 100644 --- a/src/main/java/mousemaster/GridManager.java +++ b/src/main/java/mousemaster/GridManager.java @@ -1,6 +1,7 @@ package mousemaster; import mousemaster.Grid.GridBuilder; +import mousemaster.platform.Overlay; /** * Displays the grid and handles grid commands. @@ -9,13 +10,16 @@ public class GridManager implements MousePositionListener, ModeListener { private final ScreenManager screenManager; private final MouseController mouseController; + private final Overlay overlay; private Grid grid; private int mouseX, mouseY; private Mode currentMode; - public GridManager(ScreenManager screenManager, MouseController mouseController) { + public GridManager(ScreenManager screenManager, MouseController mouseController, + Overlay overlay) { this.screenManager = screenManager; this.mouseController = mouseController; + this.overlay = overlay; } public void shrinkGridUp() { @@ -294,7 +298,7 @@ public void modeChanged(Mode newMode) { .height(gridHeight); } case GridArea.ActiveWindowGridArea activeWindowGridArea -> { - Rectangle activeWindowRectangle = WindowsOverlay.activeWindowRectangle( + Rectangle activeWindowRectangle = overlay.activeWindowRectangle( activeWindowGridArea.widthPercent(), activeWindowGridArea.heightPercent(), scaledTopInset, scaledBottomInset, scaledLeftInset, scaledRightInset); @@ -325,9 +329,9 @@ private void gridChanged() { private void setOverlay() { if (grid.lineVisible()) - WindowsOverlay.setGrid(grid); + overlay.setGrid(grid); else - WindowsOverlay.hideGrid(); + overlay.hideGrid(); } @Override @@ -336,4 +340,3 @@ public void modeTimedOut() { } } - diff --git a/src/main/java/mousemaster/Hint.java b/src/main/java/mousemaster/Hint.java index 9a578294..a856688e 100644 --- a/src/main/java/mousemaster/Hint.java +++ b/src/main/java/mousemaster/Hint.java @@ -5,7 +5,7 @@ public record Hint(double centerX, double centerY, double cellWidth, double cellHeight, List keySequence) { - boolean startsWith(List selectedHintKeySequence) { + public boolean startsWith(List selectedHintKeySequence) { if (selectedHintKeySequence.isEmpty()) return true; return keySequence.subList(0, selectedHintKeySequence.size()) diff --git a/src/main/java/mousemaster/HintManager.java b/src/main/java/mousemaster/HintManager.java index 0d131145..0fd479af 100644 --- a/src/main/java/mousemaster/HintManager.java +++ b/src/main/java/mousemaster/HintManager.java @@ -4,6 +4,9 @@ import mousemaster.HintGridArea.ActiveWindowHintGridArea; import mousemaster.HintGridArea.AllScreensHintGridArea; import mousemaster.HintMesh.HintMeshBuilder; +import mousemaster.platform.Overlay; +import mousemaster.platform.UiAutomation; +import mousemaster.platform.UiAutomation.UiElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +21,8 @@ public class HintManager implements ModeListener, MousePositionListener { private final ScreenManager screenManager; private final MouseController mouseController; + private final Overlay overlay; + private final UiAutomation uiAutomation; private ModeController modeController; private HintMesh hintMesh; private ViewportFilter screenFilter; @@ -40,14 +45,14 @@ public class HintManager implements ModeListener, MousePositionListener { private boolean lastHintCommandSupercedesOtherCommands; private record PendingUiHintQuery( - Future> future, + Future> future, HintMeshConfiguration hintMeshConfiguration, Zoom zoom, ViewportFilter screenFilter) { } private PendingUiHintQuery pendingUiHintQuery; - private List lastUiElements; + private List lastUiElements; /** * It would be better to have an instance of Zoom instead of ZoomConfiguration @@ -69,10 +74,13 @@ private record HintMeshState(HintMesh hintMesh, } public HintManager(int maxPositionHistorySize, ScreenManager screenManager, - MouseController mouseController) { + MouseController mouseController, Overlay overlay, + UiAutomation uiAutomation) { this.maxPositionHistorySize = maxPositionHistorySize; this.screenManager = screenManager; this.mouseController = mouseController; + this.overlay = overlay; + this.uiAutomation = uiAutomation; } public void setModeController(ModeController modeController) { @@ -158,7 +166,7 @@ else if (hintMeshConfiguration.type() instanceof HintMeshType.HintGrid hintGrid currentMode = newMode; hintMeshStates.clear(); hintMesh = null; - WindowsOverlay.hideHintMesh(); + overlay.hideHintMesh(); return; } if (!hintMeshConfiguration.visible()) { @@ -166,7 +174,7 @@ else if (hintMeshConfiguration.type() instanceof HintMeshType.HintGrid hintGrid // An alternative would be a setting like hint.reset-selected-key-sequence-history-after-selection=true. hintMeshStates.clear(); hintMesh = null; - WindowsOverlay.hideHintMesh(); + overlay.hideHintMesh(); } Point zoomCenterPoint = newMode.zoom().center().centerPoint( screenManager.activeScreen().rectangle(), mouseX, mouseY, @@ -180,7 +188,7 @@ else if (hintMeshConfiguration.type() instanceof HintMeshType.HintGrid hintGrid if (currentMode == null || !(currentMode.hintMesh().type() instanceof HintMeshType.UiHintMesh)) { pendingUiHintQuery = new PendingUiHintQuery( - WindowsUiAutomation.startFindInteractiveUiElements(), + uiAutomation.startFindInteractiveUiElements(), hintMeshConfiguration, newZoom, newScreenFilter); currentMode = newMode; currentZoom = newZoom; @@ -229,7 +237,7 @@ private void activateHintMesh(Mode newMode, HintMesh newHintMesh, newMode.zoom()), new HintMeshState(newHintMesh, lastSelectedHintPoint)); hintMesh = newHintMesh; - WindowsOverlay.setHintMesh(hintMesh, newZoom); + overlay.setHintMesh(hintMesh, newZoom); if (hintMeshConfiguration.mouseMovement() == HintMouseMovement.MOUSE_FOLLOWS_HINT_GRID_CENTER) { moveMouse(hintMeshCenter(hintMesh.hints(), hintMesh.selectedKeySequence())); @@ -287,7 +295,7 @@ public void completePendingUiHintQuery() { return; PendingUiHintQuery pending = pendingUiHintQuery; pendingUiHintQuery = null; - List uiElements; + List uiElements; try { uiElements = pending.future().get(); } @@ -324,7 +332,7 @@ private ViewportFilter screenFilter(HintMeshConfiguration hintMeshConfiguration) } case ActiveWindowHintGridArea activeWindowHintGridArea -> { Rectangle activeWindowRectangle = - WindowsOverlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); + overlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); Point gridCenter = activeWindowRectangle.center(); Screen screen = screenManager.nearestScreenContaining(gridCenter.x(), @@ -335,7 +343,7 @@ private ViewportFilter screenFilter(HintMeshConfiguration hintMeshConfiguration) } else if (type instanceof HintMeshType.UiHintMesh) { Rectangle activeWindowRectangle = - WindowsOverlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); + overlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); Point center = activeWindowRectangle.center(); Screen screen = screenManager.nearestScreenContaining(center.x(), center.y()); return ViewportFilter.of(screen); @@ -352,7 +360,7 @@ private HintMesh buildHintMesh( HintMeshConfiguration hintMeshConfiguration, ZoomConfiguration zoomConfiguration, Zoom zoom, ViewportFilter screenFilter, - List uiElements) { + List uiElements) { HintMeshBuilder hintMesh = new HintMeshBuilder(); hintMesh.visible(hintMeshConfiguration.visible()) .styleByFilter(hintMeshConfiguration.styleByFilter()); @@ -403,7 +411,7 @@ else if (hintGrid.area() instanceof AllScreensHintGridArea allScreensHintGridAre } else if (hintGrid.area() instanceof ActiveWindowHintGridArea activeWindowHintGridArea) { Rectangle activeWindowRectangle = - WindowsOverlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); + overlay.activeWindowRectangle(1, 1, 0, 0, 0, 0); Point gridCenter = activeWindowRectangle.center(); Screen screen = screenManager.nearestScreenContaining(gridCenter.x(), gridCenter.y()); @@ -468,7 +476,7 @@ else if (type instanceof HintMeshType.UiHintMesh) { .prefixLength(prefixLengths.size() == 1 ? prefixLengths.iterator().next() : -1) .backgroundArea( - WindowsOverlay.activeWindowRectangle(1, 1, 0, 0, 0, 0)); + overlay.activeWindowRectangle(1, 1, 0, 0, 0, 0)); } else { int hintCount = positionHistory.size(); @@ -544,14 +552,14 @@ else if (type instanceof HintMeshType.UiHintMesh) { private void buildUiHints(HintMeshConfiguration hintMeshConfiguration, ViewportFilter screenFilter, - List uiElements, + List uiElements, Set prefixLengths, List hints) { HintMeshKeys hintMeshKeys = hintMeshConfiguration.keysByFilter().get(screenFilter); List selectionKeys = hintMeshKeys.selectionKeys(); int rowKeyOffset = hintMeshKeys.rowKeyOffset(); for (int i = 0; i < uiElements.size(); i++) { - WindowsUiAutomation.UiElement element = uiElements.get(i); + UiElement element = uiElements.get(i); List keySequence = hintKeySequence( selectionKeys, rowKeyOffset, uiElements.size(), 0, -1, i, @@ -911,7 +919,7 @@ public void unselectHintKey() { hintMeshStates.get(hintMeshKey).previousModeSelectedHintPoint ) ); - WindowsOverlay.setHintMesh(hintMesh, currentZoom); + overlay.setHintMesh(hintMesh, currentZoom); if (hintMeshConfiguration.mouseMovement() == HintMouseMovement.MOUSE_FOLLOWS_HINT_GRID_CENTER) { moveMouse(hintMeshCenter(hintMesh.hints(), hintMesh.selectedKeySequence())); @@ -987,7 +995,7 @@ public void selectHintKey(Key key) { hintMesh, hintMeshStates.get(hintMeshKey).previousModeSelectedHintPoint )); - WindowsOverlay.setHintMesh(hintMesh, currentZoom); + overlay.setHintMesh(hintMesh, currentZoom); if (hintMeshConfiguration.mouseMovement() == HintMouseMovement.MOUSE_FOLLOWS_HINT_GRID_CENTER) { moveMouse(hintMeshCenter(hintMesh.hints(), newSelectedKeySequence)); } @@ -1031,7 +1039,7 @@ private void finalizeHintSelection(Hint hint, List newSelectedKeySequence) .map(Key::name) .toList() + " selected"); - WindowsOverlay.animateHintMatch(hint); + overlay.animateHintMatch(hint); hintMesh = hintMesh.builder() .selectedKeySequence(newSelectedKeySequence) diff --git a/src/main/java/mousemaster/IndicatorManager.java b/src/main/java/mousemaster/IndicatorManager.java index d7e1fd5e..444ffa25 100644 --- a/src/main/java/mousemaster/IndicatorManager.java +++ b/src/main/java/mousemaster/IndicatorManager.java @@ -1,12 +1,17 @@ package mousemaster; +import mousemaster.platform.Overlay; + public class IndicatorManager implements ModeListener { + private final Overlay overlay; private final MouseState mouseState; private final KeyboardState keyboardState; private Mode currentMode; - public IndicatorManager(MouseState mouseState, KeyboardState keyboardState) { + public IndicatorManager(Overlay overlay, MouseState mouseState, + KeyboardState keyboardState) { + this.overlay = overlay; this.mouseState = mouseState; this.keyboardState = keyboardState; } @@ -28,15 +33,15 @@ private void updateIndicator(boolean allowFade) { if (currentMode.indicator().enabled()) { Indicator indicator = activeIndicator(); if (indicator.hexColor() == null) - WindowsOverlay.hideIndicator(allowFade); + overlay.hideIndicator(allowFade); else - WindowsOverlay.setIndicator(indicator, + overlay.setIndicator(indicator, currentMode.indicator().fadeAnimationEnabled(), currentMode.indicator().fadeAnimationDuration(), allowFade); } else - WindowsOverlay.hideIndicator(allowFade); + overlay.hideIndicator(allowFade); } @Override diff --git a/src/main/java/mousemaster/KeyboardLayout.java b/src/main/java/mousemaster/KeyboardLayout.java index 0a2a640e..64f37a88 100644 --- a/src/main/java/mousemaster/KeyboardLayout.java +++ b/src/main/java/mousemaster/KeyboardLayout.java @@ -2,6 +2,7 @@ import com.google.gson.*; import com.google.gson.reflect.TypeToken; +import mousemaster.platform.windows.WindowsVirtualKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/mousemaster/KeyboardManager.java b/src/main/java/mousemaster/KeyboardManager.java index 1d061f86..61a184a4 100644 --- a/src/main/java/mousemaster/KeyboardManager.java +++ b/src/main/java/mousemaster/KeyboardManager.java @@ -1,6 +1,7 @@ package mousemaster; import mousemaster.ComboWatcher.ComboWatcherUpdateResult; +import mousemaster.platform.KeyRegurgitator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/mousemaster/MacroPlayer.java b/src/main/java/mousemaster/MacroPlayer.java index ab82520d..901387b5 100644 --- a/src/main/java/mousemaster/MacroPlayer.java +++ b/src/main/java/mousemaster/MacroPlayer.java @@ -2,6 +2,7 @@ import mousemaster.KeyEvent.PressKeyEvent; import mousemaster.KeyEvent.ReleaseKeyEvent; +import mousemaster.platform.Keyboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +18,7 @@ public class MacroPlayer { private final PlatformClock clock; private final ComboWatcher comboWatcher; private final KeyboardManager keyboardManager; + private final Keyboard keyboard; private final List macrosToExecute = new ArrayList<>(); private MacroInProgress macroInProgress; private final Set keysPressedByMacro = new HashSet<>(); @@ -51,10 +53,11 @@ public class MacroPlayer { private final Set deferredUserReleases = new HashSet<>(); public MacroPlayer(PlatformClock clock, ComboWatcher comboWatcher, - KeyboardManager keyboardManager) { + KeyboardManager keyboardManager, Keyboard keyboard) { this.clock = clock; this.comboWatcher = comboWatcher; this.keyboardManager = keyboardManager; + this.keyboard = keyboard; } private static class MacroInProgress { @@ -113,7 +116,7 @@ public void breakMacro() { List releases = new ArrayList<>(); for (Key key : keysPressedByMacro) releases.add(new ResolvedKeyMacroMove(key, false, MacroMoveDestination.OS)); - WindowsKeyboard.sendInputMoves(releases, false); + keyboard.sendInputMoves(releases, false); } reset(); } @@ -130,7 +133,7 @@ public void reset() { public void keyPressedNotEaten(Key key) { macroReleasedKeys.remove(key); - WindowsKeyboard.keyPressedNotEaten(key); + keyboard.keyPressedNotEaten(key); } public void keyReleasedNotEaten(Key key) { @@ -139,7 +142,7 @@ public void keyReleasedNotEaten(Key key) { macroReleasedKeys.remove(key); // Scenario where user-press-eaten, then macro-press (uneats the key), then user-release (not eaten as per rule 1). // The macro-press is a repeating SendInput that should be stopped when user releases the key. - WindowsKeyboard.keyReleasedNotEaten(key); + keyboard.keyReleasedNotEaten(key); } public void newKeyEvent() { @@ -184,12 +187,12 @@ private void processDeferredReleases() { deferredUserReleases.clear(); if (!releases.isEmpty()) { logger.debug("Processing deferred user releases: " + releases); - WindowsKeyboard.sendInputMoves(releases, false); + keyboard.sendInputMoves(releases, false); } } public void recordEarlyRelease(Key key) { - WindowsKeyboard.recordEarlyReleaseForQueuedPress(key); + keyboard.recordEarlyReleaseForQueuedPress(key); if (macroInProgress == null) return; for (int i = macroInProgress.currentIndex + 1; @@ -214,7 +217,7 @@ public void recordEarlyRelease(Key key) { */ public void clearEarlyRelease(Key key) { earlyReleasedKeys.remove(key); - WindowsKeyboard.clearEarlyReleaseForQueuedPress(key); + keyboard.clearEarlyReleaseForQueuedPress(key); } public void update(double delta) { @@ -315,7 +318,7 @@ private void executeParallel(ResolvedMacroParallel parallel) { else { // Flush any pending OS moves before sending to ComboWatcher. if (!osMoves.isEmpty()) { - WindowsKeyboard.sendInputMoves(osMoves, true); + keyboard.sendInputMoves(osMoves, true); osMoves.clear(); } KeyEvent event = keyMove.press() @@ -328,7 +331,7 @@ private void executeParallel(ResolvedMacroParallel parallel) { } // Flush remaining OS moves. if (!osMoves.isEmpty()) { - WindowsKeyboard.sendInputMoves(osMoves, true); + keyboard.sendInputMoves(osMoves, true); } } diff --git a/src/main/java/mousemaster/MouseController.java b/src/main/java/mousemaster/MouseController.java index 0a6c3610..495d282b 100644 --- a/src/main/java/mousemaster/MouseController.java +++ b/src/main/java/mousemaster/MouseController.java @@ -7,6 +7,7 @@ public class MouseController implements ModeListener, MousePositionListener { private final ScreenManager screenManager; + private final mousemaster.platform.Mouse platformMouse; private Mouse mouse; private Wheel wheel; private double moveDuration; @@ -43,8 +44,10 @@ public class MouseController implements ModeListener, MousePositionListener { private int jumpBeginX, jumpBeginY; private int jumpEndX, jumpEndY; - public MouseController(ScreenManager screenManager) { + public MouseController(ScreenManager screenManager, + mousemaster.platform.Mouse platformMouse) { this.screenManager = screenManager; + this.platformMouse = platformMouse; } public void reset() { @@ -104,7 +107,7 @@ private boolean activelyWheeling() { public void update(double delta) { if (activelyMoving()) { - WindowsMouse.beginMove(); + platformMouse.beginMove(); moveDuration += delta; double moveVelocity = moveVelocity(); // Save direction for potential deceleration. @@ -131,7 +134,7 @@ else if (!xMoveForwardStack.isEmpty()) { deltaBigEnough = deltaDistanceY >= 1; } if (deltaBigEnough && !jumping) { - WindowsMouse.moveBy( + platformMouse.moveBy( !xMoveForwardStack.isEmpty() && xMoveForwardStack.peek(), deltaDistanceX, !yMoveForwardStack.isEmpty() && yMoveForwardStack.peek(), @@ -140,7 +143,7 @@ else if (!xMoveForwardStack.isEmpty()) { } } else if (decelerating) { - WindowsMouse.beginMove(); + platformMouse.beginMove(); decelerationDuration += delta; double velocity = decelerationVelocity(mouse.velocity(), velocityAtDecelerationStart, decelerationDuration); @@ -166,7 +169,7 @@ else if (decelerateXActive) { deltaBigEnough = deltaDistanceY >= 1; } if (deltaBigEnough && !jumping) { - WindowsMouse.moveBy( + platformMouse.moveBy( decelerateXActive && decelerateXForward, deltaDistanceX, decelerateYActive && decelerateYForward, @@ -176,7 +179,7 @@ else if (decelerateXActive) { } } else if (!jumping) - WindowsMouse.endMove(); + platformMouse.endMove(); if (jumping) { jumpDuration += delta; double jumpVelocity = mouse.smoothJumpVelocity(); // Pixels per second. @@ -205,7 +208,7 @@ else if (!jumping) } } if (nextJumpX != jumpX || nextJumpY != jumpY) { - WindowsMouse.synchronousMoveTo(nextJumpX, nextJumpY); + platformMouse.synchronousMoveTo(nextJumpX, nextJumpY); jumpX = nextJumpX; jumpY = nextJumpY; } @@ -222,9 +225,9 @@ else if (!jumping) if (lastWheelYActive) lastWheelYForward = yWheelForwardStack.peek(); if (!xWheelForwardStack.isEmpty()) - WindowsMouse.wheelHorizontallyBy(xWheelForwardStack.peek(), deltaDistance); + platformMouse.wheelHorizontallyBy(xWheelForwardStack.peek(), deltaDistance); if (!yWheelForwardStack.isEmpty()) - WindowsMouse.wheelVerticallyBy(yWheelForwardStack.peek(), deltaDistance); + platformMouse.wheelVerticallyBy(yWheelForwardStack.peek(), deltaDistance); } else if (wheelDecelerating) { wheelDecelerationDuration += delta; @@ -236,9 +239,9 @@ else if (wheelDecelerating) { } else { if (wheelDecelerateXActive) - WindowsMouse.wheelHorizontallyBy(wheelDecelerateXForward, deltaDistance); + platformMouse.wheelHorizontallyBy(wheelDecelerateXForward, deltaDistance); if (wheelDecelerateYActive) - WindowsMouse.wheelVerticallyBy(wheelDecelerateYForward, deltaDistance); + platformMouse.wheelVerticallyBy(wheelDecelerateYForward, deltaDistance); } } } @@ -464,20 +467,20 @@ public void stopMoveRight() { public void clickLeft() { if (!leftPressing) - WindowsMouse.pressLeft(); - WindowsMouse.releaseLeft(); + platformMouse.pressLeft(); + platformMouse.releaseLeft(); } public void clickMiddle() { if (!middlePressing) - WindowsMouse.pressMiddle(); - WindowsMouse.releaseMiddle(); + platformMouse.pressMiddle(); + platformMouse.releaseMiddle(); } public void clickRight() { if (!rightPressing) - WindowsMouse.pressRight(); - WindowsMouse.releaseRight(); + platformMouse.pressRight(); + platformMouse.releaseRight(); } public void pressLeft() { @@ -485,7 +488,7 @@ public void pressLeft() { return; releaseAll(); leftPressing = true; - WindowsMouse.pressLeft(); + platformMouse.pressLeft(); } public void pressMiddle() { @@ -493,7 +496,7 @@ public void pressMiddle() { return; releaseAll(); middlePressing = true; - WindowsMouse.pressMiddle(); + platformMouse.pressMiddle(); } public void pressRight() { @@ -501,24 +504,24 @@ public void pressRight() { return; releaseAll(); rightPressing = true; - WindowsMouse.pressRight(); + platformMouse.pressRight(); } public void releaseLeft() { if (leftPressing) - WindowsMouse.releaseLeft(); + platformMouse.releaseLeft(); leftPressing = false; } public void releaseMiddle() { if (middlePressing) - WindowsMouse.releaseMiddle(); + platformMouse.releaseMiddle(); middlePressing = false; } public void releaseRight() { if (rightPressing) - WindowsMouse.releaseRight(); + platformMouse.releaseRight(); rightPressing = false; } @@ -606,11 +609,11 @@ public void stopWheelRight() { } public void showCursor() { - WindowsMouse.showCursor(); + platformMouse.showCursor(); } public void hideCursor() { - WindowsMouse.hideCursor(); + platformMouse.hideCursor(); } public void moveTo(int x, int y) { @@ -623,8 +626,8 @@ public void moveTo(int x, int y) { return; // Move a single pixel. Skype's titlebar does not like being dragged too quick too far. if (!mouse.smoothJumpEnabled()) { - WindowsMouse.beginMove(); - WindowsMouse.synchronousMoveTo(x, y); + platformMouse.beginMove(); + platformMouse.synchronousMoveTo(x, y); return; } // If already jumping but one direction changes, then reset velocity. @@ -638,7 +641,7 @@ public void moveTo(int x, int y) { jumpX = mouseX; jumpY = mouseY; jumping = true; - WindowsMouse.beginMove(); + platformMouse.beginMove(); jumpEndX = x; jumpEndY = y; } @@ -701,8 +704,8 @@ public void modeChanged(Mode newMode) { if (jumping && !mouse.smoothJumpEnabled()) { jumping = false; jumpDuration = 0; - WindowsMouse.beginMove(); - WindowsMouse.synchronousMoveTo(jumpEndX, jumpEndY); + platformMouse.beginMove(); + platformMouse.synchronousMoveTo(jumpEndX, jumpEndY); } if (newMode.stopCommandsFromPreviousMode()) { stopMoveDown(); diff --git a/src/main/java/mousemaster/Mousemaster.java b/src/main/java/mousemaster/Mousemaster.java index 1bf4c73e..d2cf8218 100644 --- a/src/main/java/mousemaster/Mousemaster.java +++ b/src/main/java/mousemaster/Mousemaster.java @@ -31,7 +31,7 @@ public class Mousemaster { public Mousemaster(Path configurationPath, Platform platform) throws IOException { this.configurationPath = configurationPath; this.platform = platform; - this.activeKeyboardLayout = platform.activeKeyboardLayout(); + this.activeKeyboardLayout = platform.keyboardLayoutProvider().activeKeyboardLayout(); QtManager.initialize(); loadConfiguration(true); watchService = FileSystems.getDefault().newWatchService(); @@ -51,42 +51,42 @@ public void run() throws InterruptedException { updateConfiguration(); long timeAfterOp = System.nanoTime(); long updateConfigurationDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; updateActiveKeyboardLayout(delta); timeAfterOp = System.nanoTime(); long updateActiveKeyboardLayoutDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; QtManager.processEvents(); platform.update(delta); timeAfterOp = System.nanoTime(); long platformDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; modeController.update(delta); timeAfterOp = System.nanoTime(); long modeControllerDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; mouseController.update(delta); timeAfterOp = System.nanoTime(); long mouseControllerDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; keyboardManager.update(delta); timeAfterOp = System.nanoTime(); long keyboardManagerDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; indicatorManager.update(delta); timeAfterOp = System.nanoTime(); long indicatorManagerDuration = (long) ((timeAfterOp - timeBeforeOp) / 1e6); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; zoomManager.update(delta); timeAfterOp = System.nanoTime(); - platform.windowsMessagePump(); + platform.pumpEvents(); timeBeforeOp = timeAfterOp; macroPlayer.update(delta); timeAfterOp = System.nanoTime(); @@ -112,7 +112,7 @@ public void run() throws InterruptedException { private void updateActiveKeyboardLayout(double delta) { if (forcedActiveKeyboardLayout != null) return; - KeyboardLayout newActiveKeyboardLayout = platform.activeKeyboardLayout(); + KeyboardLayout newActiveKeyboardLayout = platform.keyboardLayoutProvider().activeKeyboardLayout(); if (!newActiveKeyboardLayout.equals(activeKeyboardLayout)) { activeKeyboardLayout = newActiveKeyboardLayout; tryLoadConfiguration(false); @@ -172,18 +172,18 @@ private void loadConfiguration(boolean readFile) throws IOException { else MousemasterApplication.disableLogToFile(); if (configuration.hideConsole()) - MousemasterApplication.hideConsole(); + platform.console().hide(); else - MousemasterApplication.showConsole(); + platform.console().show(); logger.info((reload ? "Reloaded" : "Loaded") + " configuration " + (readFile ? "file " + configurationPath + " " : "") + "with active keyboard layout " + activeKeyboardLayout); - ScreenManager screenManager = new ScreenManager(); - mouseController = new MouseController(screenManager); + ScreenManager screenManager = new ScreenManager(platform.screens()); + mouseController = new MouseController(screenManager, platform.mouse()); MouseState mouseState = new MouseState(mouseController); - GridManager gridManager = new GridManager(screenManager, mouseController); + GridManager gridManager = new GridManager(screenManager, mouseController, platform.overlay()); HintManager hintManager = new HintManager(configuration.maxPositionHistorySize(), - screenManager, mouseController); + screenManager, mouseController, platform.overlay(), platform.uiAutomation()); commandRunner = new CommandRunner(mouseController, gridManager, hintManager); Set unpressedComboPreconditionKeys = new HashSet<>(); Set pressedComboPreconditionKeys = new HashSet<>(); @@ -201,18 +201,18 @@ private void loadConfiguration(boolean readFile) throws IOException { } } ComboWatcher comboWatcher = - new ComboWatcher(commandRunner, hintManager, new ActiveAppFinder(), + new ComboWatcher(commandRunner, hintManager, platform.activeAppFinder(), platform.clock(), unpressedComboPreconditionKeys, pressedComboPreconditionKeys, configuration.logRedactKeys(), configuration.modeMap()); keyboardManager = new KeyboardManager(comboWatcher, hintManager, platform.keyRegurgitator()); - macroPlayer = new MacroPlayer(platform.clock(), comboWatcher, keyboardManager); + macroPlayer = new MacroPlayer(platform.clock(), comboWatcher, keyboardManager, platform.keyboard()); keyboardManager.setMacroPlayer(macroPlayer); KeyboardState keyboardState = new KeyboardState(keyboardManager); - indicatorManager = new IndicatorManager(mouseState, keyboardState); - zoomManager = new ZoomManager(screenManager, hintManager); + indicatorManager = new IndicatorManager(platform.overlay(), mouseState, keyboardState); + zoomManager = new ZoomManager(screenManager, hintManager, platform.overlay()); // ComboWatcher is the sole broadcaster to ModeListeners: it broadcasts // on mode switch (delegated from ModeController) and on mode mutation. // ZoomManager must be notified after HintManager because it calls diff --git a/src/main/java/mousemaster/MousemasterApplication.java b/src/main/java/mousemaster/MousemasterApplication.java index fa557490..386a9eaf 100644 --- a/src/main/java/mousemaster/MousemasterApplication.java +++ b/src/main/java/mousemaster/MousemasterApplication.java @@ -8,10 +8,7 @@ import ch.qos.logback.core.Appender; import ch.qos.logback.core.FileAppender; import com.sun.jna.Native; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.User32; -import com.sun.jna.platform.win32.WinDef; -import com.sun.jna.platform.win32.WinUser; +import mousemaster.platform.windows.WindowsPlatform; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; @@ -107,7 +104,7 @@ public static void main(String[] args) throws InterruptedException, IOException System.exit(0); }).start(); } - WindowsPlatform platform = platform(multipleInstancesAllowed, keyRegurgitationEnabled, pauseOnError); + Platform platform = platform(multipleInstancesAllowed, keyRegurgitationEnabled, pauseOnError); logger.info("mousemaster v" + version + " (" + commitId + ")"); if (platform == null) return; @@ -138,9 +135,9 @@ private static void setTempDirectory(String tempDirectory) { System.setProperty("jna.tmpdir", MousemasterApplication.tempDirectory + "/jna"); } - private static WindowsPlatform platform(boolean multipleInstancesAllowed, - boolean keyRegurgitationEnabled, - boolean pauseOnError) { + private static Platform platform(boolean multipleInstancesAllowed, + boolean keyRegurgitationEnabled, + boolean pauseOnError) { try { return new WindowsPlatform(multipleInstancesAllowed, keyRegurgitationEnabled); } catch (Exception e) { @@ -197,15 +194,4 @@ public static void setLogLevel(String level) { logger.setLevel(Level.valueOf(level)); } - public static void showConsole() { - // This should be moved to a Console interface (with one implementation per OS). - WinDef.HWND hwnd = Kernel32.INSTANCE.GetConsoleWindow(); - User32.INSTANCE.ShowWindow(hwnd, WinUser.SW_SHOW); - } - - public static void hideConsole() { - WinDef.HWND hwnd = Kernel32.INSTANCE.GetConsoleWindow(); - User32.INSTANCE.ShowWindow(hwnd, WinUser.SW_HIDE); - } - } diff --git a/src/main/java/mousemaster/Platform.java b/src/main/java/mousemaster/Platform.java index 34c00604..3755016a 100644 --- a/src/main/java/mousemaster/Platform.java +++ b/src/main/java/mousemaster/Platform.java @@ -1,12 +1,22 @@ package mousemaster; +import mousemaster.platform.ActiveAppFinder; +import mousemaster.platform.Console; +import mousemaster.platform.Keyboard; +import mousemaster.platform.KeyboardLayoutProvider; +import mousemaster.platform.KeyRegurgitator; +import mousemaster.platform.Mouse; +import mousemaster.platform.Overlay; +import mousemaster.platform.Screens; +import mousemaster.platform.UiAutomation; + import java.util.List; public interface Platform extends ModeListener { void update(double delta); - void windowsMessagePump(); + void pumpEvents(); void sleep() throws InterruptedException; @@ -21,6 +31,20 @@ void reset(MouseController mouseController, KeyboardManager keyboardManager, PlatformClock clock(); - KeyboardLayout activeKeyboardLayout(); + KeyboardLayoutProvider keyboardLayoutProvider(); + + Keyboard keyboard(); + + Mouse mouse(); + + Screens screens(); + + Overlay overlay(); + + UiAutomation uiAutomation(); + + ActiveAppFinder activeAppFinder(); + + Console console(); } diff --git a/src/main/java/mousemaster/ScreenManager.java b/src/main/java/mousemaster/ScreenManager.java index 01a8ae44..bd5801be 100644 --- a/src/main/java/mousemaster/ScreenManager.java +++ b/src/main/java/mousemaster/ScreenManager.java @@ -1,12 +1,19 @@ package mousemaster; +import mousemaster.platform.Screens; + import java.util.Set; public class ScreenManager implements MousePositionListener { + private final Screens screens; private int mouseX; private int mouseY; + public ScreenManager(Screens screens) { + this.screens = screens; + } + public Screen activeScreen() { return nearestScreenContaining(mouseX, mouseY); } @@ -35,7 +42,7 @@ public Screen nearestScreenContaining(double pointX, double pointY) { } public Set screens() { - return WindowsScreen.findScreens(); + return screens.findScreens(); } public Screen screenContaining(double x, double y) { diff --git a/src/main/java/mousemaster/ZoomManager.java b/src/main/java/mousemaster/ZoomManager.java index 03042e43..0dd966d6 100644 --- a/src/main/java/mousemaster/ZoomManager.java +++ b/src/main/java/mousemaster/ZoomManager.java @@ -1,5 +1,7 @@ package mousemaster; +import mousemaster.platform.Overlay; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -8,6 +10,7 @@ public class ZoomManager implements ModeListener, MousePositionListener { private final ScreenManager screenManager; private final HintManager hintManager; + private final Overlay overlay; private Mode currentMode; private int mouseX, mouseY; @@ -27,9 +30,11 @@ public class ZoomManager implements ModeListener, MousePositionListener { // The zoom center used to build endHintMesh. private Point endHintZoomCenter; - public ZoomManager(ScreenManager screenManager, HintManager hintManager) { + public ZoomManager(ScreenManager screenManager, HintManager hintManager, + Overlay overlay) { this.screenManager = screenManager; this.hintManager = hintManager; + this.overlay = overlay; } @Override @@ -50,7 +55,7 @@ public void modeChanged(Mode newMode) { currentPercent = endPercent; if (endIsNoZoom) { currentCenterPoint = null; - WindowsOverlay.setZoom(null); + overlay.setZoom(null); } else { Point centerPoint = newMode.zoom().center().centerPoint( @@ -59,7 +64,7 @@ public void modeChanged(Mode newMode) { currentCenterPoint = centerPoint; Screen screen = screenManager.nearestScreenContaining(centerPoint.x(), centerPoint.y()); - WindowsOverlay.setZoom(new Zoom(endPercent, + overlay.setZoom(new Zoom(endPercent, centerPoint, screen.rectangle())); } } @@ -98,7 +103,7 @@ public void modeChanged(Mode newMode) { HintMesh interpolatedMesh = interpolateHintMesh(endHintMesh, endHintZoomCenter, beginCenterPoint, screen.rectangle().center(), endPercent, beginPercent); - WindowsOverlay.setHintMesh(interpolatedMesh, + overlay.setHintMesh(interpolatedMesh, new Zoom(beginPercent, beginCenterPoint, screen.rectangle())); } else { @@ -109,7 +114,7 @@ public void modeChanged(Mode newMode) { Screen screen = screenManager.nearestScreenContaining( beginCenterPoint.x(), beginCenterPoint.y()); Zoom beginZoom = new Zoom(beginPercent, beginCenterPoint, screen.rectangle()); - WindowsOverlay.startScreenshotZoomAnimation(screen.rectangle(), beginZoom); + overlay.startScreenshotZoomAnimation(screen.rectangle(), beginZoom); } } @@ -132,23 +137,23 @@ public void update(double delta) { Screen screen = screenManager.nearestScreenContaining(centerPoint.x(), centerPoint.y()); Zoom currentZoom = new Zoom(currentPercent, centerPoint, screen.rectangle()); - WindowsOverlay.updateScreenshotZoom(currentZoom); + overlay.updateScreenshotZoom(currentZoom); if (endHintMesh != null) { HintMesh interpolatedMesh = interpolateHintMesh(endHintMesh, endHintZoomCenter, centerPoint, screen.rectangle().center(), endPercent, currentPercent); - WindowsOverlay.setHintMesh(interpolatedMesh, currentZoom); + overlay.setHintMesh(interpolatedMesh, currentZoom); } if (t >= 1.0) { animating = false; Zoom endZoom = endIsNoZoom ? null : new Zoom(currentPercent, centerPoint, screen.rectangle()); - WindowsOverlay.endScreenshotZoomAnimation(endZoom); + overlay.endScreenshotZoomAnimation(endZoom); if (endHintMesh != null) { // Restore the final hint mesh. if (endZoom == null) endZoom = new Zoom(currentPercent, centerPoint, screen.rectangle()); - WindowsOverlay.setHintMesh(endHintMesh, endZoom); + overlay.setHintMesh(endHintMesh, endZoom); endHintMesh = null; endHintZoomCenter = null; } @@ -252,7 +257,7 @@ public void mouseMoved(int x, int y) { currentCenterPoint = centerPoint; Screen screen = screenManager.nearestScreenContaining(centerPoint.x(), centerPoint.y()); - WindowsOverlay.setZoom(new Zoom(currentMode.zoom().percent(), + overlay.setZoom(new Zoom(currentMode.zoom().percent(), centerPoint, screen.rectangle())); } } diff --git a/src/main/java/mousemaster/platform/ActiveAppFinder.java b/src/main/java/mousemaster/platform/ActiveAppFinder.java new file mode 100644 index 00000000..51798721 --- /dev/null +++ b/src/main/java/mousemaster/platform/ActiveAppFinder.java @@ -0,0 +1,8 @@ +package mousemaster.platform; + +import mousemaster.App; + +public interface ActiveAppFinder { + + App activeApp(); +} diff --git a/src/main/java/mousemaster/platform/Console.java b/src/main/java/mousemaster/platform/Console.java new file mode 100644 index 00000000..76735045 --- /dev/null +++ b/src/main/java/mousemaster/platform/Console.java @@ -0,0 +1,8 @@ +package mousemaster.platform; + +public interface Console { + + void show(); + + void hide(); +} diff --git a/src/main/java/mousemaster/platform/KeyRegurgitator.java b/src/main/java/mousemaster/platform/KeyRegurgitator.java new file mode 100644 index 00000000..13a12b6f --- /dev/null +++ b/src/main/java/mousemaster/platform/KeyRegurgitator.java @@ -0,0 +1,9 @@ +package mousemaster.platform; + +import mousemaster.KeyboardManager; + +public interface KeyRegurgitator { + + void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat); + +} diff --git a/src/main/java/mousemaster/platform/Keyboard.java b/src/main/java/mousemaster/platform/Keyboard.java new file mode 100644 index 00000000..a31811d2 --- /dev/null +++ b/src/main/java/mousemaster/platform/Keyboard.java @@ -0,0 +1,23 @@ +package mousemaster.platform; + +import mousemaster.Key; +import mousemaster.ResolvedMacroMove; + +import java.util.List; + +public interface Keyboard { + + void update(double delta); + + void reset(); + + void sendInputMoves(List moves, boolean startRepeat); + + void keyPressedNotEaten(Key key); + + void keyReleasedNotEaten(Key key); + + void recordEarlyReleaseForQueuedPress(Key key); + + void clearEarlyReleaseForQueuedPress(Key key); +} diff --git a/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java new file mode 100644 index 00000000..9072a99f --- /dev/null +++ b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java @@ -0,0 +1,9 @@ +package mousemaster.platform; + +import mousemaster.KeyboardLayout; + +public interface KeyboardLayoutProvider { + + KeyboardLayout activeKeyboardLayout(); + +} diff --git a/src/main/java/mousemaster/platform/Mouse.java b/src/main/java/mousemaster/platform/Mouse.java new file mode 100644 index 00000000..9d009c7a --- /dev/null +++ b/src/main/java/mousemaster/platform/Mouse.java @@ -0,0 +1,33 @@ +package mousemaster.platform; + +public interface Mouse { + + void beginMove(); + + void endMove(); + + void moveBy(boolean xForward, double dx, boolean yForward, double dy); + + void synchronousMoveTo(int x, int y); + + void pressLeft(); + + void pressMiddle(); + + void pressRight(); + + void releaseLeft(); + + void releaseMiddle(); + + void releaseRight(); + + void wheelHorizontallyBy(boolean forward, double delta); + + void wheelVerticallyBy(boolean forward, double delta); + + void showCursor(); + + void hideCursor(); + +} diff --git a/src/main/java/mousemaster/platform/Overlay.java b/src/main/java/mousemaster/platform/Overlay.java new file mode 100644 index 00000000..da8ddfd9 --- /dev/null +++ b/src/main/java/mousemaster/platform/Overlay.java @@ -0,0 +1,61 @@ +package mousemaster.platform; + +import mousemaster.Grid; +import mousemaster.Hint; +import mousemaster.HintMesh; +import mousemaster.HintMeshConfiguration; +import mousemaster.Indicator; +import mousemaster.Rectangle; +import mousemaster.Zoom; + +import java.util.Set; +import java.time.Duration; + +public interface Overlay { + + void update(double delta); + + void flushCache(); + + void setTopmost(); + + void setMessagePump(Runnable pump); + + void preWarmFontStyles(Set configs); + + void preWarmHintMeshWindows(); + + Rectangle activeWindowRectangle(double widthPct, double heightPct, + int topInset, int bottomInset, + int leftInset, int rightInset); + + void setIndicator(Indicator indicator, boolean fadeAnimationEnabled, + Duration fadeAnimationDuration, boolean allowFade); + + void hideIndicator(boolean allowFade); + + void setGrid(Grid grid); + + void hideGrid(); + + void setHintMesh(HintMesh hintMesh, Zoom zoom); + + void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch); + + void hideHintMesh(); + + void animateHintMatch(Hint hint); + + void setZoom(Zoom zoom); + + void startScreenshotZoomAnimation(Rectangle screenRect, Zoom beginZoom); + + void updateScreenshotZoom(Zoom zoom); + + void endScreenshotZoomAnimation(Zoom finalZoom); + + boolean waitForZoomBeforeRepainting(); + + void setWaitForZoomBeforeRepainting(boolean value); + +} diff --git a/src/main/java/mousemaster/platform/Screens.java b/src/main/java/mousemaster/platform/Screens.java new file mode 100644 index 00000000..5f72bcbc --- /dev/null +++ b/src/main/java/mousemaster/platform/Screens.java @@ -0,0 +1,11 @@ +package mousemaster.platform; + +import mousemaster.Screen; + +import java.util.Set; + +public interface Screens { + + Set findScreens(); + +} diff --git a/src/main/java/mousemaster/platform/UiAutomation.java b/src/main/java/mousemaster/platform/UiAutomation.java new file mode 100644 index 00000000..1eec5473 --- /dev/null +++ b/src/main/java/mousemaster/platform/UiAutomation.java @@ -0,0 +1,13 @@ +package mousemaster.platform; + +import java.util.List; +import java.util.concurrent.Future; + +public interface UiAutomation { + + Future> startFindInteractiveUiElements(); + + record UiElement(double centerX, double centerY) { + } + +} diff --git a/src/main/java/mousemaster/Dwmapi.java b/src/main/java/mousemaster/platform/windows/Dwmapi.java similarity index 86% rename from src/main/java/mousemaster/Dwmapi.java rename to src/main/java/mousemaster/platform/windows/Dwmapi.java index 55413c97..212cb38f 100644 --- a/src/main/java/mousemaster/Dwmapi.java +++ b/src/main/java/mousemaster/platform/windows/Dwmapi.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.WinDef; diff --git a/src/main/java/mousemaster/ExtendedAdvapi32.java b/src/main/java/mousemaster/platform/windows/ExtendedAdvapi32.java similarity index 94% rename from src/main/java/mousemaster/ExtendedAdvapi32.java rename to src/main/java/mousemaster/platform/windows/ExtendedAdvapi32.java index faba5a2c..ed498a58 100644 --- a/src/main/java/mousemaster/ExtendedAdvapi32.java +++ b/src/main/java/mousemaster/platform/windows/ExtendedAdvapi32.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.WinNT; diff --git a/src/main/java/mousemaster/ExtendedGDI32.java b/src/main/java/mousemaster/platform/windows/ExtendedGDI32.java similarity index 98% rename from src/main/java/mousemaster/ExtendedGDI32.java rename to src/main/java/mousemaster/platform/windows/ExtendedGDI32.java index 87ffaf03..c7a39d40 100644 --- a/src/main/java/mousemaster/ExtendedGDI32.java +++ b/src/main/java/mousemaster/platform/windows/ExtendedGDI32.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.Structure; diff --git a/src/main/java/mousemaster/ExtendedKernel32.java b/src/main/java/mousemaster/platform/windows/ExtendedKernel32.java similarity index 87% rename from src/main/java/mousemaster/ExtendedKernel32.java rename to src/main/java/mousemaster/platform/windows/ExtendedKernel32.java index 90f8d483..075955f8 100644 --- a/src/main/java/mousemaster/ExtendedKernel32.java +++ b/src/main/java/mousemaster/platform/windows/ExtendedKernel32.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.WinBase; diff --git a/src/main/java/mousemaster/ExtendedPsapi.java b/src/main/java/mousemaster/platform/windows/ExtendedPsapi.java similarity index 86% rename from src/main/java/mousemaster/ExtendedPsapi.java rename to src/main/java/mousemaster/platform/windows/ExtendedPsapi.java index f08b3472..603ad44c 100644 --- a/src/main/java/mousemaster/ExtendedPsapi.java +++ b/src/main/java/mousemaster/platform/windows/ExtendedPsapi.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.Psapi; diff --git a/src/main/java/mousemaster/ExtendedUser32.java b/src/main/java/mousemaster/platform/windows/ExtendedUser32.java similarity index 98% rename from src/main/java/mousemaster/ExtendedUser32.java rename to src/main/java/mousemaster/platform/windows/ExtendedUser32.java index 29d364d5..5733037b 100644 --- a/src/main/java/mousemaster/ExtendedUser32.java +++ b/src/main/java/mousemaster/platform/windows/ExtendedUser32.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.Pointer; diff --git a/src/main/java/mousemaster/FadeAnimator.java b/src/main/java/mousemaster/platform/windows/FadeAnimator.java similarity index 98% rename from src/main/java/mousemaster/FadeAnimator.java rename to src/main/java/mousemaster/platform/windows/FadeAnimator.java index e5a5e52f..2daafcf6 100644 --- a/src/main/java/mousemaster/FadeAnimator.java +++ b/src/main/java/mousemaster/platform/windows/FadeAnimator.java @@ -1,8 +1,9 @@ -package mousemaster; +package mousemaster.platform.windows; import io.qt.core.QEasingCurve; import io.qt.core.QMetaObject; import io.qt.core.QVariantAnimation; +import mousemaster.Easing; import java.time.Duration; import java.util.function.Consumer; diff --git a/src/main/java/mousemaster/Gdiplus.java b/src/main/java/mousemaster/platform/windows/Gdiplus.java similarity index 99% rename from src/main/java/mousemaster/Gdiplus.java rename to src/main/java/mousemaster/platform/windows/Gdiplus.java index 1997dc1c..02a0c459 100644 --- a/src/main/java/mousemaster/Gdiplus.java +++ b/src/main/java/mousemaster/platform/windows/Gdiplus.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.Pointer; diff --git a/src/main/java/mousemaster/KbdlayoutinfoParser.java b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java similarity index 99% rename from src/main/java/mousemaster/KbdlayoutinfoParser.java rename to src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java index 027c7870..27a8388c 100644 --- a/src/main/java/mousemaster/KbdlayoutinfoParser.java +++ b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/mousemaster/Magnification.java b/src/main/java/mousemaster/platform/windows/Magnification.java similarity index 95% rename from src/main/java/mousemaster/Magnification.java rename to src/main/java/mousemaster/platform/windows/Magnification.java index 0aff6f7e..5c8a903e 100644 --- a/src/main/java/mousemaster/Magnification.java +++ b/src/main/java/mousemaster/platform/windows/Magnification.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Library; import com.sun.jna.Native; diff --git a/src/main/java/mousemaster/Shcore.java b/src/main/java/mousemaster/platform/windows/Shcore.java similarity index 94% rename from src/main/java/mousemaster/Shcore.java rename to src/main/java/mousemaster/platform/windows/Shcore.java index 3a90ec92..38b5fe79 100644 --- a/src/main/java/mousemaster/Shcore.java +++ b/src/main/java/mousemaster/platform/windows/Shcore.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.IntegerType; import com.sun.jna.Native; diff --git a/src/main/java/mousemaster/ActiveAppFinder.java b/src/main/java/mousemaster/platform/windows/WindowsActiveAppFinder.java similarity index 94% rename from src/main/java/mousemaster/ActiveAppFinder.java rename to src/main/java/mousemaster/platform/windows/WindowsActiveAppFinder.java index d79ec5c8..9627cec0 100644 --- a/src/main/java/mousemaster/ActiveAppFinder.java +++ b/src/main/java/mousemaster/platform/windows/WindowsActiveAppFinder.java @@ -1,16 +1,19 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.*; import com.sun.jna.ptr.IntByReference; +import mousemaster.platform.ActiveAppFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; -public class ActiveAppFinder { +public class WindowsActiveAppFinder implements ActiveAppFinder { - private static final Logger logger = LoggerFactory.getLogger(ActiveAppFinder.class); + private static final Logger logger = LoggerFactory.getLogger(WindowsActiveAppFinder.class); private IntByReference processId = new IntByReference(); private byte[] executableNameBytes = new byte[1024]; @@ -18,6 +21,7 @@ public class ActiveAppFinder { private String lastIgnoredExecutableName; private boolean lastAttemptFailed; + @Override public App activeApp() { WinDef.HWND hwnd = User32.INSTANCE.GetForegroundWindow(); User32.INSTANCE.GetWindowThreadProcessId(hwnd, processId); diff --git a/src/main/java/mousemaster/WindowsClock.java b/src/main/java/mousemaster/platform/windows/WindowsClock.java similarity index 94% rename from src/main/java/mousemaster/WindowsClock.java rename to src/main/java/mousemaster/platform/windows/WindowsClock.java index 816bd3cb..9891bfbf 100644 --- a/src/main/java/mousemaster/WindowsClock.java +++ b/src/main/java/mousemaster/platform/windows/WindowsClock.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import java.time.Instant; diff --git a/src/main/java/mousemaster/platform/windows/WindowsConsole.java b/src/main/java/mousemaster/platform/windows/WindowsConsole.java new file mode 100644 index 00000000..fa9b38c7 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsConsole.java @@ -0,0 +1,25 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.User32; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinUser; +import mousemaster.platform.Console; + +public class WindowsConsole implements Console { + + @Override + public void show() { + WinDef.HWND hwnd = Kernel32.INSTANCE.GetConsoleWindow(); + User32.INSTANCE.ShowWindow(hwnd, WinUser.SW_SHOW); + } + + @Override + public void hide() { + WinDef.HWND hwnd = Kernel32.INSTANCE.GetConsoleWindow(); + User32.INSTANCE.ShowWindow(hwnd, WinUser.SW_HIDE); + } + +} diff --git a/src/main/java/mousemaster/KeyRegurgitator.java b/src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java similarity index 75% rename from src/main/java/mousemaster/KeyRegurgitator.java rename to src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java index 6b61829d..9c943d76 100644 --- a/src/main/java/mousemaster/KeyRegurgitator.java +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java @@ -1,17 +1,24 @@ -package mousemaster; +package mousemaster.platform.windows; +import mousemaster.*; + +import mousemaster.platform.Keyboard; +import mousemaster.platform.KeyRegurgitator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; -/** - * This should be an interface with one implementation per platform. - */ -public class KeyRegurgitator { +public class WindowsKeyRegurgitator implements KeyRegurgitator { + + private static final Logger logger = LoggerFactory.getLogger(WindowsKeyRegurgitator.class); + private final Keyboard keyboard; - private static final Logger logger = LoggerFactory.getLogger(KeyRegurgitator.class); + public WindowsKeyRegurgitator(Keyboard keyboard) { + this.keyboard = keyboard; + } + @Override public void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat) { logger.debug( "Regurgitating " + regurgitate.key() + ", startRepeat = " + startRepeat + @@ -24,7 +31,7 @@ public void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRe // then just pressing g again would open the Windows Game popup, as if leftwin // was still being pressed. Key key = regurgitate.key(); - WindowsKeyboard.sendInputMoves( + keyboard.sendInputMoves( !regurgitate.alsoRelease() ? List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS)) : List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS), diff --git a/src/main/java/mousemaster/WindowsKeyboard.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java similarity index 99% rename from src/main/java/mousemaster/WindowsKeyboard.java rename to src/main/java/mousemaster/platform/windows/WindowsKeyboard.java index fa03abf8..3a912061 100644 --- a/src/main/java/mousemaster/WindowsKeyboard.java +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.WinDef; diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java new file mode 100644 index 00000000..c97ea047 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java @@ -0,0 +1,46 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import mousemaster.platform.Keyboard; + +import java.util.List; + +public class WindowsKeyboardAdapter implements Keyboard { + + @Override + public void update(double delta) { + WindowsKeyboard.update(delta); + } + + @Override + public void reset() { + WindowsKeyboard.reset(); + } + + @Override + public void sendInputMoves(List moves, boolean startRepeat) { + WindowsKeyboard.sendInputMoves(moves, startRepeat); + } + + @Override + public void keyPressedNotEaten(Key key) { + WindowsKeyboard.keyPressedNotEaten(key); + } + + @Override + public void keyReleasedNotEaten(Key key) { + WindowsKeyboard.keyReleasedNotEaten(key); + } + + @Override + public void recordEarlyReleaseForQueuedPress(Key key) { + WindowsKeyboard.recordEarlyReleaseForQueuedPress(key); + } + + @Override + public void clearEarlyReleaseForQueuedPress(Key key) { + WindowsKeyboard.clearEarlyReleaseForQueuedPress(key); + } + +} diff --git a/src/main/java/mousemaster/WindowsMouse.java b/src/main/java/mousemaster/platform/windows/WindowsMouse.java similarity index 99% rename from src/main/java/mousemaster/WindowsMouse.java rename to src/main/java/mousemaster/platform/windows/WindowsMouse.java index 6152b925..38d345fc 100644 --- a/src/main/java/mousemaster/WindowsMouse.java +++ b/src/main/java/mousemaster/platform/windows/WindowsMouse.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Memory; import com.sun.jna.Pointer; diff --git a/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java new file mode 100644 index 00000000..a6b65eaf --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java @@ -0,0 +1,79 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import mousemaster.platform.Mouse; + +public class WindowsMouseAdapter implements Mouse { + + @Override + public void beginMove() { + WindowsMouse.beginMove(); + } + + @Override + public void endMove() { + WindowsMouse.endMove(); + } + + @Override + public void moveBy(boolean xForward, double dx, boolean yForward, double dy) { + WindowsMouse.moveBy(xForward, dx, yForward, dy); + } + + @Override + public void synchronousMoveTo(int x, int y) { + WindowsMouse.synchronousMoveTo(x, y); + } + + @Override + public void pressLeft() { + WindowsMouse.pressLeft(); + } + + @Override + public void pressMiddle() { + WindowsMouse.pressMiddle(); + } + + @Override + public void pressRight() { + WindowsMouse.pressRight(); + } + + @Override + public void releaseLeft() { + WindowsMouse.releaseLeft(); + } + + @Override + public void releaseMiddle() { + WindowsMouse.releaseMiddle(); + } + + @Override + public void releaseRight() { + WindowsMouse.releaseRight(); + } + + @Override + public void wheelHorizontallyBy(boolean forward, double delta) { + WindowsMouse.wheelHorizontallyBy(forward, delta); + } + + @Override + public void wheelVerticallyBy(boolean forward, double delta) { + WindowsMouse.wheelVerticallyBy(forward, delta); + } + + @Override + public void showCursor() { + WindowsMouse.showCursor(); + } + + @Override + public void hideCursor() { + WindowsMouse.hideCursor(); + } + +} diff --git a/src/main/java/mousemaster/WindowsOverlay.java b/src/main/java/mousemaster/platform/windows/WindowsOverlay.java similarity index 99% rename from src/main/java/mousemaster/WindowsOverlay.java rename to src/main/java/mousemaster/platform/windows/WindowsOverlay.java index 6befbd67..da9da24d 100644 --- a/src/main/java/mousemaster/WindowsOverlay.java +++ b/src/main/java/mousemaster/platform/windows/WindowsOverlay.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Memory; import com.sun.jna.Native; @@ -13,7 +15,7 @@ import io.qt.widgets.QGraphicsScene; import io.qt.widgets.QLabel; import io.qt.widgets.QWidget; -import mousemaster.WindowsMouse.MouseSize; +import mousemaster.platform.windows.WindowsMouse.MouseSize; import mousemaster.qt.TransparentWindow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -4499,4 +4501,4 @@ static void mouseMoved(WinDef.POINT mousePosition) { moveAndResizeIndicatorWindow(mousePosition); } -} \ No newline at end of file +} diff --git a/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java new file mode 100644 index 00000000..e5aec284 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java @@ -0,0 +1,122 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import mousemaster.platform.Overlay; + +import java.time.Duration; +import java.util.Set; + +public class WindowsOverlayAdapter implements Overlay { + + @Override + public void update(double delta) { + WindowsOverlay.update(delta); + } + + @Override + public void flushCache() { + WindowsOverlay.flushCache(); + } + + @Override + public void setTopmost() { + WindowsOverlay.setTopmost(); + } + + @Override + public void setMessagePump(Runnable pump) { + WindowsOverlay.setMessagePump(pump); + } + + @Override + public void preWarmFontStyles(Set configs) { + WindowsOverlay.preWarmFontStyles(configs); + } + + @Override + public void preWarmHintMeshWindows() { + WindowsOverlay.preWarmHintMeshWindows(); + } + + @Override + public Rectangle activeWindowRectangle(double widthPct, double heightPct, + int topInset, int bottomInset, + int leftInset, int rightInset) { + return WindowsOverlay.activeWindowRectangle(widthPct, heightPct, + topInset, bottomInset, leftInset, rightInset); + } + + @Override + public void setIndicator(Indicator indicator, boolean fadeAnimationEnabled, + Duration fadeAnimationDuration, boolean allowFade) { + WindowsOverlay.setIndicator(indicator, fadeAnimationEnabled, + fadeAnimationDuration, allowFade); + } + + @Override + public void hideIndicator(boolean allowFade) { + WindowsOverlay.hideIndicator(allowFade); + } + + @Override + public void setGrid(Grid grid) { + WindowsOverlay.setGrid(grid); + } + + @Override + public void hideGrid() { + WindowsOverlay.hideGrid(); + } + + @Override + public void setHintMesh(HintMesh hintMesh, Zoom zoom) { + WindowsOverlay.setHintMesh(hintMesh, zoom); + } + + @Override + public void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) { + WindowsOverlay.setHintMesh(hintMesh, zoom, hintMatch); + } + + @Override + public void hideHintMesh() { + WindowsOverlay.hideHintMesh(); + } + + @Override + public void animateHintMatch(Hint hint) { + WindowsOverlay.animateHintMatch(hint); + } + + @Override + public void setZoom(Zoom zoom) { + WindowsOverlay.setZoom(zoom); + } + + @Override + public void startScreenshotZoomAnimation(Rectangle screenRect, Zoom beginZoom) { + WindowsOverlay.startScreenshotZoomAnimation(screenRect, beginZoom); + } + + @Override + public void updateScreenshotZoom(Zoom zoom) { + WindowsOverlay.updateScreenshotZoom(zoom); + } + + @Override + public void endScreenshotZoomAnimation(Zoom finalZoom) { + WindowsOverlay.endScreenshotZoomAnimation(finalZoom); + } + + @Override + public boolean waitForZoomBeforeRepainting() { + return WindowsOverlay.waitForZoomBeforeRepainting; + } + + @Override + public void setWaitForZoomBeforeRepainting(boolean value) { + WindowsOverlay.waitForZoomBeforeRepainting = value; + } + +} diff --git a/src/main/java/mousemaster/WindowsPlatform.java b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java similarity index 94% rename from src/main/java/mousemaster/WindowsPlatform.java rename to src/main/java/mousemaster/platform/windows/WindowsPlatform.java index 45e6f66f..83173667 100644 --- a/src/main/java/mousemaster/WindowsPlatform.java +++ b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java @@ -1,8 +1,19 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.*; +import mousemaster.platform.ActiveAppFinder; +import mousemaster.platform.Console; +import mousemaster.platform.Keyboard; +import mousemaster.platform.KeyboardLayoutProvider; +import mousemaster.platform.KeyRegurgitator; +import mousemaster.platform.Mouse; +import mousemaster.platform.Overlay; +import mousemaster.platform.Screens; +import mousemaster.platform.UiAutomation; import mousemaster.KeyEvent.PressKeyEvent; import mousemaster.KeyEvent.ReleaseKeyEvent; import org.slf4j.Logger; @@ -18,7 +29,15 @@ public class WindowsPlatform implements Platform { private static final Logger logger = LoggerFactory.getLogger(WindowsPlatform.class); private final boolean keyRegurgitationEnabled; - private final KeyRegurgitator keyRegurgitator = new KeyRegurgitator(); + private final Keyboard keyboard = new WindowsKeyboardAdapter(); + private final Mouse mouse = new WindowsMouseAdapter(); + private final Screens screens = new WindowsScreens(); + private final Overlay overlay = new WindowsOverlayAdapter(); + private final UiAutomation uiAutomation = new WindowsUiAutomationAdapter(); + private final ActiveAppFinder activeAppFinder = new WindowsActiveAppFinder(); + private final Console console = new WindowsConsole(); + private final KeyboardLayoutProvider keyboardLayoutProvider = WindowsVirtualKey::activeKeyboardLayout; + private final KeyRegurgitator keyRegurgitator = new WindowsKeyRegurgitator(keyboard); private final WindowsClock clock = new WindowsClock(); private MouseController mouseController; @@ -83,7 +102,7 @@ public void update(double delta) { } @Override - public void windowsMessagePump() { + public void pumpEvents() { while (User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1)) { User32.INSTANCE.TranslateMessage(msg); User32.INSTANCE.DispatchMessage(msg); @@ -93,7 +112,7 @@ public void windowsMessagePump() { @Override public void sleep() throws InterruptedException { - windowsMessagePump(); + pumpEvents(); WinDef.POINT mousePosition = null; for (WinDef.POINT p; (p = mousePositionQueue.poll()) != null;) mousePosition = p; @@ -115,14 +134,14 @@ else if (lastMousePosition != null) { // Reposition the indicator with the updated cursor visual center. WindowsOverlay.mouseMoved(lastMousePosition); } - windowsMessagePump(); + pumpEvents(); long beforeTime = System.nanoTime(); while (true) { long currentTime = System.nanoTime(); if ((currentTime - beforeTime) / 1e6 >= 10) break; Thread.sleep(1); - windowsMessagePump(); + pumpEvents(); } } @@ -166,7 +185,7 @@ public void reset(MouseController mouseController, KeyboardManager keyboardManag mousePositionListeners.forEach( mousePositionListener -> mousePositionListener.mouseMoved(mousePosition.x, mousePosition.y)); - WindowsOverlay.setMessagePump(this::windowsMessagePump); + WindowsOverlay.setMessagePump(this::pumpEvents); if (keyboardHookCallback == null) installHooks(); } @@ -257,8 +276,43 @@ public PlatformClock clock() { } @Override - public KeyboardLayout activeKeyboardLayout() { - return WindowsVirtualKey.activeKeyboardLayout(); + public KeyboardLayoutProvider keyboardLayoutProvider() { + return keyboardLayoutProvider; + } + + @Override + public Keyboard keyboard() { + return keyboard; + } + + @Override + public Mouse mouse() { + return mouse; + } + + @Override + public Screens screens() { + return screens; + } + + @Override + public Overlay overlay() { + return overlay; + } + + @Override + public UiAutomation uiAutomation() { + return uiAutomation; + } + + @Override + public ActiveAppFinder activeAppFinder() { + return activeAppFinder; + } + + @Override + public Console console() { + return console; } diff --git a/src/main/java/mousemaster/WindowsScreen.java b/src/main/java/mousemaster/platform/windows/WindowsScreen.java similarity index 98% rename from src/main/java/mousemaster/WindowsScreen.java rename to src/main/java/mousemaster/platform/windows/WindowsScreen.java index 199424c3..5f0fd36a 100644 --- a/src/main/java/mousemaster/WindowsScreen.java +++ b/src/main/java/mousemaster/platform/windows/WindowsScreen.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.WinDef; diff --git a/src/main/java/mousemaster/platform/windows/WindowsScreens.java b/src/main/java/mousemaster/platform/windows/WindowsScreens.java new file mode 100644 index 00000000..9dc9b697 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsScreens.java @@ -0,0 +1,16 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import mousemaster.platform.Screens; + +import java.util.Set; + +public class WindowsScreens implements Screens { + + @Override + public Set findScreens() { + return WindowsScreen.findScreens(); + } + +} diff --git a/src/main/java/mousemaster/WindowsUiAccess.java b/src/main/java/mousemaster/platform/windows/WindowsUiAccess.java similarity index 99% rename from src/main/java/mousemaster/WindowsUiAccess.java rename to src/main/java/mousemaster/platform/windows/WindowsUiAccess.java index 95485830..8db81298 100644 --- a/src/main/java/mousemaster/WindowsUiAccess.java +++ b/src/main/java/mousemaster/platform/windows/WindowsUiAccess.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.platform.win32.*; diff --git a/src/main/java/mousemaster/WindowsUiAutomation.java b/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java similarity index 99% rename from src/main/java/mousemaster/WindowsUiAutomation.java rename to src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java index 75287595..da06fb56 100644 --- a/src/main/java/mousemaster/WindowsUiAutomation.java +++ b/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.*; import com.sun.jna.platform.win32.*; @@ -6,6 +8,7 @@ import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; +import mousemaster.platform.UiAutomation.UiElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,9 +61,6 @@ public class WindowsUiAutomation { }); private static volatile boolean backgroundComInitialized; - record UiElement(double centerX, double centerY) { - } - private static Memory createBoolVariantTrue() { Memory variant = new Memory(16); variant.clear(); diff --git a/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java new file mode 100644 index 00000000..51456f48 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java @@ -0,0 +1,17 @@ +package mousemaster.platform.windows; + +import mousemaster.*; + +import mousemaster.platform.UiAutomation; + +import java.util.List; +import java.util.concurrent.Future; + +public class WindowsUiAutomationAdapter implements UiAutomation { + + @Override + public Future> startFindInteractiveUiElements() { + return WindowsUiAutomation.startFindInteractiveUiElements(); + } + +} diff --git a/src/main/java/mousemaster/WindowsVirtualKey.java b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java similarity index 99% rename from src/main/java/mousemaster/WindowsVirtualKey.java rename to src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java index 65008a08..40a009b0 100644 --- a/src/main/java/mousemaster/WindowsVirtualKey.java +++ b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.Pointer; diff --git a/src/main/java/mousemaster/Winmm.java b/src/main/java/mousemaster/platform/windows/Winmm.java similarity index 78% rename from src/main/java/mousemaster/Winmm.java rename to src/main/java/mousemaster/platform/windows/Winmm.java index cb912f29..5b5b7ced 100644 --- a/src/main/java/mousemaster/Winmm.java +++ b/src/main/java/mousemaster/platform/windows/Winmm.java @@ -1,4 +1,6 @@ -package mousemaster; +package mousemaster.platform.windows; + +import mousemaster.*; import com.sun.jna.Native; import com.sun.jna.win32.StdCallLibrary; From e37641024e3da3d267f210eec23c877c89564cbb Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Mon, 18 May 2026 12:01:16 +0200 Subject: [PATCH 2/6] Reorganizing existing impl usage --- pom.xml | 2 +- .../java/mousemaster/ApplicationOptions.java | 50 ++++++++ src/main/java/mousemaster/KeyboardLayout.java | 28 ++++- .../mousemaster/MousemasterApplication.java | 113 ++---------------- .../platform/windows/KbdlayoutinfoParser.java | 6 +- .../platform/windows/WindowsMain.java | 78 ++++++++++++ .../platform/windows/WindowsPlatform.java | 3 +- .../platform/windows/WindowsVirtualKey.java | 14 ++- 8 files changed, 172 insertions(+), 122 deletions(-) create mode 100644 src/main/java/mousemaster/ApplicationOptions.java create mode 100644 src/main/java/mousemaster/platform/windows/WindowsMain.java diff --git a/pom.xml b/pom.xml index 279193ec..ba1864dd 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 21 0.9.28 mousemaster - mousemaster.MousemasterApplication + mousemaster.platform.windows.WindowsMain 5.13.0 diff --git a/src/main/java/mousemaster/ApplicationOptions.java b/src/main/java/mousemaster/ApplicationOptions.java new file mode 100644 index 00000000..f0517ef5 --- /dev/null +++ b/src/main/java/mousemaster/ApplicationOptions.java @@ -0,0 +1,50 @@ +package mousemaster; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public record ApplicationOptions(String tempDirectory, + String logLevel, + boolean logToFile, + boolean pauseOnError, + boolean showVersion, + Path configurationPath, + boolean multipleInstancesAllowed, + boolean keyRegurgitationEnabled, + boolean graalvmAgentRun) { + + public static ApplicationOptions parse(String[] args) { + return new ApplicationOptions( + stringArg(args, "--temp-directory=", null), + stringArg(args, "--log-level=", null), + booleanArg(args, "--log-to-file=", false), + booleanArg(args, "--pause-on-error=", true), + Stream.of(args).anyMatch(Predicate.isEqual("--version")), + Paths.get(stringArg(args, "--configuration-file=", + "mousemaster.properties")), + booleanArg(args, "--multiple-instances-allowed=", false), + booleanArg(args, "--key-regurgitation-enabled=", true), + Stream.of(args).anyMatch(Predicate.isEqual("--graalvm-agent-run")) + ); + } + + private static String stringArg(String[] args, String prefix, String defaultValue) { + return Stream.of(args) + .filter(arg -> arg.startsWith(prefix)) + .map(arg -> arg.substring(prefix.length())) + .findFirst() + .orElse(defaultValue); + } + + private static boolean booleanArg(String[] args, String prefix, boolean defaultValue) { + return Stream.of(args) + .filter(arg -> arg.startsWith(prefix)) + .map(arg -> arg.substring(prefix.length())) + .findFirst() + .map(Boolean::parseBoolean) + .orElse(defaultValue); + } + +} diff --git a/src/main/java/mousemaster/KeyboardLayout.java b/src/main/java/mousemaster/KeyboardLayout.java index 64f37a88..1dd91ac4 100644 --- a/src/main/java/mousemaster/KeyboardLayout.java +++ b/src/main/java/mousemaster/KeyboardLayout.java @@ -2,7 +2,6 @@ import com.google.gson.*; import com.google.gson.reflect.TypeToken; -import mousemaster.platform.windows.WindowsVirtualKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +29,7 @@ public final class KeyboardLayout { Type listType = new TypeToken>() {}.getType(); Gson gson = new GsonBuilder() .registerTypeAdapter(Key.class, new KeyDeserializer()) + .registerTypeAdapter(KeyboardLayoutKey.class, new KeyboardLayoutKeyDeserializer()) .create(); long before = System.nanoTime(); List keyboardLayouts = gson.fromJson(reader, listType); @@ -58,11 +58,27 @@ public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContex } } - public record KeyboardLayoutKey(int scanCode, WindowsVirtualKey virtualKey, Key key, + public record KeyboardLayoutKey(int scanCode, String virtualKeyName, Key key, String text, String name) { } + public static class KeyboardLayoutKeyDeserializer implements JsonDeserializer { + + @Override + public KeyboardLayoutKey deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) + throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + int scanCode = obj.get("scanCode").getAsInt(); + String virtualKeyName = obj.get("virtualKey").getAsString(); + Key key = context.deserialize(obj.get("key"), Key.class); + String text = obj.has("text") ? obj.get("text").getAsString() : null; + String name = obj.has("name") ? obj.get("name").getAsString() : null; + return new KeyboardLayoutKey(scanCode, virtualKeyName, key, text, name); + } + } + private final String identifier; private final String displayName; private final String driverName; @@ -111,9 +127,9 @@ public Key keyFromScanCode(int scanCode) { return null; } - public Key keyFromVirtualKey(WindowsVirtualKey virtualKey) { + public Key keyFromVirtualKeyName(String virtualKeyName) { for (KeyboardLayoutKey keyboardLayoutKey : keys) { - if (keyboardLayoutKey.virtualKey == virtualKey) + if (virtualKeyName.equals(keyboardLayoutKey.virtualKeyName())) return keyboardLayoutKey.key(); } return null; @@ -127,10 +143,10 @@ public int scanCode(Key key) { return -1; } - public WindowsVirtualKey virtualKey(Key key) { + public String virtualKeyName(Key key) { for (KeyboardLayoutKey keyboardLayoutKey : keys) { if (keyboardLayoutKey.key.equals(key)) - return keyboardLayoutKey.virtualKey(); + return keyboardLayoutKey.virtualKeyName(); } return null; } diff --git a/src/main/java/mousemaster/MousemasterApplication.java b/src/main/java/mousemaster/MousemasterApplication.java index 386a9eaf..7c6cfeaa 100644 --- a/src/main/java/mousemaster/MousemasterApplication.java +++ b/src/main/java/mousemaster/MousemasterApplication.java @@ -7,20 +7,11 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.FileAppender; -import com.sun.jna.Native; -import mousemaster.platform.windows.WindowsPlatform; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Properties; import java.util.Scanner; -import java.util.function.Predicate; import java.util.logging.LogManager; -import java.util.stream.Stream; public class MousemasterApplication { @@ -35,88 +26,7 @@ public class MousemasterApplication { SLF4JBridgeHandler.install(); } - public static void main(String[] args) throws InterruptedException, IOException { - String tempDirectory = Stream.of(args) - .filter(arg -> arg.startsWith("--temp-directory=")) - .map(arg -> arg.split("=")[1]) - .findFirst().orElse(null); - setTempDirectory(tempDirectory); - Stream.of(args) - .filter(arg -> arg.startsWith("--log-level=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .ifPresent(MousemasterApplication::setLogLevel); - boolean logToFile = Stream.of(args) - .filter(arg -> arg.startsWith("--log-to-file=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .map(Boolean::parseBoolean) - .orElse(false); - if (logToFile) - enableLogToFile(); - boolean pauseOnError = Stream.of(args) - .filter(arg -> arg.startsWith("--pause-on-error=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .map(Boolean::parseBoolean) - .orElse(true); - String version; - String commitId; - try (InputStream versionInputStream = MousemasterApplication.class.getClassLoader().getResourceAsStream("application.properties")) { - Properties versionProp = new Properties(); - versionProp.load(versionInputStream); - version = versionProp.getProperty("version"); - commitId = versionProp.getProperty("commitId"); - } - if (Stream.of(args).anyMatch(Predicate.isEqual(("--version")))) { - System.out.println("mousemaster v" + version + " (" + commitId + ")"); - return; - } - Path configurationPath = Stream.of(args) - .filter(arg -> arg.startsWith( - "--configuration-file=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .map(Paths::get) - .orElse(Paths.get("mousemaster.properties")); - boolean multipleInstancesAllowed = - Stream.of(args) - .filter(arg -> arg.startsWith("--multiple-instances-allowed=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .map(Boolean::parseBoolean) - .orElse(false); - boolean keyRegurgitationEnabled = // Feature flag. - Stream.of(args) - .filter(arg -> arg.startsWith("--key-regurgitation-enabled=")) - .map(arg -> arg.split("=")[1]) - .findFirst() - .map(Boolean::parseBoolean) - .orElse(true); // Remove this feature flag if confirmed working - if (Stream.of(args).anyMatch(Predicate.isEqual(("--graalvm-agent-run")))) { - logger.info("--graalvm-agent-run flag found, exiting in 20s"); - new Thread(() -> { - try { - Thread.sleep(20000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - System.exit(0); - }).start(); - } - Platform platform = platform(multipleInstancesAllowed, keyRegurgitationEnabled, pauseOnError); - logger.info("mousemaster v" + version + " (" + commitId + ")"); - if (platform == null) - return; - try { - Native.setCallbackExceptionHandler((c, e) -> shutdownAfterException(e, platform, true, pauseOnError)); - new Mousemaster(configurationPath, platform).run(); - } catch (Throwable e) { - shutdownAfterException(e, platform, false, pauseOnError); - } - } - - private static void setTempDirectory(String tempDirectory) { + public static void setTempDirectory(String tempDirectory) { if (tempDirectory != null) MousemasterApplication.tempDirectory = tempDirectory; else { @@ -131,24 +41,15 @@ private static void setTempDirectory(String tempDirectory) { } } if (MousemasterApplication.tempDirectory == null) - MousemasterApplication.tempDirectory = System.getProperty("java.io.tmpdir") + "mousemaster-" + System.getProperty("user.name").hashCode(); + MousemasterApplication.tempDirectory = + System.getProperty("java.io.tmpdir") + "mousemaster-" + + System.getProperty("user.name").hashCode(); System.setProperty("jna.tmpdir", MousemasterApplication.tempDirectory + "/jna"); } - private static Platform platform(boolean multipleInstancesAllowed, - boolean keyRegurgitationEnabled, - boolean pauseOnError) { - try { - return new WindowsPlatform(multipleInstancesAllowed, keyRegurgitationEnabled); - } catch (Exception e) { - shutdownAfterException(e, null, false, pauseOnError); - } - return null; - } - - private static void shutdownAfterException(Throwable e, Platform platform, - boolean jnaCallback, - boolean pauseOnError) { + public static void shutdownAfterException(Throwable e, Platform platform, + boolean jnaCallback, + boolean pauseOnError) { if (platform != null) platform.shutdown(); logger.error(jnaCallback ? "Error in JNA callback" : "", e); diff --git a/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java index 27a8388c..39af1831 100644 --- a/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java +++ b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java @@ -301,7 +301,7 @@ else if (text != null) key = Key.ofCharacter(text); if (key != null) keyboardLayout.keys() - .add(new KeyboardLayout.KeyboardLayoutKey(sc, vk, key, + .add(new KeyboardLayout.KeyboardLayoutKey(sc, vk.name(), key, text == null || text.isBlank() ? null : text, name.isBlank() ? null : name)); } @@ -355,7 +355,7 @@ private static void addCustomKeyboardLayouts(List keyboardLayout .orElseThrow(); KeyboardLayout.KeyboardLayoutKey usHalmakKey = new KeyboardLayout.KeyboardLayoutKey( usQwertyKey.scanCode(), - sameCharacterUsQwertyKey.virtualKey(), + sameCharacterUsQwertyKey.virtualKeyName(), sameCharacterUsQwertyKey.key(), sameCharacterUsQwertyKey.text(), sameCharacterUsQwertyKey.name() @@ -376,7 +376,7 @@ private static void fixKoreanKeyboardLayout(List keyboardLayouts .orElseThrow(); koreanKeyboardLayout.keys() .add(new KeyboardLayout.KeyboardLayoutKey(57400, - WindowsVirtualKey.VK_RMENU, Key.rightalt, null, + WindowsVirtualKey.VK_RMENU.name(), Key.rightalt, null, "Right Alt")); } diff --git a/src/main/java/mousemaster/platform/windows/WindowsMain.java b/src/main/java/mousemaster/platform/windows/WindowsMain.java new file mode 100644 index 00000000..d3b9ec23 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsMain.java @@ -0,0 +1,78 @@ +package mousemaster.platform.windows; + +import com.sun.jna.Native; +import mousemaster.ApplicationOptions; +import mousemaster.Mousemaster; +import mousemaster.MousemasterApplication; +import mousemaster.Platform; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class WindowsMain { + + private static final Logger logger = LoggerFactory.getLogger(WindowsMain.class); + + public static void main(String[] args) throws InterruptedException, IOException { + ApplicationOptions options = ApplicationOptions.parse(args); + MousemasterApplication.setTempDirectory(options.tempDirectory()); + if (options.logLevel() != null) + MousemasterApplication.setLogLevel(options.logLevel()); + if (options.logToFile()) + MousemasterApplication.enableLogToFile(); + String version; + String commitId; + try (InputStream versionInputStream = WindowsMain.class.getClassLoader() + .getResourceAsStream( + "application.properties")) { + Properties versionProp = new Properties(); + versionProp.load(versionInputStream); + version = versionProp.getProperty("version"); + commitId = versionProp.getProperty("commitId"); + } + if (options.showVersion()) { + System.out.println("mousemaster v" + version + " (" + commitId + ")"); + return; + } + if (options.graalvmAgentRun()) { + logger.info("--graalvm-agent-run flag found, exiting in 20s"); + new Thread(() -> { + try { + Thread.sleep(20000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.exit(0); + }).start(); + } + Platform platform = createPlatform(options.multipleInstancesAllowed(), + options.keyRegurgitationEnabled(), options.pauseOnError()); + logger.info("mousemaster v" + version + " (" + commitId + ")"); + if (platform == null) + return; + try { + Native.setCallbackExceptionHandler((c, e) -> + MousemasterApplication.shutdownAfterException(e, platform, true, + options.pauseOnError())); + new Mousemaster(options.configurationPath(), platform).run(); + } catch (Throwable e) { + MousemasterApplication.shutdownAfterException(e, platform, false, + options.pauseOnError()); + } + } + + private static Platform createPlatform(boolean multipleInstancesAllowed, + boolean keyRegurgitationEnabled, + boolean pauseOnError) { + try { + return new WindowsPlatform(multipleInstancesAllowed, keyRegurgitationEnabled); + } catch (Exception e) { + MousemasterApplication.shutdownAfterException(e, null, false, pauseOnError); + } + return null; + } + +} diff --git a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java index 83173667..2cbc05a9 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java +++ b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java @@ -315,7 +315,6 @@ public Console console() { return console; } - /** * On the Windows lock screen, hit space then enter the pin. Space press is recorded by the app but the * corresponding release is never received. That is why we need to double-check if the key is still pressed @@ -375,7 +374,7 @@ private void sanityCheckCurrentlyPressedKeys(double delta) { boolean pressedAccordingToOs = (state & 0x8000) != 0; if (!pressedAccordingToOs) continue; - Key key = layout.keyFromVirtualKey(virtualKey); + Key key = layout.keyFromVirtualKeyName(virtualKey.name()); if (key != null && keysPressedInHook.contains(key) && !currentlyPressedNotEatenKeys.containsKey(key)) { if (key.equals(Key.leftctrl) && diff --git a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java index 40a009b0..48cea009 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java +++ b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java @@ -474,7 +474,7 @@ private static KeyboardLayout startupKeyboardLayout() { public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int scanCode, int flags) { if (scanCode == 0) { // Injected key event have scanCode 0. - return WindowsVirtualKey.activeKeyboardLayout().keyFromVirtualKey(windowsVirtualKey); + return WindowsVirtualKey.activeKeyboardLayout().keyFromVirtualKeyName(windowsVirtualKey.name()); } // When pressing rightctrl the scanCode should be E01D but is 1D (which is leftctrl's scanCode). // rightctrl: @@ -495,12 +495,18 @@ public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int s public static WindowsVirtualKey windowsVirtualKeyFromKey(Key key, KeyboardLayout keyboardLayout) { - WindowsVirtualKey virtualKey = keyboardLayout.virtualKey(key); - if (virtualKey == null) { + String virtualKeyName = keyboardLayout.virtualKeyName(key); + if (virtualKeyName == null) { logger.debug("Unable to map key " + key + " to a Windows virtual key using " + keyboardLayout); + return null; + } + try { + return WindowsVirtualKey.valueOf(virtualKeyName); + } catch (IllegalArgumentException e) { + logger.debug("Unknown virtual key name: " + virtualKeyName); + return null; } - return virtualKey; } } From 654bb529e7481b02513b09e84d9c6f732bcb1655 Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Tue, 19 May 2026 08:42:15 +0200 Subject: [PATCH 3/6] Clean up --- src/main/java/mousemaster/platform/KeyRegurgitator.java | 1 - src/main/java/mousemaster/platform/KeyboardLayoutProvider.java | 1 - src/main/java/mousemaster/platform/Mouse.java | 1 - src/main/java/mousemaster/platform/Overlay.java | 1 - src/main/java/mousemaster/platform/Screens.java | 1 - src/main/java/mousemaster/platform/UiAutomation.java | 1 - 6 files changed, 6 deletions(-) diff --git a/src/main/java/mousemaster/platform/KeyRegurgitator.java b/src/main/java/mousemaster/platform/KeyRegurgitator.java index 13a12b6f..f36aebbe 100644 --- a/src/main/java/mousemaster/platform/KeyRegurgitator.java +++ b/src/main/java/mousemaster/platform/KeyRegurgitator.java @@ -5,5 +5,4 @@ public interface KeyRegurgitator { void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat); - } diff --git a/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java index 9072a99f..27286d43 100644 --- a/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java +++ b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java @@ -5,5 +5,4 @@ public interface KeyboardLayoutProvider { KeyboardLayout activeKeyboardLayout(); - } diff --git a/src/main/java/mousemaster/platform/Mouse.java b/src/main/java/mousemaster/platform/Mouse.java index 9d009c7a..e68a0998 100644 --- a/src/main/java/mousemaster/platform/Mouse.java +++ b/src/main/java/mousemaster/platform/Mouse.java @@ -29,5 +29,4 @@ public interface Mouse { void showCursor(); void hideCursor(); - } diff --git a/src/main/java/mousemaster/platform/Overlay.java b/src/main/java/mousemaster/platform/Overlay.java index da8ddfd9..3337e4cf 100644 --- a/src/main/java/mousemaster/platform/Overlay.java +++ b/src/main/java/mousemaster/platform/Overlay.java @@ -57,5 +57,4 @@ void setIndicator(Indicator indicator, boolean fadeAnimationEnabled, boolean waitForZoomBeforeRepainting(); void setWaitForZoomBeforeRepainting(boolean value); - } diff --git a/src/main/java/mousemaster/platform/Screens.java b/src/main/java/mousemaster/platform/Screens.java index 5f72bcbc..25d12e07 100644 --- a/src/main/java/mousemaster/platform/Screens.java +++ b/src/main/java/mousemaster/platform/Screens.java @@ -7,5 +7,4 @@ public interface Screens { Set findScreens(); - } diff --git a/src/main/java/mousemaster/platform/UiAutomation.java b/src/main/java/mousemaster/platform/UiAutomation.java index 1eec5473..de4959aa 100644 --- a/src/main/java/mousemaster/platform/UiAutomation.java +++ b/src/main/java/mousemaster/platform/UiAutomation.java @@ -9,5 +9,4 @@ public interface UiAutomation { record UiElement(double centerX, double centerY) { } - } From a5219faf713ba657bb4fcbbd0d66d9b0e1fb20bc Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Tue, 19 May 2026 09:46:22 +0200 Subject: [PATCH 4/6] Fixing fqn usages --- src/main/java/mousemaster/MouseController.java | 6 ++++-- src/main/java/mousemaster/Platform.java | 4 ++-- .../mousemaster/platform/{Mouse.java => PlatformMouse.java} | 2 +- .../mousemaster/platform/windows/WindowsMouseAdapter.java | 4 ++-- .../java/mousemaster/platform/windows/WindowsPlatform.java | 6 +++--- 5 files changed, 12 insertions(+), 10 deletions(-) rename src/main/java/mousemaster/platform/{Mouse.java => PlatformMouse.java} (93%) diff --git a/src/main/java/mousemaster/MouseController.java b/src/main/java/mousemaster/MouseController.java index 495d282b..6a295f11 100644 --- a/src/main/java/mousemaster/MouseController.java +++ b/src/main/java/mousemaster/MouseController.java @@ -1,5 +1,7 @@ package mousemaster; +import mousemaster.platform.PlatformMouse; + import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; @@ -7,7 +9,7 @@ public class MouseController implements ModeListener, MousePositionListener { private final ScreenManager screenManager; - private final mousemaster.platform.Mouse platformMouse; + private final PlatformMouse platformMouse; private Mouse mouse; private Wheel wheel; private double moveDuration; @@ -45,7 +47,7 @@ public class MouseController implements ModeListener, MousePositionListener { private int jumpEndX, jumpEndY; public MouseController(ScreenManager screenManager, - mousemaster.platform.Mouse platformMouse) { + PlatformMouse platformMouse) { this.screenManager = screenManager; this.platformMouse = platformMouse; } diff --git a/src/main/java/mousemaster/Platform.java b/src/main/java/mousemaster/Platform.java index 3755016a..109e0533 100644 --- a/src/main/java/mousemaster/Platform.java +++ b/src/main/java/mousemaster/Platform.java @@ -5,8 +5,8 @@ import mousemaster.platform.Keyboard; import mousemaster.platform.KeyboardLayoutProvider; import mousemaster.platform.KeyRegurgitator; -import mousemaster.platform.Mouse; import mousemaster.platform.Overlay; +import mousemaster.platform.PlatformMouse; import mousemaster.platform.Screens; import mousemaster.platform.UiAutomation; @@ -35,7 +35,7 @@ void reset(MouseController mouseController, KeyboardManager keyboardManager, Keyboard keyboard(); - Mouse mouse(); + PlatformMouse mouse(); Screens screens(); diff --git a/src/main/java/mousemaster/platform/Mouse.java b/src/main/java/mousemaster/platform/PlatformMouse.java similarity index 93% rename from src/main/java/mousemaster/platform/Mouse.java rename to src/main/java/mousemaster/platform/PlatformMouse.java index e68a0998..ad7b9928 100644 --- a/src/main/java/mousemaster/platform/Mouse.java +++ b/src/main/java/mousemaster/platform/PlatformMouse.java @@ -1,6 +1,6 @@ package mousemaster.platform; -public interface Mouse { +public interface PlatformMouse { void beginMove(); diff --git a/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java index a6b65eaf..32409fab 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java +++ b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java @@ -2,9 +2,9 @@ import mousemaster.*; -import mousemaster.platform.Mouse; +import mousemaster.platform.PlatformMouse; -public class WindowsMouseAdapter implements Mouse { +public class WindowsMouseAdapter implements PlatformMouse { @Override public void beginMove() { diff --git a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java index 2cbc05a9..b4a8e262 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java +++ b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java @@ -10,8 +10,8 @@ import mousemaster.platform.Keyboard; import mousemaster.platform.KeyboardLayoutProvider; import mousemaster.platform.KeyRegurgitator; -import mousemaster.platform.Mouse; import mousemaster.platform.Overlay; +import mousemaster.platform.PlatformMouse; import mousemaster.platform.Screens; import mousemaster.platform.UiAutomation; import mousemaster.KeyEvent.PressKeyEvent; @@ -30,7 +30,7 @@ public class WindowsPlatform implements Platform { private final boolean keyRegurgitationEnabled; private final Keyboard keyboard = new WindowsKeyboardAdapter(); - private final Mouse mouse = new WindowsMouseAdapter(); + private final PlatformMouse mouse = new WindowsMouseAdapter(); private final Screens screens = new WindowsScreens(); private final Overlay overlay = new WindowsOverlayAdapter(); private final UiAutomation uiAutomation = new WindowsUiAutomationAdapter(); @@ -286,7 +286,7 @@ public Keyboard keyboard() { } @Override - public Mouse mouse() { + public PlatformMouse mouse() { return mouse; } From 8bfdeb33837e53171093dd29a0a24619b12e3384 Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Sat, 20 Jun 2026 14:18:03 +0200 Subject: [PATCH 5/6] Removing adapters and statics --- src/main/java/mousemaster/GridManager.java | 6 +- src/main/java/mousemaster/HintManager.java | 14 +- .../java/mousemaster/IndicatorManager.java | 6 +- src/main/java/mousemaster/MacroPlayer.java | 6 +- src/main/java/mousemaster/Platform.java | 14 +- src/main/java/mousemaster/ZoomManager.java | 6 +- .../mousemaster/platform/KeyRegurgitator.java | 30 +- .../{Keyboard.java => PlatformKeyboard.java} | 2 +- .../{Overlay.java => PlatformOverlay.java} | 2 +- ...omation.java => PlatformUiAutomation.java} | 2 +- .../windows/WindowsKeyRegurgitator.java | 42 --- .../platform/windows/WindowsKeyboard.java | 56 +-- .../windows/WindowsKeyboardAdapter.java | 46 --- .../platform/windows/WindowsMouse.java | 102 +++--- .../platform/windows/WindowsMouseAdapter.java | 79 ----- .../platform/windows/WindowsOverlay.java | 330 ++++++++++-------- .../windows/WindowsOverlayAdapter.java | 122 ------- .../platform/windows/WindowsPlatform.java | 80 ++--- .../platform/windows/WindowsUiAutomation.java | 8 +- .../windows/WindowsUiAutomationAdapter.java | 17 - .../platform/windows/WindowsVirtualKey.java | 7 +- 21 files changed, 384 insertions(+), 593 deletions(-) rename src/main/java/mousemaster/platform/{Keyboard.java => PlatformKeyboard.java} (92%) rename src/main/java/mousemaster/platform/{Overlay.java => PlatformOverlay.java} (97%) rename src/main/java/mousemaster/platform/{UiAutomation.java => PlatformUiAutomation.java} (84%) delete mode 100644 src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java delete mode 100644 src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java delete mode 100644 src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java delete mode 100644 src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java delete mode 100644 src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java diff --git a/src/main/java/mousemaster/GridManager.java b/src/main/java/mousemaster/GridManager.java index 8b5d41c3..b773c867 100644 --- a/src/main/java/mousemaster/GridManager.java +++ b/src/main/java/mousemaster/GridManager.java @@ -1,7 +1,7 @@ package mousemaster; import mousemaster.Grid.GridBuilder; -import mousemaster.platform.Overlay; +import mousemaster.platform.PlatformOverlay; /** * Displays the grid and handles grid commands. @@ -10,13 +10,13 @@ public class GridManager implements MousePositionListener, ModeListener { private final ScreenManager screenManager; private final MouseController mouseController; - private final Overlay overlay; + private final PlatformOverlay overlay; private Grid grid; private int mouseX, mouseY; private Mode currentMode; public GridManager(ScreenManager screenManager, MouseController mouseController, - Overlay overlay) { + PlatformOverlay overlay) { this.screenManager = screenManager; this.mouseController = mouseController; this.overlay = overlay; diff --git a/src/main/java/mousemaster/HintManager.java b/src/main/java/mousemaster/HintManager.java index 0fd479af..e8a5c691 100644 --- a/src/main/java/mousemaster/HintManager.java +++ b/src/main/java/mousemaster/HintManager.java @@ -4,9 +4,9 @@ import mousemaster.HintGridArea.ActiveWindowHintGridArea; import mousemaster.HintGridArea.AllScreensHintGridArea; import mousemaster.HintMesh.HintMeshBuilder; -import mousemaster.platform.Overlay; -import mousemaster.platform.UiAutomation; -import mousemaster.platform.UiAutomation.UiElement; +import mousemaster.platform.PlatformOverlay; +import mousemaster.platform.PlatformUiAutomation; +import mousemaster.platform.PlatformUiAutomation.UiElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +21,8 @@ public class HintManager implements ModeListener, MousePositionListener { private final ScreenManager screenManager; private final MouseController mouseController; - private final Overlay overlay; - private final UiAutomation uiAutomation; + private final PlatformOverlay overlay; + private final PlatformUiAutomation uiAutomation; private ModeController modeController; private HintMesh hintMesh; private ViewportFilter screenFilter; @@ -74,8 +74,8 @@ private record HintMeshState(HintMesh hintMesh, } public HintManager(int maxPositionHistorySize, ScreenManager screenManager, - MouseController mouseController, Overlay overlay, - UiAutomation uiAutomation) { + MouseController mouseController, PlatformOverlay overlay, + PlatformUiAutomation uiAutomation) { this.maxPositionHistorySize = maxPositionHistorySize; this.screenManager = screenManager; this.mouseController = mouseController; diff --git a/src/main/java/mousemaster/IndicatorManager.java b/src/main/java/mousemaster/IndicatorManager.java index 444ffa25..b7185f7d 100644 --- a/src/main/java/mousemaster/IndicatorManager.java +++ b/src/main/java/mousemaster/IndicatorManager.java @@ -1,15 +1,15 @@ package mousemaster; -import mousemaster.platform.Overlay; +import mousemaster.platform.PlatformOverlay; public class IndicatorManager implements ModeListener { - private final Overlay overlay; + private final PlatformOverlay overlay; private final MouseState mouseState; private final KeyboardState keyboardState; private Mode currentMode; - public IndicatorManager(Overlay overlay, MouseState mouseState, + public IndicatorManager(PlatformOverlay overlay, MouseState mouseState, KeyboardState keyboardState) { this.overlay = overlay; this.mouseState = mouseState; diff --git a/src/main/java/mousemaster/MacroPlayer.java b/src/main/java/mousemaster/MacroPlayer.java index 901387b5..eb6f4bf0 100644 --- a/src/main/java/mousemaster/MacroPlayer.java +++ b/src/main/java/mousemaster/MacroPlayer.java @@ -2,7 +2,7 @@ import mousemaster.KeyEvent.PressKeyEvent; import mousemaster.KeyEvent.ReleaseKeyEvent; -import mousemaster.platform.Keyboard; +import mousemaster.platform.PlatformKeyboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +18,7 @@ public class MacroPlayer { private final PlatformClock clock; private final ComboWatcher comboWatcher; private final KeyboardManager keyboardManager; - private final Keyboard keyboard; + private final PlatformKeyboard keyboard; private final List macrosToExecute = new ArrayList<>(); private MacroInProgress macroInProgress; private final Set keysPressedByMacro = new HashSet<>(); @@ -53,7 +53,7 @@ public class MacroPlayer { private final Set deferredUserReleases = new HashSet<>(); public MacroPlayer(PlatformClock clock, ComboWatcher comboWatcher, - KeyboardManager keyboardManager, Keyboard keyboard) { + KeyboardManager keyboardManager, PlatformKeyboard keyboard) { this.clock = clock; this.comboWatcher = comboWatcher; this.keyboardManager = keyboardManager; diff --git a/src/main/java/mousemaster/Platform.java b/src/main/java/mousemaster/Platform.java index 109e0533..5715539f 100644 --- a/src/main/java/mousemaster/Platform.java +++ b/src/main/java/mousemaster/Platform.java @@ -2,13 +2,13 @@ import mousemaster.platform.ActiveAppFinder; import mousemaster.platform.Console; -import mousemaster.platform.Keyboard; -import mousemaster.platform.KeyboardLayoutProvider; import mousemaster.platform.KeyRegurgitator; -import mousemaster.platform.Overlay; +import mousemaster.platform.KeyboardLayoutProvider; +import mousemaster.platform.PlatformKeyboard; import mousemaster.platform.PlatformMouse; +import mousemaster.platform.PlatformOverlay; +import mousemaster.platform.PlatformUiAutomation; import mousemaster.platform.Screens; -import mousemaster.platform.UiAutomation; import java.util.List; @@ -33,15 +33,15 @@ void reset(MouseController mouseController, KeyboardManager keyboardManager, KeyboardLayoutProvider keyboardLayoutProvider(); - Keyboard keyboard(); + PlatformKeyboard keyboard(); PlatformMouse mouse(); Screens screens(); - Overlay overlay(); + PlatformOverlay overlay(); - UiAutomation uiAutomation(); + PlatformUiAutomation uiAutomation(); ActiveAppFinder activeAppFinder(); diff --git a/src/main/java/mousemaster/ZoomManager.java b/src/main/java/mousemaster/ZoomManager.java index 0dd966d6..17ee6147 100644 --- a/src/main/java/mousemaster/ZoomManager.java +++ b/src/main/java/mousemaster/ZoomManager.java @@ -1,6 +1,6 @@ package mousemaster; -import mousemaster.platform.Overlay; +import mousemaster.platform.PlatformOverlay; import java.util.HashMap; import java.util.List; @@ -10,7 +10,7 @@ public class ZoomManager implements ModeListener, MousePositionListener { private final ScreenManager screenManager; private final HintManager hintManager; - private final Overlay overlay; + private final PlatformOverlay overlay; private Mode currentMode; private int mouseX, mouseY; @@ -31,7 +31,7 @@ public class ZoomManager implements ModeListener, MousePositionListener { private Point endHintZoomCenter; public ZoomManager(ScreenManager screenManager, HintManager hintManager, - Overlay overlay) { + PlatformOverlay overlay) { this.screenManager = screenManager; this.hintManager = hintManager; this.overlay = overlay; diff --git a/src/main/java/mousemaster/platform/KeyRegurgitator.java b/src/main/java/mousemaster/platform/KeyRegurgitator.java index f36aebbe..9ea9c20b 100644 --- a/src/main/java/mousemaster/platform/KeyRegurgitator.java +++ b/src/main/java/mousemaster/platform/KeyRegurgitator.java @@ -1,8 +1,34 @@ package mousemaster.platform; +import mousemaster.Key; import mousemaster.KeyboardManager; +import mousemaster.MacroMoveDestination; +import mousemaster.ResolvedKeyMacroMove; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public interface KeyRegurgitator { +import java.util.List; + +public class KeyRegurgitator { + + private static final Logger logger = LoggerFactory.getLogger(KeyRegurgitator.class); + private final PlatformKeyboard keyboard; + + public KeyRegurgitator(PlatformKeyboard keyboard) { + this.keyboard = keyboard; + } + + public void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat) { + logger.debug( + "Regurgitating " + regurgitate.key() + ", startRepeat = " + startRepeat + + ", release = " + regurgitate.alsoRelease()); + Key key = regurgitate.key(); + keyboard.sendInputMoves( + !regurgitate.alsoRelease() + ? List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS)) + : List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS), + new ResolvedKeyMacroMove(key, false, MacroMoveDestination.OS)), + startRepeat); + } - void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat); } diff --git a/src/main/java/mousemaster/platform/Keyboard.java b/src/main/java/mousemaster/platform/PlatformKeyboard.java similarity index 92% rename from src/main/java/mousemaster/platform/Keyboard.java rename to src/main/java/mousemaster/platform/PlatformKeyboard.java index a31811d2..8319e184 100644 --- a/src/main/java/mousemaster/platform/Keyboard.java +++ b/src/main/java/mousemaster/platform/PlatformKeyboard.java @@ -5,7 +5,7 @@ import java.util.List; -public interface Keyboard { +public interface PlatformKeyboard { void update(double delta); diff --git a/src/main/java/mousemaster/platform/Overlay.java b/src/main/java/mousemaster/platform/PlatformOverlay.java similarity index 97% rename from src/main/java/mousemaster/platform/Overlay.java rename to src/main/java/mousemaster/platform/PlatformOverlay.java index 3337e4cf..76bbff4e 100644 --- a/src/main/java/mousemaster/platform/Overlay.java +++ b/src/main/java/mousemaster/platform/PlatformOverlay.java @@ -11,7 +11,7 @@ import java.util.Set; import java.time.Duration; -public interface Overlay { +public interface PlatformOverlay { void update(double delta); diff --git a/src/main/java/mousemaster/platform/UiAutomation.java b/src/main/java/mousemaster/platform/PlatformUiAutomation.java similarity index 84% rename from src/main/java/mousemaster/platform/UiAutomation.java rename to src/main/java/mousemaster/platform/PlatformUiAutomation.java index de4959aa..375f5118 100644 --- a/src/main/java/mousemaster/platform/UiAutomation.java +++ b/src/main/java/mousemaster/platform/PlatformUiAutomation.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.concurrent.Future; -public interface UiAutomation { +public interface PlatformUiAutomation { Future> startFindInteractiveUiElements(); diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java b/src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java deleted file mode 100644 index 9c943d76..00000000 --- a/src/main/java/mousemaster/platform/windows/WindowsKeyRegurgitator.java +++ /dev/null @@ -1,42 +0,0 @@ -package mousemaster.platform.windows; - -import mousemaster.*; - -import mousemaster.platform.Keyboard; -import mousemaster.platform.KeyRegurgitator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -public class WindowsKeyRegurgitator implements KeyRegurgitator { - - private static final Logger logger = LoggerFactory.getLogger(WindowsKeyRegurgitator.class); - private final Keyboard keyboard; - - public WindowsKeyRegurgitator(Keyboard keyboard) { - this.keyboard = keyboard; - } - - @Override - public void regurgitate(KeyboardManager.Regurgitate regurgitate, boolean startRepeat) { - logger.debug( - "Regurgitating " + regurgitate.key() + ", startRepeat = " + startRepeat + - ", release = " + regurgitate.alsoRelease()); - // Note about release: - // If the following combo is defined: +leftwin-0 +e, - // Then, when pressing leftwin + g, the Windows Game popup shows up. - // Then, when pressing and releasing leftwin, the popup is closed. - // But, if leftwin is not released by mousemaster after being regurgitated, - // then just pressing g again would open the Windows Game popup, as if leftwin - // was still being pressed. - Key key = regurgitate.key(); - keyboard.sendInputMoves( - !regurgitate.alsoRelease() - ? List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS)) - : List.of(new ResolvedKeyMacroMove(key, true, MacroMoveDestination.OS), - new ResolvedKeyMacroMove(key, false, MacroMoveDestination.OS)), - startRepeat); - } - -} diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java index 3a912061..1165c068 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java @@ -5,12 +5,13 @@ import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.WinDef; import com.sun.jna.platform.win32.WinUser; +import mousemaster.platform.PlatformKeyboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; -public class WindowsKeyboard { +public class WindowsKeyboard implements PlatformKeyboard { private static final Logger logger = LoggerFactory.getLogger(WindowsKeyboard.class); @@ -37,9 +38,10 @@ public class WindowsKeyboard { Key.enter // Only enter from numpad? ); - public static KeyboardLayout activeKeyboardLayout; + public KeyboardLayout activeKeyboardLayout; - public static void reset() { + @Override + public void reset() { sendInputQueue.clear(); earlyReleasedQueuedKeys.clear(); moveWaitingForKeyboardHookCallbackAcknowledgment = null; @@ -59,7 +61,7 @@ public static void reset() { * 2026-02-21T23:30:38.341 [main] TRACE mousemaster.WindowsPlatform - Received key event: vkCode = 0xa4 (VK_LMENU), scanCode = 0x38, flags = 0x30, wParam = WM_SYSKEYDOWN */ private record SendInputMove(ResolvedMacroMove move, boolean startRepeat) {} - private static final List sendInputQueue = new LinkedList<>(); + private final List sendInputQueue = new LinkedList<>(); /** * Keys where the user physically released the key while a pending +key * was sitting in the sendInputQueue. When the deferred +key is sent, @@ -68,11 +70,12 @@ private record SendInputMove(ResolvedMacroMove move, boolean startRepeat) {} * a single parallel's sends (e.g. [+leftalt, +i, -leftalt] becomes * [+leftalt] then deferred [+i, -leftalt]). */ - private static final Set earlyReleasedQueuedKeys = new HashSet<>(); - private static ResolvedKeyMacroMove moveWaitingForKeyboardHookCallbackAcknowledgment; - private static int ticksWaitingForAcknowledgment; + private final Set earlyReleasedQueuedKeys = new HashSet<>(); + private ResolvedKeyMacroMove moveWaitingForKeyboardHookCallbackAcknowledgment; + private int ticksWaitingForAcknowledgment; - public static void sendInputMoves(List moves, boolean startRepeat) { + @Override + public void sendInputMoves(List moves, boolean startRepeat) { boolean sendInputQueueWasEmpty = sendInputQueue.isEmpty(); for (ResolvedMacroMove move : moves) sendInputQueue.add(new SendInputMove(move, startRepeat)); @@ -81,27 +84,30 @@ public static void sendInputMoves(List moves, boolean startRe processOneSendInputMove(); } - private static Key pressedKeyToRepeat; - private static double durationUntilNextKeyPressRepeat; + private Key pressedKeyToRepeat; + private double durationUntilNextKeyPressRepeat; /** * Used to prevent keyPressedNotEaten from killing a repeat that was just * started by a macro during the same event processing (e.g. combo triggers * macro +a which starts repeating, then keyPressedNotEaten is called for * the triggering key: we must not stop the repeat). */ - private static boolean repeatStartedDuringCurrentTick; + private boolean repeatStartedDuringCurrentTick; - public static void keyPressedNotEaten(Key key) { + @Override + public void keyPressedNotEaten(Key key) { if (!repeatStartedDuringCurrentTick || !key.equals(pressedKeyToRepeat)) pressedKeyToRepeat = null; } - public static void keyReleasedNotEaten(Key key) { + @Override + public void keyReleasedNotEaten(Key key) { if (key.equals(pressedKeyToRepeat)) pressedKeyToRepeat = null; } - public static void recordEarlyReleaseForQueuedPress(Key key) { + @Override + public void recordEarlyReleaseForQueuedPress(Key key) { for (SendInputMove queued : sendInputQueue) { if (queued.move() instanceof ResolvedKeyMacroMove km && km.press() && km.key().equals(key) && @@ -112,11 +118,12 @@ public static void recordEarlyReleaseForQueuedPress(Key key) { } } - public static void clearEarlyReleaseForQueuedPress(Key key) { + @Override + public void clearEarlyReleaseForQueuedPress(Key key) { earlyReleasedQueuedKeys.remove(key); } - private static final Set userPressedKeys = new HashSet<>(); + private final Set userPressedKeys = new HashSet<>(); /** * When we inject -leftalt while the user physically holds alt, some apps (e.g. @@ -125,7 +132,7 @@ public static void clearEarlyReleaseForQueuedPress(Key key) { * When this flag is true, injected +leftalt events that are not our own ack * are eaten (suppressed) to prevent this. */ - private static boolean suppressExternalLeftalt; + private boolean suppressExternalLeftalt; private static final Set modifierKeys = Set.of(Key.leftshift, Key.rightshift, Key.leftctrl, Key.rightctrl, @@ -135,7 +142,7 @@ public static void clearEarlyReleaseForQueuedPress(Key key) { * Returns true if this injected +leftalt event is from an external app * and should be eaten to prevent apps from seeing alt+key. */ - public static boolean shouldSuppressExternalLeftalt(KeyEvent keyEvent, boolean injected) { + public boolean shouldSuppressExternalLeftalt(KeyEvent keyEvent, boolean injected) { if (true) // Does not work for double tap of leftalt in IntelliJ. return false; @@ -150,7 +157,7 @@ public static boolean shouldSuppressExternalLeftalt(KeyEvent keyEvent, boolean i return true; } - public static void keyboardHookCallback(WinUser.KBDLLHOOKSTRUCT info, + public void keyboardHookCallback(WinUser.KBDLLHOOKSTRUCT info, WinDef.WPARAM wParam, String wParamString, KeyEvent keyEvent, boolean injected, boolean altgrLeftctrl) { if (!injected) { @@ -214,7 +221,8 @@ public static void keyboardHookCallback(WinUser.KBDLLHOOKSTRUCT info, } } - public static void update(double delta) { + @Override + public void update(double delta) { repeatStartedDuringCurrentTick = false; if (moveWaitingForKeyboardHookCallbackAcknowledgment != null) { ticksWaitingForAcknowledgment++; @@ -238,7 +246,7 @@ public static void update(double delta) { } } - private static boolean processOneSendInputMove() { + private boolean processOneSendInputMove() { while (!sendInputQueue.isEmpty()) { SendInputMove sendInputMove = sendInputQueue.removeFirst(); switch (sendInputMove.move()) { @@ -287,7 +295,7 @@ private static boolean processOneSendInputMove() { return false; } - private static void sendInputKeys(List moves, boolean triggerKeyRepeating) { + private void sendInputKeys(List moves, boolean triggerKeyRepeating) { // triggerKeyRepeating = false; // Send a press event for the key to regurgitate. WinUser.INPUT[] pInputs = @@ -352,7 +360,7 @@ else if (userPressedKeys.contains(Key.leftalt)) pInputs[0].size()); } - public static void sendInputString(String string) { + public void sendInputString(String string) { logger.trace("Sending input string: " + string); int inputCount = string.length() * 2; // down + up per character WinUser.INPUT[] pInputs = @@ -380,7 +388,7 @@ public static void sendInputString(String string) { pInputs[0].size()); } - public static void sendInputKeyRelease(int virtualKeyCode, boolean extendedKey) { + public void sendInputKeyRelease(int virtualKeyCode, boolean extendedKey) { WinUser.INPUT[] pInputs = (WinUser.INPUT[]) new WinUser.INPUT().toArray(1); pInputs[0].type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD); diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java deleted file mode 100644 index c97ea047..00000000 --- a/src/main/java/mousemaster/platform/windows/WindowsKeyboardAdapter.java +++ /dev/null @@ -1,46 +0,0 @@ -package mousemaster.platform.windows; - -import mousemaster.*; - -import mousemaster.platform.Keyboard; - -import java.util.List; - -public class WindowsKeyboardAdapter implements Keyboard { - - @Override - public void update(double delta) { - WindowsKeyboard.update(delta); - } - - @Override - public void reset() { - WindowsKeyboard.reset(); - } - - @Override - public void sendInputMoves(List moves, boolean startRepeat) { - WindowsKeyboard.sendInputMoves(moves, startRepeat); - } - - @Override - public void keyPressedNotEaten(Key key) { - WindowsKeyboard.keyPressedNotEaten(key); - } - - @Override - public void keyReleasedNotEaten(Key key) { - WindowsKeyboard.keyReleasedNotEaten(key); - } - - @Override - public void recordEarlyReleaseForQueuedPress(Key key) { - WindowsKeyboard.recordEarlyReleaseForQueuedPress(key); - } - - @Override - public void clearEarlyReleaseForQueuedPress(Key key) { - WindowsKeyboard.clearEarlyReleaseForQueuedPress(key); - } - -} diff --git a/src/main/java/mousemaster/platform/windows/WindowsMouse.java b/src/main/java/mousemaster/platform/windows/WindowsMouse.java index 38d345fc..5250abfe 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsMouse.java +++ b/src/main/java/mousemaster/platform/windows/WindowsMouse.java @@ -5,19 +5,28 @@ import com.sun.jna.Memory; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.*; +import mousemaster.platform.PlatformMouse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.function.Consumer; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -public class WindowsMouse { +public class WindowsMouse implements PlatformMouse { private static final Logger logger = LoggerFactory.getLogger(WindowsMouse.class); - public static void moveBy(boolean xForward, double deltaX, boolean yForward, - double deltaY) { + private final Consumer mousePositionSetCallback; + + public WindowsMouse(Consumer mousePositionSetCallback) { + this.mousePositionSetCallback = mousePositionSetCallback; + } + + @Override + public void moveBy(boolean xForward, double deltaX, boolean yForward, + double deltaY) { if (((long) deltaX) == 0 && ((long) deltaY) == 0) return; sendInput((long) deltaX * (xForward ? 1 : -1), @@ -27,13 +36,13 @@ public static void moveBy(boolean xForward, double deltaX, boolean yForward, // Mutable field that will contain the user's mouse settings // (Control panel > Mouse settings > Pointer options tab). - private static final WinDef.DWORD[] originalMouseThresholdsAndAcceleration = + private final WinDef.DWORD[] originalMouseThresholdsAndAcceleration = new WinDef.DWORD[]{ new WinDef.DWORD(0), // Threshold1. new WinDef.DWORD(0), // Threshold2. new WinDef.DWORD(0) // Acceleration. }; - private static final WinDef.DWORD[] originalMouseSpeed = new WinDef.DWORD[] { new WinDef.DWORD(0) }; + private final WinDef.DWORD[] originalMouseSpeed = new WinDef.DWORD[] { new WinDef.DWORD(0) }; // Following is a "no enhanced pointer precision" mouse setting that we use to be // able to predict where the mouse will end up when moving the mouse to a target // position using SendInput. @@ -54,7 +63,7 @@ public static void moveBy(boolean xForward, double deltaX, boolean yForward, * If enhanced pointer precision is disabled, then the values are all 0. * On my computer, SPI_GETMOUSESPEED is 10 (that's the default). */ - private static void findMouseThresholdsAndAccelerationAndSpeed( + private void findMouseThresholdsAndAccelerationAndSpeed( WinDef.DWORD[] mouseThresholdsAndAcceleration, WinDef.DWORD[] originalMouseSpeed) { boolean getMouseSuccess = ExtendedUser32.INSTANCE.SystemParametersInfoA( @@ -80,7 +89,7 @@ private static void findMouseThresholdsAndAccelerationAndSpeed( } } - private static void setMouseThresholdsAndAccelerationAndSpeed( + private void setMouseThresholdsAndAccelerationAndSpeed( WinDef.DWORD[] mouseThresholdsAndAcceleration, int mouseSpeed) { int SPIF_SENDCHANGE = 0x02; boolean setMouseSuccess = ExtendedUser32.INSTANCE.SystemParametersInfoA( @@ -100,9 +109,10 @@ private static void setMouseThresholdsAndAccelerationAndSpeed( } } - private static boolean moving; + private boolean moving; - public static void beginMove() { + @Override + public void beginMove() { if (moving) return; findMouseThresholdsAndAccelerationAndSpeed(originalMouseThresholdsAndAcceleration, @@ -112,7 +122,8 @@ public static void beginMove() { moving = true; } - public static void endMove() { + @Override + public void endMove() { if (!moving) return; moving = false; @@ -120,7 +131,8 @@ public static void endMove() { originalMouseSpeed[0].intValue()); } - public static void synchronousMoveTo(int x, int y) { + @Override + public void synchronousMoveTo(int x, int y) { WinDef.POINT mousePosition = tryFindMousePosition(); // GetCursorPos can fail on the Win+L lock screen. if (mousePosition == null) { @@ -164,27 +176,33 @@ public static void synchronousMoveTo(int x, int y) { */ private static final Executor buttonExecutor = Executors.newSingleThreadExecutor(); - public static void pressLeft() { + @Override + public void pressLeft() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_LEFTDOWN)); } - public static void pressMiddle() { + @Override + public void pressMiddle() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_MIDDLEDOWN)); } - public static void pressRight() { + @Override + public void pressRight() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_RIGHTDOWN)); } - public static void releaseLeft() { + @Override + public void releaseLeft() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_LEFTUP)); } - public static void releaseMiddle() { + @Override + public void releaseMiddle() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_MIDDLEUP)); } - public static void releaseRight() { + @Override + public void releaseRight() { buttonExecutor.execute(() -> sendInput(0, 0, 0, ExtendedUser32.MOUSEEVENTF_RIGHTUP)); } @@ -195,17 +213,19 @@ public static void releaseRight() { */ private static final Executor wheelExecutor = Executors.newSingleThreadExecutor(); - public static void wheelHorizontallyBy(boolean forward, double delta) { + @Override + public void wheelHorizontallyBy(boolean forward, double delta) { wheelExecutor.execute(() -> sendInput(0, 0, (int) delta * (forward ? 1 : -1), ExtendedUser32.MOUSEEVENTF_HWHEEL)); } - public static void wheelVerticallyBy(boolean forward, double delta) { + @Override + public void wheelVerticallyBy(boolean forward, double delta) { wheelExecutor.execute(() -> sendInput(0, 0, (int) delta * (forward ? -1 : 1), ExtendedUser32.MOUSEEVENTF_WHEEL)); } - private static void sendInput(long dx, long dy, int wheelDelta, int eventFlag) { + private void sendInput(long dx, long dy, int wheelDelta, int eventFlag) { WinUser.INPUT input = new WinUser.INPUT(); input.type = new WinDef.DWORD(WinUser.INPUT.INPUT_MOUSE); WinUser.MOUSEINPUT mouseInput = new WinUser.MOUSEINPUT(); @@ -223,16 +243,15 @@ private static void sendInput(long dx, long dy, int wheelDelta, int eventFlag) { User32.INSTANCE.SendInput(nInputs, pInputs, size); } - public static WindowsPlatform windowsPlatform; // TODO Get rid of this field. - - private static boolean setMousePosition(WinDef.POINT mousePosition) { - windowsPlatform.mousePositionSet(mousePosition); + private boolean setMousePosition(WinDef.POINT mousePosition) { + mousePositionSetCallback.accept(mousePosition); return User32.INSTANCE.SetCursorPos(mousePosition.x, mousePosition.y); } - private static boolean cursorHidden = false; + private boolean cursorHidden = false; - public static void showCursor() { + @Override + public void showCursor() { if (!cursorHidden) return; cursorHidden = false; @@ -241,7 +260,8 @@ public static void showCursor() { new WinDef.UINT(0)); } - public static void hideCursor() { + @Override + public void hideCursor() { // User32 ShowCursor(false) always returns -1 and does not hide the cursor. if (cursorHidden) return; @@ -275,14 +295,14 @@ public static void hideCursor() { ExtendedUser32.INSTANCE.DestroyCursor(transparentCursor); } - public static WinDef.POINT findMousePosition() { + public WinDef.POINT findMousePosition() { WinDef.POINT mousePosition = tryFindMousePosition(); if (mousePosition == null) throw new IllegalStateException("Unable to find mouse position"); return mousePosition; } - public static WinDef.POINT tryFindMousePosition() { + public WinDef.POINT tryFindMousePosition() { WinDef.POINT mousePosition = new WinDef.POINT(); boolean getCursorPosResult = User32.INSTANCE.GetCursorPos(mousePosition); if (!getCursorPosResult) @@ -290,9 +310,9 @@ public static WinDef.POINT tryFindMousePosition() { return mousePosition; } - private static MouseSize mouseSize; + private MouseSize mouseSize; - static MouseSize mouseSize() { + MouseSize mouseSize() { if (mouseSize != null) return mouseSize; ExtendedUser32.CURSORINFO cursorInfo = new ExtendedUser32.CURSORINFO(); @@ -325,12 +345,12 @@ static MouseSize mouseSize() { GDI32.INSTANCE.DeleteObject(iconInfo.hbmColor); if (iconInfo.hbmMask != null) GDI32.INSTANCE.DeleteObject(iconInfo.hbmMask); - MouseSize mouseSize = new MouseSize(cursorWidth, cursorHeight); - WindowsMouse.mouseSize = mouseSize; - return mouseSize; + MouseSize result = new MouseSize(cursorWidth, cursorHeight); + this.mouseSize = result; + return result; } - private static MouseSize mouseSizeFallback() { + private MouseSize mouseSizeFallback() { Screen activeScreen = WindowsScreen.findActiveScreen(findMousePosition()); double scale = activeScreen.scale(); logger.info("Unable to find mouse size, using 32x32 (multiplied by scale " + @@ -342,14 +362,14 @@ public record MouseSize(int width, int height) { } - private static final Map centerByCursorHandle = new HashMap<>(); + private final Map centerByCursorHandle = new HashMap<>(); /** * Returns the visual center of the current cursor relative to its hotspot, * computed by finding the bounding box of non-transparent pixels. * Results are cached per cursor handle. */ - static Point cursorVisualCenter() { + Point cursorVisualCenter() { ExtendedUser32.CURSORINFO cursorInfo = new ExtendedUser32.CURSORINFO(); if (!ExtendedUser32.INSTANCE.GetCursorInfo(cursorInfo) || cursorInfo.hCursor == null) @@ -364,7 +384,7 @@ static Point cursorVisualCenter() { return center != null ? center : new Point(0, 0); } - private static Point computeCursorVisualCenter( + private Point computeCursorVisualCenter( ExtendedUser32.CURSORINFO cursorInfo) { WinGDI.ICONINFO iconInfo = new WinGDI.ICONINFO(); if (!User32.INSTANCE.GetIconInfo( @@ -412,7 +432,7 @@ private static Point computeCursorVisualCenter( * checking for non-black RGB (for XOR/inversion cursors like I-beam where * alpha is 0 but RGB encodes the visible shape). */ - private static Rectangle colorBitmapBounds(WinDef.HBITMAP bitmap) { + private Rectangle colorBitmapBounds(WinDef.HBITMAP bitmap) { WinGDI.BITMAP bmp = new WinGDI.BITMAP(); GDI32.INSTANCE.GetObject(bitmap, bmp.size(), bmp.getPointer()); bmp.read(); @@ -464,7 +484,7 @@ private static Rectangle colorBitmapBounds(WinDef.HBITMAP bitmap) { * false for monochrome cursors (double-height: top=AND, bottom=XOR; * visible if AND=0 or XOR is non-zero). */ - private static Rectangle maskBitmapBounds(WinDef.HBITMAP bitmap, boolean andOnly) { + private Rectangle maskBitmapBounds(WinDef.HBITMAP bitmap, boolean andOnly) { WinGDI.BITMAP bmp = new WinGDI.BITMAP(); GDI32.INSTANCE.GetObject(bitmap, bmp.size(), bmp.getPointer()); bmp.read(); @@ -508,7 +528,7 @@ private static Rectangle maskBitmapBounds(WinDef.HBITMAP bitmap, boolean andOnly } } - private static Memory readBitmap32(WinDef.HBITMAP bitmap, int width, int height) { + private Memory readBitmap32(WinDef.HBITMAP bitmap, int width, int height) { WinGDI.BITMAPINFO bitmapInfo = new WinGDI.BITMAPINFO(); bitmapInfo.bmiHeader.biWidth = width; bitmapInfo.bmiHeader.biHeight = -height; // Negative = top-down diff --git a/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java deleted file mode 100644 index 32409fab..00000000 --- a/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java +++ /dev/null @@ -1,79 +0,0 @@ -package mousemaster.platform.windows; - -import mousemaster.*; - -import mousemaster.platform.PlatformMouse; - -public class WindowsMouseAdapter implements PlatformMouse { - - @Override - public void beginMove() { - WindowsMouse.beginMove(); - } - - @Override - public void endMove() { - WindowsMouse.endMove(); - } - - @Override - public void moveBy(boolean xForward, double dx, boolean yForward, double dy) { - WindowsMouse.moveBy(xForward, dx, yForward, dy); - } - - @Override - public void synchronousMoveTo(int x, int y) { - WindowsMouse.synchronousMoveTo(x, y); - } - - @Override - public void pressLeft() { - WindowsMouse.pressLeft(); - } - - @Override - public void pressMiddle() { - WindowsMouse.pressMiddle(); - } - - @Override - public void pressRight() { - WindowsMouse.pressRight(); - } - - @Override - public void releaseLeft() { - WindowsMouse.releaseLeft(); - } - - @Override - public void releaseMiddle() { - WindowsMouse.releaseMiddle(); - } - - @Override - public void releaseRight() { - WindowsMouse.releaseRight(); - } - - @Override - public void wheelHorizontallyBy(boolean forward, double delta) { - WindowsMouse.wheelHorizontallyBy(forward, delta); - } - - @Override - public void wheelVerticallyBy(boolean forward, double delta) { - WindowsMouse.wheelVerticallyBy(forward, delta); - } - - @Override - public void showCursor() { - WindowsMouse.showCursor(); - } - - @Override - public void hideCursor() { - WindowsMouse.hideCursor(); - } - -} diff --git a/src/main/java/mousemaster/platform/windows/WindowsOverlay.java b/src/main/java/mousemaster/platform/windows/WindowsOverlay.java index da9da24d..b025e578 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsOverlay.java +++ b/src/main/java/mousemaster/platform/windows/WindowsOverlay.java @@ -7,6 +7,7 @@ import com.sun.jna.Pointer; import com.sun.jna.platform.win32.*; import com.sun.jna.ptr.PointerByReference; +import mousemaster.platform.PlatformOverlay; import io.qt.core.*; import io.qt.gui.*; import io.qt.widgets.QApplication; @@ -27,60 +28,78 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -public class WindowsOverlay { +public class WindowsOverlay implements PlatformOverlay { private static final Logger logger = LoggerFactory.getLogger(WindowsOverlay.class); - public static boolean waitForZoomBeforeRepainting; - - private static IndicatorWindow indicatorWindow; - private static boolean showingIndicator; - private static Indicator currentIndicator; - private static int maxIndicatorShadowPadding; - private static FadeAnimator indicatorFadeAnimator; - private static GridWindow gridWindow, standByGridWindow; - private static boolean standByGridCanBeHidden; - private static boolean showingGrid; - private static Grid currentGrid; - private static final Map hintMeshWindows = + private final WindowsMouse mouse; + + public WindowsOverlay(WindowsMouse mouse) { + this.mouse = mouse; + } + + private boolean waitForZoom; + + @Override + public boolean waitForZoomBeforeRepainting() { + return waitForZoom; + } + + @Override + public void setWaitForZoomBeforeRepainting(boolean value) { + waitForZoom = value; + } + + private IndicatorWindow indicatorWindow; + private boolean showingIndicator; + private Indicator currentIndicator; + private int maxIndicatorShadowPadding; + private FadeAnimator indicatorFadeAnimator; + private GridWindow gridWindow, standByGridWindow; + private boolean standByGridCanBeHidden; + private boolean showingGrid; + private Grid currentGrid; + private final Map hintMeshWindows = new LinkedHashMap<>(); // Ordered for topmost handling. - private static final Map hintMeshPixmaps = new HashMap<>(); - private static final Map, QRect>> hintBoxGeometriesByHintMeshKey = new HashMap<>(); - private static boolean showingHintMesh; - private static boolean hintMeshEndAnimation; - private static boolean zoomAfterHintMeshEndAnimation; - private static Zoom afterHintMeshEndAnimationZoom; - private static HintMesh currentHintMesh; - private static ZoomWindow zoomWindow, standByZoomWindow; - private static Zoom currentZoom; - private static boolean mustUpdateMagnifierSource; + private final Map hintMeshPixmaps = new HashMap<>(); + private final Map, QRect>> hintBoxGeometriesByHintMeshKey = new HashMap<>(); + private boolean showingHintMesh; + private boolean hintMeshEndAnimation; + private boolean zoomAfterHintMeshEndAnimation; + private Zoom afterHintMeshEndAnimationZoom; + private HintMesh currentHintMesh; + private ZoomWindow zoomWindow, standByZoomWindow; + private Zoom currentZoom; + private boolean mustUpdateMagnifierSource; // Screenshot-based zoom animation fields. - private static ScreenshotWidget screenshotWidget; - private static WinDef.HWND screenshotHwnd; - private static QPixmap screenshotPixmap; - private static boolean screenshotAnimating; - private static boolean screenshotPendingHide; - private static FadeAnimator hintMeshFadeAnimator; + private ScreenshotWidget screenshotWidget; + private WinDef.HWND screenshotHwnd; + private QPixmap screenshotPixmap; + private boolean screenshotAnimating; + private boolean screenshotPendingHide; + private FadeAnimator hintMeshFadeAnimator; /** * Building the hint window is expensive and when it is done from the keyboard hook, * Windows will cancel the hook and the key press will go through to the other apps. * Windows won't wait for the keyboard hook to return if it's taking too long. */ - private static Runnable setUncachedHintMeshWindowRunnable; - private static Runnable cacheQtHintWindowIntoPixmapRunnable; - private static Runnable messagePump; + private Runnable setUncachedHintMeshWindowRunnable; + private Runnable cacheQtHintWindowIntoPixmapRunnable; + private Runnable messagePump; /** * True when the build is running from update() (deferred), meaning we are * NOT inside a keyboard hook callback and can safely pump messages to keep * the hook responsive. False when running inline from the hook callback. */ - private static boolean pumpDuringHintBuild; + private boolean pumpDuringHintBuild; - static void setMessagePump(Runnable pump) { + @Override + public void setMessagePump(Runnable pump) { messagePump = pump; } - public static void update(double delta) { + @Override + public void update(double delta) { if (setUncachedHintMeshWindowRunnable != null) { pumpDuringHintBuild = true; setUncachedHintMeshWindowRunnable.run(); @@ -113,7 +132,7 @@ else if (cacheQtHintWindowIntoPixmapRunnable != null) { } } - private static void updateZoomWindow() { + private void updateZoomWindow() { if (screenshotAnimating) return; if (currentZoom == null) @@ -142,7 +161,8 @@ private static void updateZoomWindow() { setTopmost(); } - public static void flushCache() { + @Override + public void flushCache() { for (PixmapAndPosition pixmapAndPosition : hintMeshPixmaps.values()) pixmapAndPosition.pixmap().dispose(); hintMeshPixmaps.clear(); @@ -152,7 +172,8 @@ public static void flushCache() { } } - public static Rectangle activeWindowRectangle(double windowWidthPercent, + @Override + public Rectangle activeWindowRectangle(double windowWidthPercent, double windowHeightPercent, int scaledTopInset, int scaledBottomInset, @@ -184,7 +205,8 @@ static WinDef.RECT windowRectExcludingShadow(WinDef.HWND hwnd) { return rect; } - public static void setTopmost() { + @Override + public void setTopmost() { List hwnds = new ArrayList<>(); // First in the hwnds list means drawn on top. if (gridWindow != null) @@ -233,13 +255,13 @@ public static void setTopmost() { setWindowTopmost(hwnds.get(windowIndex), ExtendedUser32.HWND_TOPMOST); } - private static WinDef.HWND windowBelow(WinDef.HWND hwnd) { + private WinDef.HWND windowBelow(WinDef.HWND hwnd) { WinDef.HWND nextHwnd = User32.INSTANCE.GetWindow(hwnd, new WinDef.DWORD(User32.GW_HWNDNEXT)); return nextHwnd; } - private static void setWindowTopmost(WinDef.HWND hwnd, WinDef.HWND hwndTopmost) { + private void setWindowTopmost(WinDef.HWND hwnd, WinDef.HWND hwndTopmost) { User32.INSTANCE.SetWindowPos(hwnd, hwndTopmost, 0, 0, 0, 0, WinUser.SWP_NOMOVE | WinUser.SWP_NOSIZE); } @@ -249,7 +271,7 @@ private record IndicatorWindow(WinDef.HWND hwnd, TransparentWindow window, IndicatorLabelWidget labelWidget) { } - private static class IndicatorWidget extends QWidget { + private class IndicatorWidget extends QWidget { private QColor color; private int edgeCount; @@ -838,12 +860,14 @@ static QImage bakeStacking(QImage image, int stackCount) { public static class IndicatorShadowEffect extends StackedShadowEffect { private final IndicatorWidget widget; + private final WindowsOverlay overlay; private QColor fillColor; private QColor outerOutlineColor; private QColor innerOutlineColor; - IndicatorShadowEffect(IndicatorWidget widget) { + IndicatorShadowEffect(IndicatorWidget widget, WindowsOverlay overlay) { this.widget = widget; + this.overlay = overlay; } void setColors(QColor fillColor, QColor outerOutlineColor, @@ -872,7 +896,7 @@ protected void draw(QPainter painter) { @Override protected void redrawSourceOverShadow(QPainter painter) { - if (!indicatorHasTransparency()) + if (!overlay.indicatorHasTransparency()) return; painter.setRenderHint(QPainter.RenderHint.Antialiasing, true); widget.drawContent(painter, fillColor, outerOutlineColor, innerOutlineColor, false); @@ -880,7 +904,7 @@ protected void redrawSourceOverShadow(QPainter painter) { } - private static class IndicatorLabelWidget extends QWidget { + private class IndicatorLabelWidget extends QWidget { private String labelText; private QFont labelFont; @@ -984,7 +1008,7 @@ private record ZoomWindow(WinDef.HWND hwnd, WinDef.HWND hostHwnd, WinUser.Window } - private static class ScreenshotWidget extends QWidget { + private class ScreenshotWidget extends QWidget { private QPixmap pixmap; private Zoom zoom; private Rectangle screenRect; @@ -1027,17 +1051,17 @@ protected void paintEvent(QPaintEvent event) { } } - private static int indicatorSize(double screenScale) { + private int indicatorSize(double screenScale) { return scaledPixels(currentIndicator.size(), screenScale); } - private static double zoomedX(double x) { + private double zoomedX(double x) { if (currentZoom == null) return x; return currentZoom.zoomedX(x); } - private static double zoomedY(double y) { + private double zoomedY(double y) { if (currentZoom == null) return y; return currentZoom.zoomedY(y); @@ -1051,11 +1075,11 @@ private static double zoomedY(double y) { * For corner positions, the indicator is placed in that corner relative to the cursor, * flipping to the opposite side when near the corresponding screen edge. */ - private static Point indicatorTopLeft(WinDef.POINT mousePosition, + private Point indicatorTopLeft(WinDef.POINT mousePosition, Screen activeScreen, int visualSize) { Rectangle screen = activeScreen.rectangle(); if (currentIndicator.position() == IndicatorPosition.CENTER) { - Point cursorCenter = WindowsMouse.cursorVisualCenter(); + Point cursorCenter = mouse.cursorVisualCenter(); double centerX = mousePosition.x + cursorCenter.x(); double centerY = mousePosition.y + cursorCenter.y(); centerX = Math.max(screen.x(), Math.min(centerX, @@ -1065,7 +1089,7 @@ private static Point indicatorTopLeft(WinDef.POINT mousePosition, return new Point(zoomedX(centerX) - visualSize / 2.0, zoomedY(centerY) - visualSize / 2.0); } - WindowsMouse.MouseSize mouseSize = WindowsMouse.mouseSize(); + WindowsMouse.MouseSize mouseSize = mouse.mouseSize(); int mouseX = Math.max(screen.x(), Math.min(mousePosition.x, screen.x() + screen.width())); int mouseY = Math.max(screen.y(), Math.min(mousePosition.y, @@ -1092,14 +1116,14 @@ private static Point indicatorTopLeft(WinDef.POINT mousePosition, return new Point(zoomedX(indicatorX), zoomedY(indicatorY)); } - private static int indicatorOutlinePadding(double scale) { + private int indicatorOutlinePadding(double scale) { double scaled = Math.max( currentIndicator.outerOutline().thickness(), currentIndicator.innerOutline().thickness()) * scale * zoomPercent(); return (int) Math.ceil(IndicatorWidget.miterPadding(scaled, currentIndicator.edgeCount())); } - private static int indicatorShadowPadding(double scale) { + private int indicatorShadowPadding(double scale) { if (currentIndicator.shadow().blurRadius() == 0) return 0; return (int) Math.ceil((currentIndicator.shadow().blurRadius() + @@ -1107,11 +1131,11 @@ private static int indicatorShadowPadding(double scale) { Math.abs(currentIndicator.shadow().verticalOffset()))) * scale); } - private static void moveAndResizeIndicatorWindow() { - moveAndResizeIndicatorWindow(WindowsMouse.findMousePosition()); + private void moveAndResizeIndicatorWindow() { + moveAndResizeIndicatorWindow(mouse.findMousePosition()); } - private static void moveAndResizeIndicatorWindow(WinDef.POINT mousePosition) { + private void moveAndResizeIndicatorWindow(WinDef.POINT mousePosition) { Screen activeScreen = WindowsScreen.findActiveScreen(mousePosition); double screenScale = activeScreen.scale(); int size = indicatorSize(screenScale); @@ -1149,7 +1173,7 @@ private static void moveAndResizeIndicatorWindow(WinDef.POINT mousePosition) { indicatorWindow.labelWidget.setLabelFontScale(zoomPercent()); } - private static boolean indicatorHasTransparency() { + private boolean indicatorHasTransparency() { if (currentIndicator.opacity() < 1.0) return true; IndicatorOutline outer = currentIndicator.outerOutline(); @@ -1158,7 +1182,7 @@ private static boolean indicatorHasTransparency() { (inner.thickness() > 0 && inner.opacity() < 1.0); } - private static boolean qtFontStyleHasTransparency(QtFontStyle qtFontStyle) { + private boolean qtFontStyleHasTransparency(QtFontStyle qtFontStyle) { if (qtFontStyle.outlineThickness() != 0 && qtFontStyle.outlineColor().alpha() < 255 && // 0 means outline will not be rendered. @@ -1169,7 +1193,7 @@ private static boolean qtFontStyleHasTransparency(QtFontStyle qtFontStyle) { return false; } - private static boolean qtHintFontStyleHasTransparency(QtHintFontStyle style, + private boolean qtHintFontStyleHasTransparency(QtHintFontStyle style, boolean hasSelectedKeys) { if (qtFontStyleHasTransparency(style.defaultStyle()) || (hasSelectedKeys && qtFontStyleHasTransparency(style.selectedStyle())) || @@ -1184,7 +1208,7 @@ private static boolean qtHintFontStyleHasTransparency(QtHintFontStyle style, return false; } - private static void setIndicatorEffectColors(IndicatorShadowEffect effect) { + private void setIndicatorEffectColors(IndicatorShadowEffect effect) { IndicatorOutline outer = currentIndicator.outerOutline(); IndicatorOutline inner = currentIndicator.innerOutline(); effect.setColors( @@ -1193,12 +1217,12 @@ private static void setIndicatorEffectColors(IndicatorShadowEffect effect) { qColor(inner.hexColor(), inner.opacity())); } - private static void applyIndicatorShadowEffect(double scale) { + private void applyIndicatorShadowEffect(double scale) { Shadow shadow = currentIndicator.shadow(); QColor baseColor = qColor(shadow.hexColor(), 1.0); boolean hasShadow = shadow.opacity() > 0 && shadow.blurRadius() > 0; if (hasShadow) { - IndicatorShadowEffect effect = new IndicatorShadowEffect(indicatorWindow.widget); + IndicatorShadowEffect effect = new IndicatorShadowEffect(indicatorWindow.widget, this); effect.setBlurRadius(shadow.blurRadius() * scale); effect.setOffset(shadow.horizontalOffset() * scale, shadow.verticalOffset() * scale); @@ -1213,7 +1237,7 @@ private static void applyIndicatorShadowEffect(double scale) { indicatorWindow.widget.setGraphicsEffect(effect); } else if (indicatorHasTransparency()) { - IndicatorShadowEffect effect = new IndicatorShadowEffect(indicatorWindow.widget); + IndicatorShadowEffect effect = new IndicatorShadowEffect(indicatorWindow.widget, this); effect.setTransparencyOnly(true); setIndicatorEffectColors(effect); indicatorWindow.widget.customGraphicsEffect = effect; @@ -1226,7 +1250,7 @@ else if (indicatorHasTransparency()) { baseColor.dispose(); } - private static void createIndicatorWindow() { + private void createIndicatorWindow() { TransparentWindow window = new TransparentWindow(); IndicatorWidget widget = new IndicatorWidget(window); WinDef.HWND hwnd = new WinDef.HWND(new Pointer(window.winId())); @@ -1243,24 +1267,24 @@ private static void createIndicatorWindow() { // of the shadow effect and can clear/redraw the fill area. IndicatorLabelWidget labelWidget = new IndicatorLabelWidget(window); indicatorWindow = new IndicatorWindow(hwnd, window, widget, labelWidget); - WinDef.POINT mousePosition = WindowsMouse.findMousePosition(); + WinDef.POINT mousePosition = mouse.findMousePosition(); Screen activeScreen = WindowsScreen.findActiveScreen(mousePosition); applyIndicatorShadowEffect(activeScreen.scale() * zoomPercent()); updateZoomExcludedWindows(); } - private static int scaledPixels(double originalInPixels, double scale) { + private int scaledPixels(double originalInPixels, double scale) { return (int) Math.floor(originalInPixels * scale * zoomPercent()); } - private static double zoomPercent() { + private double zoomPercent() { if (currentZoom == null) return 1; return currentZoom.percent(); } - private static void createGridWindow(int x, int y, int width, int height) { - WinUser.WindowProc callback = WindowsOverlay::gridWindowCallback; + private void createGridWindow(int x, int y, int width, int height) { + WinUser.WindowProc callback = this::gridWindowCallback; WinDef.HWND hwnd = createWindow("Grid" + (gridWindow == null ? 1 : 2), x, y, width, height, callback); @@ -1268,7 +1292,7 @@ private static void createGridWindow(int x, int y, int width, int height) { updateZoomExcludedWindows(); } - private static void createOrUpdateHintMeshWindows(HintMesh hintMesh, Zoom zoom) { + private void createOrUpdateHintMeshWindows(HintMesh hintMesh, Zoom zoom) { Map> hintsByScreen = hintsByScreen(hintMesh.hints()); if (hintsByScreen.isEmpty() && hintMesh.backgroundArea() != null) { Rectangle backgroundArea = hintMesh.backgroundArea(); @@ -1322,7 +1346,7 @@ private static void createOrUpdateHintMeshWindows(HintMesh hintMesh, Zoom zoom) updateZoomExcludedWindows(); } - private static Map> hintsByScreen(List hints) { + private Map> hintsByScreen(List hints) { Set screens = WindowsScreen.findScreens(); Map> hintsByScreen = new HashMap<>(); for (Hint hint : hints) { @@ -1358,7 +1382,7 @@ private static Map> hintsByScreen(List hints) { return hintsByScreen; } - private static class ClearBackgroundQLabel extends QLabel { + private class ClearBackgroundQLabel extends QLabel { private QColor clearColor = new QColor(0, 0, 0, 0); void setClearColor(QColor clearColor) { @@ -1381,7 +1405,7 @@ protected void paintEvent(QPaintEvent event) { } } - private static void setHintMeshWindow(HintMeshWindow hintMeshWindow, + private void setHintMeshWindow(HintMeshWindow hintMeshWindow, HintMesh hintMesh, double screenScale, HintMeshStyle style, boolean zoomChanged, @@ -1550,7 +1574,7 @@ private static void setHintMeshWindow(HintMeshWindow hintMeshWindow, } } - private static void transitionHintContainers(boolean animateTransition, QWidget oldContainer, + private void transitionHintContainers(boolean animateTransition, QWidget oldContainer, QWidget newContainer, TransparentWindow window, HintMeshWindow hintMeshWindow, Duration animationDuration, @@ -1592,7 +1616,7 @@ private static void transitionHintContainers(boolean animateTransition, QWidget animation.valueChanged.connect(animationChanged); HintContainerAnimationFinished animationFinished = new HintContainerAnimationFinished(oldContainer, oldContainer, - endRect); + endRect, this); animation.finished.connect(animationFinished); // It may be necessary to save those instances somewhere (HintMeshWindow), // because they could get GC'd while they are still used by Qt (?). @@ -1632,7 +1656,7 @@ else if (animateTransition && newContainsOld) { animation.valueChanged.connect(animationChanged); HintContainerAnimationFinished animationFinished = new HintContainerAnimationFinished(null, newContainer, - endRect); + endRect, this); animation.finished.connect(animationFinished); hintMeshWindow.animations.add(animation); hintMeshWindow.animationCallbacks.add(animationChanged); @@ -1657,7 +1681,7 @@ else if (animateTransition && newContainsOld) { window.show(); } - private static QRect paddedRect(QRect rect) { + private QRect paddedRect(QRect rect) { int extraWidth = (int) (rect.width() * 0.05d); int extraHeight = (int) (rect.height() * 0.05d); return new QRect( @@ -1690,12 +1714,14 @@ public static class HintContainerAnimationFinished implements QMetaObject.Slot0 private final QWidget oldContainer; private final QWidget animatedContainer; private final QRect endRect; + private final WindowsOverlay overlay; public HintContainerAnimationFinished(QWidget oldContainer, QWidget animatedContainer, - QRect endRect) { + QRect endRect, WindowsOverlay overlay) { this.oldContainer = oldContainer; this.animatedContainer = animatedContainer; this.endRect = endRect; + this.overlay = overlay; } @Override @@ -1708,11 +1734,11 @@ public void invoke() { oldContainer.setParent(null); oldContainer.disposeLater(); } - hintContainerAnimationEnded(); + overlay.hintContainerAnimationEnded(); } } - private static void hintContainerAnimationEnded() { + private void hintContainerAnimationEnded() { if (hintMeshEndAnimation) { hintMeshEndAnimation = false; hideHintMesh(); @@ -1725,7 +1751,7 @@ private static void hintContainerAnimationEnded() { } - private static QVariantAnimation hintContainerAnimation(QRect beginRect, + private QVariantAnimation hintContainerAnimation(QRect beginRect, QRect endRect, Duration animationDuration) { QVariantAnimation animation = new QVariantAnimation(); @@ -1742,7 +1768,7 @@ private static QVariantAnimation hintContainerAnimation(QRect beginRect, return animation; } - private static class HintGroup { + private class HintGroup { double minHintCenterX = Double.MAX_VALUE; double minHintCenterY = Double.MAX_VALUE; @@ -1759,7 +1785,7 @@ private static class HintGroup { } - private static Map, QRect> setUncachedHintMeshWindow(HintMeshWindow hintMeshWindow, HintMesh hintMesh, + private Map, QRect> setUncachedHintMeshWindow(HintMeshWindow hintMeshWindow, HintMesh hintMesh, double screenScale, HintMeshStyle style, double qtScaleFactor, QWidget container) { @@ -2130,7 +2156,7 @@ else if (y + boxHeight == hintMeshWindow.window.y() + hintMeshWindow.window.heig return hintBoxGeometries; } - private static HintMeshWindow createHintMeshWindow(Screen screen, List hints, + private HintMeshWindow createHintMeshWindow(Screen screen, List hints, Zoom zoom) { TransparentWindow window = new TransparentWindow(); WinDef.HWND hwnd = new WinDef.HWND(new Pointer(window.winId())); @@ -2148,7 +2174,8 @@ private static HintMeshWindow createHintMeshWindow(Screen screen, List hin new ArrayList<>(), new ArrayList<>(), new AtomicReference<>()); } - static void preWarmHintMeshWindows() { + @Override + public void preWarmHintMeshWindows() { long before = System.nanoTime(); Set screens = WindowsScreen.findScreens(); for (Screen screen : screens) { @@ -2167,7 +2194,8 @@ static void preWarmHintMeshWindows() { * GDI font engine initialization (~130ms). By doing this at startup, we shift that * cost away from the first hint mesh render. */ - static void preWarmFontStyles(Set hintMeshConfigurations) { + @Override + public void preWarmFontStyles(Set hintMeshConfigurations) { long before = System.nanoTime(); Set fontStyles = new HashSet<>(); for (HintMeshConfiguration hintMeshConfiguration : hintMeshConfigurations) { @@ -2201,7 +2229,7 @@ static void preWarmFontStyles(Set hintMeshConfigurations) (long) ((System.nanoTime() - before) / 1e6) + "ms"); } - private static QFont qFont(String fontName, double fontSize, FontWeight fontWeight) { + private QFont qFont(String fontName, double fontSize, FontWeight fontWeight) { QFont font = new QFont(fontName, (int) Math.round(fontSize), fontWeight.qtWeight().value()); font.setStyleStrategy(QFont.StyleStrategy.PreferAntialias); @@ -2221,7 +2249,7 @@ private static QFont qFont(String fontName, double fontSize, FontWeight fontWeig * shown on a non-primary screen (Qt detects the monitor DPI), creating a * false "no correction needed" match on subsequent calls. */ - private static QFontMetrics correctedFontMetricsForScreenDpi(QFont renderFont, double fontSizePoints, + private QFontMetrics correctedFontMetricsForScreenDpi(QFont renderFont, double fontSizePoints, double screenScale) { double primaryScreenDpi = QApplication.primaryScreen().logicalDotsPerInchX(); double targetDpi = screenScale * 96.0; @@ -2247,13 +2275,13 @@ private static QFontMetrics correctedFontMetricsForScreenDpi(QFont renderFont, d * QImages uses the primary screen's DPI, causing wrong-sized glyphs on * secondary screens with different scaling. */ - private static void setQImageDpiForScreen(QImage image, double screenScale) { + private void setQImageDpiForScreen(QImage image, double screenScale) { int dotsPerMeter = (int) Math.round(screenScale * 96.0 / 0.0254); image.setDotsPerMeterX(dotsPerMeter); image.setDotsPerMeterY(dotsPerMeter); } - private static void cacheQtHintWindowIntoPixmap(TransparentWindow window, QWidget container, + private void cacheQtHintWindowIntoPixmap(TransparentWindow window, QWidget container, HintMesh hintMeshKey, HintMesh hintMesh) { long before = System.nanoTime(); QPixmap pixmap = container.grab(); @@ -2267,7 +2295,7 @@ private static void cacheQtHintWindowIntoPixmap(TransparentWindow window, QWidge hintMeshPixmaps.put(hintMeshKey, pixmapAndPosition); } - private static List trimmedHints(List hints, + private List trimmedHints(List hints, List selectedKeySequence) { double minHintCenterX = Double.MAX_VALUE; double minHintCenterY = Double.MAX_VALUE; @@ -2290,7 +2318,7 @@ private static List trimmedHints(List hints, return trimmedHints; } - private static HintBox[][] addSubgridBoxes(HintBox hintBox, + private HintBox[][] addSubgridBoxes(HintBox hintBox, double qtScaleFactor, QColor subgridBoxColor, QColor subgridBoxBorderColor, int subgridRowCount, @@ -2332,7 +2360,7 @@ public String toString() { } } - private static int hintGridColumnCount(List hints) { + private int hintGridColumnCount(List hints) { if (hints.size() == 1) return 1; double left = hints.getFirst().centerX(); @@ -2343,12 +2371,12 @@ private static int hintGridColumnCount(List hints) { return hints.size(); } - private static int hintRoundedX(double centerX, double cellWidth, + private int hintRoundedX(double centerX, double cellWidth, double qtScaleFactor) { return (int) Math.round((centerX - cellWidth / 2) / qtScaleFactor); } - private static int hintRoundedY(double centerY, double cellHeight, + private int hintRoundedY(double centerY, double cellHeight, double qtScaleFactor) { return (int) Math.round((centerY - cellHeight / 2) / qtScaleFactor); } @@ -2764,15 +2792,15 @@ private QPen createPen(QColor color, int penWidth) { } - private static QColor qColor(String hexColor, double opacity) { + private QColor qColor(String hexColor, double opacity) { return QColor.fromRgba(hexColorStringToRgba(hexColor, opacity)); } - private static QColor shadowColor(Shadow shadow) { + private QColor shadowColor(Shadow shadow) { return qColor(shadow.hexColor(), shadow.opacity()); } - private static QtFontStyle buildQtFontStyle(FontStyle fs, QFont font, + private QtFontStyle buildQtFontStyle(FontStyle fs, QFont font, QFontMetrics metrics, double screenScale) { return new QtFontStyle( @@ -2794,18 +2822,18 @@ private static QtFontStyle buildQtFontStyle(FontStyle fs, QFont font, * When one is zero and the other is not, per-key processing is needed to exclude * the zero-opacity keys from the other's shadow. */ - private static boolean shadowsNeedPerKeyProcessing(Shadow a, Shadow b) { + private boolean shadowsNeedPerKeyProcessing(Shadow a, Shadow b) { return (a.opacity() != 0 || b.opacity() != 0) && !a.equals(b); } - private static boolean fontShapeEquals(FontStyle a, FontStyle b) { + private boolean fontShapeEquals(FontStyle a, FontStyle b) { return a.name().equals(b.name()) && a.weight() == b.weight() && Double.compare(a.size(), b.size()) == 0; } - private static QtHintFontStyle buildQtHintFontStyle(HintFontStyle hintFontStyle, + private QtHintFontStyle buildQtHintFontStyle(HintFontStyle hintFontStyle, HintFontStyle prefixHintFontStyle, double screenScale, boolean hasSelectedKeys) { @@ -3191,7 +3219,7 @@ private record HintKeyText(String text, int x, int y, int width, boolean isSelec * stackCount == 1, uses Qt's effect directly (fast path). Otherwise, * pre-renders the shadow off-screen into a separate layer. */ - private static void applyBoxShadow(HintPaintLayer boxLayer, + private void applyBoxShadow(HintPaintLayer boxLayer, HintPaintLayer boxShadowLayer, List hintBoxes, Shadow boxShadow, @@ -3254,7 +3282,7 @@ private static void applyBoxShadow(HintPaintLayer boxLayer, * has transparency, pre-renders the shadow off-screen and punches out the * text shape so shadow doesn't show through transparent text. */ - private static void applyLabelShadow(HintPaintLayer layer, + private void applyLabelShadow(HintPaintLayer layer, List labels, QtHintFontStyle style, boolean hasSelectedKeys, @@ -3295,7 +3323,7 @@ private static void applyLabelShadow(HintPaintLayer layer, } } - private static void preRenderLabelShadow(HintPaintLayer layer, + private void preRenderLabelShadow(HintPaintLayer layer, List labels, QtHintFontStyle style, int containerWidth, @@ -3406,7 +3434,7 @@ private static ShadowImage renderShadowOnly( * settings (state + prefix/non-prefix), renders each group separately, * bakes stacking, and composites into a single shadow pixmap. */ - private static void preRenderPerGroupShadow( + private void preRenderPerGroupShadow( HintPaintLayer layer, List labels, int containerWidth, int containerHeight, double screenScale) { @@ -3487,7 +3515,7 @@ private static void preRenderPerGroupShadow( } } - private static class HintPaintLayer extends QWidget { + private class HintPaintLayer extends QWidget { private final List boxes; private final List labels; @@ -3526,7 +3554,7 @@ protected void paintEvent(QPaintEvent event) { } } - private static WinDef.HWND createWindow(String windowName, int windowX, int windowY, + private WinDef.HWND createWindow(String windowName, int windowX, int windowY, int windowWidth, int windowHeight, WinUser.WindowProc windowCallback) { WinUser.WNDCLASSEX wClass = new WinUser.WNDCLASSEX(); @@ -3546,12 +3574,12 @@ private static WinDef.HWND createWindow(String windowName, int windowX, int wind return hwnd; } - private static WinDef.HWND createZoomWindow() { + private WinDef.HWND createZoomWindow() { if (!Magnification.INSTANCE.MagInitialize()) logger.error("Failed MagInitialize: " + Integer.toHexString(Native.getLastError())); WinUser.WNDCLASSEX wClass = new WinUser.WNDCLASSEX(); - WinUser.WindowProc callback = WindowsOverlay::zoomWindowCallback; + WinUser.WindowProc callback = this::zoomWindowCallback; wClass.hbrBackground = null; String WC_MAGNIFIER = "Magnifier"; wClass.lpszClassName = "MagnifierWindow"; @@ -3585,7 +3613,7 @@ private static WinDef.HWND createZoomWindow() { - private static WinDef.LRESULT gridWindowCallback(WinDef.HWND hwnd, int uMsg, + private WinDef.LRESULT gridWindowCallback(WinDef.HWND hwnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam) { switch (uMsg) { @@ -3627,13 +3655,13 @@ private static WinDef.LRESULT gridWindowCallback(WinDef.HWND hwnd, int uMsg, return User32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam); } - private static void clearWindow(WinDef.HDC hdc, WinDef.RECT windowRect, int color) { + private void clearWindow(WinDef.HDC hdc, WinDef.RECT windowRect, int color) { WinDef.HBRUSH hbrBackground = ExtendedGDI32.INSTANCE.CreateSolidBrush(color); ExtendedUser32.INSTANCE.FillRect(hdc, windowRect, hbrBackground); GDI32.INSTANCE.DeleteObject(hbrBackground); } - private static void drawGrid(WinDef.HDC hdc, WinDef.RECT windowRect) { + private void drawGrid(WinDef.HDC hdc, WinDef.RECT windowRect) { int rowCount = currentGrid.rowCount(); int columnCount = currentGrid.columnCount(); int cellWidth = currentGrid.width() / columnCount; @@ -3719,7 +3747,7 @@ public static String blendColorOverWhite(String hexColor, double opacity) { } // color1 is background, color2 is foreground - private static int blend(int color1, int color2, double color2Opacity) { + private int blend(int color1, int color2, double color2Opacity) { int red1 = (color1 >> 16) & 0xFF; int green1 = (color1 >> 8) & 0xFF; int blue1 = color1 & 0xFF; @@ -3736,7 +3764,7 @@ private record HintSequenceText(Hint hint, List keyTexts) { } - private static int hexColorStringToInt(String hexColor) { + private int hexColorStringToInt(String hexColor) { if (hexColor.startsWith("#")) hexColor = hexColor.substring(1); int colorInt = Integer.parseUnsignedInt(hexColor, 16); @@ -3747,7 +3775,7 @@ private static int hexColorStringToInt(String hexColor) { return (blue << 16) | (green << 8) | red; } - private static int hexColorStringToRgba(String hexColor, double opacity) { + private int hexColorStringToRgba(String hexColor, double opacity) { if (hexColor.startsWith("#")) hexColor = hexColor.substring(1); int colorInt = Integer.parseUnsignedInt(hexColor, 16); @@ -3758,7 +3786,7 @@ private static int hexColorStringToRgba(String hexColor, double opacity) { return (alpha << 24) | (red << 16) | (green << 8) | blue; } - private static int hexColorStringToRgb(String hexColor, double opacity) { + private int hexColorStringToRgb(String hexColor, double opacity) { // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-blendfunction // Note that the APIs use premultiplied alpha, which means that the red, green // and blue channel values in the bitmap must be premultiplied with the alpha channel value. @@ -3772,7 +3800,7 @@ private static int hexColorStringToRgb(String hexColor, double opacity) { return (red << 16) | (green << 8) | blue; } - private static int alphaMultipliedChannelsColor(int color, double opacity) { + private int alphaMultipliedChannelsColor(int color, double opacity) { int red = (color >> 16) & 0xFF; int green = (color >> 8) & 0xFF; int blue = color & 0xFF; @@ -3780,12 +3808,13 @@ private static int alphaMultipliedChannelsColor(int color, double opacity) { (int) Math.round(blue * opacity); } - public static void setIndicator(Indicator indicator, + @Override + public void setIndicator(Indicator indicator, boolean fadeAnimationEnabled, Duration fadeAnimationDuration, boolean allowFade) { Objects.requireNonNull(indicator); - if (WindowsMouse.tryFindMousePosition() == null) { + if (mouse.tryFindMousePosition() == null) { logger.warn("Unable to find mouse position for indicator"); return; } @@ -3817,7 +3846,7 @@ public static void setIndicator(Indicator indicator, boolean positionChanged = oldIndicator == null || indicator.position() != oldIndicator.position(); if (sizeOrShadowChanged) { - WinDef.POINT mousePosition = WindowsMouse.findMousePosition(); + WinDef.POINT mousePosition = mouse.findMousePosition(); Screen activeScreen = WindowsScreen.findActiveScreen(mousePosition); applyIndicatorShadowEffect(activeScreen.scale() * zoomPercent()); } @@ -3859,7 +3888,7 @@ public static void setIndicator(Indicator indicator, Shadow labelShadow = labelFontStyle.shadow(); QColor labelShadowColor = qColor(labelShadow.hexColor(), labelShadow.opacity()); if (labelShadowColor.alpha() != 0) { - WinDef.POINT mousePosition = WindowsMouse.findMousePosition(); + WinDef.POINT mousePosition = mouse.findMousePosition(); Screen activeScreen = WindowsScreen.findActiveScreen(mousePosition); double labelShadowScale = activeScreen.scale() * zoomPercent(); StackedShadowEffect effect = new StackedShadowEffect(); @@ -3886,7 +3915,7 @@ public static void setIndicator(Indicator indicator, if (!wasShowing) { indicatorFadeAnimator = new FadeAnimator( opacity -> indicatorWindow.window.setWindowOpacity(opacity), - WindowsOverlay::doHideIndicator, + this::doHideIndicator, fadeAnimationEnabled, fadeAnimationDuration); if (allowFade && indicatorFadeAnimator.isEnabled()) { @@ -3896,7 +3925,7 @@ public static void setIndicator(Indicator indicator, } } - private static void createScreenshotWindow() { + private void createScreenshotWindow() { screenshotWidget = new ScreenshotWidget(); screenshotHwnd = new WinDef.HWND(new Pointer(screenshotWidget.winId())); long currentStyle = @@ -3914,7 +3943,8 @@ private static void createScreenshotWindow() { WinUser.SWP_NOMOVE | WinUser.SWP_NOSIZE | WinUser.SWP_NOACTIVATE); } - public static void startScreenshotZoomAnimation(Rectangle screenRect, + @Override + public void startScreenshotZoomAnimation(Rectangle screenRect, Zoom beginZoom) { boolean interruptingMidAnimation = screenshotAnimating; if (screenshotAnimating || screenshotPendingHide) { @@ -3986,7 +4016,7 @@ public static void startScreenshotZoomAnimation(Rectangle screenRect, setTopmost(); } - private static void drawCursorOnto(QPixmap pixmap, Rectangle screenRect) { + private void drawCursorOnto(QPixmap pixmap, Rectangle screenRect) { ExtendedUser32.CURSORINFO cursorInfo = new ExtendedUser32.CURSORINFO(); if (!ExtendedUser32.INSTANCE.GetCursorInfo(cursorInfo) || cursorInfo.hCursor == null) @@ -4104,7 +4134,7 @@ else if (cB != 0 || cG != 0 || cR != 0) { } } - private static byte[] readBitmap32(WinDef.HBITMAP bitmap, int width, + private byte[] readBitmap32(WinDef.HBITMAP bitmap, int width, int height) { WinGDI.BITMAPINFO bi = new WinGDI.BITMAPINFO(); bi.bmiHeader.biWidth = width; @@ -4121,7 +4151,8 @@ private static byte[] readBitmap32(WinDef.HBITMAP bitmap, int width, return pixels.getByteArray(0, width * height * 4); } - public static void updateScreenshotZoom(Zoom zoom) { + @Override + public void updateScreenshotZoom(Zoom zoom) { if (!screenshotAnimating) return; currentZoom = zoom; @@ -4132,7 +4163,8 @@ public static void updateScreenshotZoom(Zoom zoom) { setTopmost(); } - public static void endScreenshotZoomAnimation(Zoom finalZoom) { + @Override + public void endScreenshotZoomAnimation(Zoom finalZoom) { if (!screenshotAnimating) return; screenshotAnimating = false; @@ -4157,7 +4189,8 @@ public static void endScreenshotZoomAnimation(Zoom finalZoom) { } } - public static void setZoom(Zoom zoom) { + @Override + public void setZoom(Zoom zoom) { if (currentZoom != null && currentZoom.equals(zoom)) return; if (hintMeshEndAnimation) { @@ -4228,7 +4261,7 @@ public static void setZoom(Zoom zoom) { updateZoomWindow(); } - private static void updateZoomExcludedWindows() { + private void updateZoomExcludedWindows() { if (zoomWindow == null) return; List hwnds = new ArrayList<>(); @@ -4252,7 +4285,7 @@ private static void updateZoomExcludedWindows() { Integer.toHexString(Native.getLastError())); } - private static WinDef.LRESULT zoomWindowCallback(WinDef.HWND hwnd, int uMsg, + private WinDef.LRESULT zoomWindowCallback(WinDef.HWND hwnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam) { // switch (uMsg) { @@ -4268,7 +4301,8 @@ private static WinDef.LRESULT zoomWindowCallback(WinDef.HWND hwnd, int uMsg, return User32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam); } - public static void hideIndicator(boolean allowFade) { + @Override + public void hideIndicator(boolean allowFade) { if (!showingIndicator) return; if (allowFade && indicatorFadeAnimator != null && @@ -4277,7 +4311,7 @@ public static void hideIndicator(boolean allowFade) { doHideIndicator(); } - private static void doHideIndicator() { + private void doHideIndicator() { showingIndicator = false; if (indicatorFadeAnimator != null) indicatorFadeAnimator.cancel(); @@ -4288,7 +4322,8 @@ private static void doHideIndicator() { indicatorWindow.window.setWindowOpacity(1.0); } - public static void setGrid(Grid grid) { + @Override + public void setGrid(Grid grid) { Objects.requireNonNull(grid); if (showingGrid && currentGrid != null && currentGrid.equals(grid)) return; @@ -4330,7 +4365,8 @@ public static void setGrid(Grid grid) { * The reason we don't call setHintMesh with the match hint is because * that does not keep the prefix box borders of the previous hint mesh. */ - public static void animateHintMatch(Hint hint) { + @Override + public void animateHintMatch(Hint hint) { if (!showingHintMesh) // Invisible hint mesh. return; Map> hintsByScreen = hintsByScreen(List.of(hint)); @@ -4368,11 +4404,13 @@ public static void animateHintMatch(Hint hint) { setHintMeshWindow(hintMeshWindow, hintMesh, -1, style, false, pixmapAndPosition); } - public static void setHintMesh(HintMesh hintMesh, Zoom zoom) { + @Override + public void setHintMesh(HintMesh hintMesh, Zoom zoom) { setHintMesh(hintMesh, zoom, false); } - public static void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) { + @Override + public void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) { Objects.requireNonNull(hintMesh); if (!hintMesh.visible()) { hideHintMesh(); @@ -4424,7 +4462,7 @@ public static void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) for (HintMeshWindow w : hintMeshWindows.values()) w.window.setWindowOpacity(opacity); }, - WindowsOverlay::doHideHintMesh, + this::doHideHintMesh, style.fadeAnimationEnabled(), style.fadeAnimationDuration()); if (hintMeshFadeAnimator.isEnabled()) { @@ -4433,21 +4471,23 @@ public static void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) hintMeshFadeAnimator.startFadeIn(); } } - if (!waitForZoomBeforeRepainting) { + if (!waitForZoom) { for (HintMeshWindow hintMeshWindow : hintMeshWindows.values()) { // requestWindowRepaint(hintMeshWindow.hwnd); } } } - public static void hideGrid() { + @Override + public void hideGrid() { if (!showingGrid) return; showingGrid = false; requestWindowRepaint(gridWindow.hwnd); } - public static void hideHintMesh() { + @Override + public void hideHintMesh() { if (!showingHintMesh) return; if (hintMeshEndAnimation) @@ -4457,7 +4497,7 @@ public static void hideHintMesh() { doHideHintMesh(); } - private static void doHideHintMesh() { + private void doHideHintMesh() { showingHintMesh = false; if (hintMeshFadeAnimator != null) hintMeshFadeAnimator.cancel(); @@ -4484,12 +4524,12 @@ private static void doHideHintMesh() { } } - private static void requestWindowRepaint(WinDef.HWND hwnd) { + private void requestWindowRepaint(WinDef.HWND hwnd) { User32.INSTANCE.InvalidateRect(hwnd, null, true); User32.INSTANCE.UpdateWindow(hwnd); } - static void mouseMoved(WinDef.POINT mousePosition) { + void mouseMoved(WinDef.POINT mousePosition) { if (indicatorWindow == null) return; // During zoom, currentZoom still has the previous frame's zoom center diff --git a/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java deleted file mode 100644 index e5aec284..00000000 --- a/src/main/java/mousemaster/platform/windows/WindowsOverlayAdapter.java +++ /dev/null @@ -1,122 +0,0 @@ -package mousemaster.platform.windows; - -import mousemaster.*; - -import mousemaster.platform.Overlay; - -import java.time.Duration; -import java.util.Set; - -public class WindowsOverlayAdapter implements Overlay { - - @Override - public void update(double delta) { - WindowsOverlay.update(delta); - } - - @Override - public void flushCache() { - WindowsOverlay.flushCache(); - } - - @Override - public void setTopmost() { - WindowsOverlay.setTopmost(); - } - - @Override - public void setMessagePump(Runnable pump) { - WindowsOverlay.setMessagePump(pump); - } - - @Override - public void preWarmFontStyles(Set configs) { - WindowsOverlay.preWarmFontStyles(configs); - } - - @Override - public void preWarmHintMeshWindows() { - WindowsOverlay.preWarmHintMeshWindows(); - } - - @Override - public Rectangle activeWindowRectangle(double widthPct, double heightPct, - int topInset, int bottomInset, - int leftInset, int rightInset) { - return WindowsOverlay.activeWindowRectangle(widthPct, heightPct, - topInset, bottomInset, leftInset, rightInset); - } - - @Override - public void setIndicator(Indicator indicator, boolean fadeAnimationEnabled, - Duration fadeAnimationDuration, boolean allowFade) { - WindowsOverlay.setIndicator(indicator, fadeAnimationEnabled, - fadeAnimationDuration, allowFade); - } - - @Override - public void hideIndicator(boolean allowFade) { - WindowsOverlay.hideIndicator(allowFade); - } - - @Override - public void setGrid(Grid grid) { - WindowsOverlay.setGrid(grid); - } - - @Override - public void hideGrid() { - WindowsOverlay.hideGrid(); - } - - @Override - public void setHintMesh(HintMesh hintMesh, Zoom zoom) { - WindowsOverlay.setHintMesh(hintMesh, zoom); - } - - @Override - public void setHintMesh(HintMesh hintMesh, Zoom zoom, boolean hintMatch) { - WindowsOverlay.setHintMesh(hintMesh, zoom, hintMatch); - } - - @Override - public void hideHintMesh() { - WindowsOverlay.hideHintMesh(); - } - - @Override - public void animateHintMatch(Hint hint) { - WindowsOverlay.animateHintMatch(hint); - } - - @Override - public void setZoom(Zoom zoom) { - WindowsOverlay.setZoom(zoom); - } - - @Override - public void startScreenshotZoomAnimation(Rectangle screenRect, Zoom beginZoom) { - WindowsOverlay.startScreenshotZoomAnimation(screenRect, beginZoom); - } - - @Override - public void updateScreenshotZoom(Zoom zoom) { - WindowsOverlay.updateScreenshotZoom(zoom); - } - - @Override - public void endScreenshotZoomAnimation(Zoom finalZoom) { - WindowsOverlay.endScreenshotZoomAnimation(finalZoom); - } - - @Override - public boolean waitForZoomBeforeRepainting() { - return WindowsOverlay.waitForZoomBeforeRepainting; - } - - @Override - public void setWaitForZoomBeforeRepainting(boolean value) { - WindowsOverlay.waitForZoomBeforeRepainting = value; - } - -} diff --git a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java index b4a8e262..c24e42a9 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java +++ b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java @@ -7,13 +7,13 @@ import com.sun.jna.platform.win32.*; import mousemaster.platform.ActiveAppFinder; import mousemaster.platform.Console; -import mousemaster.platform.Keyboard; -import mousemaster.platform.KeyboardLayoutProvider; import mousemaster.platform.KeyRegurgitator; -import mousemaster.platform.Overlay; +import mousemaster.platform.KeyboardLayoutProvider; +import mousemaster.platform.PlatformKeyboard; import mousemaster.platform.PlatformMouse; +import mousemaster.platform.PlatformOverlay; +import mousemaster.platform.PlatformUiAutomation; import mousemaster.platform.Screens; -import mousemaster.platform.UiAutomation; import mousemaster.KeyEvent.PressKeyEvent; import mousemaster.KeyEvent.ReleaseKeyEvent; import org.slf4j.Logger; @@ -29,15 +29,15 @@ public class WindowsPlatform implements Platform { private static final Logger logger = LoggerFactory.getLogger(WindowsPlatform.class); private final boolean keyRegurgitationEnabled; - private final Keyboard keyboard = new WindowsKeyboardAdapter(); - private final PlatformMouse mouse = new WindowsMouseAdapter(); + private final WindowsKeyboard keyboard = new WindowsKeyboard(); + private final WindowsMouse mouse = new WindowsMouse(this::mousePositionSet); private final Screens screens = new WindowsScreens(); - private final Overlay overlay = new WindowsOverlayAdapter(); - private final UiAutomation uiAutomation = new WindowsUiAutomationAdapter(); + private final WindowsOverlay overlay = new WindowsOverlay(mouse); + private final PlatformUiAutomation uiAutomation = new WindowsUiAutomation(); private final ActiveAppFinder activeAppFinder = new WindowsActiveAppFinder(); private final Console console = new WindowsConsole(); private final KeyboardLayoutProvider keyboardLayoutProvider = WindowsVirtualKey::activeKeyboardLayout; - private final KeyRegurgitator keyRegurgitator = new WindowsKeyRegurgitator(keyboard); + private final KeyRegurgitator keyRegurgitator = new KeyRegurgitator(keyboard); private final WindowsClock clock = new WindowsClock(); private MouseController mouseController; @@ -76,7 +76,6 @@ private record ReentrantKeyEvent(KeyEvent keyEvent, int infoFlags, public WindowsPlatform(boolean multipleInstancesAllowed, boolean keyRegurgitationEnabled) { this.keyRegurgitationEnabled = keyRegurgitationEnabled; - WindowsMouse.windowsPlatform = this; // TODO Get rid of this. WindowsUiAccess.checkAndTryToGetUiAccess(); // Done before acquiring the single instance mutex. if (!multipleInstancesAllowed && !acquireSingleInstanceMutex()) throw new IllegalStateException("Another instance is already running"); @@ -89,16 +88,16 @@ public WindowsPlatform(boolean multipleInstancesAllowed, boolean keyRegurgitatio @Override public void update(double delta) { - WindowsOverlay.waitForZoomBeforeRepainting = false; - WindowsKeyboard.update(delta); + overlay.setWaitForZoomBeforeRepainting(false); + keyboard.update(delta); sanityCheckCurrentlyPressedKeys(delta); enforceWindowsTopmostTimer -= delta; if (enforceWindowsTopmostTimer < 0) { // Every 200ms. enforceWindowsTopmostTimer = 0.2; - WindowsOverlay.setTopmost(); + overlay.setTopmost(); } - WindowsOverlay.update(delta); + overlay.update(delta); } @Override @@ -117,12 +116,12 @@ public void sleep() throws InterruptedException { for (WinDef.POINT p; (p = mousePositionQueue.poll()) != null;) mousePosition = p; if (mousePosition == null) - mousePosition = WindowsMouse.tryFindMousePosition(); + mousePosition = mouse.tryFindMousePosition(); if (mousePosition != null && (lastMousePosition == null || mousePosition.x != lastMousePosition.x || mousePosition.y != lastMousePosition.y)) { lastMousePosition = mousePosition; - WindowsOverlay.mouseMoved(mousePosition); + overlay.mouseMoved(mousePosition); for (MousePositionListener listener : mousePositionListeners) { // ZoomListener can take up to 30ms. listener.mouseMoved(mousePosition.x, mousePosition.y); @@ -132,7 +131,7 @@ else if (lastMousePosition != null) { // Mouse position hasn't changed, but the cursor shape may have changed // (e.g., arrow to hand after keyboard-driven movement stopped). // Reposition the indicator with the updated cursor visual center. - WindowsOverlay.mouseMoved(lastMousePosition); + overlay.mouseMoved(lastMousePosition); } pumpEvents(); long beforeTime = System.nanoTime(); @@ -161,12 +160,12 @@ public void reset(MouseController mouseController, KeyboardManager keyboardManag this.mouseController = mouseController; this.keyboardManager = keyboardManager; this.mousePositionListeners = mousePositionListeners; - if (WindowsKeyboard.activeKeyboardLayout != null && - !WindowsKeyboard.activeKeyboardLayout.equals(activeKeyboardLayout)) { + if (keyboard.activeKeyboardLayout != null && + !keyboard.activeKeyboardLayout.equals(activeKeyboardLayout)) { keyboardManager.reset(); - WindowsKeyboard.reset(); + keyboard.reset(); } - WindowsKeyboard.activeKeyboardLayout = activeKeyboardLayout; + keyboard.activeKeyboardLayout = activeKeyboardLayout; Set newHintMeshConfigurations = new HashSet<>(); for (Mode mode : newModeMap.modes()) { newHintMeshConfigurations.add(mode.hintMesh()); @@ -175,17 +174,17 @@ public void reset(MouseController mouseController, KeyboardManager keyboardManag if (oldModeMap != null) { logger.debug( "Flushing overlay cache because hint mesh configurations have changed"); - WindowsOverlay.flushCache(); + overlay.flushCache(); } - WindowsOverlay.preWarmFontStyles(newHintMeshConfigurations); - WindowsOverlay.preWarmHintMeshWindows(); + overlay.preWarmFontStyles(newHintMeshConfigurations); + overlay.preWarmHintMeshWindows(); } this.modeMap = newModeMap; - WinDef.POINT mousePosition = WindowsMouse.findMousePosition(); + WinDef.POINT mousePosition = mouse.findMousePosition(); mousePositionListeners.forEach( mousePositionListener -> mousePositionListener.mouseMoved(mousePosition.x, mousePosition.y)); - WindowsOverlay.setMessagePump(this::pumpEvents); + overlay.setMessagePump(this::pumpEvents); if (keyboardHookCallback == null) installHooks(); } @@ -254,7 +253,7 @@ public void shutdown() { if (shutdown) return; shutdown = true; - WindowsMouse.showCursor(); // Just in case we are shutting down while cursor is hidden. + mouse.showCursor(); // Just in case we are shutting down while cursor is hidden. boolean keyboardHookUnhooked = User32.INSTANCE.UnhookWindowsHookEx(keyboardHook); boolean mouseHookUnhooked = User32.INSTANCE.UnhookWindowsHookEx(mouseHook); @@ -281,7 +280,7 @@ public KeyboardLayoutProvider keyboardLayoutProvider() { } @Override - public Keyboard keyboard() { + public PlatformKeyboard keyboard() { return keyboard; } @@ -296,12 +295,12 @@ public Screens screens() { } @Override - public Overlay overlay() { + public PlatformOverlay overlay() { return overlay; } @Override - public UiAutomation uiAutomation() { + public PlatformUiAutomation uiAutomation() { return uiAutomation; } @@ -337,7 +336,7 @@ private void sanityCheckCurrentlyPressedKeys(double delta) { continue; WindowsVirtualKey windowsVirtualKey = WindowsVirtualKey.windowsVirtualKeyFromKey(key, - WindowsKeyboard.activeKeyboardLayout); + keyboard.activeKeyboardLayout); if (windowsVirtualKey == null) { // Can be null if key was added to currentlyPressedNotEatenKeys // then the layout changed. The following crash has happened: @@ -368,7 +367,7 @@ private void sanityCheckCurrentlyPressedKeys(double delta) { stuckKeyCheckTimer += delta; if (stuckKeyCheckTimer >= 1) { stuckKeyCheckTimer = 0; - KeyboardLayout layout = WindowsKeyboard.activeKeyboardLayout; + KeyboardLayout layout = keyboard.activeKeyboardLayout; for (WindowsVirtualKey virtualKey : WindowsVirtualKey.values()) { short state = User32.INSTANCE.GetAsyncKeyState(virtualKey.virtualKeyCode); boolean pressedAccordingToOs = (state & 0x8000) != 0; @@ -387,7 +386,7 @@ private void sanityCheckCurrentlyPressedKeys(double delta) { " is pressed according to GetAsyncKeyState" + " but not in currentlyPressedNotEatenKeys" + ", injecting release"); - WindowsKeyboard.sendInputKeyRelease( + keyboard.sendInputKeyRelease( virtualKey.virtualKeyCode, extendedKeys.contains(key)); } @@ -424,10 +423,10 @@ private WinDef.LRESULT keyboardHookCallback(int nCode, WinDef.WPARAM wParam, ExtendedUser32.LLKHF_INJECTED; logger.trace("Reentrant keyboard hook callback, skipping KeyboardManager: " + keyEventString(info, wParamString(wParam), keyEvent, injected, altgrLeftctrl)); - WindowsKeyboard.keyboardHookCallback(info, wParam, null, + keyboard.keyboardHookCallback(info, wParam, null, keyEvent, injected, altgrLeftctrl); if (injected && keyEvent != null) { - if (WindowsKeyboard.shouldSuppressExternalLeftalt(keyEvent, true)) { + if (keyboard.shouldSuppressExternalLeftalt(keyEvent, true)) { logger.trace("Suppressing external +leftalt (reentrant)"); return new WinDef.LRESULT(1); } @@ -479,11 +478,11 @@ private WinDef.LRESULT keyboardHookCallback(int nCode, WinDef.WPARAM wParam, KeyEvent keyEvent = buildKeyEvent(info, wParam, altgrLeftctrl); boolean injected = (info.flags & ExtendedUser32.LLKHF_INJECTED) == ExtendedUser32.LLKHF_INJECTED; logKeyEvent(info, wParamString, keyEvent, injected, altgrLeftctrl); - WindowsKeyboard.keyboardHookCallback(info, wParam, wParamString, + keyboard.keyboardHookCallback(info, wParam, wParamString, keyEvent, injected, altgrLeftctrl); if (injected) { if (keyEvent != null) { - if (WindowsKeyboard.shouldSuppressExternalLeftalt(keyEvent, true)) { + if (keyboard.shouldSuppressExternalLeftalt(keyEvent, true)) { logger.trace("Suppressing external +leftalt"); eaten = true; } @@ -574,7 +573,8 @@ private KeyEvent buildKeyEvent(WinUser.KBDLLHOOKSTRUCT info, WinDef.WPARAM wPara key = Key.rightalt; else key = WindowsVirtualKey.keyFromWindowsEvent( - WindowsVirtualKey.values.get(info.vkCode), info.scanCode, info.flags); + WindowsVirtualKey.values.get(info.vkCode), info.scanCode, info.flags, + keyboard.activeKeyboardLayout); if (key == null) return null; Instant time = clock.now(); @@ -732,8 +732,8 @@ public void mousePositionSet(WinDef.POINT mousePosition) { public void modeChanged(Mode newMode) { // If zoom is going to change, WindowsOverlay.setZoom() will repaint the hint mesh // after the zoom window is initialized. - WindowsOverlay.waitForZoomBeforeRepainting = - currentMode != null && !currentMode.zoom().equals(newMode.zoom()); + overlay.setWaitForZoomBeforeRepainting( + currentMode != null && !currentMode.zoom().equals(newMode.zoom())); currentMode = newMode; } diff --git a/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java b/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java index da06fb56..d01ee206 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java +++ b/src/main/java/mousemaster/platform/windows/WindowsUiAutomation.java @@ -8,7 +8,8 @@ import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; -import mousemaster.platform.UiAutomation.UiElement; +import mousemaster.platform.PlatformUiAutomation; +import mousemaster.platform.PlatformUiAutomation.UiElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +21,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -public class WindowsUiAutomation { +public class WindowsUiAutomation implements PlatformUiAutomation { private static final Logger logger = LoggerFactory.getLogger(WindowsUiAutomation.class); @@ -365,7 +366,8 @@ private static void collectElements(UIAutomationElementArray array, /** * Starts an asynchronous UI element query on a background thread. */ - public static Future> startFindInteractiveUiElements() { + @Override + public Future> startFindInteractiveUiElements() { ensureInitialized(); HWND hwnd = User32.INSTANCE.GetForegroundWindow(); long hwndKey = hwnd != null ? Pointer.nativeValue(hwnd.getPointer()) : 0; diff --git a/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java b/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java deleted file mode 100644 index 51456f48..00000000 --- a/src/main/java/mousemaster/platform/windows/WindowsUiAutomationAdapter.java +++ /dev/null @@ -1,17 +0,0 @@ -package mousemaster.platform.windows; - -import mousemaster.*; - -import mousemaster.platform.UiAutomation; - -import java.util.List; -import java.util.concurrent.Future; - -public class WindowsUiAutomationAdapter implements UiAutomation { - - @Override - public Future> startFindInteractiveUiElements() { - return WindowsUiAutomation.startFindInteractiveUiElements(); - } - -} diff --git a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java index 48cea009..d682b2b1 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java +++ b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java @@ -471,7 +471,8 @@ private static KeyboardLayout startupKeyboardLayout() { new String(nameBuffer, 0, nameLength)); } - public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int scanCode, int flags) { + public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int scanCode, + int flags, KeyboardLayout activeKeyboardLayout) { if (scanCode == 0) { // Injected key event have scanCode 0. return WindowsVirtualKey.activeKeyboardLayout().keyFromVirtualKeyName(windowsVirtualKey.name()); @@ -486,11 +487,11 @@ public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int s boolean isExtended = (flags & 0x1) != 0; if (isExtended) { int extendedKeyScanCode = 0xE000 | scanCode; - Key extendedKey = WindowsKeyboard.activeKeyboardLayout.keyFromScanCode(extendedKeyScanCode); + Key extendedKey = activeKeyboardLayout.keyFromScanCode(extendedKeyScanCode); if (extendedKey != null) return extendedKey; } - return WindowsKeyboard.activeKeyboardLayout.keyFromScanCode(scanCode); + return activeKeyboardLayout.keyFromScanCode(scanCode); } public static WindowsVirtualKey windowsVirtualKeyFromKey(Key key, From e9986dc7372ca76ade0e462fb88fc2a0a540568f Mon Sep 17 00:00:00 2001 From: Cesar Munoz Date: Sat, 20 Jun 2026 15:11:31 +0200 Subject: [PATCH 6/6] Abstracting KeyboardLayout --- .../java/mousemaster/ConfigurationParser.java | 38 ++-- src/main/java/mousemaster/KeyboardLayout.java | 179 +----------------- src/main/java/mousemaster/Mousemaster.java | 3 +- .../platform/KeyboardLayoutProvider.java | 8 + .../platform/windows/KbdlayoutinfoParser.java | 38 ++-- .../platform/windows/WindowsKeyboard.java | 2 +- .../windows/WindowsKeyboardLayout.java | 175 +++++++++++++++++ .../platform/windows/WindowsPlatform.java | 13 +- .../platform/windows/WindowsVirtualKey.java | 34 ++-- .../mousemaster/ComboPreparationTest.java | 5 +- .../ComboVariablePreconditionTest.java | 5 +- .../mousemaster/ComboWatcherRetainTest.java | 5 +- .../mousemaster/ExpandableSequenceTest.java | 5 +- 13 files changed, 268 insertions(+), 242 deletions(-) create mode 100644 src/main/java/mousemaster/platform/windows/WindowsKeyboardLayout.java diff --git a/src/main/java/mousemaster/ConfigurationParser.java b/src/main/java/mousemaster/ConfigurationParser.java index bde63330..30ad5950 100644 --- a/src/main/java/mousemaster/ConfigurationParser.java +++ b/src/main/java/mousemaster/ConfigurationParser.java @@ -1,6 +1,7 @@ package mousemaster; import io.qt.gui.QFontDatabase; +import mousemaster.platform.KeyboardLayoutProvider; import mousemaster.ComboMove.KeyComboMove; import mousemaster.ComboMove.PressComboMove; import mousemaster.GridArea.GridAreaType; @@ -306,9 +307,10 @@ private record ForcedActiveAndConfigurationKeyboardLayouts( } public static Configuration parse(List properties, - KeyboardLayout platformActiveKeyboardLayout) { + KeyboardLayout platformActiveKeyboardLayout, + KeyboardLayoutProvider layoutProvider) { ForcedActiveAndConfigurationKeyboardLayouts - forcedActiveAndConfigurationKeyboardLayouts = parseForcedAndConfigurationLayouts(properties); + forcedActiveAndConfigurationKeyboardLayouts = parseForcedAndConfigurationLayouts(properties, layoutProvider); KeyboardLayout activeKeyboardLayout = forcedActiveAndConfigurationKeyboardLayouts.forcedActiveKeyboardLayout == null ? platformActiveKeyboardLayout : @@ -319,7 +321,7 @@ public static Configuration parse(List properties, forcedActiveAndConfigurationKeyboardLayouts.configurationKeyboardLayout; KeyResolver keyResolver = new KeyResolver(activeKeyboardLayout, configurationKeyboardLayout); - Aliases configurationAliases = parseAliases(properties); + Aliases configurationAliases = parseAliases(properties, layoutProvider); Map keyAliases = buildKeyAliasesForActiveKeyboardLayout( configurationAliases.layoutKeyAliasByName, activeKeyboardLayout, configurationKeyboardLayout); @@ -567,7 +569,8 @@ private static List deriveSelectCombosFromHintSelectionKeys(ModeBuilder m } private static ForcedActiveAndConfigurationKeyboardLayouts parseForcedAndConfigurationLayouts( - List properties) { + List properties, + KeyboardLayoutProvider layoutProvider) { Set visitedPropertyKeys = new HashSet<>(); KeyboardLayout forcedActiveKeyboardLayout = null; KeyboardLayout configurationKeyboardLayout = null; @@ -581,13 +584,13 @@ private static ForcedActiveAndConfigurationKeyboardLayouts parseForcedAndConfigu if (propertyKey.equals("keyboard-layout")) { logger.warn( "key-layout has been deprecated: use forced-active-keyboard-layout instead"); - forcedActiveKeyboardLayout = parseKeyboardLayout(propertyValue); + forcedActiveKeyboardLayout = parseKeyboardLayout(propertyValue, layoutProvider); } if (propertyKey.equals("forced-active-keyboard-layout")) { - forcedActiveKeyboardLayout = parseKeyboardLayout(propertyValue); + forcedActiveKeyboardLayout = parseKeyboardLayout(propertyValue, layoutProvider); } if (propertyKey.equals("configuration-keyboard-layout")) { - configurationKeyboardLayout = parseKeyboardLayout(propertyValue); + configurationKeyboardLayout = parseKeyboardLayout(propertyValue, layoutProvider); } } return new ForcedActiveAndConfigurationKeyboardLayouts(forcedActiveKeyboardLayout, @@ -1658,7 +1661,8 @@ private static Set parseVariableNames(List properties, return variableNames; } - private static Aliases parseAliases(List properties) { + private static Aliases parseAliases(List properties, + KeyboardLayoutProvider layoutProvider) { Map layoutKeyAliasByName = new HashMap<>(); Map appAliasByName = new HashMap<>(); Set visitedPropertyKeys = new HashSet<>(); @@ -1670,7 +1674,7 @@ private static Aliases parseAliases(List properties) { String propertyKey = lineMatcher.group(1).strip(); String propertyValue = lineMatcher.group(2).strip(); try { - parseAlias(propertyKey, propertyValue, appAliasByName, layoutKeyAliasByName); + parseAlias(propertyKey, propertyValue, appAliasByName, layoutKeyAliasByName, layoutProvider); } catch (IllegalArgumentException e) { IllegalArgumentException e2 = new IllegalArgumentException("[" + propertyKey + "] " + e.getMessage()); @@ -1683,7 +1687,8 @@ private static Aliases parseAliases(List properties) { private static void parseAlias(String propertyKey, String propertyValue, Map appAliasByName, - Map layoutKeyAliasByName) { + Map layoutKeyAliasByName, + KeyboardLayoutProvider layoutProvider) { if (propertyKey.startsWith("app-alias.")) { String aliasName = propertyKey.substring("app-alias.".length()); Set apps = Arrays.stream(propertyValue.split("\\s+")) @@ -1708,23 +1713,22 @@ else if (propertyKey.startsWith("key-alias.")) { } else { String layoutName = keyMatcher.group(3); - KeyboardLayout layout = parseKeyboardLayout(layoutName); + KeyboardLayout layout = parseKeyboardLayout(layoutName, layoutProvider); layoutKeyAlias.tokensByLayout.put(layout, tokens); } } } - private static KeyboardLayout parseKeyboardLayout(String layoutName) { - KeyboardLayout layout = - KeyboardLayout.keyboardLayoutByShortName.get(layoutName); + private static KeyboardLayout parseKeyboardLayout(String layoutName, + KeyboardLayoutProvider layoutProvider) { + KeyboardLayout layout = layoutProvider.byShortName(layoutName); if (layout == null) { - String layoutIdentifier = layoutName; - layout = KeyboardLayout.keyboardLayoutByIdentifier.get(layoutIdentifier); + layout = layoutProvider.byIdentifier(layoutName); if (layout == null) throw new IllegalArgumentException( "Invalid keyboard layout: " + layoutName + ", available keyboard layouts: " + - KeyboardLayout.keyboardLayoutByShortName.keySet()); + layoutProvider.shortNames()); } return layout; } diff --git a/src/main/java/mousemaster/KeyboardLayout.java b/src/main/java/mousemaster/KeyboardLayout.java index 1dd91ac4..03a68712 100644 --- a/src/main/java/mousemaster/KeyboardLayout.java +++ b/src/main/java/mousemaster/KeyboardLayout.java @@ -1,181 +1,16 @@ package mousemaster; -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +public interface KeyboardLayout { -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; + String identifier(); -public final class KeyboardLayout { + String displayName(); - private static final Logger logger = LoggerFactory.getLogger(KeyboardLayout.class); + String shortName(); - public static final Map keyboardLayoutByIdentifier = new HashMap<>(); - public static final Map keyboardLayoutByShortName = new HashMap<>(); + boolean containsKey(Key key); - static { - InputStreamReader reader = new InputStreamReader( - KeyboardLayout.class.getClassLoader().getResourceAsStream("keyboard-layouts.json"), - StandardCharsets.UTF_8 - ); - Type listType = new TypeToken>() {}.getType(); - Gson gson = new GsonBuilder() - .registerTypeAdapter(Key.class, new KeyDeserializer()) - .registerTypeAdapter(KeyboardLayoutKey.class, new KeyboardLayoutKeyDeserializer()) - .create(); - long before = System.nanoTime(); - List keyboardLayouts = gson.fromJson(reader, listType); - for (KeyboardLayout keyboardLayout : keyboardLayouts) { - keyboardLayoutByIdentifier.put(keyboardLayout.identifier, keyboardLayout); - if (keyboardLayout.shortName != null) - keyboardLayoutByShortName.put(keyboardLayout.shortName, keyboardLayout); - } - long after = System.nanoTime(); - logger.trace("Loaded " + keyboardLayouts.size() + " keyboard layouts in " + (after - before) / 1000_000 + "ms"); - } - - - public static class KeyDeserializer implements JsonDeserializer { - - private final Map cache = new HashMap<>(); - - @Override - public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - JsonObject keyObject = json.getAsJsonObject(); - String staticName = !keyObject.has("staticName") ? null : keyObject.get("staticName").getAsString(); - String staticSingleCharacterName = !keyObject.has("staticSingleCharacterName") ? null : keyObject.get("staticSingleCharacterName").getAsString(); - String character = !keyObject.has("character") ? null : keyObject.get("character").getAsString(); - Key temp = new Key(staticName, staticSingleCharacterName, character); - return cache.computeIfAbsent(temp, Function.identity()); - } - } - - public record KeyboardLayoutKey(int scanCode, String virtualKeyName, Key key, - String text, String name) { - - } - - public static class KeyboardLayoutKeyDeserializer implements JsonDeserializer { - - @Override - public KeyboardLayoutKey deserialize(JsonElement json, Type typeOfT, - JsonDeserializationContext context) - throws JsonParseException { - JsonObject obj = json.getAsJsonObject(); - int scanCode = obj.get("scanCode").getAsInt(); - String virtualKeyName = obj.get("virtualKey").getAsString(); - Key key = context.deserialize(obj.get("key"), Key.class); - String text = obj.has("text") ? obj.get("text").getAsString() : null; - String name = obj.has("name") ? obj.get("name").getAsString() : null; - return new KeyboardLayoutKey(scanCode, virtualKeyName, key, text, name); - } - } - - private final String identifier; - private final String displayName; - private final String driverName; - private final String shortName; - private final List keys; - - public KeyboardLayout(String identifier, String displayName, String driverName, - String shortName, - List keys) { - this.identifier = identifier; - this.displayName = displayName; - this.driverName = driverName; - this.shortName = shortName; - this.keys = keys; - } - - public String identifier() { - return identifier; - } - - public String displayName() { - return displayName; - } - - public String driverName() { - return driverName; - } - - public String shortName() { - return shortName; - } - - public List keys() { - return keys; - } - - public boolean containsKey(Key key) { - return scanCode(key) != -1; - } - - public Key keyFromScanCode(int scanCode) { - for (KeyboardLayoutKey keyboardLayoutKey : keys) { - if (keyboardLayoutKey.scanCode == scanCode) - return keyboardLayoutKey.key(); - } - return null; - } - - public Key keyFromVirtualKeyName(String virtualKeyName) { - for (KeyboardLayoutKey keyboardLayoutKey : keys) { - if (virtualKeyName.equals(keyboardLayoutKey.virtualKeyName())) - return keyboardLayoutKey.key(); - } - return null; - } - - public int scanCode(Key key) { - for (KeyboardLayoutKey keyboardLayoutKey : keys) { - if (keyboardLayoutKey.key.equals(key)) - return keyboardLayoutKey.scanCode(); - } - return -1; - } - - public String virtualKeyName(Key key) { - for (KeyboardLayoutKey keyboardLayoutKey : keys) { - if (keyboardLayoutKey.key.equals(key)) - return keyboardLayoutKey.virtualKeyName(); - } - return null; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) - return true; - if (obj == null || obj.getClass() != this.getClass()) - return false; - var that = (KeyboardLayout) obj; - return Objects.equals(this.identifier, that.identifier) && - Objects.equals(this.displayName, that.displayName) && - Objects.equals(this.driverName, that.driverName) && - Objects.equals(this.keys, that.keys); - } - - @Override - public int hashCode() { - return Objects.hash(identifier, displayName, driverName, keys); - } - - @Override - public String toString() { - return "KeyboardLayout[" + - "identifier=" + identifier + ", " + - "displayName=" + displayName + ", " + - "driverName=" + driverName + ", " + - "shortName=" + shortName + ']'; - } + Key keyFromScanCode(int scanCode); + int scanCode(Key key); } diff --git a/src/main/java/mousemaster/Mousemaster.java b/src/main/java/mousemaster/Mousemaster.java index d2cf8218..ddefc94f 100644 --- a/src/main/java/mousemaster/Mousemaster.java +++ b/src/main/java/mousemaster/Mousemaster.java @@ -158,7 +158,8 @@ private void loadConfiguration(boolean readFile) throws IOException { } } configuration = - ConfigurationParser.parse(configurationProperties, activeKeyboardLayout); + ConfigurationParser.parse(configurationProperties, activeKeyboardLayout, + platform.keyboardLayoutProvider()); // User can override the layout. When active layout is dvorak, Windows HKL only // gives the language identifier, which is 0409. But it is missing the other part // of the layout identifier (00010409). diff --git a/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java index 27286d43..08bdd8b4 100644 --- a/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java +++ b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java @@ -2,7 +2,15 @@ import mousemaster.KeyboardLayout; +import java.util.Set; + public interface KeyboardLayoutProvider { KeyboardLayout activeKeyboardLayout(); + + KeyboardLayout byShortName(String shortName); + + KeyboardLayout byIdentifier(String identifier); + + Set shortNames(); } diff --git a/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java index 39af1831..810eea6f 100644 --- a/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java +++ b/src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java @@ -151,13 +151,13 @@ public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException { List keyboardLayoutIdentifiers = parseRootPage(); // keyboardLayoutIdentifiers.removeIf(Predicate.not(Predicate.isEqual("00000412"))); - List keyboardLayouts = new ArrayList<>(); + List keyboardLayouts = new ArrayList<>(); for (String keyboardLayoutIdentifier : keyboardLayoutIdentifiers) { logger.info("Parsing keyboard layout page " + keyboardLayoutIdentifier); - KeyboardLayout keyboardLayout = parseKeyboardLayoutPage(keyboardLayoutIdentifier); + WindowsKeyboardLayout keyboardLayout = parseKeyboardLayoutPage(keyboardLayoutIdentifier); keyboardLayouts.add(keyboardLayout); } - for (KeyboardLayout keyboardLayout : keyboardLayouts) { + for (WindowsKeyboardLayout keyboardLayout : keyboardLayouts) { logger.info("Parsing keyboard layout XML for processing " + keyboardLayout); parseKeyboardLayoutXmlForProcessing(keyboardLayout); } @@ -183,7 +183,7 @@ private static List parseRootPage() throws IOException { return keyboardLayoutIdentifiers; } - private static KeyboardLayout parseKeyboardLayoutPage(String keyboardLayoutIdentifier) + private static WindowsKeyboardLayout parseKeyboardLayoutPage(String keyboardLayoutIdentifier) throws IOException { org.jsoup.nodes.Document document = Jsoup.connect("https://kbdlayout.info/" + keyboardLayoutIdentifier).get(); @@ -205,13 +205,13 @@ private static KeyboardLayout parseKeyboardLayoutPage(String keyboardLayoutIdent } if (displayName == null || driverName == null) throw new IllegalStateException(); - return new KeyboardLayout(keyboardLayoutIdentifier, displayName, driverName, + return new WindowsKeyboardLayout(keyboardLayoutIdentifier, displayName, driverName, keyboardLayoutShortNameByIdentifier.get(keyboardLayoutIdentifier), new ArrayList<>()); } private static void parseKeyboardLayoutXmlForProcessing( - KeyboardLayout keyboardLayout) + WindowsKeyboardLayout keyboardLayout) throws IOException, ParserConfigurationException, SAXException { String urlString = "https://kbdlayout.info/" + keyboardLayout.identifier() + "/download/xml"; URL url = URI.create(urlString).toURL(); @@ -301,7 +301,7 @@ else if (text != null) key = Key.ofCharacter(text); if (key != null) keyboardLayout.keys() - .add(new KeyboardLayout.KeyboardLayoutKey(sc, vk.name(), key, + .add(new WindowsKeyboardLayout.KeyboardLayoutKey(sc, vk, key, text == null || text.isBlank() ? null : text, name.isBlank() ? null : name)); } @@ -315,7 +315,7 @@ else if (text != null) conn.disconnect(); } - private static void addCustomKeyboardLayouts(List keyboardLayouts) { + private static void addCustomKeyboardLayouts(List keyboardLayouts) { String usQwertyCharacterKeys = """ `1234567890-= qwertyuiop[] @@ -328,13 +328,13 @@ private static void addCustomKeyboardLayouts(List keyboardLayout shnt,.aeoi'\\ fmvc/gpxky """.replaceAll("\\s", ""); - KeyboardLayout usQwertyLayout = keyboardLayouts.stream() + WindowsKeyboardLayout usQwertyLayout = keyboardLayouts.stream() .filter(keyboardLayout -> "us-qwerty".equals( keyboardLayout.shortName())) .findFirst() .orElseThrow(); - List usHalmakKeys = new ArrayList<>(); - for (KeyboardLayout.KeyboardLayoutKey usQwertyKey : usQwertyLayout.keys()) { + List usHalmakKeys = new ArrayList<>(); + for (WindowsKeyboardLayout.KeyboardLayoutKey usQwertyKey : usQwertyLayout.keys()) { if (usQwertyKey.key().character() == null) { usHalmakKeys.add(usQwertyKey); continue; @@ -345,7 +345,7 @@ private static void addCustomKeyboardLayouts(List keyboardLayout continue; } String usHalmakKeyCharacter = "" + usHalmakCharacterKeys.charAt(characterKeyIndex); - KeyboardLayout.KeyboardLayoutKey sameCharacterUsQwertyKey = + WindowsKeyboardLayout.KeyboardLayoutKey sameCharacterUsQwertyKey = usQwertyLayout.keys() .stream() .filter(keyboardLayoutKey -> usHalmakKeyCharacter.equals( @@ -353,9 +353,9 @@ private static void addCustomKeyboardLayouts(List keyboardLayout .character())) .findFirst() .orElseThrow(); - KeyboardLayout.KeyboardLayoutKey usHalmakKey = new KeyboardLayout.KeyboardLayoutKey( + WindowsKeyboardLayout.KeyboardLayoutKey usHalmakKey = new WindowsKeyboardLayout.KeyboardLayoutKey( usQwertyKey.scanCode(), - sameCharacterUsQwertyKey.virtualKeyName(), + sameCharacterUsQwertyKey.virtualKey(), sameCharacterUsQwertyKey.key(), sameCharacterUsQwertyKey.text(), sameCharacterUsQwertyKey.name() @@ -363,20 +363,20 @@ private static void addCustomKeyboardLayouts(List keyboardLayout usHalmakKeys.add(usHalmakKey); } keyboardLayouts.add( - new KeyboardLayout(null, null, null, "us-halmak", usHalmakKeys)); + new WindowsKeyboardLayout(null, null, null, "us-halmak", usHalmakKeys)); } - private static void fixKoreanKeyboardLayout(List keyboardLayouts) { + private static void fixKoreanKeyboardLayout(List keyboardLayouts) { // https://kbdlayout.info/00000412/download/xml is missing RMENU. - KeyboardLayout koreanKeyboardLayout = + WindowsKeyboardLayout koreanKeyboardLayout = keyboardLayouts.stream() .filter(keyboardLayout -> keyboardLayout.identifier() .equals("00000412")) .findFirst() .orElseThrow(); koreanKeyboardLayout.keys() - .add(new KeyboardLayout.KeyboardLayoutKey(57400, - WindowsVirtualKey.VK_RMENU.name(), Key.rightalt, null, + .add(new WindowsKeyboardLayout.KeyboardLayoutKey(57400, + WindowsVirtualKey.VK_RMENU, Key.rightalt, null, "Right Alt")); } diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java index 1165c068..391eea8a 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyboard.java @@ -38,7 +38,7 @@ public class WindowsKeyboard implements PlatformKeyboard { Key.enter // Only enter from numpad? ); - public KeyboardLayout activeKeyboardLayout; + public WindowsKeyboardLayout activeKeyboardLayout; @Override public void reset() { diff --git a/src/main/java/mousemaster/platform/windows/WindowsKeyboardLayout.java b/src/main/java/mousemaster/platform/windows/WindowsKeyboardLayout.java new file mode 100644 index 00000000..f4a65b92 --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsKeyboardLayout.java @@ -0,0 +1,175 @@ +package mousemaster.platform.windows; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import mousemaster.Key; +import mousemaster.KeyboardLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +public final class WindowsKeyboardLayout implements KeyboardLayout { + + private static final Logger logger = LoggerFactory.getLogger(WindowsKeyboardLayout.class); + + public static final Map keyboardLayoutByIdentifier = new HashMap<>(); + public static final Map keyboardLayoutByShortName = new HashMap<>(); + + static { + InputStreamReader reader = new InputStreamReader( + WindowsKeyboardLayout.class.getClassLoader() + .getResourceAsStream("keyboard-layouts.json"), + StandardCharsets.UTF_8 + ); + Type listType = new TypeToken>() {}.getType(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Key.class, new KeyDeserializer()) + .create(); + long before = System.nanoTime(); + List keyboardLayouts = gson.fromJson(reader, listType); + for (WindowsKeyboardLayout keyboardLayout : keyboardLayouts) { + keyboardLayoutByIdentifier.put(keyboardLayout.identifier, keyboardLayout); + if (keyboardLayout.shortName != null) + keyboardLayoutByShortName.put(keyboardLayout.shortName, keyboardLayout); + } + long after = System.nanoTime(); + logger.trace("Loaded " + keyboardLayouts.size() + " keyboard layouts in " + + (after - before) / 1000_000 + "ms"); + } + + public static class KeyDeserializer implements JsonDeserializer { + + private final Map cache = new HashMap<>(); + + @Override + public Key deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + JsonObject keyObject = json.getAsJsonObject(); + String staticName = !keyObject.has("staticName") ? null : + keyObject.get("staticName").getAsString(); + String staticSingleCharacterName = + !keyObject.has("staticSingleCharacterName") ? null : + keyObject.get("staticSingleCharacterName").getAsString(); + String character = !keyObject.has("character") ? null : + keyObject.get("character").getAsString(); + Key temp = new Key(staticName, staticSingleCharacterName, character); + return cache.computeIfAbsent(temp, Function.identity()); + } + } + + // Gson deserializes the "virtualKey" JSON field into the enum by name automatically. + public record KeyboardLayoutKey(int scanCode, WindowsVirtualKey virtualKey, Key key, + String text, String name) { + } + + private final String identifier; + private final String displayName; + private final String driverName; + private final String shortName; + private final List keys; + + public WindowsKeyboardLayout(String identifier, String displayName, String driverName, + String shortName, List keys) { + this.identifier = identifier; + this.displayName = displayName; + this.driverName = driverName; + this.shortName = shortName; + this.keys = keys; + } + + @Override + public String identifier() { + return identifier; + } + + @Override + public String displayName() { + return displayName; + } + + public String driverName() { + return driverName; + } + + @Override + public String shortName() { + return shortName; + } + + public List keys() { + return keys; + } + + @Override + public boolean containsKey(Key key) { + return scanCode(key) != -1; + } + + @Override + public Key keyFromScanCode(int scanCode) { + for (KeyboardLayoutKey keyboardLayoutKey : keys) { + if (keyboardLayoutKey.scanCode == scanCode) + return keyboardLayoutKey.key(); + } + return null; + } + + public Key keyFromVirtualKey(WindowsVirtualKey virtualKey) { + for (KeyboardLayoutKey keyboardLayoutKey : keys) { + if (keyboardLayoutKey.virtualKey == virtualKey) + return keyboardLayoutKey.key(); + } + return null; + } + + @Override + public int scanCode(Key key) { + for (KeyboardLayoutKey keyboardLayoutKey : keys) { + if (keyboardLayoutKey.key.equals(key)) + return keyboardLayoutKey.scanCode(); + } + return -1; + } + + public WindowsVirtualKey virtualKey(Key key) { + for (KeyboardLayoutKey keyboardLayoutKey : keys) { + if (keyboardLayoutKey.key.equals(key)) + return keyboardLayoutKey.virtualKey(); + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (!(obj instanceof WindowsKeyboardLayout that)) + return false; + return Objects.equals(this.identifier, that.identifier) && + Objects.equals(this.displayName, that.displayName) && + Objects.equals(this.driverName, that.driverName) && + Objects.equals(this.keys, that.keys); + } + + @Override + public int hashCode() { + return Objects.hash(identifier, displayName, driverName, keys); + } + + @Override + public String toString() { + return "WindowsKeyboardLayout[" + + "identifier=" + identifier + ", " + + "displayName=" + displayName + ", " + + "driverName=" + driverName + ", " + + "shortName=" + shortName + ']'; + } +} diff --git a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java index c24e42a9..6ad52592 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsPlatform.java +++ b/src/main/java/mousemaster/platform/windows/WindowsPlatform.java @@ -36,7 +36,12 @@ public class WindowsPlatform implements Platform { private final PlatformUiAutomation uiAutomation = new WindowsUiAutomation(); private final ActiveAppFinder activeAppFinder = new WindowsActiveAppFinder(); private final Console console = new WindowsConsole(); - private final KeyboardLayoutProvider keyboardLayoutProvider = WindowsVirtualKey::activeKeyboardLayout; + private final KeyboardLayoutProvider keyboardLayoutProvider = new KeyboardLayoutProvider() { + @Override public KeyboardLayout activeKeyboardLayout() { return WindowsVirtualKey.activeKeyboardLayout(); } + @Override public KeyboardLayout byShortName(String name) { return WindowsKeyboardLayout.keyboardLayoutByShortName.get(name); } + @Override public KeyboardLayout byIdentifier(String id) { return WindowsKeyboardLayout.keyboardLayoutByIdentifier.get(id); } + @Override public java.util.Set shortNames() { return WindowsKeyboardLayout.keyboardLayoutByShortName.keySet(); } + }; private final KeyRegurgitator keyRegurgitator = new KeyRegurgitator(keyboard); private final WindowsClock clock = new WindowsClock(); @@ -165,7 +170,7 @@ public void reset(MouseController mouseController, KeyboardManager keyboardManag keyboardManager.reset(); keyboard.reset(); } - keyboard.activeKeyboardLayout = activeKeyboardLayout; + keyboard.activeKeyboardLayout = (WindowsKeyboardLayout) activeKeyboardLayout; Set newHintMeshConfigurations = new HashSet<>(); for (Mode mode : newModeMap.modes()) { newHintMeshConfigurations.add(mode.hintMesh()); @@ -367,13 +372,13 @@ private void sanityCheckCurrentlyPressedKeys(double delta) { stuckKeyCheckTimer += delta; if (stuckKeyCheckTimer >= 1) { stuckKeyCheckTimer = 0; - KeyboardLayout layout = keyboard.activeKeyboardLayout; + WindowsKeyboardLayout layout = keyboard.activeKeyboardLayout; for (WindowsVirtualKey virtualKey : WindowsVirtualKey.values()) { short state = User32.INSTANCE.GetAsyncKeyState(virtualKey.virtualKeyCode); boolean pressedAccordingToOs = (state & 0x8000) != 0; if (!pressedAccordingToOs) continue; - Key key = layout.keyFromVirtualKeyName(virtualKey.name()); + Key key = layout.keyFromVirtualKey(virtualKey); if (key != null && keysPressedInHook.contains(key) && !currentlyPressedNotEatenKeys.containsKey(key)) { if (key.equals(Key.leftctrl) && diff --git a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java index d682b2b1..1f76b5e1 100644 --- a/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java +++ b/src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java @@ -358,7 +358,7 @@ public enum WindowsVirtualKey { values = Arrays.stream(valueArrayWithoutDuplicateCodes).toList(); } - private static KeyboardLayout lastPolledActiveKeyboardLayout; + private static WindowsKeyboardLayout lastPolledActiveKeyboardLayout; private static int keyboardLayoutSeenCount; private static boolean lastActiveKeyboardLayoutFailed; @@ -368,8 +368,8 @@ public enum WindowsVirtualKey { * a few milliseconds. The workaround here waits for the layout to show up twice before * confirming it has changed. */ - public static KeyboardLayout activeKeyboardLayout() { - KeyboardLayout foregroundWindowKeyboardLayout = foregroundWindowKeyboardLayout(); + public static WindowsKeyboardLayout activeKeyboardLayout() { + WindowsKeyboardLayout foregroundWindowKeyboardLayout = foregroundWindowKeyboardLayout(); if (foregroundWindowKeyboardLayout != null) { if (!foregroundWindowKeyboardLayout.equals(lastPolledActiveKeyboardLayout)) { if (lastPolledActiveKeyboardLayout != null && keyboardLayoutSeenCount++ < 2) @@ -395,7 +395,7 @@ public static KeyboardLayout activeKeyboardLayout() { lastActiveKeyboardLayoutFailed = true; return lastPolledActiveKeyboardLayout; } - KeyboardLayout startupKeyboardLayout = startupKeyboardLayout(); + WindowsKeyboardLayout startupKeyboardLayout = startupKeyboardLayout(); if (!lastActiveKeyboardLayoutFailed) logger.trace( "Unable to find the foreground window's keyboard layout, using start up keyboard layout " + @@ -438,7 +438,7 @@ private static WinDef.HKL foregroundWindowHkl() { return null; } - private static KeyboardLayout foregroundWindowKeyboardLayout() { + private static WindowsKeyboardLayout foregroundWindowKeyboardLayout() { WinDef.HKL hkl = foregroundWindowHkl(); if (hkl != null) { // The mousemaster.exe command line window does not handle the WM_INPUTLANGCHANGE message. @@ -446,7 +446,7 @@ private static KeyboardLayout foregroundWindowKeyboardLayout() { // We call ActivateKeyboardLayout to change the layout of the command line window. ExtendedUser32.INSTANCE.ActivateKeyboardLayout(hkl, 0); int languageIdentifier = hkl.getLanguageIdentifier(); - KeyboardLayout keyboardLayout = KeyboardLayout.keyboardLayoutByIdentifier.get( + WindowsKeyboardLayout keyboardLayout = WindowsKeyboardLayout.keyboardLayoutByIdentifier.get( String.format("%08X", languageIdentifier)); // logger.debug("Found active window keyboard layout: " + keyboardLayout); return keyboardLayout; @@ -454,7 +454,7 @@ private static KeyboardLayout foregroundWindowKeyboardLayout() { return null; } - private static KeyboardLayout startupKeyboardLayout() { + private static WindowsKeyboardLayout startupKeyboardLayout() { // GetKeyboardLayoutName returns the layout at the time of when the app was started. // If the system layout is changed after the app is started, GetKeyboardLayoutName // still returns the old layout. @@ -467,15 +467,15 @@ private static KeyboardLayout startupKeyboardLayout() { break; } } - return KeyboardLayout.keyboardLayoutByIdentifier.get( + return WindowsKeyboardLayout.keyboardLayoutByIdentifier.get( new String(nameBuffer, 0, nameLength)); } public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int scanCode, - int flags, KeyboardLayout activeKeyboardLayout) { + int flags, WindowsKeyboardLayout activeKeyboardLayout) { if (scanCode == 0) { // Injected key event have scanCode 0. - return WindowsVirtualKey.activeKeyboardLayout().keyFromVirtualKeyName(windowsVirtualKey.name()); + return WindowsVirtualKey.activeKeyboardLayout().keyFromVirtualKey(windowsVirtualKey); } // When pressing rightctrl the scanCode should be E01D but is 1D (which is leftctrl's scanCode). // rightctrl: @@ -495,19 +495,13 @@ public static Key keyFromWindowsEvent(WindowsVirtualKey windowsVirtualKey, int s } public static WindowsVirtualKey windowsVirtualKeyFromKey(Key key, - KeyboardLayout keyboardLayout) { - String virtualKeyName = keyboardLayout.virtualKeyName(key); - if (virtualKeyName == null) { + WindowsKeyboardLayout keyboardLayout) { + WindowsVirtualKey virtualKey = keyboardLayout.virtualKey(key); + if (virtualKey == null) { logger.debug("Unable to map key " + key + " to a Windows virtual key using " + keyboardLayout); - return null; - } - try { - return WindowsVirtualKey.valueOf(virtualKeyName); - } catch (IllegalArgumentException e) { - logger.debug("Unknown virtual key name: " + virtualKeyName); - return null; } + return virtualKey; } } diff --git a/src/test/java/mousemaster/ComboPreparationTest.java b/src/test/java/mousemaster/ComboPreparationTest.java index ed4375d5..40c6082e 100644 --- a/src/test/java/mousemaster/ComboPreparationTest.java +++ b/src/test/java/mousemaster/ComboPreparationTest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import mousemaster.platform.windows.WindowsKeyboardLayout; import static org.junit.jupiter.api.Assertions.*; @@ -397,8 +398,8 @@ void partialMatch_suffixAligns() { // --- Alias expansion tests --- static final KeyResolver identityKeyResolver = new KeyResolver( - new KeyboardLayout("test", "test", "test", "test", List.of()), - new KeyboardLayout("test", "test", "test", "test", List.of())); + new WindowsKeyboardLayout("test", "test", "test", "test", List.of()), + new WindowsKeyboardLayout("test", "test", "test", "test", List.of())); static ComboSequence parseCombo(String comboString, Map aliases) { diff --git a/src/test/java/mousemaster/ComboVariablePreconditionTest.java b/src/test/java/mousemaster/ComboVariablePreconditionTest.java index 5aa383b8..84088635 100644 --- a/src/test/java/mousemaster/ComboVariablePreconditionTest.java +++ b/src/test/java/mousemaster/ComboVariablePreconditionTest.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import mousemaster.platform.windows.WindowsKeyboardLayout; import static org.junit.jupiter.api.Assertions.*; @@ -17,8 +18,8 @@ class ComboVariablePreconditionTest { new ComboMoveDuration(Duration.ZERO, null); static final KeyResolver identityKeyResolver = new KeyResolver( - new KeyboardLayout("test", "test", "test", "test", List.of()), - new KeyboardLayout("test", "test", "test", "test", List.of())); + new WindowsKeyboardLayout("test", "test", "test", "test", List.of()), + new WindowsKeyboardLayout("test", "test", "test", "test", List.of())); private static List parse(String comboString, Set allVariableNames) { return Combo.of("test", comboString, defaultDuration, Map.of(), Map.of(), diff --git a/src/test/java/mousemaster/ComboWatcherRetainTest.java b/src/test/java/mousemaster/ComboWatcherRetainTest.java index ffdc2569..876d44b4 100644 --- a/src/test/java/mousemaster/ComboWatcherRetainTest.java +++ b/src/test/java/mousemaster/ComboWatcherRetainTest.java @@ -7,6 +7,7 @@ import java.time.Duration; import java.util.*; +import mousemaster.platform.windows.WindowsKeyboardLayout; import static org.junit.jupiter.api.Assertions.*; @@ -16,8 +17,8 @@ class ComboWatcherRetainTest { new ComboMoveDuration(Duration.ZERO, null); static final KeyResolver identityKeyResolver = new KeyResolver( - new KeyboardLayout("test", "test", "test", "test", List.of()), - new KeyboardLayout("test", "test", "test", "test", List.of())); + new WindowsKeyboardLayout("test", "test", "test", "test", List.of()), + new WindowsKeyboardLayout("test", "test", "test", "test", List.of())); static final ComboPrecondition emptyPrecondition = new ComboPrecondition( new ComboKeyPrecondition(Set.of(), new PressedKeyPrecondition(List.of())), diff --git a/src/test/java/mousemaster/ExpandableSequenceTest.java b/src/test/java/mousemaster/ExpandableSequenceTest.java index 93dbe48d..021656b5 100644 --- a/src/test/java/mousemaster/ExpandableSequenceTest.java +++ b/src/test/java/mousemaster/ExpandableSequenceTest.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import mousemaster.platform.windows.WindowsKeyboardLayout; import static org.junit.jupiter.api.Assertions.*; @@ -350,8 +351,8 @@ void bareTapWithoutDurationUsesDefault() { // --- #{}/+{} inside braces (ignored keys in KeyMoveSet) --- static final KeyResolver identityKeyResolver = new KeyResolver( - new KeyboardLayout("test", "test", "test", "test", List.of()), - new KeyboardLayout("test", "test", "test", "test", List.of())); + new WindowsKeyboardLayout("test", "test", "test", "test", List.of()), + new WindowsKeyboardLayout("test", "test", "test", "test", List.of())); private static KeyMoveSet parseMoveSetWithIgnoredKeys(String input) { ExpandableSequence seq =