An open-source Android cursor for people who use a mouse or trackball instead of a touchscreen. OpenMouse draws a large cross-hair pointer that follows your mouse and clicks for you when the pointer rests still, so no buttons are required. It is built for people with tremors, limited fine motor control, or conditions like cerebral palsy.
It is a clean-room, open-source clone of the excellent (closed-source) Ease Mouse by crea_si, rebuilt in Kotlin so the approach can be studied, forked, and improved. OpenMouse is not affiliated with or endorsed by Ease Apps.
Status: v0.1.0, an early release. The cursor follows the mouse with dwell and standard clicks; an on-screen menu adds double-tap, long-press, drag, swipe, scroll, and Back / Home / Recent / Notifications / Quick Settings; and a live settings screen tunes it. The logic is unit tested and the app is verified on an emulator. Remaining roadmap items (tremor filter, pinch-to-zoom) are below.
- Direct APK: download the latest
openmouse-*.apkfrom the Releases page and sideload it (Android 7.0+). - F-Droid and Google Play: planned; the metadata and submission guides are ready (see docs/RELEASING.md).
Then open OpenMouse, enable OpenMouse cursor in Accessibility settings, and plug in a mouse (details under Enabling).
OpenMouse is a standard Android AccessibilityService, not an overlay hack or a custom ROM. It hooks into the same accessibility framework that screen readers use, which is the only stock-Android mechanism that can both observe pointer input and inject taps/gestures across every app. That means:
- No root.
- No custom Android build.
- Installs on any device running Android 7.0 (API 24) or newer.
AccessibilityService.dispatchGesture() (the tap-injection API) landed in
Android 7.0, which is exactly why 7.0 is the floor.
physical mouse
│ motion events
▼
┌─────────────────────────────┐
│ input capture │ API 34+: service.onMotionEvent (overlay
│ - reports x,y │ non-touchable, touch passes through)
│ │ API 24-33: CursorView captures the mouse
└──────────────┬──────────────┘
│ x,y (CursorView draws the cross-hair + menu)
▼
┌─────────────────────────────┐
│ DwellClicker │ fires when the cursor rests still
│ - countdown → click │ for the dwell time; locks until the
│ - progress → cursor │ cursor moves away again
└──────────────┬──────────────┘
│ tap(x,y)
▼
┌─────────────────────────────┐
│ GestureDispatcher │ dispatchGesture() injects a real tap
│ via AccessibilityService │ into the app underneath
└─────────────────────────────┘
See docs/ARCHITECTURE.md for the details, including the
two input-capture paths and how injected gestures reach the app below.
Working now
- Big cross-hair cursor that follows a USB or Bluetooth mouse / trackball
- Dwell click: rest the pointer and it clicks automatically, with a shrinking countdown for feedback
- Standard click: a physical left-button press taps immediately
- Scroll wheel scrolls the content under the cursor
- On-screen gesture menu (collapsible grid, left or right edge, widens on short screens so every entry stays reachable)
- Tap modes: single tap, double-tap, long-press
- Drag and swipe: two-point gestures (click a start point, then an end point)
- Scroll up / down at the cursor
- Navigation from the menu: Back, Home, Recent, Notifications, Quick Settings
- Menu entries never re-fire while the pointer sits on them: a tremor inside a cell does not restart or repeat it, only leaving and coming back does
- Pause / Resume from the menu: stops dwell clicking on the app while the menu (and physical clicks) keep working
- Scroll modes repeat while the pointer rests: one notch per dwell period
- Settings screen (applied live): dwell time, movement tolerance, re-arm distance after a click, cursor size, cursor color, menu side, and a dwell-click on/off toggle
- On Android 14+ the overlay is non-touchable, so the touchscreen keeps working normally and a stray finger can never move the cursor or fire a click
Roadmap (mirrors the original's feature set)
- "Ease click": hold the button through a countdown, with a tremor filter
- Pinch-to-zoom (two-finger gesture)
- Drag-and-drop pickup (initial long-press before the drag)
- Hide the system pointer on Android 14+ (only the cross-hair is shown)
You need Android Studio (Koala or newer) or a local JDK 17+ and the Android SDK. The project uses AGP 9.0.1 with AGP's built-in Kotlin (no separate Kotlin plugin), targets SDK 36, and the committed Gradle wrapper pins Gradle 9.2.1.
# In Android Studio: File ▸ Open ▸ select this folder, then Run ▸ app.
# Or from the command line:
./gradlew assembleDebug # build the APK
./gradlew test # run the unit testsIf Android Studio does not create local.properties, add it with your SDK path:
sdk.dir=/path/to/Android/Sdk
- Install the app and plug in a mouse (USB OTG or Bluetooth).
- Open OpenMouse and tap Open accessibility settings.
- Under Installed services, enable OpenMouse cursor and accept the prompt.
- Move the mouse: the big cross-hair appears. Hold it still to click.
To stop, turn the service back off in accessibility settings.
app/src/main/
├── java/io/github/owenpkent/openmouse/
│ ├── MainActivity.kt onboarding + enable button
│ ├── SettingsActivity.kt settings screen (live-applied)
│ ├── service/MouseAccessibilityService.kt the engine
│ ├── cursor/CursorView.kt overlay: draws + captures input
│ ├── click/DwellClicker.kt Android timer wrapper
│ ├── click/DwellMachine.kt pure dwell logic (unit tested)
│ ├── menu/GestureMenu.kt menu: drawing + action mapping
│ ├── menu/MenuGeometry.kt pure layout + hit-testing (unit tested)
│ ├── settings/OpenMouseSettings.kt SharedPreferences-backed settings
│ └── gesture/GestureDispatcher.kt dispatchGesture() wrapper
├── res/xml/accessibility_service_config.xml
└── AndroidManifest.xml
app/src/test/java/io/github/owenpkent/openmouse/ JVM unit tests (JUnit 5)
├── click/DwellMachineTest.kt
├── menu/MenuGeometryTest.kt
└── fuzz/ Jazzer fuzz targets (run nightly)
├── DwellMachineFuzzTest.kt
└── MenuGeometryFuzzTest.kt
The Android-coupled classes (the service, the overlay view, the gesture dispatcher) are deliberately thin. The decision logic they wrap lives in pure classes with no Android dependencies, so it runs as fast JVM unit tests:
DwellMachine-- the dwell-to-click state machine (clock injected, not read)MenuGeometry-- the menu's layout and hit-testing
./gradlew test # run all unit tests (fuzz targets replay their seed corpus)
./gradlew testDebugUnitTestThe same pure classes are also fuzzed with Jazzer,
the JVM libFuzzer, mirroring the fuzz target PowerToys ships for its Dwell Click
engine. The targets under app/src/test/.../fuzz/ feed arbitrary event
sequences, coordinates, timestamps and settings into DwellMachine and
MenuGeometry and check invariants after every step (progress stays in 0..1,
nothing fires before the pointer moves, the same rest never fires twice, hit-test
results agree with the cell bounds). They run as ordinary unit tests unless
fuzzing is switched on:
JAZZER_FUZZ=1 ./gradlew testDebugUnitTest --tests '*FuzzTest' # ten minutes per targetThe nightly workflow runs each target for
ten minutes and uploads any reproducing input as an artifact; commit it under
app/src/test/resources/ to keep it as a regression test.
When adding behavior, prefer putting the logic in a pure class and testing it there, keeping the Android wrapper a straight pass-through.
Issues and pull requests are welcome, especially from the AAC / accessibility community. The roadmap items above are good starting points.
- CONTRIBUTING.md — setup, conventions, emulator testing, and how to add a new gesture
- CODE_OF_CONDUCT.md — community standards
- SECURITY.md — reporting a vulnerability
- docs/RELEASING.md — the plan for shipping to F-Droid / Play
- docs/UPSTREAMING.md — the plan for getting it into Android natively (AOSP / LineageOS)
MIT © 2026 Owen Kent.