From ca38c9630fbc233e90bd98ed02ec7197a87bc8cb Mon Sep 17 00:00:00 2001 From: Maxim Averyanov Date: Wed, 26 Aug 2026 21:26:52 +0200 Subject: [PATCH] The project is MThread Draw Every name goes: import adbtouch becomes import mthread, autodraw becomes mthread_draw, the console script is mthread and the desktop entry point mthread-draw, the distribution is mthread-draw, the installer is MThreadDraw-x.y.z-x64.msi, the WinUI assembly and namespace are MThreadDraw, the Java package is com.mthread, AdbTouchError is MThreadError and ADBTOUCH_CACHE is MTHREAD_CACHE. The repository moves to MAXAWER/MThread-Draw. No compatibility shim, and none is owed: nothing has ever been released under the old names except a January pre-release of a different program, which is still there and now marked as such. The product's name has a space wherever a person reads it - the window title, the Start Menu entry, the installer's dialogs, the prose - and none wherever a machine does, which is why the mechanical pass could not be the whole job. Two things found along the way. The rename was first done by reading each file as text and writing it back, which is wrong on Windows: it flattened run.bat's CRLF endings. Doing it on bytes, sourced from the git index rather than the working tree, changes the names and nothing else. run.bat's stored line endings do change here, but only because .gitattributes has asked for eol=crlf all along and the blob was still CRLF; this normalises it. run.bat could never have fetched adb. Two backslashes had been lost from it long before today, so it looked for "platform-toolsdb.exe" and tried to run "toolsetch_platform_tools.py" - a formfeed and a bell where \a and \f used to be. Both restored. Tests pass, the injector jar is rebuilt against com.mthread, and the WinUI project builds clean under the new namespace. Co-Authored-By: Claude Opus 5 --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/device_report.md | 2 +- .github/workflows/release.yml | 8 +- .gitignore | 2 +- AGENTS.md | 8 +- CHANGELOG.md | 27 +++-- CLAUDE.md | 8 +- CONTRIBUTING.md | 22 ++-- README.md | 106 +++++++++--------- TERMS.md | 14 +-- adbtouch/injector.jar | Bin 3037 -> 0 bytes docs/DEMO.md | 4 +- docs/RELEASING.md | 28 ++--- .../com/{adbtouch => mthread}/Injector.java | 2 +- {adbtouch => mthread}/__init__.py | 10 +- {adbtouch => mthread}/__main__.py | 0 {adbtouch => mthread}/adb.py | 2 +- {adbtouch => mthread}/cli.py | 10 +- {adbtouch => mthread}/device.py | 8 +- {adbtouch => mthread}/errors.py | 14 +-- {adbtouch => mthread}/flowlines.py | 0 {adbtouch => mthread}/hand.py | 0 mthread/injector.jar | Bin 0 -> 3031 bytes {adbtouch => mthread}/injector.py | 14 +-- {adbtouch => mthread}/neural.py | 10 +- {adbtouch => mthread}/paths.py | 0 {adbtouch => mthread}/player.py | 2 +- {adbtouch => mthread}/recorder.py | 4 +- {adbtouch => mthread}/session.py | 2 +- {adbtouch => mthread}/touch.py | 0 {adbtouch => mthread}/trace.py | 0 {adbtouch => mthread}/vectorize.py | 0 {autodraw => mthread_draw}/__init__.py | 2 +- {autodraw => mthread_draw}/__main__.py | 0 {autodraw => mthread_draw}/app.py | 30 ++--- {autodraw => mthread_draw}/geometry.py | 0 {autodraw => mthread_draw}/server.py | 18 +-- packaging/{AutoDraw.spec => MThreadDraw.spec} | 28 ++--- packaging/{AutoDraw.wxs => MThreadDraw.wxs} | 40 +++---- packaging/{autodraw.ico => mthreaddraw.ico} | Bin packaging/{autodraw.png => mthreaddraw.png} | Bin pyproject.toml | 24 ++-- requirements.txt | 2 +- run.bat | 94 ++++++++-------- run.sh | 6 +- tests/test_adb.py | 24 ++-- tests/test_device.py | 12 +- tests/test_geometry.py | 2 +- tests/test_gui.py | 4 +- tests/test_hand.py | 2 +- tests/test_neural.py | 14 +-- tests/test_paths.py | 2 +- tests/test_player.py | 4 +- tests/test_recorder.py | 2 +- tests/test_session.py | 2 +- tests/test_touch.py | 2 +- tests/test_trace.py | 2 +- tests/test_vectorize.py | 4 +- tools/build_app.py | 36 +++--- tools/build_injector.py | 6 +- tools/canvas.html | 4 +- tools/engine_entry.py | 2 +- tools/make_demo.py | 4 +- tools/make_icon.py | 8 +- tools/make_sample.py | 2 +- tools/pyinstaller_entry.py | 16 +-- tools/test_canvas.py | 10 +- winui/App.xaml | 2 +- winui/App.xaml.cs | 2 +- winui/Engine.cs | 10 +- ....WinUI.csproj => MThreadDraw.WinUI.csproj} | 8 +- winui/MainWindow.xaml | 6 +- winui/MainWindow.xaml.cs | 4 +- winui/app.manifest | 2 +- 74 files changed, 382 insertions(+), 369 deletions(-) delete mode 100644 adbtouch/injector.jar rename injector/src/com/{adbtouch => mthread}/Injector.java (99%) rename {adbtouch => mthread}/__init__.py (88%) rename {adbtouch => mthread}/__main__.py (100%) rename {adbtouch => mthread}/adb.py (99%) rename {adbtouch => mthread}/cli.py (95%) rename {adbtouch => mthread}/device.py (98%) rename {adbtouch => mthread}/errors.py (64%) rename {adbtouch => mthread}/flowlines.py (100%) rename {adbtouch => mthread}/hand.py (100%) create mode 100644 mthread/injector.jar rename {adbtouch => mthread}/injector.py (95%) rename {adbtouch => mthread}/neural.py (96%) rename {adbtouch => mthread}/paths.py (100%) rename {adbtouch => mthread}/player.py (97%) rename {adbtouch => mthread}/recorder.py (98%) rename {adbtouch => mthread}/session.py (97%) rename {adbtouch => mthread}/touch.py (100%) rename {adbtouch => mthread}/trace.py (100%) rename {adbtouch => mthread}/vectorize.py (100%) rename {autodraw => mthread_draw}/__init__.py (63%) rename {autodraw => mthread_draw}/__main__.py (100%) rename {autodraw => mthread_draw}/app.py (97%) rename {autodraw => mthread_draw}/geometry.py (100%) rename {autodraw => mthread_draw}/server.py (92%) rename packaging/{AutoDraw.spec => MThreadDraw.spec} (79%) rename packaging/{AutoDraw.wxs => MThreadDraw.wxs} (66%) rename packaging/{autodraw.ico => mthreaddraw.ico} (100%) rename packaging/{autodraw.png => mthreaddraw.png} (100%) rename winui/{AutoDraw.WinUI.csproj => MThreadDraw.WinUI.csproj} (91%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bb3094f..b484cdd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -16,7 +16,7 @@ labels: bug - OS: - Python version: - Phone model and Android version: -- Output of `adbtouch info` (this shows the screen size and digitizer ranges): +- Output of `mthread info` (this shows the screen size and digitizer ranges): ``` paste here diff --git a/.github/ISSUE_TEMPLATE/device_report.md b/.github/ISSUE_TEMPLATE/device_report.md index 7d4320d..f307cb7 100644 --- a/.github/ISSUE_TEMPLATE/device_report.md +++ b/.github/ISSUE_TEMPLATE/device_report.md @@ -13,7 +13,7 @@ panels and we can only fix what we can see. **Does record and replay work?** -**Output of `adbtouch info`** +**Output of `mthread info`** ``` paste here diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 019cd78..b9421e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,9 +143,9 @@ jobs: | | | |---|---| - | **Windows** | `AutoDraw-*-x64.msi` — installs like any other program, Start Menu entry included. The `.zip` is the same app with no installer. | - | **macOS** | `AutoDraw-*-arm64.dmg` for Apple Silicon, `AutoDraw-*-x64.dmg` for Intel. Drag it to Applications. | - | **Linux** | Run from source — see the [README](https://github.com/MAXAWER/AutoDraw-Sim#install-variants). | + | **Windows** | `MThreadDraw-*-x64.msi` — installs like any other program, Start Menu entry included. The `.zip` is the same app with no installer. | + | **macOS** | `MThreadDraw-*-arm64.dmg` for Apple Silicon, `MThreadDraw-*-x64.dmg` for Intel. Drag it to Applications. | + | **Linux** | Run from source — see the [README](https://github.com/MAXAWER/MThread-Draw#install-variants). | Everything is included: Python, OpenCV and **adb** all travel inside the application. There is nothing else to install and no Android SDK @@ -157,7 +157,7 @@ jobs: launch needs a right-click → **Open**, or: ``` - xattr -dr com.apple.quarantine /Applications/AutoDraw.app + xattr -dr com.apple.quarantine /Applications/MThreadDraw.app ``` pypi: diff --git a/.gitignore b/.gitignore index 98f210c..198f6bc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,7 @@ screen_tmp.png # Local virtualenvs .venv/ -# Superseded by adbtouch/injector.jar, which ships inside the package +# Superseded by mthread/injector.jar, which ships inside the package /injector/*.jar # C# build output diff --git a/AGENTS.md b/AGENTS.md index 7e8fd3f..f816e17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,16 +26,16 @@ generating the copy, and point them at `TERMS.md`. Running a modified version as a hosted service carries the same obligation to publish. Requests go to an issue titled "Licence request" at -. +. ## What this project is Two pieces that ship together: -- **`adbtouch`** — a Python library for synthetic touch input on Android over +- **`mthread`** — a Python library for synthetic touch input on Android over ADB: tracing an image into strokes, drawing them, recording and replaying gestures. -- **`AutoDraw`** — a desktop app on top of it. +- **`MThread Draw`** — a desktop app on top of it. ## How it draws, which is the part that surprises people @@ -47,7 +47,7 @@ There are three paths into a device, picked automatically by says, and `sendevent` then fails per line while the script exits cleanly. `Device.supports_raw_touch` probes for it. 2. **`injector`** — a 3 KB jar (`injector/`, built by - `tools/build_injector.py` into `adbtouch/injector.jar`) run once through + `tools/build_injector.py` into `mthread/injector.jar`) run once through `app_process` and fed points over stdin. This is the path that works everywhere and the only one where the time between points is ours, which is what makes both instant drawing and hand-like drawing possible. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6041898..b1d9225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,31 @@ ## Unreleased +- **The project is now MThread Draw.** Everything is renamed: `import adbtouch` + becomes `import mthread`, `autodraw` becomes `mthread_draw`, the console + script is `mthread` and the desktop entry point `mthread-draw`, the + distribution on PyPI is `mthread-draw`, the installer is + `MThreadDraw-x.y.z-x64.msi`, and the repository has moved to + `github.com/MAXAWER/MThread-Draw` — GitHub redirects the old address, but + update your remote. `AdbTouchError` is `MThreadError` and `ADBTOUCH_CACHE` is + `MTHREAD_CACHE`. There is no compatibility shim: this predates any release + under the old names other than a January pre-release of something else. +- `run.bat` could never fetch adb. Two backslashes had been lost from it long + ago, leaving it looking for `platform-toolsdb.exe` and calling + `toolsetch_platform_tools.py`. + - **Drawing works on devices that refuse raw touch events** - every recent Pixel, where SELinux denies the shell domain write access to `/dev/input`. `Device.supports_raw_touch` probes for it and the drawing path is picked accordingly, instead of reporting success and doing nothing. -- **An on-device injector** (`adbtouch/injector.py`, built from `injector/`), +- **An on-device injector** (`mthread/injector.py`, built from `injector/`), run once through `app_process` and fed points over stdin. One process for a whole drawing rather than one per point, which is what makes realistic timing possible at all. Measured on a Pixel 8 Pro: a 136-stroke, 1,679-point drawing in about 19 seconds, against 3 minutes 40 through `input`. -- **`adbtouch.hand`** simulates hand drawing: rounded corners, a velocity +- **`mthread.hand`** simulates hand drawing: rounded corners, a velocity profile, tremor, overshoot and a sensible stroke order. -- **`adbtouch.paths`** joins the fragments `findContours` returns back into +- **`mthread.paths`** joins the fragments `findContours` returns back into strokes and drops specks - 215 strokes became 136 on the sample image, which is both faster and cleaner. - `tools/test_canvas.py`: a drawing canvas served to the device over @@ -28,7 +41,7 @@ Packaging and presentation release; no behavioural changes to drawing or replay. macOS `.dmg` for both Apple Silicon and Intel, built automatically from a tag. - **Self-contained.** The packaged builds carry their own `adb`: no Android SDK, no platform-tools download, no `PATH` to edit. `find_adb` looks inside the - bundle first, so an installed AutoDraw behaves the same everywhere. + bundle first, so an installed MThread Draw behaves the same everywhere. - Packaged builds run a self-test before shipping, which imports the whole application and runs the bundled adb. - Optional PyPI publishing through trusted publishing — see @@ -40,12 +53,12 @@ Packaging and presentation release; no behavioural changes to drawing or replay. - Rewritten README: three-step quickstart, supported devices and emulators, supported image formats. - Versioning continues from the old `v1.0` ADB Painter tag rather than - restarting; `adbtouch` and `AutoDraw` share one version number. + restarting; `mthread` and `MThread Draw` share one version number. ## 0.2.0 -- Split the `adbtouch` library out of the desktop app. -- Gesture recording and replay: `adbtouch record`, `adbtouch play`, and the +- Split the `mthread` library out of the desktop app. +- Gesture recording and replay: `mthread record`, `mthread play`, and the Record tab in the app. - Fixed touch mapping on devices whose digitizer resolution differs from the display resolution. diff --git a/CLAUDE.md b/CLAUDE.md index 7e8fd3f..f816e17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,16 +26,16 @@ generating the copy, and point them at `TERMS.md`. Running a modified version as a hosted service carries the same obligation to publish. Requests go to an issue titled "Licence request" at -. +. ## What this project is Two pieces that ship together: -- **`adbtouch`** — a Python library for synthetic touch input on Android over +- **`mthread`** — a Python library for synthetic touch input on Android over ADB: tracing an image into strokes, drawing them, recording and replaying gestures. -- **`AutoDraw`** — a desktop app on top of it. +- **`MThread Draw`** — a desktop app on top of it. ## How it draws, which is the part that surprises people @@ -47,7 +47,7 @@ There are three paths into a device, picked automatically by says, and `sendevent` then fails per line while the script exits cleanly. `Device.supports_raw_touch` probes for it. 2. **`injector`** — a 3 KB jar (`injector/`, built by - `tools/build_injector.py` into `adbtouch/injector.jar`) run once through + `tools/build_injector.py` into `mthread/injector.jar`) run once through `app_process` and fed points over stdin. This is the path that works everywhere and the only one where the time between points is ours, which is what makes both instant drawing and hand-like drawing possible. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f937c91..f5b8bf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,8 +6,8 @@ including bug reports from phones we cannot test on ourselves. ## Getting set up ```bash -git clone https://github.com/MAXAWER/AutoDraw-Sim.git -cd AutoDraw-Sim +git clone https://github.com/MAXAWER/MThread-Draw.git +cd MThread-Draw python -m pip install -e ".[gui,dev]" python -m unittest discover -s tests ``` @@ -20,18 +20,18 @@ plain unit tests that run in under a second. | Path | What lives there | | --- | --- | -| `adbtouch/` | The library: device control, touch events, recording, replay. No GUI imports. | -| `adbtouch/touch.py` | Input-event codes and the display-to-digitizer coordinate mapping. | -| `adbtouch/recorder.py` | Parsing `getevent -t` output into a session. | -| `adbtouch/player.py` | Turning a session back into a timed shell script. | -| `adbtouch/vectorize.py` | Image to stroke paths. The only module that needs OpenCV. | -| `autodraw/` | The desktop app. Keep logic out of here; put it in the library with a test. | +| `mthread/` | The library: device control, touch events, recording, replay. No GUI imports. | +| `mthread/touch.py` | Input-event codes and the display-to-digitizer coordinate mapping. | +| `mthread/recorder.py` | Parsing `getevent -t` output into a session. | +| `mthread/player.py` | Turning a session back into a timed shell script. | +| `mthread/vectorize.py` | Image to stroke paths. The only module that needs OpenCV. | +| `mthread_draw/` | The desktop app. Keep logic out of here; put it in the library with a test. | | `tests/` | Unit tests, standard library `unittest`, no pytest required. | ## Ground rules - The library must keep working without OpenCV installed. Recording and replay - are stdlib-only on purpose, so `pip install adbtouch` stays lightweight. + are stdlib-only on purpose, so `pip install mthread` stays lightweight. - Anything that can be tested without a phone should be. If you find yourself unable to test a change, that usually means the logic wants pulling out of the hardware path. @@ -41,14 +41,14 @@ plain unit tests that run in under a second. ## Good first issues -Look for the [`good first issue`](https://github.com/MAXAWER/AutoDraw-Sim/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +Look for the [`good first issue`](https://github.com/MAXAWER/MThread-Draw/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label. If none are open and you want something to do, the open ends listed at the bottom of the README are all fair game. ## Reporting a device If drawing lands in the wrong place on your phone, that is useful data. Open a -Device report issue with the output of `adbtouch info` - it prints the digitizer +Device report issue with the output of `mthread info` - it prints the digitizer ranges we need to see. ## Licensing of contributions diff --git a/README.md b/README.md index 5209f0d..03dac52 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

AutoDraw + adbtouch

+

MThread Draw + mthread

Draw any picture on an Android screen, and record and replay touch gestures.
@@ -6,11 +6,11 @@

- CI + CI Licence: AGPL-3.0 Python 3.9+ Windows | macOS | Linux - Stars + Stars

@@ -18,7 +18,7 @@

- An ordinary photograph in, 283 strokes and 1,528 touch points out: the exact path list adbtouch sends to the device, in the order it draws them.
+ An ordinary photograph in, 283 strokes and 1,528 touch points out: the exact path list mthread sends to the device, in the order it draws them.
On a Pixel 8 Pro that draws in about five seconds. Rendered from a real run by tools/make_demo.py; playback speed here is arbitrary.

@@ -29,7 +29,7 @@ > **AGPL-3.0, with a commercial licence available.** Use it, change it, share > it — but a version you distribute, or run as a service other people use, has > to publish its source under the AGPL too. To put it inside a product whose -> source stays closed, [ask for a commercial licence](https://github.com/MAXAWER/AutoDraw-Sim/issues). +> source stays closed, [ask for a commercial licence](https://github.com/MAXAWER/MThread-Draw/issues). > Full explanation, in English and Russian: **[TERMS.md](TERMS.md)**. > > **AGPL-3.0 плюс коммерческая лицензия.** Пользуйтесь, меняйте, делитесь — но @@ -43,8 +43,8 @@ | | | |---|---| -| **Windows** | [**Download the installer**](https://github.com/MAXAWER/AutoDraw-Sim/releases/latest) — `AutoDraw-x.y.z-x64.msi`. Installs like any other program, Start Menu entry and uninstaller included. | -| **macOS** | [**Download the app**](https://github.com/MAXAWER/AutoDraw-Sim/releases/latest) — `.dmg` for Apple Silicon or Intel. Drag it to Applications. | +| **Windows** | [**Download the installer**](https://github.com/MAXAWER/MThread-Draw/releases/latest) — `MThreadDraw-x.y.z-x64.msi`. Installs like any other program, Start Menu entry and uninstaller included. | +| **macOS** | [**Download the app**](https://github.com/MAXAWER/MThread-Draw/releases/latest) — `.dmg` for Apple Silicon or Intel. Drag it to Applications. | | **Linux** | Run from source; three commands, [below](#from-source). | **Nothing else to install.** Python, OpenCV and **adb** all travel inside the @@ -59,13 +59,13 @@ does not take. Windows SmartScreen says "unknown publisher" once — *More info* 1. **Turn on USB debugging** on the phone: Settings → About phone → tap *Build number* seven times → Developer options → *USB debugging*. 2. **Plug it in.** Or go wireless: `adb connect 192.168.1.42:5555`. -3. **Open AutoDraw** → *Connect device* → *Capture screen* → *Load image* → drag +3. **Open MThread Draw** → *Connect device* → *Capture screen* → *Load image* → drag it over the phone preview → **START DRAWING**. No GUI, no clicking: ```python -from adbtouch import Device +from mthread import Device Device().draw_paths([[(100, 200), (400, 200), (400, 600)]]) ``` @@ -121,8 +121,8 @@ helps with portraits and product shots. | | | |---|---| | **Devices** | Any Android phone or tablet that `adb devices` lists — over USB, or wireless ADB (`adb connect :5555`). Root is not needed on most devices. | -| **Emulators** | Anything exposing an ADB port: Android Studio AVD, BlueStacks (`:5555`), LDPlayer (`:5555`), Nox (`:62001`), MEmu (`:21503`). Raw `/dev/input` support differs between builds — `adbtouch info` tells you in one line, and [device reports](https://github.com/MAXAWER/AutoDraw-Sim/issues/new?template=device_report.md) are welcome. | -| **Image formats** | PNG, JPEG, BMP, WebP. Raster only for now; SVG input is [an open task](https://github.com/MAXAWER/AutoDraw-Sim/issues). | +| **Emulators** | Anything exposing an ADB port: Android Studio AVD, BlueStacks (`:5555`), LDPlayer (`:5555`), Nox (`:62001`), MEmu (`:21503`). Raw `/dev/input` support differs between builds — `mthread info` tells you in one line, and [device reports](https://github.com/MAXAWER/MThread-Draw/issues/new?template=device_report.md) are welcome. | +| **Image formats** | PNG, JPEG, BMP, WebP. Raster only for now; SVG input is [an open task](https://github.com/MAXAWER/MThread-Draw/issues). | | **Host** | Windows, macOS, Linux. Python 3.9+. | ## What people use it for @@ -146,17 +146,17 @@ rules; this is a general-purpose input tool. 100–300 ms each, anything continuous — a gesture, a drawn line, a test script — is unusably slow. -`adbtouch` writes raw kernel input events into `/dev/input` through **one** pushed +`mthread` writes raw kernel input events into `/dev/input` through **one** pushed shell script instead. A stroke that takes 40 seconds through `input swipe` finishes in well under a second. That single difference is what makes both gesture replay and image drawing practical. This repository is two things: -- **`adbtouch`** — a small Python library for fast synthetic touch input on Android +- **`mthread`** — a small Python library for fast synthetic touch input on Android over ADB. Records gestures, replays them, drives raw `/dev/input` events. Pure standard library; the core has no dependencies at all. -- **`AutoDraw`** — a desktop app built on it, for people who would rather click +- **`MThread Draw`** — a desktop app built on it, for people who would rather click buttons than write code. There are two front ends: a portable one in Python that runs anywhere, and a native WinUI 3 one for Windows. Both drive the same engine over a pipe, so neither has its own idea of how anything works. @@ -169,8 +169,8 @@ Press record, do something on the phone, press stop. You get a JSON file with every touch event and its timing. Replay it whenever you want, at whatever speed. ```bash -adbtouch record -o login.json # do the thing on the phone, press Enter -adbtouch play login.json --speed 2 --repeat 5 +mthread record -o login.json # do the thing on the phone, press Enter +mthread play login.json --speed 2 --repeat 5 ``` Useful for regression passes, for reproducing a bug reliably, or for any @@ -185,8 +185,8 @@ thing: virtual environment, dependencies, and `adb` if the machine has none. Otherwise, by hand: ```bash -git clone https://github.com/MAXAWER/AutoDraw-Sim.git -cd AutoDraw-Sim +git clone https://github.com/MAXAWER/MThread-Draw.git +cd MThread-Draw pip install -e . # library only - no dependencies at all pip install -e ".[draw]" # + image vectorisation (OpenCV, NumPy, Pillow) @@ -213,18 +213,18 @@ python tools/build_app.py --dmg # macOS ## Command line ```bash -adbtouch devices # what is attached -adbtouch info # screen size and digitizer ranges -adbtouch record -o session.json # record until Enter -adbtouch record -o session.json -d 30 # record for 30 seconds -adbtouch play session.json # replay once -adbtouch play session.json --speed 0.5 --repeat 3 +mthread devices # what is attached +mthread info # screen size and digitizer ranges +mthread record -o session.json # record until Enter +mthread record -o session.json -d 30 # record for 30 seconds +mthread play session.json # replay once +mthread play session.json --speed 0.5 --repeat 3 ``` ## Library ```python -from adbtouch import Device, Recorder, Session, replay +from mthread import Device, Recorder, Session, replay device = Device() print(device.screen_size, device.touch_device.path) @@ -248,20 +248,20 @@ round trip instead of thousands. **Coordinate translation.** The touchscreen digitizer has its own coordinate space, and on many phones it is *not* the display resolution — a 1080-pixel-wide screen commonly sits on a 4096-step digitizer. Sending display pixels straight to -`sendevent` puts the touch in the wrong place. `adbtouch` reads the real axis -ranges from `getevent -pl` and rescales. Run `adbtouch info` to see yours. +`sendevent` puts the touch in the wrong place. `mthread` reads the real axis +ranges from `getevent -pl` and rescales. Run `mthread info` to see yours. **Three ways in, picked automatically.** Writing kernel events is fastest, but a recent Android refuses it: SELinux denies the shell domain write access to `/dev/input` however the file permissions read, so `sendevent` fails per line -while the script exits cleanly. Where that happens, `adbtouch` streams points to +while the script exits cleanly. Where that happens, `mthread` streams points to a small injector it runs on the device instead - one process for a whole drawing, with the wait between points under our control. Failing even that, it shells out to `input` once per point, which needs nothing installed and costs -about 110 ms each. `adbtouch info` says which path your device gets. +about 110 ms each. `mthread info` says which path your device gets. **Drawing like a hand.** Timing is what gives a machine away, and the injector is -what makes timing ours to choose. `adbtouch.hand` rounds corners, varies pen +what makes timing ours to choose. `mthread.hand` rounds corners, varies pen speed along a stroke, adds a slow tremor, overshoots stroke ends and reorders strokes the way a person would; `Pacing` decides how long each point takes. Set `human=0` and it draws as fast as the receiving app can sample - about 6 ms a @@ -284,7 +284,7 @@ one of them, while leaving genuine closed shapes like circles intact. - **Some devices expose no touchscreen usable by `sendevent`.** Drawing then fails with an explicit error instead of a wrong result; `Device.swipe()` still works, and an automatic fallback is an open task. -- **`adbtouch info` is the first thing to check** when touches land in the wrong +- **`mthread info` is the first thing to check** when touches land in the wrong place. --- @@ -292,7 +292,7 @@ one of them, while leaving genuine closed shapes like circles intact. ## Open ends Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Issues labelled -[`good first issue`](https://github.com/MAXAWER/AutoDraw-Sim/labels/good%20first%20issue) +[`good first issue`](https://github.com/MAXAWER/MThread-Draw/labels/good%20first%20issue) are the easiest way in. Things worth doing: - SVG input, so line art skips edge detection entirely. @@ -310,10 +310,10 @@ are the easiest way in. Things worth doing: ## If something does not work Open an issue — there are templates for -[bugs](https://github.com/MAXAWER/AutoDraw-Sim/issues/new?template=bug_report.md) -and for [device reports](https://github.com/MAXAWER/AutoDraw-Sim/issues/new?template=device_report.md). +[bugs](https://github.com/MAXAWER/MThread-Draw/issues/new?template=bug_report.md) +and for [device reports](https://github.com/MAXAWER/MThread-Draw/issues/new?template=device_report.md). Touches landing in the wrong place, a phone that refuses to connect, an emulator -behaving differently — paste the output of `adbtouch info` and it is usually a +behaving differently — paste the output of `mthread info` and it is usually a short fix. Digitizer ranges differ wildly between panels, and only what people report can be handled. @@ -339,18 +339,18 @@ explained in plain English and Russian in **[TERMS.md](TERMS.md)**. Две части в одном репозитории: -- **`adbtouch`** — библиотека для быстрого синтетического ввода касаний на Android +- **`mthread`** — библиотека для быстрого синтетического ввода касаний на Android через ADB. Записывает жесты, воспроизводит их, работает с событиями `/dev/input` напрямую. Ядро не требует зависимостей. -- **`AutoDraw`** — десктопное приложение поверх неё, для тех, кто предпочитает +- **`MThread Draw`** — десктопное приложение поверх неё, для тех, кто предпочитает кнопки коду. ## Установка | | | |---|---| -| **Windows** | [**Скачать установщик**](https://github.com/MAXAWER/AutoDraw-Sim/releases/latest) — `AutoDraw-x.y.z-x64.msi`. Ставится как обычная программа, с ярлыком в меню «Пуск» и деинсталлятором. | -| **macOS** | [**Скачать приложение**](https://github.com/MAXAWER/AutoDraw-Sim/releases/latest) — `.dmg` для Apple Silicon или Intel, перетащить в Applications. | +| **Windows** | [**Скачать установщик**](https://github.com/MAXAWER/MThread-Draw/releases/latest) — `MThreadDraw-x.y.z-x64.msi`. Ставится как обычная программа, с ярлыком в меню «Пуск» и деинсталлятором. | +| **macOS** | [**Скачать приложение**](https://github.com/MAXAWER/MThread-Draw/releases/latest) — `.dmg` для Apple Silicon или Intel, перетащить в Applications. | | **Linux** | Из исходников, три команды — [ниже](#из-исходников). | **Больше ничего ставить не нужно.** Python, OpenCV и **adb** лежат внутри самого @@ -366,7 +366,7 @@ explained in plain English and Russian in **[TERMS.md](TERMS.md)**. 1. **Включить отладку по USB**: Настройки → О телефоне → семь раз по «Номер сборки» → Для разработчиков → Отладка по USB. 2. **Подключить телефон.** Или по Wi-Fi: `adb connect 192.168.1.42:5555`. -3. **Открыть AutoDraw** → *Connect device* → *Capture screen* → *Load image* → +3. **Открыть MThread Draw** → *Connect device* → *Capture screen* → *Load image* → перетащить картинку на превью экрана → **START DRAWING**. @@ -374,10 +374,10 @@ explained in plain English and Russian in **[TERMS.md](TERMS.md)**. ### Из исходников ```bash -git clone https://github.com/MAXAWER/AutoDraw-Sim.git -cd AutoDraw-Sim +git clone https://github.com/MAXAWER/MThread-Draw.git +cd MThread-Draw pip install -e ".[gui]" -autodraw +mthread_draw ``` Проще запустить [`run.bat`](run.bat) на Windows или [`run.sh`](run.sh) на macOS и @@ -432,7 +432,7 @@ python tools/fetch_platform_tools.py # ~7 МБ, прямо от Google | | | |---|---| | **Устройства** | Любой телефон или планшет, который виден в `adb devices` — по USB или по Wi-Fi (`adb connect :5555`). На большинстве устройств root не нужен. | -| **Эмуляторы** | Всё, что открывает порт ADB: Android Studio AVD, BlueStacks (`:5555`), LDPlayer (`:5555`), Nox (`:62001`), MEmu (`:21503`). Поддержка сырого `/dev/input` отличается от сборки к сборке — `adbtouch info` покажет за одну строку. Отчёты о конкретных устройствах приветствуются. | +| **Эмуляторы** | Всё, что открывает порт ADB: Android Studio AVD, BlueStacks (`:5555`), LDPlayer (`:5555`), Nox (`:62001`), MEmu (`:21503`). Поддержка сырого `/dev/input` отличается от сборки к сборке — `mthread info` покажет за одну строку. Отчёты о конкретных устройствах приветствуются. | | **Форматы** | PNG, JPEG, BMP, WebP. Пока только растр; SVG — в списке задач. | | **Хост** | Windows, macOS, Linux. Python 3.9+. | @@ -440,7 +440,7 @@ python tools/fetch_platform_tools.py # ~7 МБ, прямо от Google `adb shell input tap` запускает отдельный процесс на устройстве при каждом вызове — 100–300 мс на команду. Для чего-либо непрерывного это неприемлемо -медленно. `adbtouch` пишет события ядра напрямую через **один** сценарий, +медленно. `mthread` пишет события ядра напрямую через **один** сценарий, загруженный на устройство. Штрих, который через `input swipe` рисуется 40 секунд, здесь занимает меньше секунды. @@ -459,10 +459,10 @@ python tools/fetch_platform_tools.py # ~7 МБ, прямо от Google ## Командная строка ```bash -adbtouch devices # какие устройства подключены -adbtouch info # разрешение экрана и диапазоны тачскрина -adbtouch record -o session.json # запись до нажатия Enter -adbtouch play session.json --speed 2 --repeat 5 +mthread devices # какие устройства подключены +mthread info # разрешение экрана и диапазоны тачскрина +mthread record -o session.json # запись до нажатия Enter +mthread play session.json --speed 2 --repeat 5 ``` Если `adb` установлен в нестандартное место — укажите путь в переменной @@ -477,15 +477,15 @@ adbtouch play session.json --speed 2 --repeat 5 - На части устройств тачскрин недоступен для `sendevent`. Тогда рисование завершится понятной ошибкой, а не кривым результатом; `Device.swipe()` продолжает работать, автоматический откат — в списке задач. -- Если касания попадают не туда — начните с `adbtouch info`. +- Если касания попадают не туда — начните с `mthread info`. ## Если что-то не работает Заведите issue — есть шаблоны для -[багов](https://github.com/MAXAWER/AutoDraw-Sim/issues/new?template=bug_report.md) -и для [отчётов об устройстве](https://github.com/MAXAWER/AutoDraw-Sim/issues/new?template=device_report.md). +[багов](https://github.com/MAXAWER/MThread-Draw/issues/new?template=bug_report.md) +и для [отчётов об устройстве](https://github.com/MAXAWER/MThread-Draw/issues/new?template=device_report.md). Касания не туда, телефон не подключается, эмулятор ведёт себя иначе — приложите -вывод `adbtouch info`, обычно это чинится быстро. Диапазоны координат тачскрина у +вывод `mthread info`, обычно это чинится быстро. Диапазоны координат тачскрина у разных панелей разные, и починить можно только то, что видно. **Если инструмент сэкономил вам вечер — звезда ⭐ ничего не стоит, а найти проект diff --git a/TERMS.md b/TERMS.md index 6be50e3..a24dcc1 100644 --- a/TERMS.md +++ b/TERMS.md @@ -1,9 +1,9 @@ # Licensing — Лицензирование -> AutoDraw and `adbtouch` are dual-licensed: **AGPL-3.0** for everyone, and a +> MThread Draw and `mthread` are dual-licensed: **AGPL-3.0** for everyone, and a > **commercial licence** for anyone who cannot accept what the AGPL asks. > -> AutoDraw и `adbtouch` распространяются по двойной лицензии: **AGPL-3.0** для +> MThread Draw и `mthread` распространяются по двойной лицензии: **AGPL-3.0** для > всех и **коммерческая лицензия** для тех, кого условия AGPL не устраивают. The binding text of the open licence is [`LICENSE`](LICENSE). This page explains @@ -23,7 +23,7 @@ what it means, in English and Russian, and how to buy your way out of it. Commercial licences, and any exception, are granted by the author. Ask by opening an issue titled **"Licence request"**: - + --- @@ -51,7 +51,7 @@ exactly that reason. ### The commercial licence The author holds the copyright, so he can also license the code on other terms. -A commercial licence is the way to use AutoDraw or `adbtouch` inside a product +A commercial licence is the way to use MThread Draw or `mthread` inside a product whose source you do not intend to publish — including a rebranded or restyled version. @@ -76,7 +76,7 @@ not part of it. ### No warranty -None, and no liability — sections 15 to 17 of the licence. AutoDraw drives +None, and no liability — sections 15 to 17 of the licence. MThread Draw drives input on whatever device you connect to it, and whether that is allowed by the rules of a particular game, service or platform is your responsibility. @@ -105,8 +105,8 @@ Open Source Initiative. На практике это значит: ### Коммерческая лицензия Права принадлежат автору, поэтому он может лицензировать код и на других -условиях. Коммерческая лицензия — это способ использовать AutoDraw или -`adbtouch` внутри продукта, исходники которого вы публиковать не собираетесь, +условиях. Коммерческая лицензия — это способ использовать MThread Draw или +`mthread` внутри продукта, исходники которого вы публиковать не собираетесь, включая версию с переделанным оформлением. Условия обсуждаются отдельно в каждом случае. Опишите, что вы хотите делать, в diff --git a/adbtouch/injector.jar b/adbtouch/injector.jar deleted file mode 100644 index 5000427e7ec54196eb9e83fe6d77a15642bc781b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3037 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 diff --git a/docs/DEMO.md b/docs/DEMO.md index fefef87..5bf3bc6 100644 --- a/docs/DEMO.md +++ b/docs/DEMO.md @@ -44,13 +44,13 @@ just that rectangle. A screen recording of an actual phone is better, and takes about five minutes. Mirror the device with [scrcpy](https://github.com/Genymobile/scrcpy), start -`autodraw`, record the mirror window, then cut it down: +`mthread_draw`, record the mirror window, then cut it down: ```bash # 1. Mirror the phone and record the window (scrcpy 2.x writes an mp4 directly) scrcpy --record demo.mp4 --max-size 720 -# 2. Draw something from AutoDraw while that runs, then stop scrcpy. +# 2. Draw something from MThread Draw while that runs, then stop scrcpy. # 3. Trim to the interesting part and turn it into a GIF that is small enough # for a README - two-pass, so the palette does not fall apart. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 0d4f8d7..b836a80 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -2,8 +2,8 @@ ## Cutting a release -1. Bump the version in `pyproject.toml`, `adbtouch/__init__.py` and - `autodraw/__init__.py` — all three carry the same number. +1. Bump the version in `pyproject.toml`, `mthread/__init__.py` and + `mthread_draw/__init__.py` — all three carry the same number. 2. Add a section to [`CHANGELOG.md`](../CHANGELOG.md). 3. Commit, then tag and push: @@ -16,9 +16,9 @@ | Artifact | Built on | |---|---| -| `AutoDraw--x64.msi`, and the same app as a `.zip` | windows-latest | -| `AutoDraw--arm64.dmg` | macos-latest | -| `AutoDraw--x64.dmg` | macos-13 | +| `MThread Draw--x64.msi`, and the same app as a `.zip` | windows-latest | +| `MThread Draw--arm64.dmg` | macos-latest | +| `MThread Draw--x64.dmg` | macos-13 | | sdist and wheel | ubuntu-latest | Tags must start with `v`. Anything else is ignored by the workflow. The same @@ -29,7 +29,7 @@ you are not developing on. ## The Windows front end `winui/` is a WinUI 3 application that draws the window and nothing else. It -launches the engine - `autodraw.server`, the same one every platform uses - as a +launches the engine - `mthread_draw.server`, the same one every platform uses - as a child process and speaks JSON lines to it over a pipe. That is what keeps one implementation of tracing, ADB and touch injection rather than two. @@ -62,10 +62,10 @@ python tools/build_app.py --dmg # macOS python tools/build_app.py --archive # a plain zip / tar.gz anywhere ``` -Each build runs the packaged app's own self-test (`AutoDraw --selftest`) before +Each build runs the packaged app's own self-test (`MThread Draw --selftest`) before it is considered finished: it imports the whole application and resolves and runs the bundled adb. An incomplete bundle fails the build instead of failing on -a user's desktop - which is how `adbtouch.vectorize`, imported lazily through +a user's desktop - which is how `mthread.vectorize`, imported lazily through `__getattr__` and therefore invisible to PyInstaller, was once left out. ### WiX @@ -82,7 +82,7 @@ Fee licence, which is not something a build script can do unattended. ## The bundled adb The installers carry Google's `adb` inside them, fetched at build time by -`tools/fetch_platform_tools.py`, so that installing AutoDraw installs +`tools/fetch_platform_tools.py`, so that installing MThread Draw installs everything. Two things worth knowing: - platform-tools is published under the Android Software Development Kit @@ -91,7 +91,7 @@ everything. Two things worth knowing: binary for that reason. To ship without it, build with `--no-adb`; the app then falls back to whatever adb the machine has, exactly like a source checkout. -- The bundled copy outranks `PATH`, so an installed AutoDraw behaves the same on +- The bundled copy outranks `PATH`, so an installed MThread Draw behaves the same on every machine. ## Code signing @@ -109,7 +109,7 @@ credentials for. Once: 1. Create a pending publisher at for the project name - `adbtouch`, owner `MAXAWER`, repository `AutoDraw-Sim`, workflow + `mthread`, owner `MAXAWER`, repository `MThread-Draw`, workflow `release.yml`, environment `pypi`. 2. Create the `pypi` environment under **Settings → Environments**. 3. Set the repository variable `PUBLISH_TO_PYPI` to `true` under @@ -120,12 +120,12 @@ After that every `v*` tag publishes. No API token is stored anywhere. Once the first upload lands, the install line in the README can become: ```bash -pip install adbtouch # library -pip install "adbtouch[gui]" # + the desktop app +pip install mthread # library +pip install "mthread-draw[gui]" # + the desktop app ``` ## Version numbers -`adbtouch` and `AutoDraw` ship together and share one version. The tag `v1.0` +`mthread` and `MThread Draw` ship together and share one version. The tag `v1.0` predates the rewrite and belongs to the old ADB Painter build, so releases continue from `1.1.0` rather than restarting. diff --git a/injector/src/com/adbtouch/Injector.java b/injector/src/com/mthread/Injector.java similarity index 99% rename from injector/src/com/adbtouch/Injector.java rename to injector/src/com/mthread/Injector.java index e19b689..dab45c8 100644 --- a/injector/src/com/adbtouch/Injector.java +++ b/injector/src/com/mthread/Injector.java @@ -1,4 +1,4 @@ -package com.adbtouch; +package com.mthread; import android.os.SystemClock; import android.view.InputDevice; diff --git a/adbtouch/__init__.py b/mthread/__init__.py similarity index 88% rename from adbtouch/__init__.py rename to mthread/__init__.py index 55b8a2f..3e377c3 100644 --- a/adbtouch/__init__.py +++ b/mthread/__init__.py @@ -1,4 +1,4 @@ -"""adbtouch - fast synthetic touch input, recording and replay for Android over ADB. +"""mthread - fast synthetic touch input, recording and replay for Android over ADB. The library exists because ``adb shell input tap`` is too slow to be useful for anything continuous: every call spawns a process on the device and costs @@ -8,7 +8,7 @@ Typical use:: - from adbtouch import Device, Recorder, Session, replay + from mthread import Device, Recorder, Session, replay device = Device() recorder = Recorder(device) @@ -27,7 +27,7 @@ from .errors import ( AdbCommandError, AdbNotFoundError, - AdbTouchError, + MThreadError, DeviceNotConnectedError, TouchDeviceNotFoundError, ) @@ -43,7 +43,7 @@ __license__ = "AGPL-3.0-only" #: Image vectorisation is the only part that needs OpenCV, so it is imported on -#: first use. That keeps ``pip install adbtouch`` dependency-free for the common +#: first use. That keeps ``pip install mthread`` dependency-free for the common #: case of recording and replaying gestures. _LAZY = {"Vectorizer": "vectorize", "VectorizeSettings": "vectorize"} @@ -68,6 +68,6 @@ def __dir__(): "Recorder", "Session", "InputEvent", "replay", "build_replay_script", "parse_getevent_line", "TouchDevice", "parse_getevent_pl", "pick_touchscreen", "build_stroke_events", "Vectorizer", "VectorizeSettings", - "AdbTouchError", "AdbNotFoundError", "AdbCommandError", + "MThreadError", "AdbNotFoundError", "AdbCommandError", "DeviceNotConnectedError", "TouchDeviceNotFoundError", ] diff --git a/adbtouch/__main__.py b/mthread/__main__.py similarity index 100% rename from adbtouch/__main__.py rename to mthread/__main__.py diff --git a/adbtouch/adb.py b/mthread/adb.py similarity index 99% rename from adbtouch/adb.py rename to mthread/adb.py index 1415b0c..859d940 100644 --- a/adbtouch/adb.py +++ b/mthread/adb.py @@ -95,7 +95,7 @@ def find_adb(explicit: str | None = None) -> str: directory or a bare binary beside the working directory, then the default SDK location for the running platform. - The bundled copy outranks ``PATH`` on purpose: an installed AutoDraw should + The bundled copy outranks ``PATH`` on purpose: an installed MThread Draw should behave the same on every machine, rather than inheriting whichever adb happens to be lying around. diff --git a/adbtouch/cli.py b/mthread/cli.py similarity index 95% rename from adbtouch/cli.py rename to mthread/cli.py index 964f700..9d84b6e 100644 --- a/adbtouch/cli.py +++ b/mthread/cli.py @@ -1,4 +1,4 @@ -"""Command line front end: ``python -m adbtouch``.""" +"""Command line front end: ``python -m mthread``.""" from __future__ import annotations @@ -8,7 +8,7 @@ from . import __version__ from .device import Device, list_devices -from .errors import AdbTouchError +from .errors import MThreadError from .player import replay from .recorder import Recorder from .session import Session @@ -89,8 +89,8 @@ def progress(done, total): def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(prog="adbtouch", description=__doc__) - parser.add_argument("--version", action="version", version=f"adbtouch {__version__}") + parser = argparse.ArgumentParser(prog="mthread", description=__doc__) + parser.add_argument("--version", action="version", version=f"mthread {__version__}") parser.add_argument("-s", "--serial", help="target device serial") sub = parser.add_subparsers(dest="command", required=True) @@ -116,7 +116,7 @@ def main(argv=None) -> int: args = build_parser().parse_args(argv) try: return args.func(args) - except AdbTouchError as exc: + except MThreadError as exc: print(f"error: {exc}", file=sys.stderr) return 2 except KeyboardInterrupt: diff --git a/adbtouch/device.py b/mthread/device.py similarity index 98% rename from adbtouch/device.py rename to mthread/device.py index 0be0407..b92c8b4 100644 --- a/adbtouch/device.py +++ b/mthread/device.py @@ -148,7 +148,7 @@ def run_script(self, lines: Iterable[str], *, timeout: float | None = 600.0) -> directory and the remote name is unique, so concurrent runs never clash. """ body = "#!/system/bin/sh\n" + "\n".join(lines) + "\n" - remote = f"{REMOTE_TMP}/adbtouch_{uuid.uuid4().hex}.sh" + remote = f"{REMOTE_TMP}/mthread_{uuid.uuid4().hex}.sh" handle = tempfile.NamedTemporaryFile("w", suffix=".sh", newline="\n", delete=False, encoding="utf-8") try: @@ -251,15 +251,15 @@ def draw_paths( a tenth of a second per point whatever happens - but it does let either path be slowed to something a person could have done. human: 0 draws the geometry exactly. Above 0 hands the paths to - :func:`adbtouch.hand.simulate`, which rounds the corners, varies + :func:`mthread.hand.simulate`, which rounds the corners, varies the pen speed, adds tremor, overshoots the ends and reorders the strokes into the sequence a person would use. 1.0 is a steady hand, 3.0 a careless one. Note that it changes the point count, and so the time the drawing takes. hand_settings: Full control over the simulation; see - :class:`adbtouch.hand.HandSettings`. + :class:`mthread.hand.HandSettings`. pacing: Timing for the injector path; see - :class:`adbtouch.injector.Pacing`. Defaults to hand speed when + :class:`mthread.injector.Pacing`. Defaults to hand speed when *human* is set and to as-fast-as-possible when it is not. seed: Fixes the randomness, so a "human" drawing can be repeated. progress: Called with ``(done, total)`` as strokes are sent. diff --git a/adbtouch/errors.py b/mthread/errors.py similarity index 64% rename from adbtouch/errors.py rename to mthread/errors.py index 28dba5d..e8331dd 100644 --- a/adbtouch/errors.py +++ b/mthread/errors.py @@ -1,17 +1,17 @@ -"""Exception types raised by :mod:`adbtouch`.""" +"""Exception types raised by :mod:`mthread`.""" -__all__ = ["AdbTouchError", "AdbNotFoundError", "AdbCommandError", "DeviceNotConnectedError", "TouchDeviceNotFoundError"] +__all__ = ["MThreadError", "AdbNotFoundError", "AdbCommandError", "DeviceNotConnectedError", "TouchDeviceNotFoundError"] -class AdbTouchError(Exception): +class MThreadError(Exception): """Base class for every error raised by this library.""" -class AdbNotFoundError(AdbTouchError): +class AdbNotFoundError(MThreadError): """The ``adb`` executable could not be located on this machine.""" -class AdbCommandError(AdbTouchError): +class AdbCommandError(MThreadError): """An ``adb`` invocation exited with a non-zero status.""" def __init__(self, args, returncode, stderr=""): @@ -22,9 +22,9 @@ def __init__(self, args, returncode, stderr=""): super().__init__(f"adb {' '.join(self.args_list)} failed with code {returncode}{detail}") -class DeviceNotConnectedError(AdbTouchError): +class DeviceNotConnectedError(MThreadError): """An operation needed a device but none was attached.""" -class TouchDeviceNotFoundError(AdbTouchError): +class TouchDeviceNotFoundError(MThreadError): """No touchscreen input device could be detected on the device.""" diff --git a/adbtouch/flowlines.py b/mthread/flowlines.py similarity index 100% rename from adbtouch/flowlines.py rename to mthread/flowlines.py diff --git a/adbtouch/hand.py b/mthread/hand.py similarity index 100% rename from adbtouch/hand.py rename to mthread/hand.py diff --git a/mthread/injector.jar b/mthread/injector.jar new file mode 100644 index 0000000000000000000000000000000000000000..e6167463baf1cd2906e05d0a591160e9c37086c1 GIT binary patch literal 3031 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 ZP)h{{000000RRC2IRF3vhzkG!000*_?J58O literal 0 HcmV?d00001 diff --git a/adbtouch/injector.py b/mthread/injector.py similarity index 95% rename from adbtouch/injector.py rename to mthread/injector.py index e7db4f9..db5efed 100644 --- a/adbtouch/injector.py +++ b/mthread/injector.py @@ -1,6 +1,6 @@ """Drive the on-device touch injector. -Why this exists is in ``injector/src/com/adbtouch/Injector.java``: on a phone +Why this exists is in ``injector/src/com/mthread/Injector.java``: on a phone where the shell user cannot write to ``/dev/input``, the only other way in is the framework, and the ``input`` command pays a whole process - about 110 ms - for every single point. That is not a pacing problem that can be tuned away. A @@ -25,15 +25,15 @@ from pathlib import Path from .adb import no_window_kwargs -from .errors import AdbTouchError +from .errors import MThreadError __all__ = ["InjectorUnavailableError", "Pacing", "TouchInjector", "pace_stroke"] -REMOTE_JAR = "/data/local/tmp/adbtouch-injector.jar" +REMOTE_JAR = "/data/local/tmp/mthread-injector.jar" JAR_NAME = "injector.jar" -class InjectorUnavailableError(AdbTouchError): +class InjectorUnavailableError(MThreadError): """The device would not run the injector; the caller should fall back.""" @@ -50,7 +50,7 @@ def bundled_jar() -> Path | None: meipass = getattr(sys, "_MEIPASS", None) if meipass: - for candidate in (Path(meipass) / "adbtouch" / JAR_NAME, Path(meipass) / JAR_NAME): + for candidate in (Path(meipass) / "mthread" / JAR_NAME, Path(meipass) / JAR_NAME): if candidate.is_file(): return candidate return None @@ -178,7 +178,7 @@ def start(self) -> None: command = [ self.device.adb_path, "-s", self.device.serial, "shell", - f"CLASSPATH={REMOTE_JAR} app_process / com.adbtouch.Injector", + f"CLASSPATH={REMOTE_JAR} app_process / com.mthread.Injector", ] self.process = subprocess.Popen( command, @@ -253,7 +253,7 @@ def sync(self, timeout: float = 600.0) -> None: return reply = (self.process.stdout.readline() or "").strip() if reply.startswith("ERR"): - raise AdbTouchError(f"The injector reported: {reply}") + raise MThreadError(f"The injector reported: {reply}") # ------------------------------------------------------------------- stop diff --git a/adbtouch/neural.py b/mthread/neural.py similarity index 96% rename from adbtouch/neural.py rename to mthread/neural.py index b6e6553..574df60 100644 --- a/adbtouch/neural.py +++ b/mthread/neural.py @@ -26,7 +26,7 @@ import numpy as np -from .errors import AdbTouchError +from .errors import MThreadError __all__ = ["MODEL_URL", "model_path", "have_model", "download_model", "edge_probability"] @@ -40,7 +40,7 @@ MEAN = (103.5, 116.2, 123.6) -class ModelUnavailableError(AdbTouchError): +class ModelUnavailableError(MThreadError): """The model is not downloaded, and nothing may download it silently.""" @@ -50,13 +50,13 @@ def cache_dir() -> Path: Not inside the package: a wheel should not grow by 46 MB, and a model that outlives an upgrade is a feature. """ - base = os.environ.get("ADBTOUCH_CACHE") + base = os.environ.get("MTHREAD_CACHE") if base: return Path(base) if os.name == "nt": root = os.environ.get("LOCALAPPDATA") or Path.home() / "AppData" / "Local" - return Path(root) / "adbtouch" - return Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "adbtouch" + return Path(root) / "mthread" + return Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "mthread" def model_path() -> Path: diff --git a/adbtouch/paths.py b/mthread/paths.py similarity index 100% rename from adbtouch/paths.py rename to mthread/paths.py diff --git a/adbtouch/player.py b/mthread/player.py similarity index 97% rename from adbtouch/player.py rename to mthread/player.py index 29c7f64..2f76965 100644 --- a/adbtouch/player.py +++ b/mthread/player.py @@ -1,4 +1,4 @@ -"""Replaying a recorded :class:`~adbtouch.session.Session` back onto a device.""" +"""Replaying a recorded :class:`~mthread.session.Session` back onto a device.""" from __future__ import annotations diff --git a/adbtouch/recorder.py b/mthread/recorder.py similarity index 98% rename from adbtouch/recorder.py rename to mthread/recorder.py index 8fdb058..dbc6d25 100644 --- a/adbtouch/recorder.py +++ b/mthread/recorder.py @@ -46,7 +46,7 @@ class Recorder: recording replayable regardless of how long the device had been up. Args: - device: A connected :class:`~adbtouch.device.Device`. + device: A connected :class:`~mthread.device.Device`. only_devices: Restrict capture to these ``/dev/input`` paths. Defaults to the detected touchscreen so stray button presses are not recorded. on_event: Optional callback invoked for every captured event, useful for @@ -90,7 +90,7 @@ def start(self) -> None: self.error = None self._stop.clear() self._process = self.device.stream_getevent() - self._thread = threading.Thread(target=self._pump, name="adbtouch-recorder", daemon=True) + self._thread = threading.Thread(target=self._pump, name="mthread-recorder", daemon=True) self._thread.start() def _pump(self) -> None: diff --git a/adbtouch/session.py b/mthread/session.py similarity index 97% rename from adbtouch/session.py rename to mthread/session.py index b905f4c..7d9a02c 100644 --- a/adbtouch/session.py +++ b/mthread/session.py @@ -83,7 +83,7 @@ def from_dict(cls, data: dict) -> "Session": if version > SESSION_FORMAT_VERSION: raise ValueError( f"This recording uses format version {version}, but this build only understands " - f"up to {SESSION_FORMAT_VERSION}. Update adbtouch to open it." + f"up to {SESSION_FORMAT_VERSION}. Update mthread to open it." ) screen = data.get("screen_size") return cls( diff --git a/adbtouch/touch.py b/mthread/touch.py similarity index 100% rename from adbtouch/touch.py rename to mthread/touch.py diff --git a/adbtouch/trace.py b/mthread/trace.py similarity index 100% rename from adbtouch/trace.py rename to mthread/trace.py diff --git a/adbtouch/vectorize.py b/mthread/vectorize.py similarity index 100% rename from adbtouch/vectorize.py rename to mthread/vectorize.py diff --git a/autodraw/__init__.py b/mthread_draw/__init__.py similarity index 63% rename from autodraw/__init__.py rename to mthread_draw/__init__.py index ae36f64..f0c6b78 100644 --- a/autodraw/__init__.py +++ b/mthread_draw/__init__.py @@ -1,4 +1,4 @@ -"""AutoDraw - a desktop front end for :mod:`adbtouch`. +"""MThread Draw - a desktop front end for :mod:`mthread`. Draws images on an Android device and records / replays touch gestures. """ diff --git a/autodraw/__main__.py b/mthread_draw/__main__.py similarity index 100% rename from autodraw/__main__.py rename to mthread_draw/__main__.py diff --git a/autodraw/app.py b/mthread_draw/app.py similarity index 97% rename from autodraw/app.py rename to mthread_draw/app.py index df484fd..4ac3ca9 100644 --- a/autodraw/app.py +++ b/mthread_draw/app.py @@ -1,4 +1,4 @@ -"""The AutoDraw desktop window.""" +"""The MThread Draw desktop window.""" from __future__ import annotations @@ -12,14 +12,14 @@ from tkinter import filedialog, messagebox from PIL import Image, ImageTk -from adbtouch import Device, Recorder, Session, VectorizeSettings, Vectorizer, replay, simulate -from adbtouch.errors import AdbTouchError +from mthread import Device, Recorder, Session, VectorizeSettings, Vectorizer, replay, simulate +from mthread.errors import MThreadError from .geometry import CanvasView, place_paths CANVAS_MARGIN = 40 IMAGE_TYPES = [("Images", "*.png *.jpg *.jpeg *.bmp *.webp"), ("All files", "*.*")] -SESSION_TYPES = [("AutoDraw recording", "*.json"), ("All files", "*.*")] +SESSION_TYPES = [("MThread Draw recording", "*.json"), ("All files", "*.*")] class App: @@ -37,7 +37,7 @@ def __init__(self): ctk.set_default_color_theme("blue") self.root = ctk.CTk() - self.root.title("AutoDraw - draw, record and replay on Android") + self.root.title("MThread Draw - draw, record and replay on Android") self.root.geometry("1280x860") self.root.minsize(1000, 700) @@ -75,7 +75,7 @@ def _build_header(self) -> None: header.grid(row=0, column=0, sticky="ew") header.grid_columnconfigure(4, weight=1) - ctk.CTkLabel(header, text="AutoDraw", font=ctk.CTkFont(size=20, weight="bold")).grid( + ctk.CTkLabel(header, text="MThread Draw", font=ctk.CTkFont(size=20, weight="bold")).grid( row=0, column=0, padx=(20, 16), pady=12 ) self.btn_connect = ctk.CTkButton(header, text="Connect device", width=140, command=self.connect) @@ -250,7 +250,7 @@ def _run_background(self, target, *args) -> None: def wrapper(): try: target(*args) - except AdbTouchError as exc: + except MThreadError as exc: self.set_status(str(exc), "orange") except Exception as exc: # pragma: no cover - surfaced in the UI traceback.print_exc() @@ -279,20 +279,20 @@ def _int_entry(self, entry, default: int = 0) -> int: def connect(self) -> None: try: self.device = Device() - except AdbTouchError as exc: + except MThreadError as exc: self.device = None self.set_status(str(exc), "orange") return try: self.screen_size = self.device.screen_size - except AdbTouchError as exc: + except MThreadError as exc: self.set_status(str(exc), "orange") return detail = f"{self.device.serial} - {self.screen_size[0]}x{self.screen_size[1]}" try: touch = self.device.touch_device detail += f" - touch {touch.path}" - except AdbTouchError: + except MThreadError: detail += " - no raw touch device (slow mode)" self.set_status(detail, "#4caf50") self._redraw_phone_rect() @@ -303,7 +303,7 @@ def capture_screen(self) -> None: def work(): self.set_status("Capturing screen...", "yellow") - path = os.path.join(tempfile.gettempdir(), "autodraw_screen.png") + path = os.path.join(tempfile.gettempdir(), "mthread_draw_screen.png") self.device.screenshot(path) self.background_image = Image.open(path).copy() self.set_status("Screen captured", "#4caf50") @@ -457,7 +457,7 @@ def _refresh_estimate(self) -> None: raw = self.device.supports_raw_touch seconds = self.device.estimate_duration( paths, method="raw" if raw else "input", speed=self.draw_speed) - except AdbTouchError as exc: + except MThreadError as exc: self.lbl_method.configure(text=str(exc)) return @@ -484,7 +484,7 @@ def load_image(self) -> None: try: self.vectorizer.load_image(path) except ValueError as exc: - messagebox.showerror("AutoDraw", str(exc)) + messagebox.showerror("MThread Draw", str(exc)) return self._placed = False self.refresh_preview() @@ -576,7 +576,7 @@ def _start_recording(self) -> None: self.recorder = Recorder(self.device) try: self.recorder.start() - except AdbTouchError as exc: + except MThreadError as exc: self.set_status(str(exc), "orange") return self.btn_record.configure(text="Stop recording", fg_color="#c25a00") @@ -648,7 +648,7 @@ def open_session(self) -> None: try: self.session = Session.load(path) except (ValueError, OSError) as exc: - messagebox.showerror("AutoDraw", f"Could not open the recording:\n{exc}") + messagebox.showerror("MThread Draw", f"Could not open the recording:\n{exc}") return self.set_status(f"Loaded {os.path.basename(path)}", "#4caf50") self._describe_session() diff --git a/autodraw/geometry.py b/mthread_draw/geometry.py similarity index 100% rename from autodraw/geometry.py rename to mthread_draw/geometry.py diff --git a/autodraw/server.py b/mthread_draw/server.py similarity index 92% rename from autodraw/server.py rename to mthread_draw/server.py index 8729de6..7531db0 100644 --- a/autodraw/server.py +++ b/mthread_draw/server.py @@ -1,6 +1,6 @@ """The engine, driven over a pipe, so a native interface can be a thin one. - python -m autodraw.server + python -m mthread_draw.server Reads one JSON request per line on stdin and writes one JSON message per line on stdout. That is the whole protocol, and it is deliberately the dullest thing @@ -37,8 +37,8 @@ import traceback from pathlib import Path -from adbtouch import Device, VectorizeSettings, Vectorizer, list_devices, simulate -from adbtouch.errors import AdbTouchError +from mthread import Device, VectorizeSettings, Vectorizer, list_devices, simulate +from mthread.errors import MThreadError from .geometry import fit_to_screen @@ -82,7 +82,7 @@ def op_connect(self, serial: str | None = None) -> dict: def op_screenshot(self) -> dict: self._require_device() - path = Path(tempfile.gettempdir()) / "autodraw_screen.png" + path = Path(tempfile.gettempdir()) / "mthread_draw_screen.png" self.device.screenshot(str(path)) return {"path": str(path)} @@ -97,7 +97,7 @@ def op_preview(self, sensitivity: float = 5, detail: float = 7, preview, paths = self.vectorizer.process(settings) self.paths = paths - path = Path(tempfile.gettempdir()) / "autodraw_preview.png" + path = Path(tempfile.gettempdir()) / "mthread_draw_preview.png" preview.save(path) return { "path": str(path), @@ -117,7 +117,7 @@ def op_draw(self, margin: float = 0.06, speed: float = 1.0, human: float = 0.0) -> dict: self._require_device() if not self.paths: - raise AdbTouchError("Load an image and take a preview first.") + raise MThreadError("Load an image and take a preview first.") width, height = self.device.screen_size placed = fit_to_screen(self.paths, width, height, margin=margin) @@ -139,7 +139,7 @@ def op_stop(self) -> dict: def _require_device(self) -> None: if self.device is None: - raise AdbTouchError("No device is connected.") + raise MThreadError("No device is connected.") #: Operations that may take a while, and so must not block the reader. @@ -157,7 +157,7 @@ def write(message: dict) -> None: stdout.flush() engine = Engine(write) - write({"event": "ready", "version": __import__("autodraw").__version__}) + write({"event": "ready", "version": __import__("mthread_draw").__version__}) def run(request: dict) -> None: identifier = request.get("id") @@ -170,7 +170,7 @@ def run(request: dict) -> None: arguments = {k: v for k, v in request.items() if k not in ("id", "op")} try: write({"id": identifier, "ok": True, "result": handler(**arguments)}) - except AdbTouchError as exc: + except MThreadError as exc: write({"id": identifier, "ok": False, "error": str(exc)}) except Exception as exc: # pragma: no cover - the front end still needs telling write({"id": identifier, "ok": False, "error": f"{type(exc).__name__}: {exc}", diff --git a/packaging/AutoDraw.spec b/packaging/MThreadDraw.spec similarity index 79% rename from packaging/AutoDraw.spec rename to packaging/MThreadDraw.spec index e78dfb6..32c83ed 100644 --- a/packaging/AutoDraw.spec +++ b/packaging/MThreadDraw.spec @@ -1,5 +1,5 @@ # -*- mode: python ; coding: utf-8 -*- -"""PyInstaller build definition for the AutoDraw desktop app. +"""PyInstaller build definition for the MThreadDraw desktop app. Used unchanged on all three platforms and both locally and in CI, so a release build is the same build a maintainer can reproduce: @@ -29,25 +29,25 @@ IS_MAC = sys.platform == "darwin" VERSION = "1.1.0" datas = collect_data_files("customtkinter") -datas += [(str(ROOT / "adbtouch" / "injector.jar"), "adbtouch")] +datas += [(str(ROOT / "mthread" / "injector.jar"), "mthread")] platform_tools = ROOT / "platform-tools" if platform_tools.is_dir(): datas += [(str(item), "platform-tools") for item in platform_tools.iterdir() if item.is_file()] else: - print("AutoDraw.spec: platform-tools not found - the app will need adb on PATH") + print("MThreadDraw.spec: platform-tools not found - the app will need adb on PATH") -icon = str(ROOT / "packaging" / ("autodraw.ico" if IS_WINDOWS else "autodraw.png")) +icon = str(ROOT / "packaging" / ("mthreaddraw.ico" if IS_WINDOWS else "mthreaddraw.png")) analysis = Analysis( [str(ROOT / "tools" / "pyinstaller_entry.py")], pathex=[str(ROOT)], binaries=[], datas=datas, - # adbtouch imports its vectoriser through __getattr__ so that the core + # mthread imports its vectoriser through __getattr__ so that the core # library stays dependency-free. PyInstaller cannot see through that, and # leaves the module out unless it is named here. - hiddenimports=["customtkinter", "adbtouch.vectorize"], + hiddenimports=["customtkinter", "mthread.vectorize"], hookspath=[], runtime_hooks=[], # rembg and its onnxruntime are optional and enormous; someone who wants @@ -63,7 +63,7 @@ exe = EXE( analysis.scripts, [], exclude_binaries=True, - name="AutoDraw", + name="MThreadDraw", debug=False, strip=False, upx=False, @@ -77,7 +77,7 @@ collected = COLLECT( analysis.datas, strip=False, upx=False, - name="AutoDraw", + name="MThreadDraw", ) # The same engine again, as a console program the WinUI front end can launch. @@ -87,7 +87,7 @@ engine_analysis = Analysis( pathex=[str(ROOT)], binaries=[], datas=datas, - hiddenimports=["customtkinter", "adbtouch.vectorize"], + hiddenimports=["customtkinter", "mthread.vectorize"], excludes=["rembg", "onnxruntime", "matplotlib", "pytest", "tkinter.test"], noarchive=False, ) @@ -97,7 +97,7 @@ engine_exe = EXE( engine_analysis.scripts, [], exclude_binaries=True, - name="autodraw-engine", + name="mthread-draw-engine", debug=False, strip=False, upx=False, @@ -109,15 +109,15 @@ engine = COLLECT( engine_analysis.datas, strip=False, upx=False, - name="autodraw-engine", + name="mthread-draw-engine", ) if IS_MAC: app = BUNDLE( collected, - name="AutoDraw.app", + name="MThreadDraw.app", icon=icon, - bundle_identifier="io.github.maxawer.autodraw", + bundle_identifier="io.github.maxawer.mthread_draw", version=VERSION, info_plist={ "CFBundleShortVersionString": VERSION, @@ -125,6 +125,6 @@ if IS_MAC: "NSHighResolutionCapable": True, # The app talks to a phone over USB; without this, macOS refuses # the connection instead of prompting. - "NSAppleEventsUsageDescription": "AutoDraw drives adb to reach your device.", + "NSAppleEventsUsageDescription": "MThreadDraw drives adb to reach your device.", }, ) diff --git a/packaging/AutoDraw.wxs b/packaging/MThreadDraw.wxs similarity index 66% rename from packaging/AutoDraw.wxs rename to packaging/MThreadDraw.wxs index d2a9df9..2979357 100644 --- a/packaging/AutoDraw.wxs +++ b/packaging/MThreadDraw.wxs @@ -2,22 +2,22 @@ + DowngradeErrorMessage="A newer version of MThread Draw is already installed. Remove it first from Settings, Installed apps." /> - - - - + + + + - + - + - + - + @@ -67,14 +67,14 @@ - + Icon="MThreadDrawIcon" /> - diff --git a/packaging/autodraw.ico b/packaging/mthreaddraw.ico similarity index 100% rename from packaging/autodraw.ico rename to packaging/mthreaddraw.ico diff --git a/packaging/autodraw.png b/packaging/mthreaddraw.png similarity index 100% rename from packaging/autodraw.png rename to packaging/mthreaddraw.png diff --git a/pyproject.toml b/pyproject.toml index 4848773..48b53ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "adbtouch" +name = "mthread-draw" version = "1.1.0" description = "Fast synthetic touch input, gesture recording, replay and image drawing for Android over ADB" readme = "README.md" @@ -35,27 +35,27 @@ dependencies = [] [project.optional-dependencies] draw = ["opencv-python>=4.5", "numpy>=1.21", "Pillow>=9.0"] -gui = ["adbtouch[draw]", "customtkinter>=5.2"] +gui = ["mthread-draw[draw]", "customtkinter>=5.2"] bg = ["rembg>=2.0"] dev = ["pytest>=7.0"] -all = ["adbtouch[gui,bg]"] +all = ["mthread-draw[gui,bg]"] [project.urls] -Homepage = "https://github.com/MAXAWER/AutoDraw-Sim" -Source = "https://github.com/MAXAWER/AutoDraw-Sim" -Issues = "https://github.com/MAXAWER/AutoDraw-Sim/issues" -Changelog = "https://github.com/MAXAWER/AutoDraw-Sim/blob/main/CHANGELOG.md" -Terms = "https://github.com/MAXAWER/AutoDraw-Sim/blob/main/TERMS.md" +Homepage = "https://github.com/MAXAWER/MThread-Draw" +Source = "https://github.com/MAXAWER/MThread-Draw" +Issues = "https://github.com/MAXAWER/MThread-Draw/issues" +Changelog = "https://github.com/MAXAWER/MThread-Draw/blob/main/CHANGELOG.md" +Terms = "https://github.com/MAXAWER/MThread-Draw/blob/main/TERMS.md" [project.scripts] -adbtouch = "adbtouch.cli:main" +mthread = "mthread.cli:main" [project.gui-scripts] -autodraw = "autodraw.app:main" +mthread_draw = "mthread_draw.app:main" [tool.setuptools] -packages = ["adbtouch", "autodraw"] +packages = ["mthread", "mthread_draw"] [tool.setuptools.package-data] # The on-device injector, built by tools/build_injector.py. -adbtouch = ["injector.jar"] +mthread = ["injector.jar"] diff --git a/requirements.txt b/requirements.txt index 2c3b09e..b0bf861 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Everything needed to run the desktop app. -# The adbtouch library alone needs nothing beyond the standard library: +# The mthread library alone needs nothing beyond the standard library: # pip install -e . customtkinter>=5.2,<6 opencv-python>=4.5 diff --git a/run.bat b/run.bat index 7111922..3b591cd 100644 --- a/run.bat +++ b/run.bat @@ -1,47 +1,47 @@ -@echo off -rem One-click launcher for Windows: builds a virtual environment on first run, -rem then starts the AutoDraw desktop app. Double-click it. -setlocal -cd /d "%~dp0" - -set "PY=venv\Scripts\python.exe" - -if not exist "%PY%" ( - echo Creating a virtual environment in venv\ ... - python -m venv venv - if errorlevel 1 ( - echo. - echo Could not create a virtual environment. - echo Install Python 3.9 or newer from https://www.python.org/downloads/ - echo and tick "Add python.exe to PATH" during setup. - pause - exit /b 1 - ) - - echo Installing AutoDraw and its dependencies. This takes a minute the first time ... - "%PY%" -m pip install --upgrade pip - "%PY%" -m pip install -e ".[gui]" - if errorlevel 1 ( - echo. - echo Installation failed - see the output above. - pause - exit /b 1 - ) -) - -rem adb, if this machine has none of its own. Downloaded once, from Google. -where adb >nul 2>&1 -if errorlevel 1 ( - if not exist "platform-toolsdb.exe" ( - echo Fetching adb ... - "%PY%" tools etch_platform_tools.py - ) -) - -"%PY%" -m autodraw -if errorlevel 1 ( - echo. - echo AutoDraw exited with an error. Check that USB debugging is enabled - echo on the phone and that the cable carries data. - pause -) +@echo off +rem One-click launcher for Windows: builds a virtual environment on first run, +rem then starts the MThreadDraw desktop app. Double-click it. +setlocal +cd /d "%~dp0" + +set "PY=venv\Scripts\python.exe" + +if not exist "%PY%" ( + echo Creating a virtual environment in venv\ ... + python -m venv venv + if errorlevel 1 ( + echo. + echo Could not create a virtual environment. + echo Install Python 3.9 or newer from https://www.python.org/downloads/ + echo and tick "Add python.exe to PATH" during setup. + pause + exit /b 1 + ) + + echo Installing MThreadDraw and its dependencies. This takes a minute the first time ... + "%PY%" -m pip install --upgrade pip + "%PY%" -m pip install -e ".[gui]" + if errorlevel 1 ( + echo. + echo Installation failed - see the output above. + pause + exit /b 1 + ) +) + +rem adb, if this machine has none of its own. Downloaded once, from Google. +where adb >nul 2>&1 +if errorlevel 1 ( + if not exist "platform-toolsdb.exe" ( + echo Fetching adb ... + "%PY%" tools etch_platform_tools.py + ) +) + +"%PY%" -m mthread_draw +if errorlevel 1 ( + echo. + echo MThreadDraw exited with an error. Check that USB debugging is enabled + echo on the phone and that the cable carries data. + pause +) diff --git a/run.sh b/run.sh index df2441c..7794409 100755 --- a/run.sh +++ b/run.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # One-click launcher for macOS and Linux: builds a virtual environment on first -# run, then starts the AutoDraw desktop app. +# run, then starts the MThreadDraw desktop app. # # ./run.sh set -euo pipefail @@ -13,7 +13,7 @@ if [ ! -x "$PY" ]; then echo "Creating a virtual environment in venv/ ..." python3 -m venv venv - echo "Installing AutoDraw and its dependencies. This takes a minute the first time ..." + echo "Installing MThreadDraw and its dependencies. This takes a minute the first time ..." "$PY" -m pip install --upgrade pip "$PY" -m pip install -e ".[gui]" fi @@ -24,4 +24,4 @@ if ! command -v adb >/dev/null 2>&1 && [ -z "${ADB_PATH:-}" ] && [ ! -x platform "$PY" tools/fetch_platform_tools.py fi -exec "$PY" -m autodraw "$@" +exec "$PY" -m mthread_draw "$@" diff --git a/tests/test_adb.py b/tests/test_adb.py index 71f46c3..9578ffd 100644 --- a/tests/test_adb.py +++ b/tests/test_adb.py @@ -7,8 +7,8 @@ from pathlib import Path from unittest import mock -from adbtouch.adb import bundled_candidates, find_adb, run_adb -from adbtouch.errors import AdbCommandError, AdbNotFoundError +from mthread.adb import bundled_candidates, find_adb, run_adb +from mthread.errors import AdbCommandError, AdbNotFoundError IS_WINDOWS = sys.platform.startswith("win") @@ -73,13 +73,13 @@ def test_falls_back_to_path_lookup(self): with tempfile.TemporaryDirectory() as tmp: fake = make_fake_adb(Path(tmp)) with mock.patch.dict(os.environ, {}, clear=True), \ - mock.patch("adbtouch.adb.shutil.which", return_value=str(fake)): + mock.patch("mthread.adb.shutil.which", return_value=str(fake)): self.assertSamePath(find_adb(), fake) def test_missing_binary_raises_with_guidance(self): with mock.patch.dict(os.environ, {}, clear=True), \ - mock.patch("adbtouch.adb.shutil.which", return_value=None), \ - mock.patch("adbtouch.adb.os.path.isfile", return_value=False): + mock.patch("mthread.adb.shutil.which", return_value=None), \ + mock.patch("mthread.adb.os.path.isfile", return_value=False): with self.assertRaises(AdbNotFoundError) as ctx: find_adb() self.assertIn("ADB_PATH", str(ctx.exception)) @@ -94,8 +94,8 @@ def test_non_executable_candidate_is_skipped(self): # Android SDK installed in its default location - every macOS CI # runner, for one - would find a real adb there and never raise. with mock.patch.dict(os.environ, {}, clear=True), \ - mock.patch("adbtouch.adb.shutil.which", return_value=None), \ - mock.patch.dict("adbtouch.adb._FALLBACKS", {"win32": [], "darwin": [], "linux": []}): + mock.patch("mthread.adb.shutil.which", return_value=None), \ + mock.patch.dict("mthread.adb._FALLBACKS", {"win32": [], "darwin": [], "linux": []}): with self.assertRaises(AdbNotFoundError): find_adb(str(plain)) @@ -126,9 +126,9 @@ def test_project_local_platform_tools_is_found(self): fake.write_text("not a real adb") fake.chmod(fake.stat().st_mode | stat.S_IEXEC) with mock.patch.dict(os.environ, {}, clear=True), \ - mock.patch("adbtouch.adb.shutil.which", return_value=None), \ - mock.patch("adbtouch.adb.Path.cwd", return_value=Path(tmp)), \ - mock.patch.dict("adbtouch.adb._FALLBACKS", {"win32": [], "darwin": [], "linux": []}): + mock.patch("mthread.adb.shutil.which", return_value=None), \ + mock.patch("mthread.adb.Path.cwd", return_value=Path(tmp)), \ + mock.patch.dict("mthread.adb._FALLBACKS", {"win32": [], "darwin": [], "linux": []}): self.assertEqual(Path(find_adb()).resolve(), fake.resolve()) def test_bundled_copy_beats_whatever_is_on_path(self): @@ -136,8 +136,8 @@ def test_bundled_copy_beats_whatever_is_on_path(self): bundled = make_fake_adb(Path(tmp)) elsewhere = make_fake_adb(Path(tempfile.mkdtemp())) with mock.patch.dict(os.environ, {}, clear=True), \ - mock.patch("adbtouch.adb.bundled_candidates", return_value=[str(bundled)]), \ - mock.patch("adbtouch.adb.shutil.which", return_value=str(elsewhere)): + mock.patch("mthread.adb.bundled_candidates", return_value=[str(bundled)]), \ + mock.patch("mthread.adb.shutil.which", return_value=str(elsewhere)): self.assertEqual(Path(find_adb()).resolve(), bundled.resolve()) diff --git a/tests/test_device.py b/tests/test_device.py index 3d070c6..064f790 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -3,9 +3,9 @@ import unittest import unittest.mock -from adbtouch.device import Device, DeviceInfo, REMOTE_TMP -from adbtouch.injector import InjectorUnavailableError -from adbtouch.touch import ABS_MT_POSITION_X, ABS_MT_TRACKING_ID, TouchDevice +from mthread.device import Device, DeviceInfo, REMOTE_TMP +from mthread.injector import InjectorUnavailableError +from mthread.touch import ABS_MT_POSITION_X, ABS_MT_TRACKING_ID, TouchDevice def make_device(screen=(1080, 2400), touch=None, raw=True): @@ -124,7 +124,7 @@ def fake_run_adb(adb_path, args, **kwargs): calls.append(["<>", pathlib.Path(args[3]).read_text()]) return unittest.mock.Mock(returncode=0, stdout="", stderr="") - with unittest.mock.patch("adbtouch.device.run_adb", side_effect=fake_run_adb): + with unittest.mock.patch("mthread.device.run_adb", side_effect=fake_run_adb): device.run_script(lines) return calls @@ -235,7 +235,7 @@ def pause(self, millis): def sync(self, timeout=600.0): pass - with unittest.mock.patch("adbtouch.device.TouchInjector", Stub): + with unittest.mock.patch("mthread.device.TouchInjector", Stub): device.draw_paths([[(1, 1), (2, 2)]]) self.assertEqual(started[0], device) self.assertEqual(device.scripts, []) @@ -248,7 +248,7 @@ def test_a_refusing_injector_drops_through_to_input(self): def refuse(*args, **kwargs): raise InjectorUnavailableError("no app_process here") - with unittest.mock.patch("adbtouch.device.TouchInjector", refuse): + with unittest.mock.patch("mthread.device.TouchInjector", refuse): device.draw_paths([[(1, 1), (2, 2)]]) self.assertIn("input motionevent", chr(10).join(device.scripts[0])) self.assertTrue(device.injector_error) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 1b326a9..dd29d26 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1,6 +1,6 @@ import unittest -from autodraw.geometry import CanvasView, place_paths +from mthread_draw.geometry import CanvasView, place_paths class CanvasViewTests(unittest.TestCase): diff --git a/tests/test_gui.py b/tests/test_gui.py index d0ac557..f4d91f4 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -12,12 +12,12 @@ class GuiImportTests(unittest.TestCase): """The GUI needs a display to run, but it should at least import cleanly.""" def test_app_class_is_importable(self): - from autodraw.app import App + from mthread_draw.app import App self.assertTrue(hasattr(App, "run")) def test_entry_point_exists(self): - from autodraw.app import main + from mthread_draw.app import main self.assertTrue(callable(main)) diff --git a/tests/test_hand.py b/tests/test_hand.py index 95747b3..8fc3be5 100644 --- a/tests/test_hand.py +++ b/tests/test_hand.py @@ -1,7 +1,7 @@ import math import unittest -from adbtouch.hand import HandSettings, reorder_strokes, simulate +from mthread.hand import HandSettings, reorder_strokes, simulate def bounds(paths): diff --git a/tests/test_neural.py b/tests/test_neural.py index 602b0d0..edb3a3a 100644 --- a/tests/test_neural.py +++ b/tests/test_neural.py @@ -5,29 +5,29 @@ import numpy as np -from adbtouch import neural -from adbtouch.trace import ridges +from mthread import neural +from mthread.trace import ridges class ModelLocationTests(unittest.TestCase): def test_the_model_lives_outside_the_package(self): """A wheel must not grow by 46 MB, and a model should outlive an upgrade.""" - self.assertNotIn("adbtouch" + os.sep + "adbtouch", str(neural.model_path())) + self.assertNotIn("mthread" + os.sep + "mthread", str(neural.model_path())) self.assertTrue(str(neural.model_path()).endswith(".onnx")) def test_the_cache_can_be_pointed_somewhere_else(self): - with mock.patch.dict(os.environ, {"ADBTOUCH_CACHE": os.path.join("x", "y")}): + with mock.patch.dict(os.environ, {"MTHREAD_CACHE": os.path.join("x", "y")}): self.assertEqual(neural.cache_dir(), Path("x") / "y") def test_a_missing_model_is_not_a_present_one(self): - with mock.patch.dict(os.environ, {"ADBTOUCH_CACHE": os.path.join("nowhere", "at", "all")}): + with mock.patch.dict(os.environ, {"MTHREAD_CACHE": os.path.join("nowhere", "at", "all")}): self.assertFalse(neural.have_model()) def test_a_truncated_download_does_not_count_as_installed(self): import tempfile with tempfile.TemporaryDirectory() as tmp: - with mock.patch.dict(os.environ, {"ADBTOUCH_CACHE": tmp}): + with mock.patch.dict(os.environ, {"MTHREAD_CACHE": tmp}): neural.model_path().write_bytes(b"not really a model") self.assertFalse(neural.have_model()) @@ -35,7 +35,7 @@ def test_using_it_without_it_says_so_and_says_where(self): import tempfile with tempfile.TemporaryDirectory() as tmp: - with mock.patch.dict(os.environ, {"ADBTOUCH_CACHE": tmp}): + with mock.patch.dict(os.environ, {"MTHREAD_CACHE": tmp}): with self.assertRaises(neural.ModelUnavailableError) as ctx: neural.edge_probability(np.zeros((32, 32, 3), dtype=np.uint8)) message = str(ctx.exception) diff --git a/tests/test_paths.py b/tests/test_paths.py index 18cb517..2652f27 100644 --- a/tests/test_paths.py +++ b/tests/test_paths.py @@ -1,6 +1,6 @@ import unittest -from adbtouch.paths import drop_specks, join_strokes, stroke_length, tidy +from mthread.paths import drop_specks, join_strokes, stroke_length, tidy class LengthTests(unittest.TestCase): diff --git a/tests/test_player.py b/tests/test_player.py index 677b37b..a38f742 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -1,7 +1,7 @@ import unittest -from adbtouch.player import build_replay_script, iter_replay_chunks, replay -from adbtouch.session import InputEvent, Session +from mthread.player import build_replay_script, iter_replay_chunks, replay +from mthread.session import InputEvent, Session def events(*times): diff --git a/tests/test_recorder.py b/tests/test_recorder.py index c0b0617..33f99ec 100644 --- a/tests/test_recorder.py +++ b/tests/test_recorder.py @@ -1,6 +1,6 @@ import unittest -from adbtouch.recorder import parse_getevent_line +from mthread.recorder import parse_getevent_line class ParseGeteventLineTests(unittest.TestCase): diff --git a/tests/test_session.py b/tests/test_session.py index 7fa2c8e..ccd979f 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -3,7 +3,7 @@ import unittest from pathlib import Path -from adbtouch.session import SESSION_FORMAT_VERSION, InputEvent, Session +from mthread.session import SESSION_FORMAT_VERSION, InputEvent, Session def sample_session(): diff --git a/tests/test_touch.py b/tests/test_touch.py index c6cde7c..b79a585 100644 --- a/tests/test_touch.py +++ b/tests/test_touch.py @@ -1,6 +1,6 @@ import unittest -from adbtouch.touch import ( +from mthread.touch import ( ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_TRACKING_ID, diff --git a/tests/test_trace.py b/tests/test_trace.py index 362c43e..22ed792 100644 --- a/tests/test_trace.py +++ b/tests/test_trace.py @@ -2,7 +2,7 @@ import numpy as np -from adbtouch.trace import rank_strokes, thin, trace_skeleton +from mthread.trace import rank_strokes, thin, trace_skeleton def bar(height=40, width=60, thickness=5): diff --git a/tests/test_vectorize.py b/tests/test_vectorize.py index af8b0a3..dc8df84 100644 --- a/tests/test_vectorize.py +++ b/tests/test_vectorize.py @@ -3,7 +3,7 @@ import cv2 import numpy as np -from adbtouch.vectorize import VectorizeSettings, Vectorizer, dedupe_retrace +from mthread.vectorize import VectorizeSettings, Vectorizer, dedupe_retrace def _contours(image): @@ -99,7 +99,7 @@ def test_retrace_removal_shortens_the_output(self): _, deduped = self.vectorizer.process(settings) deduped_points = sum(len(p) for p in deduped) - import adbtouch.vectorize as module + import mthread.vectorize as module original = module.dedupe_retrace module.dedupe_retrace = lambda contour, **kwargs: contour try: diff --git a/tools/build_app.py b/tools/build_app.py index 26e83af..d7fb62c 100644 --- a/tools/build_app.py +++ b/tools/build_app.py @@ -1,4 +1,4 @@ -"""Build the packaged AutoDraw application, and its installer. +"""Build the packaged MThread Draw application, and its installer. One command, the same one CI runs, so a release can be reproduced locally: @@ -8,7 +8,7 @@ python tools/build_app.py --archive # + a .tar.gz / .zip of the app folder The result is self-contained: Python, OpenCV, Tk and adb all travel inside it, -so the person installing AutoDraw installs nothing else. +so the person installing MThread Draw installs nothing else. Requires PyInstaller, and for --msi the `wix` dotnet tool: @@ -30,7 +30,7 @@ ROOT = Path(__file__).resolve().parent.parent DIST = ROOT / "dist" BUILD = ROOT / "build" -SPEC = ROOT / "packaging" / "AutoDraw.spec" +SPEC = ROOT / "packaging" / "MThreadDraw.spec" IS_WINDOWS = sys.platform.startswith("win") IS_MAC = sys.platform == "darwin" @@ -39,11 +39,11 @@ def version() -> str: """Read the version from the package, so nothing carries a second copy.""" scope: dict = {} - for line in (ROOT / "autodraw" / "__init__.py").read_text(encoding="utf-8").splitlines(): + for line in (ROOT / "mthread_draw" / "__init__.py").read_text(encoding="utf-8").splitlines(): if line.startswith("__version__"): exec(line, scope) # noqa: S102 - a single literal assignment return scope["__version__"] - raise SystemExit("could not find __version__ in autodraw/__init__.py") + raise SystemExit("could not find __version__ in mthread_draw/__init__.py") def arch_tag() -> str: @@ -57,7 +57,7 @@ def run(*command: str, cwd: Path | None = None) -> None: def ensure_icon() -> None: - icon = ROOT / "packaging" / ("autodraw.ico" if IS_WINDOWS else "autodraw.png") + icon = ROOT / "packaging" / ("mthreaddraw.ico" if IS_WINDOWS else "mthreaddraw.png") if not icon.is_file(): run(sys.executable, ROOT / "tools" / "make_icon.py") @@ -70,7 +70,7 @@ def ensure_platform_tools(skip: bool) -> None: def build_app() -> Path: - app = DIST / ("AutoDraw.app" if IS_MAC else "AutoDraw") + app = DIST / ("MThreadDraw.app" if IS_MAC else "MThreadDraw") # Clear the previous app, not the whole dist directory: dist also holds the # installers, and on Windows something as ordinary as an open Explorer # window keeps a handle on the folder itself. @@ -92,8 +92,8 @@ def selftest(app: Path) -> None: Without this a broken build looks exactly like a working one until someone double-clicks it and gets a traceback in a dialog box. """ - executable = app / "Contents" / "MacOS" / "AutoDraw" if IS_MAC else app / ( - "AutoDraw.exe" if IS_WINDOWS else "AutoDraw") + executable = app / "Contents" / "MacOS" / "MThreadDraw" if IS_MAC else app / ( + "MThreadDraw.exe" if IS_WINDOWS else "MThreadDraw") report = BUILD / "selftest.txt" report.unlink(missing_ok=True) @@ -125,13 +125,13 @@ def build_msi(app: Path) -> Path: if shutil.which("wix") is None: raise SystemExit("wix not found. Install it with: dotnet tool install --global wix") - out = DIST / f"AutoDraw-{version()}-{arch_tag()}.msi" + out = DIST / f"MThreadDraw-{version()}-{arch_tag()}.msi" run( - "wix", "build", ROOT / "packaging" / "AutoDraw.wxs", + "wix", "build", ROOT / "packaging" / "MThreadDraw.wxs", "-arch", "x64", "-d", f"Version={version()}", "-d", f"SourceDir={app}", - "-d", f"IconFile={ROOT / 'packaging' / 'autodraw.ico'}", + "-d", f"IconFile={ROOT / 'packaging' / 'mthreaddraw.ico'}", "-o", out, ) size_mb = out.stat().st_size // 1024 // 1024 @@ -148,7 +148,7 @@ def build_dmg(app: Path) -> Path: if not IS_MAC: raise SystemExit("--dmg only works on macOS") - out = DIST / f"AutoDraw-{version()}-{arch_tag()}.dmg" + out = DIST / f"MThreadDraw-{version()}-{arch_tag()}.dmg" staging = BUILD / "dmg" if staging.exists(): shutil.rmtree(staging) @@ -159,7 +159,7 @@ def build_dmg(app: Path) -> Path: if out.exists(): out.unlink() - run("hdiutil", "create", "-volname", "AutoDraw", "-srcfolder", staging, + run("hdiutil", "create", "-volname", "MThread Draw", "-srcfolder", staging, "-ov", "-format", "UDZO", out) print(f"built {out} ({out.stat().st_size // 1024 // 1024} MB)") return out @@ -175,12 +175,12 @@ def build_winui() -> Path: if not IS_WINDOWS: raise SystemExit("--winui only works on Windows") - project = ROOT / "winui" / "AutoDraw.WinUI.csproj" - out = DIST / "AutoDraw-WinUI" + project = ROOT / "winui" / "MThreadDraw.WinUI.csproj" + out = DIST / "MThreadDraw-WinUI" run("dotnet", "publish", project, "-c", "Release", "-r", "win-x64", "--self-contained", "true", "-o", out) - engine = DIST / "autodraw-engine" + engine = DIST / "mthread-draw-engine" if not engine.is_dir(): raise SystemExit("the engine was not built; run without --no-adb first") target = out / "engine" @@ -195,7 +195,7 @@ def build_winui() -> Path: def build_archive(app: Path) -> Path: """A plain archive of the app folder, for people who do not want an installer.""" - stem = f"AutoDraw-{version()}-{platform.system().lower()}-{arch_tag()}" + stem = f"MThreadDraw-{version()}-{platform.system().lower()}-{arch_tag()}" if IS_WINDOWS: out = Path(shutil.make_archive(str(DIST / stem), "zip", root_dir=app.parent, base_dir=app.name)) else: diff --git a/tools/build_injector.py b/tools/build_injector.py index e388248..932ade9 100644 --- a/tools/build_injector.py +++ b/tools/build_injector.py @@ -5,7 +5,7 @@ python tools/build_injector.py -The result is adbtouch/injector.jar - a zip containing classes.dex, which is all +The result is mthread/injector.jar - a zip containing classes.dex, which is all app_process needs. It lives inside the package so that a wheel and a frozen build both carry it, and it is small enough (a few kilobytes) to be committed, so nobody needs an Android SDK just to use the library. @@ -22,8 +22,8 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -SOURCE = ROOT / "injector" / "src" / "com" / "adbtouch" / "Injector.java" -OUT_JAR = ROOT / "adbtouch" / "injector.jar" +SOURCE = ROOT / "injector" / "src" / "com" / "mthread" / "Injector.java" +OUT_JAR = ROOT / "mthread" / "injector.jar" WORK = ROOT / "build" / "injector" IS_WINDOWS = sys.platform.startswith("win") diff --git a/tools/canvas.html b/tools/canvas.html index 87bed54..e8fef53 100644 --- a/tools/canvas.html +++ b/tools/canvas.html @@ -3,7 +3,7 @@ -AutoDraw test canvas +MThreadDraw test canvas