sclass MouseMover { Pt cur, last; Robot robot; java.util.Timer timer; int interferenceMinDistance = 100; int mouseCheckInterval = 50; int blockTimeAfterInterference = 5000; // ms bool endAfterInterference = true; volatile bool interference; bool forceActive; long blockUntil; new HashSet buttonsToRelease; sbool listenToGlobalKeyPresses; IF0 enter; bool verbose; // TODO: fix this logic... synchronized bool enabled() { ret timer != null; } synchronized bool hasInterference() { ret interference; } bool active() { ret enabled() && (forceActive || !hasInterference()); } bool active2() { ret enabled() && !blocked(); } synchronized MouseMover enable() ctex { if (robot == null) robot = new Robot; if (timer == null) { interference = false; cur = new Pt(mouseLocation()); timer = doEvery(mouseCheckInterval, r checkMouse); if (listenToGlobalKeyPresses) onGlobalKeyPress(r { temp callF(enter); if (verbose) print("Key press detected"); haveInterference(); }); // TODO: on global mouse click if (verbose) print("Mouse automation started"); } ret this; } bool farAway(Pt a, Pt b) { ret pointDistance(a, b) >= interferenceMinDistance; } synchronized void checkMouse() { temp callF(enter); if (!enabled()) ret; blocked(); // trigger resume Pt p = new Pt(mouseLocation()); if (farAway(p, cur) && neq(p, last)) { if (verbose) print("Mouse movement detected: " + nPixels(iround(ptDistance(p, cur)))); haveInterference(); } } void haveInterference() { temp callF(enter); //printStackTrace("haveInterference"); if (!enabled()) ret; interference = true; if (endAfterInterference) { if (verbose) print("User interference detected, stopping automation"); disable(); } else { bool blockedAlready = blocked(); blockUntil = now()+blockTimeAfterInterference; if (!blockedAlready) if (verbose) print("Mouse automation blocked for at least " + blockTimeAfterInterference + " ms"); releaseButtons(); cur = new Pt(mouseLocation()); if (verbose) print("Set cur to " + cur); last = null; } } synchronized void disable() { releaseButtons(); if (timer != null) { timer.cancel(); timer = null; /*if (!interference) if (verbose) print("Mouse automation ended without interference");*/ } } synchronized void moveMouseImmediate(Pt p) { checkMouse(); if (!enabled() || blocked()) ret; p = normalizePointToScreen(p); if (neq(cur, p)) { if (neq(new Pt(mouseLocation()), last)) last = cur; cur = p; robot.mouseMove(p.x, p.y); } } synchronized bool blocked() { if (!enabled() || blockUntil == 0) false; if (now() < blockUntil) true; blockUntil = 0; cur = new Pt(mouseLocation()); last = null; if (verbose) print(prefix() + "Mouse automation resumes"); false; } void moveMouse(Pt pdest) { if (pdest == null) ret; print("Moving mouse to " + pdest); Point p = getMouseLocation(); //int steps = 20, delay = 40; // slow //int steps = 20, delay = 20; // fast int steps = 20, delay = 30; // medium for (int i = 1; i <= steps; i++) { if (blocked()) break; Point p2 = blendPoints(p, pdest.getPoint(), ((double) i) / steps); moveMouseImmediate(new Pt(p2)); if (i != steps) sleep(delay); } } void click() { temp callF(enter); if (active2()) { print(prefix() + "CLICKING"); mouseClick(); } else print(prefix() + "Not clicking, have interference: " + hasInterference()); } S prefix() { ret systemHashCode(this) + ": "; } void pressButton() { robot.mousePress(addAndReturn(buttonsToRelease, InputEvent.BUTTON1_DOWN_MASK)); } void releaseButton() { robot.mouseRelease(removeAndReturn(buttonsToRelease, InputEvent.BUTTON1_DOWN_MASK)); } void click(Pt p) { moveMouse(p); click(); } void click(int x, int y) { click(new Pt(x, y)); } void cleanMeUp() { disable(); releaseButtons(); } void releaseButtons() { for (int btn : getAndClearList(buttonsToRelease)) robot.mouseRelease(btn); } // e.g. when user presses a button that is perform an automation void unblock() { blockUntil = 0; } }