Skip to content

Dylanmurzello/zed-android-port

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37,959 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Zdroid logo

Zdroid

Zed on Android.

Started as a joke. Rust on aarch64, sounded portable. Laughed about it. Kept going. Couldn't stop. There's an APK.

Alpha Android Total downloads

Zdroid is an independent port of Zed for Android, not affiliated with Zed Industries. Upstream's Editor, Workspace, Project, Search, GitGraph, Extensions, and Terminal crates run unchanged on a custom gpui_android platform backend that composites every pixel via the Adreno Vulkan driver, targeting Android 9+ with a hardware keyboard. A bundled Linux userland (Termux-derived, repackaged under com.zdroid) lets apt, bash, git, ssh, node, go, and rust-analyzer all run in-process from the app's private data dir; alternative runtime adapters route through a Kali chroot or an existing Termux install.


Zdroid demo

Vulkan via wgpu. AChoreographer-driven vsync, no JNI hop per frame. Opt-in 120Hz with Mailbox present mode. Glyph fallback into /system/fonts so Powerline arrows and CJK render without bundling fonts. The Editor, Workspace, Project, MultiWorkspace, Search, GitPanel, GitGraph, Extensions, and Terminal crates run unchanged. The Rust .so is the app process. gpui composites every pixel (yes, you read that right) straight into the Adreno Vulkan driver. Multi-Activity OS-chromed extra windows so DeX freeform renders Settings and secondary editors with real chrome.

