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/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..1dd91ac4 100644 --- a/src/main/java/mousemaster/KeyboardLayout.java +++ b/src/main/java/mousemaster/KeyboardLayout.java @@ -29,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); @@ -57,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; @@ -110,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; @@ -126,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/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..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,6 +9,7 @@ public class MouseController implements ModeListener, MousePositionListener { private final ScreenManager screenManager; + private final PlatformMouse platformMouse; private Mouse mouse; private Wheel wheel; private double moveDuration; @@ -43,8 +46,10 @@ public class MouseController implements ModeListener, MousePositionListener { private int jumpBeginX, jumpBeginY; private int jumpEndX, jumpEndY; - public MouseController(ScreenManager screenManager) { + public MouseController(ScreenManager screenManager, + PlatformMouse platformMouse) { this.screenManager = screenManager; + this.platformMouse = platformMouse; } public void reset() { @@ -104,7 +109,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 +136,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 +145,7 @@ else if (!xMoveForwardStack.isEmpty()) { } } else if (decelerating) { - WindowsMouse.beginMove(); + platformMouse.beginMove(); decelerationDuration += delta; double velocity = decelerationVelocity(mouse.velocity(), velocityAtDecelerationStart, decelerationDuration); @@ -166,7 +171,7 @@ else if (decelerateXActive) { deltaBigEnough = deltaDistanceY >= 1; } if (deltaBigEnough && !jumping) { - WindowsMouse.moveBy( + platformMouse.moveBy( decelerateXActive && decelerateXForward, deltaDistanceX, decelerateYActive && decelerateYForward, @@ -176,7 +181,7 @@ else if (decelerateXActive) { } } else if (!jumping) - WindowsMouse.endMove(); + platformMouse.endMove(); if (jumping) { jumpDuration += delta; double jumpVelocity = mouse.smoothJumpVelocity(); // Pixels per second. @@ -205,7 +210,7 @@ else if (!jumping) } } if (nextJumpX != jumpX || nextJumpY != jumpY) { - WindowsMouse.synchronousMoveTo(nextJumpX, nextJumpY); + platformMouse.synchronousMoveTo(nextJumpX, nextJumpY); jumpX = nextJumpX; jumpY = nextJumpY; } @@ -222,9 +227,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 +241,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 +469,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 +490,7 @@ public void pressLeft() { return; releaseAll(); leftPressing = true; - WindowsMouse.pressLeft(); + platformMouse.pressLeft(); } public void pressMiddle() { @@ -493,7 +498,7 @@ public void pressMiddle() { return; releaseAll(); middlePressing = true; - WindowsMouse.pressMiddle(); + platformMouse.pressMiddle(); } public void pressRight() { @@ -501,24 +506,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 +611,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 +628,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 +643,7 @@ public void moveTo(int x, int y) { jumpX = mouseX; jumpY = mouseY; jumping = true; - WindowsMouse.beginMove(); + platformMouse.beginMove(); jumpEndX = x; jumpEndY = y; } @@ -701,8 +706,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..7c6cfeaa 100644 --- a/src/main/java/mousemaster/MousemasterApplication.java +++ b/src/main/java/mousemaster/MousemasterApplication.java @@ -7,23 +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 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 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 { @@ -38,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(); - } - WindowsPlatform 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 { @@ -134,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 WindowsPlatform 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); @@ -197,15 +95,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..109e0533 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.Overlay; +import mousemaster.platform.PlatformMouse; +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(); + + PlatformMouse 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..f36aebbe --- /dev/null +++ b/src/main/java/mousemaster/platform/KeyRegurgitator.java @@ -0,0 +1,8 @@ +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..27286d43 --- /dev/null +++ b/src/main/java/mousemaster/platform/KeyboardLayoutProvider.java @@ -0,0 +1,8 @@ +package mousemaster.platform; + +import mousemaster.KeyboardLayout; + +public interface KeyboardLayoutProvider { + + KeyboardLayout activeKeyboardLayout(); +} diff --git a/src/main/java/mousemaster/platform/Overlay.java b/src/main/java/mousemaster/platform/Overlay.java new file mode 100644 index 00000000..3337e4cf --- /dev/null +++ b/src/main/java/mousemaster/platform/Overlay.java @@ -0,0 +1,60 @@ +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/PlatformMouse.java b/src/main/java/mousemaster/platform/PlatformMouse.java new file mode 100644 index 00000000..ad7b9928 --- /dev/null +++ b/src/main/java/mousemaster/platform/PlatformMouse.java @@ -0,0 +1,32 @@ +package mousemaster.platform; + +public interface PlatformMouse { + + 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/Screens.java b/src/main/java/mousemaster/platform/Screens.java new file mode 100644 index 00000000..25d12e07 --- /dev/null +++ b/src/main/java/mousemaster/platform/Screens.java @@ -0,0 +1,10 @@ +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..de4959aa --- /dev/null +++ b/src/main/java/mousemaster/platform/UiAutomation.java @@ -0,0 +1,12 @@ +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 98% rename from src/main/java/mousemaster/KbdlayoutinfoParser.java rename to src/main/java/mousemaster/platform/windows/KbdlayoutinfoParser.java index 027c7870..39af1831 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; @@ -299,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)); } @@ -353,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() @@ -374,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/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/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/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..32409fab --- /dev/null +++ b/src/main/java/mousemaster/platform/windows/WindowsMouseAdapter.java @@ -0,0 +1,79 @@ +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/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..b4a8e262 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.Overlay; +import mousemaster.platform.PlatformMouse; +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 PlatformMouse 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,10 +276,44 @@ public PlatformClock clock() { } @Override - public KeyboardLayout activeKeyboardLayout() { - return WindowsVirtualKey.activeKeyboardLayout(); + public KeyboardLayoutProvider keyboardLayoutProvider() { + return keyboardLayoutProvider; + } + + @Override + public Keyboard keyboard() { + return keyboard; } + @Override + public PlatformMouse 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; + } /** * On the Windows lock screen, hit space then enter the pin. Space press is recorded by the app but the @@ -321,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/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 1cc910fb..b01e9dd2 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 97% rename from src/main/java/mousemaster/WindowsVirtualKey.java rename to src/main/java/mousemaster/platform/windows/WindowsVirtualKey.java index 65008a08..48cea009 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; @@ -472,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: @@ -493,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; } } 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;