Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ tag names, define the order. See the table at the bottom.

### Added

- `lfa wayland` (alias `lfa wl`) adds a nested Wayland session. It installs
Sway with a profile that copies the Hyprland keybinds, gaps, borders,
colours, Waybar bar, notification centre, and wallpaper; starts it on
Termux:X11; and stops it cleanly. `--config` imports hypr, waybar, kitty,
wofi, swaync, fastfetch, and starship configs, `--resolution` sets the
nested output size, and `--wallpaper` replaces the background. Hyprland is installed and used when a
writable `/dev/dri/renderD*` exists. A stock phone has no such node, so
Hyprland aborts with `CBackend::create() failed!` and the Sway profile runs
instead. `status`, `doctor`, and `session` report which compositor is live.
- `lfa appimage` (alias `lfa app`) adds first-class AppImage support. `add`
extracts an AppImage once into `~/.local/opt/appimages`, writes a launcher
in `~/.local/bin`, and adds a desktop entry; `list`, `run`, and `remove`
Expand Down
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [GNOME (experimental)](#gnome-experimental)
- [Key features](#key-features)
- [Installation](#installation)
- [Wayland sessions](#wayland-sessions)
- [Older and custom distro versions](#older-and-custom-distro-versions)
- [First-run setup](#first-run-setup)
- [Older devices (Android 5/6)](#older-devices-android-56-eg-oppo-a37fw)
Expand Down Expand Up @@ -133,7 +134,7 @@ hardware, not simulated. Full details, root causes, and evidence are in
| Alpine 3.18 (Android 5) | Yes | Yes | legacy VNC desktop, panel works; X11 and Both hidden | v1.7.1 |
| Ubuntu GNOME 46 (experimental) | Yes | Yes | shell and apps run; session services missing | v1.7.0 |

> The test suite runs `627` checks with 0 failures. The same suite runs on the
> The test suite runs `682` checks with 0 failures. The same suite runs on the
> devices (`tests/device-matrix.sh`). The table is as of v1.7.1. Ubuntu was
> re-verified on v1.7.1, and Alpine 3.18 was re-verified on Android 5 on
> 2026-11-04; the other rows were last verified on v1.7.0. A row is
Expand Down Expand Up @@ -472,6 +473,7 @@ is accepted.
| `lfa appimage` / `lfa app` | extract an AppImage once and add a menu entry |
| `lfa run` | run an AppImage once without installing it |
| `lfa storage` | Android storage status, repair, run, and protected data helpers |
| `lfa wayland` / `lfa wl` | nested Wayland session: install, start, stop, status, doctor, and compositor choice |
| `lfa diagnostics` | write a redacted support bundle |
| `lfa notice` | `on`, `off`, or `status` for the `start-linux` change notice |
| `lfa version` / `lfa help` | version and command list |
Expand All @@ -490,6 +492,9 @@ lfa android test
lfa appimage add ~/Foo.AppImage
lfa appimage run Foo
lfa storage status
lfa wayland install -i arch --resolution 2408x1080
lfa wayland start -i arch
lfa wayland status -i arch
lfa notice off
```

Expand Down Expand Up @@ -618,6 +623,41 @@ Then `lfa storage data status` connects to `127.0.0.1:5555`. The connection
lasts until the phone reboots. After a reboot, run `adb tcpip 5555` again, or
enable Wireless debugging and pair Termux's adb once.

## Wayland sessions

Termux:X11 is an X11 server, so a Wayland compositor has to run nested inside
it. `lfa wayland` installs Sway with a profile that copies the Hyprland look,
starts it, and stops it. Hyprland is installed too, and started instead when
the phone has a DRM render node that an app can open.

The stock phone does not have one. `/dev/dri/card0` is owned by
`system:system` with mode `660`, and there is no `/dev/dri/renderD128`.
Hyprland's Aquamarine back end needs a render node for dmabuf, so it exits with
`CBackend::create() failed!`. Sway with the pixman renderer does run nested on
X11. The profile keeps the host keybinds, gaps, borders, colours, Waybar,
SwayNC, wallpaper, and the Android battery reading.

```bash
lfa wayland install -i arch --resolution 2408x1080
lfa wayland install -i arch --config ~/hypr-config.tar.gz
lfa wayland start -i arch
lfa wayland status -i arch
lfa wayland doctor -i arch
lfa wayland stop -i arch
```

`--config` imports `hypr`, `waybar`, `kitty`, `wofi`, `swaync`, and `fastfetch`
directories plus `starship.toml` from a folder or a tarball. `--resolution` sets the nested output size, which
is useful because the phone panel is portrait. `lfa wayland session` chooses
`auto`, `hyprland`, `sway`, or `xfce`.

The Vivo V2437 was tested at 2408x1080 with Waybar, SwayNC, SwayBG, the
launcher, the battery bridge, and a working `grim` screenshot. Hyprland
0.57.1 parses the host config but cannot start on that device. Full detail,
logs, and the DRM explanation are in [docs/WAYLAND.md](docs/WAYLAND.md).

![Sway session on the Vivo V2437](docs/screenshots/wayland-sway.png)

## Older and custom distro versions

The installer defaults to one image per distro. On proot-distro builds that
Expand Down Expand Up @@ -1185,7 +1225,8 @@ short list is:
- kernel modules, real `mount`/`chroot`/`unshare`, real root/SELinux control
- Bluetooth/BlueZ, NFC, camera as `/dev/video*`, sensors, USB host as device
nodes
- Wayland compositors, simultaneous X11 + VNC, foreign-architecture binaries
- Hyprland on a phone without a writable DRM render node, simultaneous X11 + VNC,
foreign-architecture binaries

Full reasons, the “works with caveats” list, Android 5/6/7 differences, and
what Termux:API can actually expose are in
Expand Down Expand Up @@ -1496,8 +1537,8 @@ test procedure, and how to add a distro or desktop.
- Bug reports: use the issue form. It asks for version, device, distro, and
logs.
- Security problems: use [SECURITY.md](SECURITY.md). Never a public issue.
- Design questions: `docs/DECISIONS.md` explains why Wayland, real `sudo`, and
systemd are out of scope.
- Design questions: `docs/DECISIONS.md` explains why real `sudo` and systemd are
out of scope, and what a nested Wayland session can and cannot do.

### Running the test suite

Expand All @@ -1510,7 +1551,7 @@ plus `script` from util-linux for the pty test, which is skipped if absent:

```bash
chmod +x tests/test-all.sh
./tests/test-all.sh # expect: PASS=627 FAIL=0
./tests/test-all.sh # expect: PASS=682 FAIL=0
```

The suite is deliberately stubbed. It cannot prove a desktop actually appears.
Expand Down
5 changes: 3 additions & 2 deletions SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ answer depends on what you need.
1. Read the [detailed troubleshooting guide](docs/TROUBLESHOOTING.md). The
[short index](README.md#troubleshooting-index) is in the README.
2. Check [issues.md](issues.md). The limitation may be known and deliberate
(for example, no Wayland, no real `sudo`, GNOME is beta).
(for example, no DRM access for Hyprland, no real `sudo`, GNOME is beta).
3. Collect a support bundle and open a bug report:

```bash
Expand Down Expand Up @@ -37,6 +37,7 @@ block plus a stub test. A pull request is welcome. See

## What is out of scope

- Wayland sessions (see [docs/DECISIONS.md](docs/DECISIONS.md), D2)
- Hyprland on a phone that does not expose a DRM render node to apps (see
[docs/WAYLAND.md](docs/WAYLAND.md))
- real privilege escalation or `systemctl` inside the guest (D10)
- Play Store Termux. It is broken by design and not supported.
85 changes: 85 additions & 0 deletions assets/wayland/start-lfa-wayland
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/sh
# Linux for Android: start a Wayland desktop inside Termux:X11.
#
# The phone has no DRM render node that a normal app may open, so Hyprland
# cannot start on its own. When a render node is available (rooted device or
# a future Android build), this script nests Hyprland inside a minimal Sway
# host. Otherwise it runs Sway with the Hyprland-style profile, which keeps
# the same keybinds, gaps, borders and bar.
set -u

COMP="${LFA_WAYLAND_COMPOSITOR:-auto}"
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
mkdir -p "$CACHE" 2>/dev/null || true

export XDG_SESSION_TYPE=wayland
export XDG_CURRENT_DESKTOP=sway
export MOZ_ENABLE_WAYLAND=1
export GTK_THEME=Adwaita-dark
export XCURSOR_THEME=Adwaita-Tiny
export XCURSOR_SIZE=16

if [ -z "${XDG_RUNTIME_DIR:-}" ]; then
export XDG_RUNTIME_DIR="/tmp/xdg-$USER-arch"
fi
mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null || true
chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true

if [ "$COMP" = "xfce" ]; then
exec startxfce4
fi

hyprland_ready() {
command -v Hyprland >/dev/null 2>&1 || return 1
# Aquamarine's nested backend needs a DRM render node it can open.
for _n in /dev/dri/renderD*; do
[ -r "$_n" ] && [ -w "$_n" ] && return 0
done
return 1
}

run_nested() {
WLR_BACKENDS=x11 WLR_RENDERER=gles2 \
sway -c "$HOME/.config/lfa-wayland/sway-nest/config" \
>"$CACHE/lfa-wayland-host.log" 2>&1 &
_pid=$!
sleep 8
if pgrep -x Hyprland >/dev/null 2>&1; then
wait "$_pid"
return 0
fi
kill "$_pid" 2>/dev/null || true
wait "$_pid" 2>/dev/null || true
return 1
}

run_sway() {
_cfg="$1"
_renderers="pixman"
for _n in /dev/dri/renderD*; do
[ -r "$_n" ] && [ -w "$_n" ] && _renderers="gles2 pixman"
done
for _r in $_renderers; do
WLR_BACKENDS=x11 WLR_RENDERER="$_r" \
sway -c "$_cfg" >"$CACHE/lfa-wayland-sway-$_r.log" 2>&1
[ "$?" = "0" ] && return 0
done
return 1
}

if [ "$COMP" = "hyprland" ] || [ "$COMP" = "auto" ]; then
if hyprland_ready; then
if run_nested; then
exit 0
fi
echo "Hyprland could not start; using Sway." >&2
elif [ "$COMP" = "hyprland" ]; then
echo "Hyprland needs an accessible /dev/dri render node; using Sway." >&2
fi
fi

if run_sway "$HOME/.config/lfa-wayland/sway/config"; then
exit 0
fi

exec startxfce4
21 changes: 21 additions & 0 deletions assets/wayland/sway-nest/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Host compositor for a nested Hyprland session.
# Termux:X11 is an X11 display, so Sway starts with its X11 backend and
# gives Hyprland a Wayland socket. No bar and no wallpaper here: Hyprland
# draws its own.

set $mod Mod4

gaps inner 0
gaps outer 0
default_border none
smart_gaps off
smart_borders off

output * bg #000000 solid_color

exec Hyprland

for_window [app_id="Hyprland"] fullscreen enable
for_window [app_id="hyprland"] fullscreen enable

bindsym $mod+Shift+E exit
85 changes: 85 additions & 0 deletions assets/wayland/sway/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Linux for Android: Wayland session.
# This profile mirrors the Hyprland desktop: same gaps, borders, keybinds,
# wallpaper and bar palette. It runs inside Termux:X11 through Sway's X11
# backend, because a phone without DRM cannot run Hyprland itself.

set $mod Mod4
set $term kitty
set $menu wofi --show drun
set $file thunar

font pango:Adwaita Sans 10

gaps inner 3
gaps outer 6
default_border pixel 2
smart_gaps off
smart_borders on

client.focused #7ad6e0 #7ad6e0 #17181d #7aa2f7 #7ad6e0
client.focused_inactive #232d31 #232d31 #d7dae1 #232d31 #232d31
client.unfocused #232d31 #232d31 #8a909c #232d31 #232d31
client.urgent #ff7b86 #ff7b86 #17181d #ff7b86 #ff7b86

output * bg /usr/share/backgrounds/lfa-wayland.jpg fill

exec waybar -c $HOME/.config/lfa-wayland/waybar/config.jsonc -s $HOME/.config/lfa-wayland/waybar/style.css
exec swaync
exec dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway

# Launcher and windows
bindsym $mod+Return exec $term
bindsym $mod+Q kill
bindsym $mod+F exec $file
bindsym $mod+V floating toggle
bindsym $mod+Space exec $menu
bindsym $mod+D split toggle
bindsym $mod+Shift+Return fullscreen toggle
bindsym $mod+Escape exec swaylock
bindsym $mod+Shift+E exit

# Focus and movement
bindsym $mod+H focus left
bindsym $mod+L focus right
bindsym $mod+K focus up
bindsym $mod+J focus down
bindsym $mod+Shift+H move left
bindsym $mod+Shift+L move right
bindsym $mod+Shift+K move up
bindsym $mod+Shift+J move down

# Resize
bindsym $mod+Ctrl+H resize shrink width 10 px
bindsym $mod+Ctrl+L resize grow width 10 px
bindsym $mod+Ctrl+K resize shrink height 10 px
bindsym $mod+Ctrl+J resize grow height 10 px

# Workspaces
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10

# Audio
bindsym XF86AudioRaiseVolume exec pamixer -i 5
bindsym XF86AudioLowerVolume exec pamixer -d 5
bindsym XF86AudioMute exec pamixer -t

# Screenshot
bindsym Print exec grim -g "$(slurp)" - | wl-copy
Loading
Loading