Omarchy's look, keybindings and theming on a phone — a Google Pixel 3a — running on Arch Linux ARM with Sway in Hyprland's place.
This is not a fork of Omarchy's installer. It is a thin overlay that vendors Omarchy's configuration and theme layer — which is architecture-neutral — onto an aarch64 base, and replaces the one part that cannot work on this hardware.
Download the image for your phone from Releases. There is no installer to run on the device and no default password to change: the account's password is locked and tty1 autologin brings the session up without one.
The bootloader must be unlocked. Unpack the sargo archive, put the phone in
fastboot (power off, hold Volume Down, tap Power), and run flash.sh. That
overwrites boot and userdata; the Android install does not survive it.
tar -xJf moarchy-sargo-*.tar.xz
cd moarchy-sargo-*
./flash.shTwo hard blockers, both verified rather than assumed.
1. Omarchy's package repo is x86_64-only.
https://pkgs.omarchy.org/stable/x86_64/omarchy.db -> 200 (211 packages)
https://pkgs.omarchy.org/stable/aarch64/omarchy.db -> 404
install/preflight/guard.sh upstream also requires x86_64, limine, a btrfs root
and vanilla-Arch markers — none of which hold on a phone.
2. There is no packaged device stack. No pacman repo anywhere carries an
SDM670 kernel or the Pixel 3a's firmware, so moarchy builds and publishes both,
at pins (docs/devices.md §2).
Omarchy 4.x generates its per-app theming from one small file per theme,
colors.toml, expanded through default/themed/*.tpl by
omarchy-theme-set-templates. The Hyprland template (hyprland.lua.tpl) is
18 lines — it sets window and group border colours and nothing else.
So the entire theme system ports by adding one file:
default/themed/sway.conf.tpl. All 22
upstream themes then work on Sway with no per-theme effort, and
shell.toml.tpl — which themes the whole quickshell shell — along with
foot.ini.tpl and btop.theme.tpl, are reused untouched. (alacritty.toml.tpl
is reused untouched too, and has been unused here since the phone went down to
one terminal on 2026-09-08 — see docs/apps.md.)
Better still, that template engine already processes a user template directory
(~/.config/omarchy/themed) ahead of its own built-ins — so moarchy adds
Sway theming without patching the vendored upstream at all.
docs/apps.md — every app the phone ships with, what each one
is, and screenshots straight off the device for the ones that have been run on
it. Short version: GNOME's
libadwaita apps and KDE's Kirigami/Plasma Mobile apps both reflow to a 360px
screen and are the comfortable fit; availability is not the constraint, screen
width is.
docs/style.md is the style contract — the type scale, the six
colour roles, the four radii, the 44px touch-target floor and the motion
budget. It binds the shell plugins in this repo and
the two surfaces that are not in it: the
keyboard and the
store. Three programs, three
toolkits — quickshell/QML, standalone Qt, Python and GTK4 — one palette, read
from the theme omarchy-theme-set stages at
~/.local/state/omarchy/current/theme/.
scripts/style-check.sh enforces the half of it that is visible in the source.
docs/build-log.md is the chronological account — every
measurement, and every dead end, including the ones that cost the most time
(RNDIS vs macOS, a write-protected SD adapter, Docker Desktop's missing Landlock,
and an assumption about Omarchy 4.x that turned out to be wrong twice over).
moarchy runs Omarchy v4.0.4 (c668141), pinned in
manifest.toml. There is no waybar, walker, mako or swayosd
in the package set: in 4.x the bar, launcher, notifications and OSD are one
quickshell/QML shell, and the phone UI is a set of plugins on top of it
rather than a patched copy of it.
An earlier port targeted v3.8.4 (8fcc9d6) — waybar, walker, mako and swayosd
— on the reading that v4.0.0 had moved to herdr, a bespoke shell shipped
only as an x86_64 binary. That reading was wrong twice over: herdr is neither
the shell nor closed source.
docs/omarchy-4x-feasibility.md is the
correction. The 3.8.4 port is gone; it exists only in git history.
Why 4.x is portable. The shell is quickshell/QML —
architecture-neutral, and quickshell builds for aarch64. What is left is
Hyprland coupling in five QML files, and pkgbuilds/omarchy-config/port-4x.patch
translates those
mechanically: Quickshell.Hyprland becomes Quickshell.I3, which speaks Sway's
IPC. The one genuine gap is HyprlandFocusGrab, which has no I3 counterpart, so
vendored popups lose click-outside-to-dismiss.
Sway's bindgesture only fires for touchpads, so the gestures are a Quickshell
plugin that owns the bottom edge as a layer surface and reads the touch itself.
Everything follows the finger rather than firing at a threshold.
One drag up from the home pill has two stops, the way Android's does:
0 -------------- 50% -------------- 100% -- past the top
closes opens HOME
| Gesture | What it does |
|---|---|
| Swipe up from the pill, short | The app drawer |
| Swipe up from the pill, further | Home — a workspace with nothing on it |
| Swipe up on the wallpaper | The app drawer, tracking the finger 1:1 |
| Press and hold the pill | The default coding agent, in a terminal — the picker, with none chosen yet |
| Swipe left / right | Next / previous workspace, which is next / previous app |
| Swipe in from the right edge | The workspace overview — every workspace as a card, one under the other |
| Tap an app on a card | Switch to that app |
| Drag an app off its card | Move it to another workspace — two on one workspace tile, one above the other — or into the bin to close it |
| Pull down from the status bar | The control center: quick settings, brightness, media |
Every up-swipe raises the app drawer — over an app, over a home screen, over
nothing (docs/gestures.md A5). The app drawer is a launcher and nothing else:
what is already running is the workspace overview's subject, and one surface answers
"where is everything" rather than two that show the same windows in two shapes.
Nothing on the strip closes a window either: apps are closed by dragging them
into the workspace overview's bin, one at a time (C4, P12).
The two vertical edges are one gesture each, and the right one is the map. A swipe in from it pulls the workspace overview across: every workspace as a card, one under the other, each showing the windows on it. Tap a card to go there, tap an app to go straight to it, or drag an app onto another card to move it — which is the one way a workspace comes to hold two. Drag it into the bin at the foot of the sheet and it closes, which is the one place a window is closed by hand.
Two windows side by side on a 360px screen get 180px each, which nothing here
can use — so a workspace that holds more than one is split vertically, one
above the other, at 370px each. bin/moarchy-one-app-per-workspace does that on
sway's own event stream, so a keyboard user's $mod+Shift+2 lands the same way
as the drag. Launching an app still gives it a workspace of its own — sharing one
is something you ask for, once, by dragging.
The hold is the other gesture Android spends on what its owner reaches for most. Here that is the coding agent, and the pill shakes while the press counts down — a 4px line under a motionless thumb otherwise looks exactly like a 4px line under a thumb that is only resting, and a gesture nobody can see happening is a gesture nobody finds (C1, C2).
Everything is reachable without a finger, which is how the selftest asserts it:
omarchy-shell workspace-overview windows # one line per open window
omarchy-shell gestures swipe home
moarchy-selftest --gestures # drives real synthetic touch via /dev/uinputTen files worth knowing about. For the whole map — the eleven shell plugins and what each owns, how the screens stack, where a given change goes, and what to run — see docs/README.md § The code.
| Path | What it is |
|---|---|
default/omarchy/plugins/ |
The phone UI: thirteen quickshell plugins and the shared code under moarchy.common/ |
manifest.toml |
The version pins. The only file that says what version of anything is built |
pkgbuilds/ |
moarchy, omarchy-config (upstream + the Sway port as a patch), moarchy-meta, moarchy-keyring |
pkgbuilds/moarchy-meta/PKGBUILD |
The aarch64 package set, as depends, with every omission explained |
default/sway/bindings.conf |
Omarchy's bindings, translated to Sway, key-for-key |
pkgbuilds/moarchy-device-sargo/sway.conf |
1080×2220 @ scale 3, touch, no gaps, the power key |
default/themed/sway.conf.tpl |
The one file that themes Sway from any Omarchy theme |
bin/moarchy-* |
Sway counterparts to Omarchy's Hyprland helpers |
bin/omarchy-* |
Shims with upstream's names, so omarchy-menu keeps working |
docker/ |
aarch64 container that builds every package natively on Apple Silicon |
image/boot/android-bootimg.sh |
The boot backend: mkbootimg, AVB, the sparse rootfs |
The image ships the [moarchy] repository, so everything updates with one
command — the kernel, firmware and modem stack from [moarchy] itself,
and the phone UI from moarchy:
sudo pacman -SyuThe database and every package are signed, and pacman.conf says
SigLevel = Required, so an unsigned or altered package is refused rather than
installed as root.
Shell files land immediately but the running shell keeps the old code until it restarts — a pacman hook says so after an upgrade:
moarchy-restart-shell # or rebootAdding the repo to a phone that predates it, or to a stock DanctNIX
install, needs the key first — moarchy-keyring is itself signed, so pacman
will not install it without already trusting the key:
S=https://github.com/SimonSchubert/moarchy/releases/download/repo
curl -fsSLO $S/moarchy.asc
sudo pacman-key --init
sudo pacman-key --add moarchy.asc
sudo pacman-key --lsign-key 3CA83612E7F3108F442006B418305B893569BAD3
printf '\n[moarchy]\nSigLevel = Required\nServer = %s\n' "$S" | sudo tee -a /etc/pacman.conf
sudo pacman -Syu moarchy-metaReflashing is still needed for changes to the partition layout, to what runs on first boot, or to the u-boot SPL — that lives outside any partition, at byte 131072.
./scripts/provision.sh build # the packages, in an aarch64 container
./scripts/build-image.sh # -> images/moarchy-sargo-<version>-<date>/
./scripts/verify-image.sh # 93 checks against the image just builtEverything comes from the commits pinned in manifest.toml, so two runs a
month apart produce the same image. The package build produces the
components (moarchy-keyboard, moarchy-store-git, moarchy-keep,
moarchy-vitals), the AUR rebuilds (yay, xdg-terminal-exec,
ttf-ia-writer, cbonsai, lcl-gui-bin, mise-bin), and pkgbuilds/
(moarchy, omarchy-config, moarchy-meta, moarchy-keyring, and a
moarchy-device-* per phone).
For a debug image that joins your wifi on first boot and enables sshd:
WIFI_SSID='MyNetwork' WIFI_PSK='secret' \
MOARCHY_SSH_KEY=~/.ssh/id_ed25519.pub ./scripts/build-image.shBoth are optional and both mark the image as a debug build. MOARCHY_SSH_KEY
authorises that key for the phone's account and enables sshd, which saves the
round trip through the card that a reflash otherwise costs — the account has no
password, so there is no other way back in.
Do not publish either. The PSK is a secret; the key is not, but an
authorized_keys in a public image would have every phone that flashes it trust
one person's key. ./scripts/verify-image.sh fails an image carrying either.
A reflash wipes /home/moarchy/.ssh, and the account has no password, so a
published image locks you out of your own phone until a key is authorised
again. Rather than pulling the SD card, type one line on the phone:
curl -sL https://raw.githubusercontent.com/SimonSchubert/moarchy/main/scripts/authorize-ssh.sh | sh -s <github-user>That authorises every public key on https://github.com/<github-user>.keys,
enables sshd, and prints the phone's address and host-key fingerprint. The
username is required and has no default, for the same reason a published image
carries no authorized_keys.
./scripts/provision.sh build
./scripts/provision.sh deploy # scp the packages over
./scripts/provision.sh install # one pacman transactionKept: Omarchy's keybindings, all 22 themes with live switching, the whole
quickshell shell — bar, launcher, notifications, OSD — the omarchy-menu
system, and the themed terminal/btop/fastfetch/starship stack.
Gone, because they are Hyprland renderer features that a GLES 2.0 device could
never have driven: blur, shadows, rounded corners, animations, gradient borders,
hyprlock (replaced with a themed swaylock).
Also dropped: universal copy/paste (Hyprland sendshortcut has no Sway
equivalent), OCR capture (tesseract has no aarch64 build), dictation
(voxtype is x86-only), and every x86-only proprietary app — 1Password, Spotify,
Obsidian, Typora. See the bottom of pkgbuilds/moarchy-meta/PKGBUILD for the
full list with reasons.
Numbers from a real Pixel 3a (SDM670, 4 GB), not estimates:
| GPU | Adreno 615 on freedreno — GLES 3.2 and Vulkan |
| Panel | 1080x2220, scale 3 -> 360x740 logical |
| Storage | rootfs in userdata, grown on first boot |
| btop minimum | 60 columns, regardless of shown_boxes |
That last row is why moarchy-launch-tui drops TUIs to font size 7 (~60
columns): at Omarchy's desktop font size, btop simply refuses to draw on this
screen. The window is a normal tiled one — the bar and the keyboard anchor to
opposite edges, so neither costs a column, and a fullscreened terminal would
only hide the keyboard.
Wi-Fi, Bluetooth, sound, camera, vibration, NFC, calls with voice on them and
SMS in both directions are all measured on the handset rather than reported;
docs/devices.md D27–D34 is the evidence.
- Telephony has only been tried on an operator with circuit-switched
fallback. A VoLTE-only network needs an IMS stack that is not here yet —
docs/devices.md§10. - The camera's colour profile is matrix-only — no HueSatMap and no look table, so colour is correct rather than pleasing.
Video recording (Megapixels' path wants GStreamer plugins nothing declares), power tuning beyond the idle/blank path, and rotation sensor handling — the control center's Rotate tile is manual.
The camera, telephony, suspend, the package repository and the on-screen
keyboard were all on this list and are not any more. The camera entry here read
"VIDIOC_STREAMON fails on both sensors" until 2026-09-06, and that was never
the problem: nothing was configuring the media graph, which is what
libmegapixels does. See docs/apps.md.





