An Android app that runs a .py file you pick, with a live console: stdout/stderr
streamed to the screen, a stdin box for input(), and Ctrl+C / Ctrl+D buttons.
Python execution is provided by Chaquopy (bundled
CPython 3.11), so no server or network connection is needed to run a script.
- MainActivity — UI: pick a file (
ACTION_OPEN_DOCUMENT) or reopen one from a recent-scripts list, then Run/Stop and drive the console. - PyWorkerService — hosts the Chaquopy interpreter in its own process
(
android:process=":pyworker"), so a stuck script (e.g.http.server.serve_forever()) can't freeze the UI, and can be killed by killing just that process. Runs as a foreground service with a notification while a script is active, so it survives the app being backgrounded or swiped from Recents. - runner.py (
app/src/main/python/runner.py) — the harness that actually loads and executes the chosen script viarunpy.run_path, withsys.stdout/stderr/stdinrewired to the app's console. - runpy_ui.py (
app/src/main/python/runpy_ui.py) — the public API scripts can import:runpy_ui.menu(options, prompt="")shows a native, fuzzy-searchable picker dialog. Tapping a row is a shortcut for typing that row's text and submitting it — rofi-style, the user can also just type or paste anything and press Enter/OK to submit that literal text instead, whether or not it matches a listed option. Returns the submitted string, orNoneif cancelled. Useful for "pick one of these" cases where a plaininput()prompt would be tedious — e.g. choosing a URL from a list.runpy_ui.prompt(message="", default="")shows a plain native text-input dialog (no list) and returns the typed text, orNoneif cancelled.
- Protocol — the
Messenger-based message contract between the activity and the worker service (stdin/stdout/interrupt/done/menu request-reply). - IoBridge — a small Kotlin object passed into Python as
sys.stdout/stderr; Chaquopy lets Python call itswrite()directly. - UiBridge / FuzzyMatch —
UiBridgeis passed into Python alongsideIoBridgeso a script'srunpy_ui.menu()/runpy_ui.prompt()calls can askMainActivityto show a dialog (an empty options list renders as a plain input box);FuzzyMatchis a small, dependency-free reimplementation of the subsequence-matching idea behind fzf (there's no real terminal orfzfbinary available inside the app's sandboxed Python process, so the live-filtering has to be done natively). - RecentScripts — copies each picked file into app-private storage under a random UUID directory (so a hostile filename can't path-traverse) and keeps the 15 most recently used. Entries can be pinned (long-press one in the "Recent scripts" dialog); pinned scripts show up as one-tap buttons at the top of the main screen — tapping one runs it immediately, no separate select-then-Run step — and are protected from the 15-entry eviction that unpinned recents are subject to.
Stop works by killing the whole :pyworker process, which is the only reliable way
to interrupt a script blocked in a C-level call (socket accept(), time.sleep(),
etc.). Ctrl+C is best-effort: it raises KeyboardInterrupt asynchronously in the
script's thread, which only takes effect on the next executed Python bytecode.
By default Path.home() / ~ resolve to the app's private storage. Granting
"All files access" (Android 11+) points it at the device's real shared storage
instead.
- Android Studio (or the Gradle wrapper + an Android SDK) with SDK 35 installed
- A device or emulator running Android 7.0+ (API 24), 64-bit (arm64-v8a or x86_64)
./gradlew assembleDebug
./gradlew installDebug # with a device/emulator connectedOr open the project in Android Studio and run the app configuration.
- Launch the app and tap Choose Python File… to pick a
.pyfile (or Recent to reopen one you've run before — long-press an entry there to pin/unpin it). - Optionally grant full storage access so
Path.home()points at real device storage. - Tap Run (or tap a pinned script's button at the top to run it immediately).
Output streams into the console below; use the stdin box for
input(), and Ctrl+C/Ctrl+D for interrupt/EOF. - Tap Stop to hard-kill a running script.
app/src/main/java/com/example/runpy/ Kotlin sources (Activity, service, protocol)
app/src/main/python/runner.py Python execution harness
app/src/main/res/ Layout, strings, theme
app/src/main/AndroidManifest.xml
- support share python script to it to run