Skip to content

Tenser-Linux/huskyfe

Repository files navigation

huskyfe

A direct-to-DRM phone OS frontend: status bar, lockscreen, app drawer, notifications, on-screen keyboard, settings panels (Wi-Fi, Bluetooth, flashlight, camera) — and the Wayland host that runs the apps themselves.

Single process. Takes DRM master directly via libdrm, renders the whole UI in GLES via glproxy (no Mesa, no mali_kbase access from this side), and hosts a Wayland compositor for everything else on the device.

   /dev/tty1 (local TTY auto-launches /root/.profile)
            │
            ▼
   /root/huskyfe/huskyfe   ← single binary, holds DRM master
            │
            ├── DRM/KMS direct       ─────► panel + vsync
            ├── libinput touch        ─────► gestures + on-screen kbd
            ├── EGL / GLES2 via glproxy ───► server-side Mali (chroot)
            │
            └── Wayland server (display=wayland-1)
                     ▲
                     │  client surface dmabufs
                     │
                ┌────┴───────────┐
                │  app processes  │
                │ (gtk, qt, etc.) │
                └────────────────┘

The whole compositor is in one binary. It loads libGLESv2.so.2 / libEGL.so.1 from glproxy so every GL call ends up at the proxy server inside the Halium chroot, which is where libGLES_mali.so actually runs. There is no Mesa or /dev/dri/renderD* involvement on this side — only /dev/dri/card0 for KMS modesetting + scanout dmabufs.

Source tree

huskyfe/
├── Makefile                              # builds natively on the phone (g++/gcc)
├── deploy.sh                             # push src/Makefile/profile/.service → scp → make → run/restart
├── profile                               # /root/.profile — auto-launches huskyfe on local TTY login
├── huskyfe.service                       # systemd unit; notify-type with WatchdogSec=10s
├── huskyfe-portal-stub.service           # tiny xdg-desktop-portal placeholder so GTK apps don't hang
├── xdg-desktop-portal.service            # override that masks the real one (we provide the stub)
└── src/
    ├── main.cpp                          # the everything-file: DRM/KMS, GBM scanout, EGL/GLES setup,
    │                                       libinput, frame loop, the entire UI state machine, gesture
    │                                       recogniser, panel hierarchies. Big.
    │
    ├── Renderer.{h,cpp}                  # GL state + draw helpers (textured quad, solid fill, scissor)
    ├── ImageRenderer.{h,cpp}             # texture cache (stb_image / nanosvg sources) keyed by path
    ├── Text.{h,cpp}                      # FreeType glyph atlas + measure/draw routines
    ├── GL.{h,cpp}                        # one-time GL program/uniform initialisation
    ├── Blur.{h,cpp}                      # separable Gaussian for panel backgrounds
    ├── Spring.h                          # critically-damped spring (used by every transition)
    │
    ├── Input.{h,cpp}                     # libinput wrapper: touch slots, key events, accel/proximity
    ├── Keyboard.{h,cpp}                  # on-screen keyboard: layouts, repeat, predictive, swipe
    ├── Haptics.{h,cpp}                   # /sys/class/leds/vibrator or input force-feedback shim
    │
    ├── Status.{h,cpp}                    # top status bar (clock, battery, signal, wifi icon)
    ├── Background.{h,cpp}                # wallpaper + lockscreen background
    ├── Icons.{h,cpp}                     # app icons: load from .desktop + image cache, swizzle, mask
    ├── Apps.{h,cpp}                      # .desktop scan, sort, launch (exec, env, cgroup), app drawer
    ├── Notifications.{h,cpp}             # org.freedesktop.Notifications D-Bus service + UI surface
    │
    ├── Wifi.{h,cpp}                      # NetworkManager D-Bus: scan, connect, password prompt
    ├── Bluetooth.{h,cpp}                 # BlueZ D-Bus: discover, pair, connect, A2DP/HFP
    ├── Camera.{h,cpp}                    # V4L2 capture preview into a GL texture
    ├── Flashlight.{h,cpp}                # /sys/class/leds/flashlight torch toggle
    │
    ├── WaylandHost.{h,cpp}               # the embedded Wayland server: xdg-shell, viewporter,
    │                                       text-input-v3, linux-dmabuf-v1. Manages client surfaces,
    │                                       routes pointer/touch/keyboard, draws their dmabufs.
    │
    ├── protocol/                         # generated by wayland-scanner from /usr/share/wayland-protocols/
    │   ├── xdg-shell-{server-protocol.h,protocol.c}
    │   ├── linux-dmabuf-unstable-v1-{server-protocol.h,protocol.c}
    │   ├── viewporter-{server-protocol.h,protocol.c}
    │   └── text-input-unstable-v3-{server-protocol.h,protocol.c}
    │
    └── third_party/                      # vendored single-header libraries
        ├── stb_image.h                   # PNG/JPG/GIF decoder
        ├── nanosvg.h                     # SVG parser
        └── nanosvgrast.h                 # SVG rasteriser (used for app icons + UI vectors)