Termux userland rebuilt under com.zdroid (applicationId byte-length pinned to 10 because prebuilt RUNPATHs in the .debs don't stretch). Musl loader hex-patched at runtime so Bun-compiled binaries like claude-code and codex resolve /etc/resolv.conf to a JNI-populated /sdcard/.zed/r. Optional Magisk-flashable zd-spawnd daemon with SCM_RIGHTS stdio relay for the chroot runtime. SurfaceControl-composited hardware cursor sprite on a sibling overlay, separate from the wgpu frame. Pointer-capture trackpad that consumes historical motion samples so finger drags don't lose 80% of their travel to event batching. SAF DocumentsProvider exposing ~/ as a system volume. Native Android trust via rustls-platform-verifier. In-app updater pulling signed APKs from GitHub Releases. Everything else is upstream. Deep-dives for the platform layer live in crates/gpui_android/docs/workarounds/.


 Install

Grab the latest Zdroid-X.Y.Z.apk from the releases page and open it in your file manager. Android prompts for unknown-source installs the first time; grant it. Reinstalls upgrade in place because every release ships from the same signing cert.

Note

Android may show a "built for an older version of Android" warning before you tap Install. Proceed anyway. targetSdk is pinned at 28 on purpose: the bundled Termux userland depends on the untrusted_app_27 SELinux domain, which permits execve on app-private files. Bumping targetSdk to 29+ lands the process in a stricter domain that denies exec, and the entire runtime stops working. See docs/workarounds/targetsdk-28-execve.md for the receipts.

First launch

  1. Storage permissions. The system prompts for read/write to /sdcard so the editor can reach anything outside its app-private dir. Grant it. Without it, "Open Project" can't see your files.
  2. Runtime adapter. A picker asks where every subprocess (shells, LSPs, terminal, git, ssh) should run. Nothing is pre-selected; you pick one of three. Bootstrap is the no-root option: a Termux-derived userland that runs entirely from the app's data dir, the right choice for most people. Kali chroot needs Magisk plus a Kali NetHunter rootfs but gives you real glibc and the fastest spawn. External Termux routes through your existing Termux app if you already daily-drive Termux.
  3. Bootstrap download. If you pick Bootstrap, the adapter pulls the userland zip from Dylanmurzello/zdroid-bootstrap and extracts it into the app's private data dir. About 30 seconds on a fast connection. Subsequent launches are instant.

Setting up your shell environment (Bootstrap)

Open the integrated terminal. First, sync the package index:

pkg update && pkg upgrade

Pre-baked in the bootstrap: rust-analyzer (the LSP binary; cargo and rustc are not bundled, install with pkg install rust if you want them), nodejs, go, bash, openssh, busybox, the bionic-compat patchelf, the hex-patched musl loader. npm is a separate Termux package and needs an install before user-facing npm calls work. Claude Code, for example:

pkg install npm
npm install -g @anthropic-ai/claude-code

Toolchains and LSPs for other languages have install recipes in the bootstrap repo: Dylanmurzello/zdroid-bootstrap.

Note

The first pkg install after extracting a fresh bootstrap will surface "broken dependencies" from apt and prompt you to run apt --fix-broken install. Run it. The pre-baked packages (go, openssh, busybox, etc.) declare dpkg dependencies that aren't formally registered in the database on a fresh extract, so apt flags the inconsistency the first time it has to resolve anything. fix-broken reconciles the state; pkg works normally afterward.

Setting up Kali chroot

Prerequisites: a rooted device with Magisk installed.

  1. Drop a Kali NetHunter aarch64 rootfs at /data/local/nhsystem/kali-arm64. NetHunter's installer is the standard path; any aarch64 Debian-derived rootfs at that location works.
  2. Flash the zd-spawnd Magisk module. Reboot.
  3. Open Zdroid and pick Kali chroot in the runtime picker.

Every subprocess the editor spawns (bash, git, LSPs, terminal shells) goes over a Unix socket to the zd-spawnd daemon, which forks, chroots, drops privileges, and execves on your behalf. Sub-millisecond spawn versus ~200 ms for su-mediated alternatives. All the bionic-vs-glibc gotchas (/usr/bin/env, /tmp, dlopen libfoo.so) disappear because subprocesses run inside a real distro.

Setting up External Termux

Important

External Termux is partially wired today. The runtime-picker entry exists and persistence works, but the JNI Intent bridge that actually dispatches subprocesses to Termux's RUN_COMMAND service hasn't landed yet (crates/zdroid_runtime/src/adapters/external_termux.rs is stubbed). Picking this adapter today writes the selection, but subprocess calls don't reach Termux. Use Bootstrap or Kali chroot for actual work in the meantime.

Setup, once the bridge lands:

  1. Install Termux from GitHub releases.
  2. The first time Zdroid attempts an external spawn, Android prompts you to grant com.termux.permission.RUN_COMMAND to Zdroid. Allow it.
  3. Open Zdroid and pick External Termux in the runtime picker.

After that, every subprocess routes into your existing Termux setup via com.termux.app.RunCommandService. Your ~/, your packages, your shell history are what subprocesses see; Zdroid stays a thin spawner.

Working with projects

Two storage realms underneath, with different exec rules.

/data/data/com.zdroid/files/ (surfaced as ~/) is exec-mounted. cargo, go, node, anything you build can execve and run. This is where projects should live. ~/projects/<name> is the default workspace root; ZedDocumentsProvider exposes ~/ to other Android apps via the SAF sidebar (look for Zdroid in any system file picker).

/storage/emulated/0/ (a.k.a. /sdcard/) is FUSE-mounted with noexec. Read, edit, and save all work; the kernel refuses to execute binaries written here. cargo run against a binary under /sdcard/... returns EACCES and there's no remount workaround (see docs/workarounds/android-noexec-mount.md for why).

Three workflows that work with this constraint:

  • Project root under ~/projects/ is the happy path. git clone, cargo new, builds, debugs, terminal subprocesses all run. Browse there from any Android app via the Zdroid → projects SAF sidebar entry.
  • Open a folder anywhere on /sdcard/ is fine if you're only reading or editing. The title bar shows a yellow Builds won't run · Move chip; one tap pops a confirm dialog that copies the project into ~/projects/<basename> and reopens it from the exec side.
  • ~/storage/{shared,downloads,dcim,documents,…} are curated symlinks into /sdcard. Use them for "open and edit a single file" workflows where you don't want to copy a whole tree.

 Gestures & input

Each surface toggles in Settings → Android Input (or on the first-run onboarding card).

Touch (default)

  • Tap to position the cursor
  • Tap and drag to scroll
  • Two-finger tap for right-click
  • Long-press to select a word
  • Long-press and drag to extend the selection
  • Scrollbar tap and drag snaps the thumb to your finger and follows

Virtual trackpad mode

Toggle the crosshair icon in the tab bar to turn touch into a pointer. Direct touch is disabled while it's on.

  • One-finger drag moves the cursor
  • Tap for left click, two-finger tap for right click
  • Hold then drag for text selection
  • Two-finger drag to scroll the content

Hardware mouse / trackpad

Plug in or pair a mouse, trackpad, or Book Cover Keyboard and it just works.

  • Scroll wheel and two-finger trackpad scroll to scroll
  • Right-click anywhere for context menus
  • The cursor hides when you switch to touch or typing and comes back on the next pointer event

Working on Samsung tablets and most Android devices we've tested. If your mouse pairs but doesn't move the cursor (seen on some OnePlus / Oppo Pad ColorOS builds), open an issue with adb shell dumpsys input output so we can triage.

Soft keyboard

Auto-opens on tap into the editor or terminal. The pane tab bar has a keyboard toggle if you want it off.

  • Programming keys row above the keyboard with Esc, Tab, Ctrl, Alt, and arrows. Ctrl and Alt are sticky: tap once for the next key, double-tap to lock.
  • Ctrl + letter combos work in the terminal: Ctrl+C sends ^C, just like a hardware keyboard.

Multi-window

Settings, the runtime picker, and any other spawned window each open as their own Recents entries and get their own soft keyboard and input.


 Userland

The editor is bionic-linked and runs as the Android app process. Every subprocess it spawns (bash, apt, language servers, formatters, terminal shells, git, ssh) routes through whichever runtime adapter the user picked. Three adapters ship; they version independently of the editor APK.

Adapter What it is Where it comes from
Bootstrap (no root) Termux userland rebuilt under com.zdroid: apt/dpkg/bash with our package name baked into RUNPATHs and shebangs. Pure bionic, no glibc; same trade-offs as any Termux install. apt and pkg install work for everything Termux ships. Downloaded from Dylanmurzello/zdroid-bootstrap after you pick Bootstrap in the runtime picker.
Kali chroot (needs Magisk) Real glibc Linux. Every spawn goes over a Unix socket to zd-spawnd (a small privileged daemon) which does fork + chroot + setuid + execve on the editor's behalf. ~5 ms per spawn vs ~200 ms for su-mediated. All the bionic gotchas (/usr/bin/env, /tmp, dlopen libfoo.so) disappear because subprocesses run inside a real distro. Flash the Magisk module from Dylanmurzello/zdroid-spawnd, plus drop a Kali NetHunter aarch64 rootfs at /data/local/nhsystem/kali-arm64.
External Termux (if you already use Termux) Talks to your existing Termux app via com.termux.permission.RUN_COMMAND intents. Lighter footprint; your existing userland stays untouched. JNI Intent bridge in progress (adapter at crates/zdroid_runtime/src/adapters/external_termux.rs is stubbed). Install Termux from GitHub releases; grant RUN_COMMAND to Zdroid.

Switching is one tap (Settings → Android Runtime). Selection persists in $PREFIX/etc/zd-runtime.toml. Restart Zdroid after switching adapters. The editor caches environment state (PATH, HOME, library search paths, spawn-router config) from whichever adapter was active at boot; without a restart, subprocesses spawned post-switch can land with stale env and fail in cryptic ways (LSPs not found, git claiming HOME doesn't exist, pkg install writing to the wrong rootfs, etc.).

When to pick which

  • No root, just want it to work: Bootstrap. apt, npm, go install, rust-analyzer all work. Rough edges: precompiled Bun CLIs (claude-code, codex) rely on a runtime hex-patch of /etc/resolv.conf to a /sdcard/.zed/r file that JNI populates each boot from ConnectivityManager.getActiveDnsServers(). Full writeup in zdroid-bootstrap/docs/hex-patch-resolv-conf.md. Some glibc-only extension binaries don't run.
  • Have Magisk, want real Linux: Kali chroot. Everything you'd expect on Debian/Kali works as-is, no shimming needed. The chroot is shared with whatever else uses that NetHunter rootfs.
  • Already on Termux: External adapter once the bridge lands. Your ~/, your packages, your shell history; Zdroid just spawns subprocesses there.

 What works

Remote SSH workspace Git graph Settings

  • Editor. Vulkan rendering, multi-pane workspace, vim mode, syntax highlighting, project panel, fuzzy file finder, command palette, buffer + project search.
  • Git. Git panel with staging, full git-graph commit history, diff view.
  • LSPs. rust-analyzer baked in. gopls, ts-server, pyright, jdtls install in one pkg/npm/go install.
  • Extensions. Browse, install, manage. Themes, language configs, grammars, slash commands.
  • Remote SSH. Server-picker pill in the title bar, persisted server list, native askpass.
  • Multi-window. Android freeform and DeX, each extra window is a real Activity with OS chrome.
  • Edge-to-edge rendering with content under the display cutout.
  • App menu bar with nested submenus (Settings, Keymap, Themes, Extensions).
  • Theme follow for system light/dark.
  • ZedDocumentsProvider exposes the project root as a SAF volume, so other Android apps can browse Zed's worktrees.

 Build from source

You'll need:

  • Rust toolchain with aarch64-linux-android (rustup target add aarch64-linux-android)
  • cargo-ndk (cargo install cargo-ndk)
  • Android NDK r27 (sdkmanager "ndk;27.0.12077973")
  • Gradle 8+, adb on $PATH
  • A device with USB debugging on
cd crates/gpui_android/examples/zed_android

ANDROID_NDK_HOME=/path/to/ndk/27.0.12077973 \
  cargo ndk -t arm64-v8a -P 26 -o android/app/src/main/jniLibs build

cd android
gradle assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.zdroid/.MainActivity

adb logcat -d | grep -E "zed_android|RustPanic|FATAL"

First build is around 10 minutes. Incremental Rust rebuilds are 20 seconds, Gradle re-pack a few seconds.


 Tested on

Samsung Galaxy Tab S9 Ultra (Snapdragon 8 Gen 2 / Adreno 740, Android 16, One UI 8) is the daily driver. Compiles for any aarch64 Android 9+ with Vulkan 1.1, but only Adreno is exercised. Mali / Xclipse will run but may want shader tweaks.

A hardware keyboard is the supported config. Tablet plus Bluetooth keyboard, foldable in tablet mode, or DeX/desktop-mode with monitor and peripherals all work. Phones technically run but are de-prioritized; see docs/workarounds/deferred-phone-form-factor-polish.md.


 License

GPL-3.0-or-later, same as upstream Zed. The Bootstrap-adapter zip (distributed from Dylanmurzello/zdroid-bootstrap, not bundled in the APK) contains Termux-rebuilt packages each under its own license (mostly BSD/MIT/Apache; gnupg/bash/coreutils are GPL). The Alpine-derived ld-musl-aarch64.so.1 inside it is MIT. The zd-spawnd daemon (Dylanmurzello/zdroid-spawnd) is GPL-3.0-or-later.

© Dylan Murzello, distributed under GPL-3.0-or-later. Zed itself is © Zed Industries.


 Acknowledgments


 So why this ?

Zed Industries' position on a mobile/tablet port: not planned.

This repo is what those threads were asking for, built independently. The Termux build attempt failed because the upstream wasmtime/cranelift deps don't compile inside Termux. We sidestep that by building the APK on a desktop with cargo-ndk and running our own custom Termux userland in process. No fork of upstream-Zed-with-android-cfg is needed; the Editor, Workspace, Project, Search, GitGraph, Terminal, Extensions crates run unchanged. The work is at the platform boundary, documented in crates/gpui_android/docs/workarounds/.

About

Native Android port of the Zed code editor, gpui rendering through Vulkan, integrated Termux for LSPs and tooling, remote SSH support. Runs as an APK on tablets and Android desktop modes.

Topics

Resources

License

Unknown and 2 other licenses found

Licenses found

Unknown
LICENSE-AGPL
Unknown
LICENSE-APACHE
GPL-3.0
LICENSE-GPL

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors