diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d055e6..ce787e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/README.md b/README.md index 652834f..1b37cc2 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 | @@ -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 ``` @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/SUPPORT.md b/SUPPORT.md index e341be7..736dcb6 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -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 @@ -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. diff --git a/assets/wayland/start-lfa-wayland b/assets/wayland/start-lfa-wayland new file mode 100755 index 0000000..75f0952 --- /dev/null +++ b/assets/wayland/start-lfa-wayland @@ -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 diff --git a/assets/wayland/sway-nest/config b/assets/wayland/sway-nest/config new file mode 100644 index 0000000..e902532 --- /dev/null +++ b/assets/wayland/sway-nest/config @@ -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 diff --git a/assets/wayland/sway/config b/assets/wayland/sway/config new file mode 100644 index 0000000..85e910e --- /dev/null +++ b/assets/wayland/sway/config @@ -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 diff --git a/assets/wayland/waybar/config.jsonc b/assets/wayland/waybar/config.jsonc new file mode 100644 index 0000000..8eccb6f --- /dev/null +++ b/assets/wayland/waybar/config.jsonc @@ -0,0 +1,71 @@ +{ + "reload_style_on_change": true, + "layer": "top", + "position": "top", + "spacing": 0, + "height": 26, + "modules-left": ["custom/launcher", "sway/workspaces", "mpris"], + "modules-center": ["sway/window"], + "modules-right": ["tray", "custom/battery", "custom/audio", "custom/net", "custom/power", "clock"], + "custom/launcher": { + "format": "󰣇", + "on-click": "wofi --show drun", + "on-click-right": "kitty", + "tooltip-format": "Launcher\n\nSuper + Space" + }, + "sway/workspaces": { + "format": "{icon}", + "format-icons": { + "1": "I", + "2": "II", + "3": "III", + "4": "IV", + "5": "V", + "6": "VI", + "7": "VII", + "8": "VIII", + "9": "IX", + "10": "X", + "default": "•" + }, + "on-click": "activate" + }, + "sway/window": { + "format": "{}", + "max-length": 50 + }, + "mpris": { + "format": "{}", + "format-paused": " {}", + "max-length": 30 + }, + "custom/battery": { + "exec": "~/.config/lfa-wayland/waybar/scripts/battery.sh", + "interval": 30, + "format": "{}", + "tooltip": false + }, + "custom/audio": { + "exec": "pamixer --get-volume-human", + "interval": 2, + "format": "󰕾 {}", + "tooltip": true, + "on-click": "pamixer -t", + "on-scroll-up": "pamixer -i 5", + "on-scroll-down": "pamixer -d 5" + }, + "custom/net": { + "format": "󰤨", + "tooltip": false + }, + "custom/power": { + "format": "\uf011", + "tooltip": false, + "on-click": "swaymsg exit" + }, + "clock": { + "format": "󰃰 {:%H:%M}", + "format-alt": " {:%a %d %b}", + "tooltip-format": "{calendar}" + } +} diff --git a/assets/wayland/waybar/scripts/battery.sh b/assets/wayland/waybar/scripts/battery.sh new file mode 100755 index 0000000..b06958a --- /dev/null +++ b/assets/wayland/waybar/scripts/battery.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Battery for the Android host, read from the Termux:API bridge in /tmp. +pct=100 +state=unknown +if [ -r /tmp/lfa-android-battery.env ]; then + # shellcheck disable=SC1090 + . /tmp/lfa-android-battery.env + pct=${BATTERY_PERCENT:-100} + state=${BATTERY_STATE:-unknown} +fi +case "$pct" in ''|*[!0-9]*) pct=100 ;; esac +if [ "$state" = "CHARGING" ] || [ "$state" = "FULL" ]; then + icon="󰂄" +elif [ "$pct" -ge 90 ]; then icon="󰁹" +elif [ "$pct" -ge 70 ]; then icon="󰂁" +elif [ "$pct" -ge 50 ]; then icon="󰁿" +elif [ "$pct" -ge 30 ]; then icon="󰁽" +elif [ "$pct" -ge 10 ]; then icon="󰁻" +else icon="󰁺" +fi +printf '%s %s%%' "$icon" "$pct" diff --git a/assets/wayland/waybar/style.css b/assets/wayland/waybar/style.css new file mode 100644 index 0000000..63d1784 --- /dev/null +++ b/assets/wayland/waybar/style.css @@ -0,0 +1,98 @@ +/* Graphite / teal. Same palette as the host: kitty, wofi, swaync and starship. */ +@define-color background #0b1013; +@define-color foreground #d7dae1; +@define-color accent #7ad6e0; +@define-color hairline #232d31; +@define-color surface #23262e; + +* { + background-color: transparent; + color: @foreground; + border: none; + border-radius: 0; + min-height: 0; + font-family: 'JetBrainsMono Nerd Font'; + font-size: 12px; +} + +window#waybar { + background-color: alpha(@background, 0.90); + border-bottom: 1px solid alpha(@foreground, 0.05); +} + +.modules-left { + margin-left: 8px; +} + +.modules-right { + margin-right: 8px; +} + +/* Workspaces: Roman numerals, accent underline on the focused one. */ +#workspaces button { + transition: all 90ms ease-in-out; + padding: 0 6px; + margin: 0 1.5px; + min-width: 9px; + opacity: 0.4; + border-bottom: 2px solid transparent; +} + +#workspaces button.occupied { + opacity: 0.9; + border-bottom-color: alpha(@foreground, 0.55); +} + +#workspaces button.focused { + opacity: 1; + border-bottom: 3px solid @accent; +} + +#workspaces button:hover { + opacity: 1; + background-color: alpha(@foreground, 0.10); + border-radius: 4px; +} + +#custom-launcher { + font-size: 15px; + margin: 0 8px 0 2px; + color: @accent; +} + +#mpris, +#tray, +#battery, +#custom-audio, +#network, +#custom-power, +#clock { + margin: 0 6px; +} + +#battery, +#network, +#custom-audio { + padding: 0 6px; + border-radius: 6px; + background-color: alpha(@foreground, 0.06); + border: 1px solid alpha(@foreground, 0.05); +} + +#custom-power { + color: @foreground; +} + +#clock { + font-weight: 600; +} + +tooltip { + background: alpha(@background, 0.96); + border: 1px solid @hairline; + border-radius: 8px; +} + +tooltip label { + color: @foreground; +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c3c32ae..dd78e52 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -92,6 +92,12 @@ the server when the login command returns. `exec` matters for shutdown: a shell loop defers SIGTERM until its current `sleep` returns, so `stop-linux` could leave the proot wrapper behind. The single sleep process dies at once. +`lfa wayland` adds a third session on the same display. It starts Sway with +`WLR_BACKENDS=x11` inside Termux:X11, so the compositor is a client of the X +server. Hyprland runs as a client of a small Sway host when a writable DRM +render node exists. The session has its own pidfile and log, and it refuses to +start while an X11 or VNC desktop is up. + An install can carry both back ends. `SERVER="both"` installs both package sets, and `start-linux` resolves the choice to `x11` or `vnc` before starting. Only one runs at a time because they share `:1` and port `5901`. The last @@ -115,10 +121,11 @@ on every install and on menu 12. That copy is what lets the command line keep working after the repository is moved, renamed, or deleted. Preferences that are not tied to one distro live in -`~/.config/linux-for-android/preferences.conf`. The only key today is +`~/.config/linux-for-android/preferences.conf`. The key there is `START_NOTICE`, written by `lfa notice on|off`. The notice is printed to stderr by the classic `start-linux` path only; `linux`, non-interactive runs, -and Termux:Boot are silent. +and Termux:Boot are silent. The Wayland compositor choice lives next to it in +`wayland.conf`. ## Android version cutoffs diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 7be4e96..3d1f11d 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -12,15 +12,22 @@ Why: no root, no bootloader, works on a stock phone, one command. Cost: no real UIDs, no systemd, no loop mounts, no kernel modules. Every other decision below follows from this one. -## D2: X11 and VNC only, no Wayland - -Decision: no Wayland backend is planned. - -Why: Wayland needs a compositor with a real session, DRM access, and often -systemd. Under proot the best case is a nested compositor on top of X, which is -slower and more fragile than the X11 session it replaces. - -Cost: X11 is legacy upstream, and some modern toolkits deprecate it. +## D2: Wayland runs nested, and Hyprland waits for a DRM node + +Decision: ship `lfa wayland` as a nested session. Sway is the compositor on a +stock phone. Hyprland starts only when the app can open a DRM render node. + +Why: Termux:X11 is an X11 server, and Sway has an X11 backend, so a nested +session works without root. Hyprland's Aquamarine back end asks for +`zwp_linux_dmabuf_v1` and a render node while it starts. A stock Android app +cannot open `/dev/dri/card0`, and there is no `/dev/dri/renderD128`, so +Hyprland aborts with `CBackend::create() failed!`. A profile that copies the +Hyprland keybinds, gaps, borders, and colours gives the same look on Sway. + +Cost: software rendering with pixman, no hardware GL in Wayland clients, and no +promise that Hyprland's blur, shadows, or screencast work. The Sway profile has +to be kept in step with the Hyprland config by hand. X11 is still the default +session for every distro. ## D3: the sudo ritual instead of a real sudo diff --git a/docs/INCOMPATIBLE.md b/docs/INCOMPATIBLE.md index 0d31f99..af7e224 100644 --- a/docs/INCOMPATIBLE.md +++ b/docs/INCOMPATIBLE.md @@ -24,7 +24,7 @@ on a fast phone. | `snapd`, Snap Store, all snap packages, Ubuntu's snap-only Firefox/Chromium | snapd needs systemd, mounts and `CAP_SETFCAP`. There is no confinement. `lfa compat` maps a few named snaps to distro packages instead. | | Flatpak, Flathub applications, any bubblewrap-sandboxed application | needs user namespaces and a working sandbox. LFA can only run the program unsandboxed when the program has a fallback. | | Docker, Podman, containerd, LXC/LXD, libvirt, VirtualBox, VMware, QEMU/KVM, the Android Emulator, Waydroid | need namespaces, cgroups, `/dev/kvm`, or a host daemon. A proot wrapper is not Docker-compatible and is not presented as one. | -| Sway, Hyprland, Wayfire, GNOME Wayland, KDE Wayland, any Wayland compositor | LFA provides X11 (Termux:X11) or VNC. There is no Wayland compositor integration. | +| Hyprland, GNOME Wayland, KDE Wayland, or any compositor that needs DRM and a seat | an Android app cannot open `/dev/dri/card0` and there is no `/dev/dri/renderD128`, so Aquamarine and libweston cannot allocate buffers. `lfa wayland` runs Sway nested on X11 instead, and starts Hyprland only where a writable render node exists. | | Real `sudo`/`su`/setuid, PAM, real `mount`, real `chroot`, `unshare` | Android marks app processes `NO_NEW_PRIVS`; setuid cannot elevate. LFA's `sudo` is a password ritual plus a narrow `uid0` library for tools such as `dpkg`. | | Kernel modules, DKMS, eBPF, `perf`, `ftrace` | an Android app cannot load modules or control the kernel. | | `tcpdump`, packet capture, `nmap -sS`, raw sockets | Termux has no raw-socket capability. `nmap -sT` (a normal connect scan) may work. | @@ -45,6 +45,7 @@ These may start, or may work on some distros, but LFA does not promise them. |---|---| | Full GNOME session | experimental. `gnome-session` needs systemd/logind, so LFA starts `gnome-shell --x11` directly. Login/logout, screen locking, suspend/inhibit, UPower, portals, notifications and some Settings panels are missing or unstable. GNOME 49+ removed X11. Not offered on Alpine/Void or Android 5/6/7. | | KDE Plasma, Cinnamon, Budgie, Deepin, Pantheon, LXQt | not offered or tested. Many assume systemd, Wayland, or a full session manager. | +| Nested Sway through `lfa wayland` | starts on X11 with the pixman renderer. Wayland clients get no hardware GL, dmabuf screencast may fail, and the bar uses Waybar's Sway modules rather than Hyprland's. | | Steam, Proton, Wine games with anti-cheat or DXVK | no GPU/anti-cheat environment. Simple Wine applications may work, but this is not a supported target. | | Blender GPU/Cycles CUDA/OptiX, DaVinci Resolve, OBS hardware encoders, GPU video editors | no CUDA/OptiX and no hardware encode. CPU rendering may work but is slow. | | CUPS/USB printing, SANE scanners, Bluetooth audio | no hardware passthrough. | diff --git a/docs/README.md b/docs/README.md index 66a1cd9..f8fd955 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,8 @@ Start here to understand the project rather than install it. | [AUTHORS.md](../AUTHORS.md) | Project authorship and credits | | [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Diagnosis: evidence, root causes, verification, recovery | | [ARCHITECTURE.md](ARCHITECTURE.md) | How the installer, launchers, and display back ends fit together | -| [DECISIONS.md](DECISIONS.md) | Why proot, why no Wayland, why the sudo shim is a ritual | +| [WAYLAND.md](WAYLAND.md) | Nested Wayland sessions, the Sway profile, and why Hyprland needs a DRM node | +| [DECISIONS.md](DECISIONS.md) | Why proot, why Wayland runs nested, why the sudo shim is a ritual | | [TEST-REPORT.md](TEST-REPORT.md) | What was verified, on which device, with which version | | [CI-ANDROID.md](CI-ANDROID.md) | Android emulator workflows, matrix tiers, pinned APKs, and gaps | | [INCOMPATIBLE.md](INCOMPATIBLE.md) | Software that cannot run, and why shims cannot fix kernel limits | @@ -23,7 +24,7 @@ Start here to understand the project rather than install it. 1. `README.md` quick start. You should be able to run the suite. 2. `ARCHITECTURE.md`. The mental model in ten minutes. -3. `DECISIONS.md`. Read this before proposing Wayland or systemd. +3. `DECISIONS.md`. Read this before proposing a real Wayland session or systemd. 4. `TROUBLESHOOTING.md`. The failures users hit. 5. `issues.md`. Pick an item marked partial or deferred. 6. `CONTRIBUTING.md`. The lint and device-test gate before a pull request. diff --git a/docs/TEST-REPORT.md b/docs/TEST-REPORT.md index bb00fb7..6c78ad1 100644 --- a/docs/TEST-REPORT.md +++ b/docs/TEST-REPORT.md @@ -1,6 +1,42 @@ # Test report for v1.7.1 -> Current development suite: `PASS=627 FAIL=0`. +> Current development suite: `PASS=682 FAIL=0`. + +## Fresh Arch reinstall, 2026-11-08 + +The Vivo V2437 was wiped and rebuilt through LFA, then themed from the host. + +| Step | Result | +|---|---| +| `lfa remove -i arch -y` | old 3.0G container removed | +| `lfa install --distro archlinux --desktop xfce4 --backend x11 --user pdev -i arch` | completed in 29m 16s | +| `lfa wayland install -i arch --config ~/host-config.tar.gz --resolution 2408x1080 --wallpaper ~/lfa-wayland-wallpaper.jpg` | 91 packages plus Hyprland, profile and host configs applied | +| Session | Sway at 2408x1080; Waybar, SwayNC, SwayBG, kitty, wofi | +| Host look | host wallpaper, graphite and teal Waybar, kitty, wofi, swaync, GTK dark, Adwaita Sans 11, Adwaita-Tiny cursor, Starship prompt with `pdev@arch` | +| Battery | 46% charging, refreshed by the Termux:API loop | +| Fastfetch | host config and Arch wordmark render correctly | +| Hyprland | installed; still blocked by the missing DRM render node | + +## Wayland session, 2026-11-08 + +Verified on the Vivo V2437 (Android 16, SDK 36, aarch64) with the unreleased +Wayland changes. + +| Check | Result | +|---|---| +| Host suite | `PASS=682 FAIL=0` | +| `lfa wayland install -i arch --resolution 2408x1080` | packages and profile installed | +| `lfa wayland start -i arch` | Termux:X11, Sway, Waybar, SwayNC, and SwayBG started | +| Nested output | `swaymsg` reported `2408x1080`; `grim` screenshot checked | +| Battery bridge | Waybar showed the Android percentage and charging state | +| `lfa wayland status -i arch` | `session: running (Sway)`, `drm: render=none` | +| Hyprland 0.57.1 | config parses; start aborts with `CBackend::create() failed!` because no writable render node exists | +| `lfa wayland stop -i arch` | compositor and bar processes removed | + +The Wayland session uses Sway's X11 backend and the pixman renderer. Hyprland +cannot start on this device: `/dev/dri/card0` is `system:system` mode `660` and +`/dev/dri/renderD128` is absent. The Sway profile keeps the Hyprland keybinds, +gaps, borders, colours, and bar layout. See [WAYLAND.md](WAYLAND.md). ## Android storage, 2026-11-06 @@ -9,7 +45,7 @@ storage changes. | Check | Result | |---|---| -| Host suite | `PASS=627 FAIL=0` | +| Host suite | `PASS=682 FAIL=0` | | Thunar false X | gid 1023; folder icons normal; screenshot checked | | `lfa storage exec` | shell and Python scripts from `/storage/emulated/0` ran from a `/tmp` copy | | `lfa storage data` | adb at `127.0.0.1:5555`; `list` and `pull` worked for `Android/data` and `Android/obb`; guest read the copy | @@ -33,7 +69,7 @@ the network during this pass, so the legacy-device rows remain pending. | Check | Result | |---|---| -| Host suite (`./tests/test-all.sh`) | `PASS=627 FAIL=0` | +| Host suite (`./tests/test-all.sh`) | `PASS=682 FAIL=0` | | On-device VNC scenario (`termux-e2e.sh`) | `PASS=14 FAIL=0` | | On-device Both → VNC/X11 switch | `PASS=22 FAIL=0` | | On-device X11 scenario | `PASS=14 FAIL=0` | diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 5a1e620..9d5667c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -48,6 +48,7 @@ Commands that say *inside the distro* are run in a guest shell opened with | Screen is the wrong size or rotated | [§4.2](#42-wrong-resolution-or-rotation) | | Touch pointer is offset, keys are missing | [§4.3](#43-touch-pointer-keyboard-and-clipboard) | | VNC black screen or connection refused | [§4.4](#44-vnc-does-not-connect-or-shows-black) | +| Wayland session fails, or Hyprland prints `CBackend::create() failed!` | [§4.5](#45-a-wayland-session-or-hyprland-fails) | | VS Code / Chromium / Antigravity do nothing | [§5.1](#51-electron-apps-do-nothing-at-all) | | Firefox or Chromium shows a sandbox warning | [§5.2](#52-firefox-or-chromium-shows-a-sandbox-warning) | | App is missing from the XFCE menu | [§5.3](#53-app-is-missing-from-the-menu) | @@ -816,6 +817,56 @@ ssh -N -L 5901:127.0.0.1:5901 -p 8022 u0_aNNN@PHONE_IP Then point the viewer at `localhost:5901`. Do not expose 5901 to the network; VNC's password is not a substitute for SSH. +### 4.5 A Wayland session or Hyprland fails + +Symptom: `lfa wayland start` exits, the session dies after a few seconds, or +Hyprland prints `CBackend::create() failed!`. + +Why: the phone has no DRM render node that an app can open. Aquamarine, the +Hyprland back end, needs one for dmabuf. Sway can fall back to its X11 backend +and the pixman renderer, so it works where Hyprland cannot. + +Fix: + +1. Read the state and the logs: + + ```bash + lfa wayland status -i arch + lfa wayland doctor -i arch + tail -n 40 ~/lfa-wayland.log + tail -n 40 ~/.cache/lfa-wayland-sway-pixman.log + ``` + +2. Check the render nodes. `render=none` means Hyprland cannot start: + + ```bash + ls -l /dev/dri/ + ``` + + On the Vivo V2437, `card0` is `system:system` mode `660` and there is no + `renderD128`. A rooted device or a vendor build with a render node may + differ. + +3. Use the Sway profile, which keeps the Hyprland keybinds and look: + + ```bash + lfa wayland session sway + lfa wayland start -i arch --restart + ``` + +4. If the session was killed from outside, clear the leftover processes: + + ```bash + lfa wayland stop -i arch + lfa wayland start -i arch + ``` + +5. To go back to XFCE, run `lfa wayland session xfce`, or stop the session and + use `lfa start -i arch`. + +The full explanation and the verified device results are in +[WAYLAND.md](WAYLAND.md). + ## 5. Applications ### 5.0 `systemctl`, `snap`, Docker, or CUDA still fail @@ -1581,5 +1632,6 @@ adb shell cmd deviceidle whitelist +com.termux | `Process completed (signal 9)` | [§10.2](#102-android-kills-termux-signal-9) | | `The security sandbox is disabled` | [§5.2](#52-firefox-or-chromium-shows-a-sandbox-warning) | | `unsupported command-line flag: --no-sandbox` | [§5.2](#52-firefox-or-chromium-shows-a-sandbox-warning) | +| `CBackend::create() failed!` | [§4.5](#45-a-wayland-session-or-hyprland-fails) | | `Unable to locate package` | [§2.10](#210-package-not-found-after-installation) | | `Release file ... is not valid yet` | [§9.2](#92-apt-tls-or-clock-errors) | diff --git a/docs/WAYLAND.md b/docs/WAYLAND.md new file mode 100644 index 0000000..e67d3c4 --- /dev/null +++ b/docs/WAYLAND.md @@ -0,0 +1,149 @@ +# Wayland sessions + +`lfa wayland` runs a Wayland desktop inside Termux:X11. It installs a session +profile, starts a compositor, and reports which compositor is actually running. + +The command exists because a phone can run a Wayland compositor nested on X11. +It cannot give that compositor a DRM device. Hyprland needs one, so on a stock +phone LFA starts Sway with a profile that matches the Hyprland look. When a +writable render node exists, the same command starts Hyprland inside a small +Sway host instead. + +## What the phone provides + +Termux:X11 is an X11 server. Sway has an X11 backend, so it starts nested in +that server with `WLR_BACKENDS=x11`. The phone has no usable DRM render node +for an app, so the nested Sway uses the pixman software renderer. + +Hyprland uses Aquamarine for its back end. Aquamarine's Wayland back end asks +for `zwp_linux_dmabuf_v1` and opens a DRM render node while it initialises +dmabuf support. On the Vivo V2437 the only node is `/dev/dri/card0`, owned by +`system:system` with mode `660`, and `/dev/dri/renderD128` does not exist. +Opening the card fails with `EACCES`. Hyprland then aborts with: + +```text +terminate called after throwing an instance of 'std::runtime_error' + what(): CBackend::create() failed! +``` + +That is a device permission limit. The packages are installed and the config +parses. A rooted device, a vendor build that exposes a render node to apps, or +a future Android release can change it. LFA checks for a writable +`/dev/dri/renderD*` at start time and only then tries Hyprland. + +## Commands + +| Command | What it does | +| --- | --- | +| `lfa wayland status -i NAME` | show instance, distro, user, preference, session state, packages, and DRM nodes | +| `lfa wayland install -i NAME` | install the compositor packages and the session profile | +| `lfa wayland start -i NAME` | start Termux:X11 if needed, then the session | +| `lfa wayland stop -i NAME` | stop the session and any orphaned compositor | +| `lfa wayland doctor -i NAME` | status plus the launcher, X server, log tail, and the DRM explanation | +| `lfa wayland session [NAME]` | read or set `auto`, `hyprland`, `sway`, or `xfce` | +| `lfa wl ...` | alias for `lfa wayland` | + +`lfa wayland start` refuses to run while an X11 or VNC desktop is already up +for the same instance. Use `--restart` to replace it. + +## Install + +```bash +lfa wayland install -i arch +lfa wayland install -i arch --resolution 2408x1080 +lfa wayland install -i arch --wallpaper ~/Pictures/wall.jpg +lfa wayland install -i arch --config ~/hypr-config.tar.gz +``` + +`--config` accepts a directory or a `.tar.gz` with `hypr`, `waybar`, `kitty`, +`wofi`, `swaync`, or `fastfetch` subdirectories, plus a top-level +`starship.toml`. Each one is copied into the guest user's `~/.config`. That is +how the host's desktop files get in without a manual `scp`. The Starship +hostname is pinned to the instance name, because proot reports the Android +kernel hostname as `localhost`. + +`--resolution` appends an output rule to the Sway profile: + +```text +output X11-1 resolution 2408x1080 position 0 0 +``` + +The rule is rewritten on the next install, so it does not pile up. The phone +screen is 1080x2408, and the session uses 2408x1080 to match it in landscape. + +On Arch the install adds `sway`, `swaybg`, `swaylock`, `waybar`, `swaync`, +`kitty`, `wofi`, `thunar`, the Adwaita and JetBrains Mono Nerd fonts, `grim`, +`slurp`, `wl-clipboard`, `xorg-xwayland`, and the Hyprland packages. Debian and +Ubuntu get the Sway side from apt where the package names exist. SwayNC is +installed in a second step because not every release packages it. The +Hyprland packages are Arch only. Alpine, Fedora, Void, and the legacy images +are not handled yet; the command says so and exits. + +## The profile + +The Sway profile is written to `~/.config/lfa-wayland/sway/config` in the +guest. It sets: + +- Super as the modifier, with the same keybinds as the Hyprland config: + terminal, launcher, file manager, kill, fullscreen, floating, focus, move, + resize, workspaces, audio, and screenshot. +- 3 px inner gaps, 6 px outer gaps, 2 px borders, and the graphite and teal + colours from the host. +- `waybar` with a Sway workspace module, the launcher, media, tray, battery, + audio, network, power, and clock. +- `swaync` for notifications and `swaybg` for the wallpaper. +- `dbus-update-activation-environment` so portals and the tray work. + +The battery module reads `/tmp/lfa-android-battery.env`, which the Termux:API +bridge writes. It shows the real Android percentage and charging state. +`lfa wayland start` keeps that file fresh with a one-minute refresher, and +`lfa wayland stop` ends it. Without Termux:API the module shows the last value +or 100%. + +The Hyprland host profile is `~/.config/lfa-wayland/sway-nest/config`. It runs +one window, Hyprland, fullscreen, with no bar and no wallpaper. + +## Verified behaviour + +On the Vivo V2437, Android 16, arm64: + +- `lfa wayland install -i arch --resolution 2408x1080` installed the packages + and the profile. +- `lfa wayland start -i arch` started Termux:X11 and Sway, with Waybar, + SwayNC, SwayBG, and a working launcher. +- `swaymsg -t get_outputs` reported `2408x1080`. +- `grim` captured the session, and the screenshot showed the wallpaper, bar, + and notification centre. +- `lfa wayland status -i arch` reported `session: running (Sway)` and + `drm: render=none`. +- Hyprland 0.57.1 is installed and its config parses, but it aborts with + `CBackend::create() failed!` on this device. + +![Sway session on the Vivo V2437 at 2408x1080, with Waybar and the Android battery reading](screenshots/wayland-sway.png) + +## Logs and recovery + +| Path | Content | +| --- | --- | +| `~/lfa-wayland.log` | session output, D-Bus, portals | +| `~/.cache/lfa-wayland-sway-pixman.log` | Sway with the software renderer | +| `~/.cache/lfa-wayland-sway-gles2.log` | Sway when a render node was found | +| `~/.cache/lfa-wayland-host.log` | the Sway host for a nested Hyprland | + +If the session dies, `lfa wayland doctor -i NAME` prints the last lines. A +killed proot parent can leave Sway behind; `lfa wayland stop` cleans those up +with `pkill` inside the guest. + +To go back to XFCE for one session, run `lfa wayland session xfce` and start +again. To remove the preference, set it to `auto`. + +## Limits + +- Software rendering only on a stock phone. Expect slower video and no + hardware GL in Wayland clients. +- No screen sharing through dmabuf. `grim` works; portal screencast may not. +- Hyprland's own features that need its renderer, such as blur and shadows, + are unavailable while it cannot start. +- One Wayland session per instance, on display `:1`. +- The Sway profile copies the look and keybinds. The bar runs Waybar's Sway + modules, so Hyprland-only modules are absent. diff --git a/docs/screenshots/wayland-sway.png b/docs/screenshots/wayland-sway.png new file mode 100644 index 0000000..ecd2bf3 Binary files /dev/null and b/docs/screenshots/wayland-sway.png differ diff --git a/ghissue.md b/ghissue.md index fad2b99..d7792e0 100644 --- a/ghissue.md +++ b/ghissue.md @@ -59,7 +59,7 @@ maintained by hand; see `tracker.md` for code work. | # | Item | Impact | Effort | Status | |---|---|---|---|---| | G29 | Add `docs/ARCHITECTURE.md`: the generator-inside-generator design, session model, and why proot and X11 are used. | 4 | 1 h | done | -| G30 | Add `docs/DECISIONS.md`: why proot, why no Wayland, why ritual sudo, why GNOME is beta. | 4 | 45 min | done | +| G30 | Add `docs/DECISIONS.md`: why proot, why nested Wayland, why ritual sudo, why GNOME is beta. | 4 | 45 min | done | | G31 | Add a `docs/` index and link `docs/TEST-REPORT.md` from the first screen. | 3 | 15 min | done: `docs/README.md` index | | G32 | Link `issues.md` from the README with severities. | 4 | 10 min | done | | G33 | Add badges for release version, CI status, downloads, and license. Replace the three static ones. | 4 | 15 min | done: CI, release, Android range, distros | diff --git a/install_linux.sh b/install_linux.sh index f4940ba..73f8a6a 100755 --- a/install_linux.sh +++ b/install_linux.sh @@ -1346,6 +1346,564 @@ lfa_diagnostics() { return 1 } +# ============================================================ +# Wayland session manager. +# +# The phone's X server is Termux:X11, which is an X11 display. A Wayland +# compositor therefore has to run nested inside it. Sway can do that with +# its X11 backend. Hyprland can only do it when Aquamarine can open a DRM +# render node, which an unrooted Android app cannot do. The manager starts +# Hyprland when that node is usable and falls back to Sway with the +# Hyprland-style profile otherwise. +# ============================================================ +LFA_WAYLAND_ASSET_SUBDIR="assets/wayland" + +lfa_wayland_usage() { + cat <<'WLHELP' +Usage: lfa wayland [options] + + status show compositor availability and session state + install install the Wayland stack in a distro + start start the Wayland session + stop stop the Wayland session + doctor diagnose packages, DRM, configs, and logs + session [NAME] choose auto, hyprland, sway, or xfce + help show this help + +Options: + -i, --instance NAME distribution to use + --compositor NAME start this session with auto, hyprland, sway, xfce + --restart stop a running session first + --config DIR import a config bundle (hypr, waybar, kitty, ...) + --resolution WxH set the X11 output size (for example 2408x1080) + --wallpaper FILE copy FILE to the session wallpaper + +Hyprland needs a DRM render node that the app can open. On an unrooted +Android device there is usually no usable /dev/dri/renderD*, so LFA starts +Sway with the same keybinds, gaps, borders, wallpaper, and bar instead. +The Hyprland profile is installed and used automatically when the device +can provide that node. +WLHELP +} + +lfa_wayland_asset_dir() { + local _d="$LFA_DIR/$LFA_WAYLAND_ASSET_SUBDIR" + [ -d "$_d" ] || _d="$PREFIX/lib/linux-for-android/$LFA_WAYLAND_ASSET_SUBDIR" + [ -d "$_d" ] || return 1 + printf '%s' "$_d" +} + +lfa_wayland_pref_file() { + printf '%s' "$HOME/.config/linux-for-android/wayland.conf" +} + +lfa_wayland_pref() { + local _f _v="auto" + _f="$(lfa_wayland_pref_file)" + if [ -r "$_f" ]; then + # shellcheck disable=SC1090 + _v="$( . "$_f" 2>/dev/null; printf '%s' "${COMPOSITOR:-auto}" )" + fi + printf '%s' "$_v" +} + +lfa_wayland_set_pref() { # $1 = auto|hyprland|sway|xfce + local _v="${1:-auto}" _f + case "$_v" in auto|hyprland|sway|xfce) ;; *) echo "Unknown compositor: $_v" >&2; return 2 ;; esac + _f="$(lfa_wayland_pref_file)" + mkdir -p "$(dirname "$_f")" 2>/dev/null || true + printf 'COMPOSITOR="%s"\n' "$_v" > "$_f" +} + +lfa_wayland_pidfile() { + printf '%s' "$PREFIX/tmp/lfa-wayland-$1.pid" +} + +lfa_wayland_running() { # $1 = instance + local _p _f + _f="$(lfa_wayland_pidfile "$1")" + if [ -f "$_f" ]; then + _p="$(tr -dc '0-9' < "$_f" 2>/dev/null)" + if [ -n "$_p" ] && lfa_pid_matches "$_p" "start-lfa-wayland" "sway" "Hyprland"; then + return 0 + fi + fi + # A killed proot parent can leave the compositor behind; the config path + # is unique to this manager. + pgrep -f 'lfa-wayland/sway/config' >/dev/null 2>&1 && return 0 + return 1 +} + +lfa_wayland_active() { + if pgrep -x Hyprland >/dev/null 2>&1; then + printf 'Hyprland' + elif pgrep -f 'lfa-wayland/sway/config' >/dev/null 2>&1; then + printf 'Sway' + else + printf 'none' + fi +} + +lfa_wayland_battery_start() { + local _pidfile="$PREFIX/tmp/lfa-wayland-battery.pid" _p _j _pct _st + command -v termux-battery-status >/dev/null 2>&1 || return 0 + _p="" + [ -f "$_pidfile" ] && _p="$(tr -dc '0-9' < "$_pidfile" 2>/dev/null || true)" + if [ -n "$_p" ] && kill -0 "$_p" 2>/dev/null; then + return 0 + fi + ( + while :; do + _j="$(lfa_android_battery_json 2>/dev/null || true)" + if [ -n "$_j" ]; then + _pct="$(printf '%s' "$_j" | sed -n 's/.*"percentage"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1)" + _st="$(printf '%s' "$_j" | sed -n 's/.*"status"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)" + { + printf 'BATTERY_PERCENT=%s\n' "${_pct:-unknown}" + printf 'BATTERY_STATE=%s\n' "${_st:-unknown}" + printf 'BATTERY_TIME_EMPTY=unknown\nBATTERY_TIME_FULL=unknown\n' + } > "$PREFIX/tmp/lfa-android-battery.env.new" 2>/dev/null && \ + mv -f "$PREFIX/tmp/lfa-android-battery.env.new" \ + "$PREFIX/tmp/lfa-android-battery.env" 2>/dev/null || true + fi + sleep 60 + done + ) >/dev/null 2>&1 & + printf '%s\n' "$!" > "$_pidfile" +} + +lfa_wayland_battery_stop() { + local _pidfile="$PREFIX/tmp/lfa-wayland-battery.pid" _p="" + [ -f "$_pidfile" ] && _p="$(tr -dc '0-9' < "$_pidfile" 2>/dev/null || true)" + [ -n "$_p" ] && kill "$_p" 2>/dev/null || true + rm -f "$_pidfile" 2>/dev/null || true +} + +lfa_wayland_guest() { # $1 = instance, $2 = user, $3 = shell command + # shellcheck disable=SC2046 # flag list, not one argument + proot-distro login $(pd_login_flags) "$1" --user "$2" --shared-tmp -- bash -c "$3" +} + +lfa_wayland_guest_root() { # $1 = instance, $2 = shell command + # shellcheck disable=SC2046 # flag list, not one argument + proot-distro login $(pd_login_flags) "$1" --shared-tmp -- bash -c "$2" +} + +lfa_wayland_status() { + local _inst="" + while [ $# -gt 0 ]; do + case "$1" in + -i|--instance) _inst="${2:-}"; shift 2 ;; + *) echo "Unknown option: $1" >&2; return 2 ;; + esac + done + require_proot_distro || return 1 + [ -n "$_inst" ] && CLI_INSTANCE="$_inst" + select_installed_instance || return 1 + local _id="$INSTANCE_NAME" _user _conf _distro="" _out _drm + _user="$(lfa_appimage_user "$_id")" + _conf="$(pd_conf "$_id")" + if [ -f "$_conf" ]; then + # shellcheck disable=SC1090 + _distro="$( . "$_conf" 2>/dev/null; printf '%s' "${DISTRO:-}" )" + fi + echo "instance: $_id" + echo "distro: ${_distro:-unknown}" + echo "user: $_user" + echo "compositor: $(lfa_wayland_pref)" + if lfa_wayland_running "$_id"; then + echo "session: running ($(lfa_wayland_active))" + else + echo "session: stopped" + fi + + _out="$(lfa_wayland_guest "$_id" "$_user" ' + for p in Hyprland sway waybar wofi kitty; do + if command -v "$p" >/dev/null 2>&1; then printf "%s=yes\n" "$p"; else printf "%s=no\n" "$p"; fi + done + if [ -x /usr/local/bin/start-lfa-wayland ]; then echo helper=yes; else echo helper=no; fi + if [ -d "$HOME/.config/lfa-wayland" ]; then echo config=yes; else echo config=no; fi + ' 2>/dev/null)" || true + printf '%s\n' "$_out" | sed 's/^/package: /' + _drm="$(lfa_wayland_guest "$_id" "$_user" ' + found=no + for n in /dev/dri/renderD*; do + [ -r "$n" ] && [ -w "$n" ] && { printf "render=%s\n" "$n"; found=yes; } + done + [ "$found" = no ] && echo render=none + for n in /dev/dri/card*; do [ -e "$n" ] && ls -l "$n"; done + ' 2>/dev/null)" || true + printf '%s\n' "$_drm" | sed 's/^/drm: /' + case "$_out" in + *"Hyprland=yes"*) + case "$_drm" in + *"render=none"*) echo "note: Hyprland is installed but cannot start without a writable DRM render node." ;; + *) echo "note: Hyprland can start on this device." ;; + esac ;; + esac +} + +lfa_wayland_install() { + local _inst="" _config="" _comp="" _res="" _wall="" + while [ $# -gt 0 ]; do + case "$1" in + -i|--instance) _inst="${2:-}"; shift 2 ;; + --config) _config="${2:-}"; shift 2 ;; + --compositor) _comp="${2:-}"; shift 2 ;; + --resolution) _res="${2:-}"; shift 2 ;; + --wallpaper) _wall="${2:-}"; shift 2 ;; + *) echo "Unknown option: $1" >&2; return 2 ;; + esac + done + if [ -n "$_comp" ]; then + case "$_comp" in + auto|hyprland|sway|xfce) ;; + *) echo "Unknown compositor: $_comp" >&2; return 2 ;; + esac + fi + if [ -n "$_res" ] && ! printf '%s' "$_res" | grep -qE '^[0-9]+x[0-9]+$'; then + echo "Resolution must look like 2408x1080." >&2 + return 2 + fi + require_proot_distro || return 1 + [ -n "$_inst" ] && CLI_INSTANCE="$_inst" + select_installed_instance || return 1 + local _id="$INSTANCE_NAME" _user _conf _distro="" _rf _assets _stage="" + _user="$(lfa_appimage_user "$_id")" + _conf="$(pd_conf "$_id")" + _rf="$(pd_rootfs "$_id")" + if [ -f "$_conf" ]; then + # shellcheck disable=SC1090 + _distro="$( . "$_conf" 2>/dev/null; printf '%s' "${DISTRO:-}" )" + fi + _assets="$(lfa_wayland_asset_dir)" || { + echo "Wayland assets are missing from this LFA build." >&2 + return 1 + } + + echo "[*] Installing the Wayland stack in $_id..." + case "$_distro" in + archlinux) + lfa_wayland_guest_root "$_id" ' + pacman -S --noconfirm --needed \ + sway swaybg swaylock waybar swaync kitty wofi thunar \ + ttf-jetbrains-mono-nerd adwaita-fonts gnome-themes-extra \ + adwaita-icon-theme starship fastfetch lsd bat playerctl pamixer \ + grim slurp wl-clipboard xorg-xwayland + pacman -S --noconfirm --needed hyprland hyprpaper hyprlock hypridle || \ + echo "[!] Hyprland packages are not available; Sway will be used." + ' || { echo "Package installation failed." >&2; return 1; } ;; + ubuntu|debian) + lfa_wayland_guest_root "$_id" ' + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + sway swaybg swaylock waybar wofi kitty thunar \ + fonts-jetbrains-mono grim slurp wl-clipboard \ + playerctl pamixer xwayland dbus-x11 || true + # SwayNC is not in every Debian/Ubuntu release. Install it + # separately so one missing name does not sink the set above. + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + sway-notification-center || \ + echo "[i] sway-notification-center is not packaged here; notifications use the default daemon." + echo "[i] Hyprland is not packaged for this distro; Sway will be used." + ' || { echo "Package installation failed." >&2; return 1; } ;; + *) + echo "Wayland install is not implemented for $_distro yet." >&2 + echo "Supported: archlinux, ubuntu, debian." >&2 + return 1 ;; + esac + + if [ -n "$_config" ]; then + if [ -d "$_config" ]; then + _stage="$_config" + elif [ -f "$_config" ]; then + _stage="$PREFIX/tmp/lfa-wayland-config-$$" + mkdir -p "$_stage" 2>/dev/null || true + tar -xzf "$_config" -C "$_stage" 2>/dev/null || { + echo "Could not extract $_config" >&2 + rm -rf "$_stage" 2>/dev/null || true + return 1 + } + else + echo "No such config path: $_config" >&2 + return 1 + fi + echo "[*] Importing desktop configs from $_config..." + for _d in hypr waybar kitty wofi swaync fastfetch; do + [ -d "$_stage/$_d" ] || continue + mkdir -p "$_rf/home/$_user/.config/$_d" 2>/dev/null || true + cp -a "$_stage/$_d/." "$_rf/home/$_user/.config/$_d/" 2>/dev/null || true + done + if [ -f "$_stage/starship.toml" ]; then + install -m 644 "$_stage/starship.toml" \ + "$_rf/home/$_user/.config/starship.toml" 2>/dev/null || true + # proot reports the Android kernel hostname, so Starship would + # print "localhost". Pin the instance hostname in the prompt. + _hostname="$( . "$_conf" 2>/dev/null; printf '%s' "${HOSTNAME:-arch}" )" + [ -n "$_hostname" ] || _hostname="arch" + sed -i "s|^format *= *\"\[@\$hostname *\](\$style)\"|format = \"[@${_hostname} ](\$style)\"|" \ + "$_rf/home/$_user/.config/starship.toml" 2>/dev/null || true + fi + [ "$_stage" != "$_config" ] && rm -rf "$_stage" 2>/dev/null || true + fi + + echo "[*] Installing the session profile..." + install -d "$_rf/home/$_user/.config/lfa-wayland/sway" \ + "$_rf/home/$_user/.config/lfa-wayland/sway-nest" \ + "$_rf/home/$_user/.config/lfa-wayland/waybar/scripts" 2>/dev/null || true + install -m 644 "$_assets/sway/config" "$_rf/home/$_user/.config/lfa-wayland/sway/config" 2>/dev/null || true + install -m 644 "$_assets/sway-nest/config" "$_rf/home/$_user/.config/lfa-wayland/sway-nest/config" 2>/dev/null || true + install -m 644 "$_assets/waybar/config.jsonc" "$_rf/home/$_user/.config/lfa-wayland/waybar/config.jsonc" 2>/dev/null || true + install -m 644 "$_assets/waybar/style.css" "$_rf/home/$_user/.config/lfa-wayland/waybar/style.css" 2>/dev/null || true + install -m 755 "$_assets/waybar/scripts/battery.sh" "$_rf/home/$_user/.config/lfa-wayland/waybar/scripts/battery.sh" 2>/dev/null || true + install -m 755 "$_assets/start-lfa-wayland" "$_rf/usr/local/bin/start-lfa-wayland" 2>/dev/null || true + + if [ -n "$_res" ]; then + _swaycfg="$_rf/home/$_user/.config/lfa-wayland/sway/config" + sed -i '/^output X11-1 resolution /d' "$_swaycfg" 2>/dev/null || true + printf '\noutput X11-1 resolution %s position 0 0\n' "$_res" \ + >> "$_swaycfg" 2>/dev/null || true + fi + if [ -n "$_wall" ] && [ -f "$_wall" ]; then + install -d "$_rf/usr/share/backgrounds" 2>/dev/null || true + install -m 644 "$_wall" "$_rf/usr/share/backgrounds/lfa-wayland.jpg" 2>/dev/null || true + elif [ -f "$LFA_DIR/assets/wallpapers/archlinux-dark.jpg" ]; then + install -d "$_rf/usr/share/backgrounds" 2>/dev/null || true + install -m 644 "$LFA_DIR/assets/wallpapers/archlinux-dark.jpg" "$_rf/usr/share/backgrounds/lfa-wayland.jpg" 2>/dev/null || true + elif [ -f "$_rf/usr/share/backgrounds/lfa-default.jpg" ]; then + cp -f "$_rf/usr/share/backgrounds/lfa-default.jpg" "$_rf/usr/share/backgrounds/lfa-wayland.jpg" 2>/dev/null || true + fi + + for _gtk in gtk-3.0 gtk-4.0; do + _gsettings="$_rf/home/$_user/.config/$_gtk/settings.ini" + mkdir -p "$(dirname "$_gsettings")" 2>/dev/null || true + { + printf '[Settings]\n' + printf 'gtk-theme-name=Adwaita-dark\n' + printf 'gtk-icon-theme-name=Adwaita\n' + printf 'gtk-cursor-theme-name=Adwaita-Tiny\n' + printf 'gtk-cursor-theme-size=16\n' + printf 'gtk-font-name=Adwaita Sans 11\n' + } > "$_gsettings" 2>/dev/null || true + done + + _bashrc="$_rf/home/$_user/.bashrc" + if [ -f "$_bashrc" ] && ! grep -q 'starship init bash' "$_bashrc" 2>/dev/null; then + { + printf '\n# Linux for Android: starship prompt\n' + printf 'if command -v starship >/dev/null 2>&1; then\n' + printf ' eval "$(starship init bash)"\n' + printf 'fi\n' + } >> "$_bashrc" 2>/dev/null || true + fi + + if [ -n "$_comp" ]; then + lfa_wayland_set_pref "$_comp" || true + fi + + cat > "$PREFIX/bin/linux-wayland" <<'LWL' +#!/bin/bash +exec "$PREFIX/bin/lfa" wayland start "$@" +LWL + chmod +x "$PREFIX/bin/linux-wayland" 2>/dev/null || true + + echo "[*] Wayland stack installed." + echo " start: lfa wayland start -i $_id" + echo " stop: lfa wayland stop -i $_id" + echo " status: lfa wayland status -i $_id" + echo " Hyprland is used when a writable /dev/dri/renderD* exists;" + echo " otherwise the Sway profile mirrors the Hyprland keybinds and look." +} + +lfa_wayland_start() { + local _inst="" _comp="" _restart=0 + while [ $# -gt 0 ]; do + case "$1" in + -i|--instance) _inst="${2:-}"; shift 2 ;; + --compositor) _comp="${2:-}"; shift 2 ;; + --restart) _restart=1; shift ;; + *) echo "Unknown option: $1" >&2; return 2 ;; + esac + done + require_proot_distro || return 1 + [ -n "$_inst" ] && CLI_INSTANCE="$_inst" + select_installed_instance || return 1 + local _id="$INSTANCE_NAME" _user _conf _runtime _need_virgl=1 + _user="$(lfa_appimage_user "$_id")" + _conf="$(pd_conf "$_id")" + if [ -f "$_conf" ]; then + # shellcheck disable=SC1090 + _need_virgl="$( . "$_conf" 2>/dev/null; printf '%s' "${NEED_VIRGL:-1}" )" + fi + if [ -n "$_comp" ]; then + lfa_wayland_set_pref "$_comp" || return 2 + fi + _comp="$(lfa_wayland_pref)" + + if lfa_wayland_running "$_id"; then + if [ "$_restart" = "1" ]; then + lfa_wayland_stop -i "$_id" >/dev/null 2>&1 || true + else + echo "A Wayland session for $_id is already running." + echo " restart: lfa wayland start -i $_id --restart" + return 0 + fi + fi + + if lfa_instance_session_running "$_id"; then + if [ "$_restart" = "1" ]; then + "$PREFIX/bin/stop-linux" -i "$_id" >/dev/null 2>&1 || true + else + echo "A desktop is already running for $_id." + echo " stop it first: lfa stop -i $_id" + return 1 + fi + fi + + if ! command -v termux-x11 >/dev/null 2>&1; then + echo "[*] Installing Termux:X11..." + pkg install -y x11-repo termux-x11-nightly > "$PREFIX/tmp/lfa-wayland-x11-install.log" 2>&1 || true + fi + if ! command -v termux-x11 >/dev/null 2>&1; then + echo "Termux:X11 is not installed. Run: pkg install x11-repo termux-x11-nightly" >&2 + return 1 + fi + if ! pgrep -f '^termux-x11 .* :1( |$)' >/dev/null 2>&1; then + echo "[*] Starting Termux:X11 on :1..." + nohup termux-x11 :1 > "$PREFIX/tmp/lfa-wayland-x11.log" 2>&1 & + sleep 2 + fi + if command -v pulseaudio >/dev/null 2>&1; then + pulseaudio --start --load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" --exit-idle-time=-1 2>/dev/null || true + fi + if [ "$_need_virgl" = "1" ] && command -v virgl_test_server_android >/dev/null 2>&1; then + nohup virgl_test_server_android >/dev/null 2>&1 & + fi + + _runtime="/tmp/xdg-$_user-$_id" + echo "[*] Starting the Wayland session ($_comp) as $_user..." + # shellcheck disable=SC2046 # flag list, not one argument + nohup proot-distro login $(pd_login_flags) "$_id" --user "$_user" --shared-tmp -- \ + env DISPLAY=:1 PULSE_SERVER=127.0.0.1 XDG_RUNTIME_DIR="$_runtime" \ + LFA_WAYLAND_COMPOSITOR="$_comp" bash -c 'if command -v dbus-run-session >/dev/null 2>&1; then exec dbus-run-session -- start-lfa-wayland; else exec start-lfa-wayland; fi' \ + > "$HOME/lfa-wayland.log" 2>&1 & + echo "$!" > "$(lfa_wayland_pidfile "$_id")" + sleep 4 + if lfa_wayland_running "$_id"; then + echo "[*] Wayland session is running." + echo " log: $HOME/lfa-wayland.log" + case "$(lfa_wayland_active)" in + Hyprland) echo " compositor: Hyprland (nested)" ;; + Sway) echo " compositor: Sway with the Hyprland profile" ;; + *) echo " compositor: $_comp" ;; + esac + lfa_wayland_battery_start + else + echo "[!] The session did not stay up. Last log lines:" >&2 + tail -n 20 "$HOME/lfa-wayland.log" 2>/dev/null >&2 || true + return 1 + fi +} + +lfa_wayland_stop() { + local _inst="" + while [ $# -gt 0 ]; do + case "$1" in + -i|--instance) _inst="${2:-}"; shift 2 ;; + *) echo "Unknown option: $1" >&2; return 2 ;; + esac + done + require_proot_distro || return 1 + [ -n "$_inst" ] && CLI_INSTANCE="$_inst" + select_installed_instance || return 1 + local _id="$INSTANCE_NAME" _p _f _user + _user="$(lfa_appimage_user "$_id")" + _f="$(lfa_wayland_pidfile "$_id")" + _p="" + [ -f "$_f" ] && _p="$(tr -dc '0-9' < "$_f" 2>/dev/null || true)" + if [ -n "$_p" ] && lfa_pid_matches "$_p" "start-lfa-wayland" "sway" "Hyprland"; then + echo "[*] Stopping the Wayland session for $_id..." + kill "$_p" 2>/dev/null || true + for _ in 1 2 3 4 5; do + kill -0 "$_p" 2>/dev/null || break + sleep 1 + done + kill -9 "$_p" 2>/dev/null || true + else + echo "No Wayland session is running for $_id." + fi + lfa_wayland_battery_stop + # A killed proot parent can leave Sway or Hyprland orphaned. + lfa_wayland_guest "$_id" "$_user" ' + pkill -f "[l]fa-wayland/sway" 2>/dev/null || true + pkill -f "[l]fa-wayland/waybar" 2>/dev/null || true + pkill -x Hyprland 2>/dev/null || true + true + ' >/dev/null 2>&1 || true + rm -f "$_f" 2>/dev/null || true +} + +lfa_wayland_doctor() { + local _inst="" + while [ $# -gt 0 ]; do + case "$1" in + -i|--instance) _inst="${2:-}"; shift 2 ;; + *) echo "Unknown option: $1" >&2; return 2 ;; + esac + done + if [ -n "$_inst" ]; then lfa_wayland_status -i "$_inst" || true; else lfa_wayland_status || true; fi + echo "" + echo "Checks:" + if [ -x "$PREFIX/bin/linux-wayland" ]; then + echo " launcher: present" + else + echo " launcher: missing (lfa wayland install -i ${_inst:-NAME})" + fi + echo " X server: $(pgrep -f '^termux-x11 .* :1( |$)' >/dev/null 2>&1 && echo running || echo stopped)" + echo " log: $HOME/lfa-wayland.log" + [ -f "$HOME/lfa-wayland.log" ] && tail -n 8 "$HOME/lfa-wayland.log" | sed 's/^/ /' + echo "" + echo "Hyprland needs a writable DRM render node. Without one, Aquamarine" + echo "cannot allocate buffers and Hyprland exits with CBackend::create()." + echo "LFA then starts Sway with the same keybinds, gaps, borders, wallpaper," + echo "and bar. Use: lfa wayland start -i ${_inst:-NAME}" +} + +lfa_wayland_session() { + local _name="${1:-}" + [ $# -gt 0 ] && shift || true + if [ $# -gt 0 ]; then + echo "Unknown option: $1" >&2 + return 2 + fi + if [ -z "$_name" ]; then + echo "compositor: $(lfa_wayland_pref)" + echo "choices: auto, hyprland, sway, xfce" + return 0 + fi + lfa_wayland_set_pref "$_name" || return $? + echo "Wayland compositor set to: $_name" + if [ "$_name" = "hyprland" ]; then + echo "Hyprland will fall back to Sway if no writable DRM render node exists." + fi +} + +lfa_wayland_cmd() { + local _cmd="${1:-}" + [ $# -gt 0 ] && shift || true + case "$_cmd" in + status) lfa_wayland_status "$@" ;; + install) lfa_wayland_install "$@" ;; + start) lfa_wayland_start "$@" ;; + stop) lfa_wayland_stop "$@" ;; + doctor) lfa_wayland_doctor "$@" ;; + session) lfa_wayland_session "$@" ;; + help|-h|--help|"") lfa_wayland_usage ;; + *) echo "Unknown wayland command: $_cmd" >&2; lfa_wayland_usage >&2; return 2 ;; + esac +} + + lfa_cli_usage() { cat <<'HELP' Linux for Android - command line @@ -1369,6 +1927,7 @@ Usage: lfa [options] compat inspect or enable compatibility shims android inspect or enable the Termux:API bridge storage Android shared storage: status, repair, run helpers + wayland Wayland session manager: Hyprland or Sway (alias: wl) appimage extract and run an AppImage (alias: app) run run an AppImage once without installing it diagnostics write a support bundle @@ -1415,6 +1974,9 @@ Examples: lfa appimage list lfa appimage run Foo lfa run ~/Foo.AppImage + lfa wayland install -i arch + lfa wayland start -i arch + lfa wayland status -i arch lfa notice off HELP } @@ -2198,7 +2760,7 @@ lfa_instance_session_running() { # $1 = instance # A terminal login also contains the instance path. Only # count a real desktop session for the X11 running state. case "$_cmd" in - *startxfce4*|*startlxde*|*mate-session*|*gnome-shell*|*start-gnome-session*) return 0 ;; + *startxfce4*|*startlxde*|*mate-session*|*gnome-shell*|*start-gnome-session*|*start-lfa-wayland*|*Hyprland*) return 0 ;; esac ;; esac done @@ -3230,6 +3792,7 @@ lfa_cli() { android) lfa_android_cmd "$@" ;; appimage|app) lfa_cli_appimage "$@" ;; storage) lfa_cli_storage "$@" ;; + wayland|wl) lfa_wayland_cmd "$@" ;; run) lfa_appimage_run "$@" ;; update) while [ $# -gt 0 ]; do case "$1" in -i|--instance) CLI_INSTANCE="${2:-}"; shift 2 ;; *) echo "Unknown option: $1" >&2; return 2 ;; esac; done @@ -4913,7 +5476,7 @@ lfa_instance_session_running() { # $1 = instance case "$_cmd" in *"/containers/$1/"*|*"/installed-rootfs/$1/"*) case "$_cmd" in - *startxfce4*|*startlxde*|*mate-session*|*gnome-shell*|*start-gnome-session*) return 0 ;; + *startxfce4*|*startlxde*|*mate-session*|*gnome-shell*|*start-gnome-session*|*start-lfa-wayland*|*Hyprland*) return 0 ;; esac ;; esac done diff --git a/tests/test-all.sh b/tests/test-all.sh index 2d828f5..4c7881a 100755 --- a/tests/test-all.sh +++ b/tests/test-all.sh @@ -11,6 +11,8 @@ # launchers syntax, manager conf, setup.conf (0600), boot script # * sudo shim: syntax + real password ritual over a pty # * manager menu smoke test: options 2-11 run clean with stubs +# * Wayland session manager: profile install, config import, resolution, +# compositor preference, doctor, and start/stop guards # # How it works: fake HOME/PREFIX/PATH with stub commands (pkg, proot-distro, # sshd, pulseaudio, termux-setup-storage, getprop, free, proot, git). No @@ -1430,6 +1432,93 @@ check "Y10 env marker added" 'APPIMAGE_EXTRACT_AND_RUN=1' "$Y10ROOT/etc/profile. check "Y10 env marker exported" 'export APPIMAGE_EXTRACT_AND_RUN' "$Y10ROOT/etc/profile.d/zz-lfa-env.sh" echo "" +echo "===== Y11: lfa wayland session manager =====" +"$LFA" help > "$T/y11-tophelp.log" 2>&1 +check "Y11 top-level help" 'Wayland session manager' "$T/y11-tophelp.log" +"$LFA" wayland help > "$T/y11-help.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 help exit $rc"; } +check "Y11 help status" 'status' "$T/y11-help.log" +check "Y11 help install" 'install the Wayland stack' "$T/y11-help.log" +check "Y11 help doctor" 'doctor' "$T/y11-help.log" +check "Y11 help resolution" '--resolution WxH' "$T/y11-help.log" +check "Y11 help drm" 'DRM render node' "$T/y11-help.log" + +"$LFA" wl status -i debian > "$T/y11-status.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 status exit $rc"; } +check "Y11 status instance" 'instance: debian' "$T/y11-status.log" +check "Y11 status distro" 'distro: debian' "$T/y11-status.log" +check "Y11 status session" 'session: stopped' "$T/y11-status.log" +check "Y11 status compositor" 'compositor: auto' "$T/y11-status.log" + +"$LFA" wayland session > "$T/y11-session.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 session exit $rc"; } +check "Y11 session choices" 'choices: auto, hyprland, sway, xfce' "$T/y11-session.log" +"$LFA" wayland session sway > "$T/y11-set.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 session set exit $rc"; } +check "Y11 pref written" 'COMPOSITOR="sway"' "$T/home/.config/linux-for-android/wayland.conf" +"$LFA" wayland session bogus > "$T/y11-bad.log" 2>&1; rc=$? +[ "$rc" = "2" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 bad compositor exit $rc"; } +check "Y11 bad compositor" 'Unknown compositor: bogus' "$T/y11-bad.log" +"$LFA" wayland session auto >/dev/null 2>&1 + +mkdir -p "$T/y11-cfg/kitty" "$T/y11-cfg/waybar" "$T/y11-cfg/fastfetch" +printf 'font_family JetBrainsMono\n' > "$T/y11-cfg/kitty/kitty.conf" +printf '{"layer":"top"}\n' > "$T/y11-cfg/waybar/config.jsonc" +printf '{"fastfetch":true}\n' > "$T/y11-cfg/fastfetch/config.jsonc" +printf '[hostname]\nformat = "[@$hostname ]($style)"\n' > "$T/y11-cfg/starship.toml" +"$LFA" wayland install -i debian --config "$T/y11-cfg" --resolution 2408x1080 > "$T/y11-install.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 install exit $rc"; } +check "Y11 install done" 'Wayland stack installed.' "$T/y11-install.log" +check "Y11 install explains drm" 'writable /dev/dri/renderD*' "$T/y11-install.log" +Y11ROOT="$T/prefix/var/lib/proot-distro/installed-rootfs/debian" +[ -x "$Y11ROOT/usr/local/bin/start-lfa-wayland" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 helper not installed"; } +[ -x "$T/prefix/bin/linux-wayland" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 launcher not installed"; } +check "Y11 sway profile" 'gaps inner 3' "$Y11ROOT/home/user/.config/lfa-wayland/sway/config" +check "Y11 sway keybind" 'bindsym $mod+Space exec $menu' "$Y11ROOT/home/user/.config/lfa-wayland/sway/config" +check "Y11 waybar profile" 'sway/workspaces' "$Y11ROOT/home/user/.config/lfa-wayland/waybar/config.jsonc" +check "Y11 resolution" 'output X11-1 resolution 2408x1080 position 0 0' "$Y11ROOT/home/user/.config/lfa-wayland/sway/config" +check "Y11 imported kitty" 'font_family JetBrainsMono' "$Y11ROOT/home/user/.config/kitty/kitty.conf" +check "Y11 imported waybar" '"layer":"top"' "$Y11ROOT/home/user/.config/waybar/config.jsonc" +check "Y11 imported fastfetch" '"fastfetch":true' "$Y11ROOT/home/user/.config/fastfetch/config.jsonc" +check "Y11 imported starship" '[hostname]' "$Y11ROOT/home/user/.config/starship.toml" +check_absent "Y11 starship hostname pinned" '@$hostname' "$Y11ROOT/home/user/.config/starship.toml" +check "Y11 gtk dark" 'gtk-theme-name=Adwaita-dark' "$Y11ROOT/home/user/.config/gtk-3.0/settings.ini" +check "Y11 gtk font" 'gtk-font-name=Adwaita Sans 11' "$Y11ROOT/home/user/.config/gtk-3.0/settings.ini" + +"$LFA" wayland install -i debian --resolution 2408x1080 >/dev/null 2>&1 +_y11_lines="$(grep -c '^output X11-1 resolution' "$Y11ROOT/home/user/.config/lfa-wayland/sway/config" 2>/dev/null || true)" +[ "$_y11_lines" = "1" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 resolution duplicated ($_y11_lines lines)"; } +"$LFA" wayland install -i debian --resolution huge > "$T/y11-res-bad.log" 2>&1; rc=$? +[ "$rc" = "2" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 bad resolution exit $rc"; } +check "Y11 resolution validation" 'Resolution must look like 2408x1080.' "$T/y11-res-bad.log" +"$LFA" wayland install -i debian --compositor bogus > "$T/y11-comp-bad.log" 2>&1; rc=$? +[ "$rc" = "2" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 bad install compositor exit $rc"; } +check "Y11 install compositor validation" 'Unknown compositor: bogus' "$T/y11-comp-bad.log" + +"$LFA" wayland doctor -i debian > "$T/y11-doctor.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 doctor exit $rc"; } +check "Y11 doctor launcher" 'launcher: present' "$T/y11-doctor.log" +check "Y11 doctor explains backend" 'CBackend::create()' "$T/y11-doctor.log" +check "Y11 doctor x server" 'X server:' "$T/y11-doctor.log" + +"$LFA" wayland start -i debian > "$T/y11-start.log" 2>&1; rc=$? +[ "$rc" = "1" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 start without X11 exit $rc"; } +check "Y11 start needs x11" 'Termux:X11 is not installed' "$T/y11-start.log" +"$LFA" wayland stop -i debian > "$T/y11-stop.log" 2>&1; rc=$? +[ "$rc" = "0" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 stop exit $rc"; } +check "Y11 stop idle" 'No Wayland session is running for debian.' "$T/y11-stop.log" + +check "Y11 battery refresher" 'lfa-wayland-battery.pid' "$T/prefix/lib/linux-for-android/install_linux.sh" +check "Y11 battery env" 'lfa-android-battery.env' "$T/prefix/lib/linux-for-android/install_linux.sh" +check "Y11 battery refresh interval" 'sleep 60' "$T/prefix/lib/linux-for-android/install_linux.sh" +"$LFA" wayland bogus > "$T/y11-bogus.log" 2>&1; rc=$? +[ "$rc" = "2" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 bogus command exit $rc"; } +check "Y11 bogus message" 'Unknown wayland command: bogus' "$T/y11-bogus.log" + +[ -s "$REPO/docs/WAYLAND.md" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 wayland guide missing"; } +grep -qF 'docs/WAYLAND.md' "$REPO/README.md" && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 README does not link the wayland guide"; } +grep -qF 'WAYLAND.md' "$REPO/docs/README.md" && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Y11 docs index does not link the wayland guide"; } + echo "===== Z: documentation links =====" [ -s "$REPO/docs/TROUBLESHOOTING.md" ] && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Z troubleshooting guide missing"; } grep -qF 'docs/TROUBLESHOOTING.md' "$REPO/README.md" && pass=$((pass+1)) || { fail=$((fail+1)); echo "FAIL: Z README does not link the troubleshooting guide"; } diff --git a/tracker.md b/tracker.md index 09fccf5..b6d1f4f 100644 --- a/tracker.md +++ b/tracker.md @@ -7,7 +7,7 @@ Status values: | Status | Meaning | |---|---| -| `done` | code changed, suite green (`PASS=627 FAIL=0`), lint clean | +| `done` | code changed, suite green (`PASS=682 FAIL=0`), lint clean | | `partial` | the main risk is addressed; a documented remainder is listed | | `deferred` | deliberately not fixed in this pass, with the reason recorded | | `device` | also exercised on the Vivo V2437 | @@ -53,9 +53,21 @@ Lint: `shellcheck -S warning` clean, `bash -n` clean, CI workflow added. | Android 5/6 refusal | `done` | explicit explanation, `--remove-old` warning, VNC restore command | | `start-linux` notice | `done` | stderr, interactive classic runs only; `lfa notice`, `--no-notice`, `LFA_NO_NOTICE` | | Boot script ownership | `done` | switching a non-autostart instance no longer hijacks another instance's boot script | -| Host suite | `done` | `PASS=627 FAIL=0`; Y1-Y10 cover CLI, switch, removal, guard, `-i`, legacy, SDK 24/25, compat/android, matrix, AppImage | +| Host suite | `done` | `PASS=682 FAIL=0`; Y1-Y11 cover CLI, switch, removal, guard, `-i`, legacy, SDK 24/25, compat/android, matrix, AppImage, Wayland | | Device verification | `done` | Vivo V2437 (SDK 36): VNC `PASS=14`, Both switch `PASS=22`, X11 `PASS=14`, VirGL fallback, shims, bridge, `--software`; Oppo SDK 22 pending (off network) | +## Wayland session (2026-11-08) + +| Item | Status | Notes | +|---|---|---| +| `lfa wayland` command | `done` | status, install, start, stop, doctor, session; alias `wl` | +| Session profile | `done` | Sway on X11 with Hyprland keybinds, gaps, borders, colours, Waybar, SwayNC, wallpaper | +| Config import | `done` | `--config` copies hypr, waybar, kitty, wofi, and swaync from a folder or tarball | +| Output size | `done` | `--resolution 2408x1080`, rewritten instead of duplicated | +| Hyprland path | `device` | installed and config-parsed; start blocked by the missing DRM render node | +| Device verification | `done` | Vivo V2437: session, bar, notifications, battery, `grim` screenshot, clean stop | +| Docs and tests | `done` | `docs/WAYLAND.md`, README section, troubleshooting entry, Y11 checks | + ## Android CI and compatibility layers (2026-11-05) | Item | Status | Notes |