How the frame loop works

main.cpp runs a single thread that loops:

  1. drmModePageFlip callback fires (or a timer if we're not vsync-locked).
  2. Input::poll() drains libinput. Touch deltas turn into gesture-recognised events; key events go to the focused Wayland client or the on-screen keyboard.
  3. The UI state machine advances all the springs and animations.
  4. WaylandHost::dispatch() services Wayland clients — accepts new surfaces, processes pending requests, gathers the latest dmabuf each client wants drawn.
  5. The GL pipeline draws everything in z-order onto the next scanout buffer:
    • Background (wallpaper or live camera, with blur if a panel is open)
    • Hosted client surfaces (WaylandHost::draw)
    • Status bar
    • Panels (Wi-Fi, Bluetooth, notification shade) with parallax
    • On-screen keyboard
    • Toasts / haptic indicators
  6. eglSwapBuffers — which here is glproxy's present, which writes into the scanout dmabuf shared with the kernel.
  7. drmModePageFlip for the new buffer; the kernel scans it out at the next vsync.
  8. sd_notify("WATCHDOG=1") so systemd doesn't think we hung.

Every per-frame UI thing is a Spring — taps don't snap, panels rubber-band, notifications slide in with momentum. src/Spring.h is a tiny critically-damped-spring header — step(dt, target) once per frame, read .value().

Wayland host (WaylandHost.cpp)

This is the second-biggest file and what makes huskyfe a compositor, not just a launcher.

  • Listens on $XDG_RUNTIME_DIR/wayland-1 (set via env in the unit, not the default wayland-0, to avoid collisions with anything else on the system).
  • Globals advertised: wl_compositor, wl_subcompositor, wl_shm, wl_seat, wl_output, xdg_wm_base, wp_viewporter, zwp_text_input_manager_v3, zwp_linux_dmabuf_v1.
  • Every client wl_surface gets a RunningApp entry with the resolved app_id (from xdg_toplevel.set_app_id) and a title for the task switcher.
  • Touch is routed: a tap inside the hosted surface region forwards through wl_touch; a vertical edge swipe from the top opens notifications; a horizontal edge swipe from the bottom opens the task switcher. The compositor itself wins the gesture before forwarding.
  • Text input goes through zwp_text_input_v3 so the on-screen keyboard's character output reaches GTK/Qt clients without them having to know it's a virtual keyboard.
  • Client dmabufs are imported via zwp_linux_dmabuf_v1 → EGLImage (server-side, inside glproxy-srv) and then sampled as a texture every frame. There is no zero-copy scanout of client dmabufs; we always composite through GL.

focus(handle) / unfocus() / close(handle) are the public API for the task switcher and the long-press-to-close gesture.

Build

There is no cross-compile. The Makefile is intended to run on the phone itself (or any aarch64 Linux with the same package set):

sudo apt install build-essential pkg-config \
    libwayland-dev libdrm-dev libxkbcommon-dev libfreetype-dev \
    libdbus-1-dev libinput-dev wayland-protocols

cd /root/huskyfe
make -j4         # produces /root/huskyfe/huskyfe (~5-8 MB)
make clean

The Makefile compiles .cpp with g++ and .c (generated wayland protocol code) with gcc, so the protocol bindings keep C linkage when they're #included into the C++ TUs.

Headers it really wants:

Dep Pkg (Debian/Ubuntu) Used for
libwayland-server libwayland-dev hosted compositor
libdrm libdrm-dev KMS modesetting + GBM scanout
libxkbcommon libxkbcommon-dev keyboard layout for text-input
libfreetype libfreetype-dev text rendering
libdbus-1 libdbus-1-dev Notifications + NM + BlueZ
libinput libinput-dev touch + key + accelerometer
wayland-protocols wayland-protocols xml inputs for wayland-scanner
GLESv2 + EGL provided by glproxy every GL call

-lGLESv2 -lEGL resolve to glproxy's libGLESv2.so.2 / libEGL.so.1 via LD_LIBRARY_PATH=/usr/local/lib/glproxy (which the service unit and /root/.profile set up).

Deploy

The single script driving everything:

./deploy.sh                  # default: push + build + run
./deploy.sh push             # scp src/ + Makefile + deploy.sh to phone
./deploy.sh build            # push then make on phone
./deploy.sh run              # stop other display owners + restart huskyfe
./deploy.sh clean            # rm -rf build artifacts on phone
./deploy.sh logs             # follow /tmp/huskyfe.log + journal -u glproxy-srv
./deploy.sh profile          # install /root/.profile (autologin → huskyfe)
./deploy.sh service          # install + enable huskyfe.service

Env knobs:

PHONE=root@192.168.1.148     # default — change for a different device
REMOTE=/root/huskyfe         # destination path on the phone

run first kills every other display owner (pkill -9 -f 'wcomp|cage|phoc|phosh|weston|modetest|huskyfe') — only one process can hold DRM master, and we want this one.

systemd integration

Two unit files in the repo, plus one drop-in profile:

  • huskyfe.service — the main unit.
    • Type=notify + WatchdogSec=10s: huskyfe calls sd_notify("WATCHDOG=1") every frame; if the render loop hangs for more than 10s, systemd kills and restarts, preventing the "frozen panel" failure mode.
    • Conflicts=phosh.service phoc.service getty@tty1.service so it won't fight other display owners on enable.
    • ExecStartPre ensures the user session bus (/run/user/0/bus) exists and starts the chrooted glproxy-srv if it isn't already up.
    • Restart=always + StartLimitBurst=10 over 60s — recovers from libwayland's wl_resource_post_error asserts (which abort()) without leaving a black panel.
  • xdg-desktop-portal.service — overrides the system unit to a no-op (we don't want the real portal stack here; it pulls in GNOME-y things).
  • huskyfe-portal-stub.service — provides a minimal org.freedesktop.portal.Desktop so GTK apps that probe for it on startup get an immediate response instead of hanging on the bus.

Install with ./deploy.sh service (copies the unit, daemon-reload, enables, starts).

TTY autolaunch (profile)

Goes to /root/.profile. On a real local TTY (/dev/tty[0-9]*) login it ensures glproxy-srv is running then exec /root/huskyfe/huskyfe with stdout/stderr to /tmp/huskyfe.log. SSH/serial sessions are exempt — tty returns /dev/pts/N for those, and the case match skips them, so we still get a normal shell for debugging.

Logs and diagnostics

Log What
/tmp/huskyfe.log huskyfe stdout/stderr — main UI log
/tmp/glp.log glproxy-srv stderr (Mali side, inside chroot)
journalctl -u huskyfe.service systemd transitions + watchdog kills
journalctl -u glproxy-srv glproxy systemd lifecycle
/tmp/huskyfe-dbus.log the per-session dbus-daemon if we started it

./deploy.sh logs opens a paned tail of both huskyfe.log and the glproxy journal.

Related repos

  • glproxy — OpenGL ES over IPC. huskyfe links against the client .sos; the server runs inside the Halium chroot.
  • vkproxy — the Vulkan counterpart. Not used by huskyfe directly, but apps hosted under huskyfe's Wayland server can use it.

About

A OpenGL Compositor for Linux Based Google Pixel Devices

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages