Skip to content

About

Use your Daylight DC1 as an external screen with Linux

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Daylight Extended Display for Omarchy

This project turns a USB-connected Daylight DC-1 into a genuine additional Hyprland output. Hyprland owns a separate headless monitor and workspace; its pixels are sent losslessly to the DC-1 over an ADB USB tunnel. It is not a VNC desktop and does not mirror an existing monitor.

For low latency, the native sender captures the headless output directly through ext-image-copy-capture-v1. It converts only compositor-reported damaged regions to grayscale and retains only the newest image generation, so receiver backpressure drops stale frames instead of accumulating display lag. There is no intermediate process, FIFO, recorder, video encoder, or container.

Repository layout

sender/    Native Linux/Wayland capture and transport
receiver/  Android application and native renderer
bin/       Display lifecycle and receiver-install helpers
tests/     Sender protocol integration tests
Makefile   Root build entry point for both components

The sender and receiver deliberately live in one repository because changes to the transport protocol generally require coordinated updates on both sides.

Supported environment

  • Linux with systemd user services and Hyprland
  • A compositor implementing ext-image-copy-capture-v1 (the current backend is tested on Hyprland)
  • A Daylight DC-1 with USB debugging enabled
  • USB data cable; no Wi-Fi connection is required
  • x86-64 host for the prebuilt bundle, or any Linux architecture capable of building the sender from source

Run the dependency check before building:

bin/daylight-doctor

It checks host commands and libraries, the pinned Android SDK components, Java, and optional device connectivity.

Linux dependencies

On Arch Linux/Omarchy, install the equivalent of:

sudo pacman -S --needed base-devel cmake jdk21-openjdk jq lz4 pkgconf wayland wayland-protocols android-tools

Hyprland and systemd are normally already present on Omarchy. On Debian/Ubuntu, the package names are typically:

sudo apt install build-essential cmake openjdk-21-jdk jq adb liblz4-dev libwayland-dev wayland-protocols pkg-config

The distribution must provide recent staging Wayland protocol XML files under /usr/share/wayland-protocols/staging/. Package names vary, so use bin/daylight-doctor as the authoritative check.

Android SDK setup

Install Android Studio or Google's command-line tools, set ANDROID_SDK_ROOT, and install these pinned components:

export ANDROID_SDK_ROOT=/path/to/Android/Sdk
export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH"

sdkmanager \
  "platform-tools" \
  "platforms;android-34" \
  "build-tools;34.0.0" \
  "ndk;26.3.11579264" \
  "cmake;3.22.1"

Set JAVA_HOME to JDK 17 or newer; JDK 21 is the tested choice. The Gradle wrapper is included, so a system Gradle installation is unnecessary.

Build from source

Build the Linux sender:

make sender
make test

Build the Android receiver:

export ANDROID_HOME=/path/to/Android/Sdk
export JAVA_HOME=/path/to/jdk-21
make receiver

The Android SDK must provide API 34, Build Tools 34.0.0, NDK 26.3.11579264, and CMake 3.22.1. Three variants are available:

Target Purpose APK
make receiver Debugging native and Kotlin code receiver/app/build/outputs/apk/debug/app-debug.apk
make receiver-profile Optimized native code with Android profiling enabled receiver/app/build/outputs/apk/profile/app-profile.apk
make receiver-release Optimized, non-debuggable daily-use build receiver/app/build/outputs/apk/release/app-release.apk

Without signing environment variables, release/profile APKs use Android's local debug key so variants can replace one another during development. Public releases should use a persistent private signing key; see “Release artifacts.”

Build both components with make. Build outputs are ignored by Git and can be removed with make clean.

Install from a release bundle

Verify downloads and extract the host bundle:

sha256sum -c SHA256SUMS
tar -xzf daylight-display-0.1.0-linux-x86_64.tar.gz
cd daylight-display-0.1.0
bin/daylight-doctor --runtime
bin/install-receiver release
bin/daylight-display start

The host bundle contains the portable sender binary and receiver APK; it does not require the Android SDK or compiler. Runtime dependencies such as ADB, Hyprland, liblz4, Wayland, jq, and systemd are still required.

Connect and install the DC-1

Enable USB debugging on the DC-1, authorize the Linux host, and confirm that adb devices -l reports the tablet in the device state. Then install the locally built receiver:

make install-receiver-release
# or, after building/downloading an APK:
bin/install-receiver release

bin/daylight-display install-receiver also installs release by default. Pass debug or profile to either helper to select another already-built variant.

Android refuses an update when the installed app and new APK use different signing keys. During development, uninstall com.daylight.mirror once before switching from any differently signed build. That uninstall removes the receiver app's local data.

Run the display

Start and manage the display:

bin/daylight-display start
bin/daylight-display status
bin/daylight-display logs
bin/daylight-display stop

Common display settings can be passed directly when starting or restarting:

bin/daylight-display restart \
  --resolution 1280x960 \
  --refresh-rate 60 \
  --brightness 10 \
  --keyframe-interval 300 \
  --damage-full-threshold 75 \
  --scale 1 \
  --workspace 9

Change receiver brightness without interrupting the display:

bin/daylight-display brightness 40
bin/daylight-display brightness 0   # backlight off

Run bin/daylight-display --help for every option.

List the native and performance-oriented 4:3 resolution presets with:

bin/daylight-display resolutions

Arbitrary positive WIDTHxHEIGHT values remain supported; the presets range from the native 1600×1200 mode down to 640×480.

The default output is DAYLIGHT-1, 1280×960 at 60 fps, scale 1, brightness 10, with workspace 9 assigned to it. It keeps only one unacknowledged frame in flight to minimize cursor latency and sends a full recovery keyframe every 300 transmitted frames (about five seconds at 60 fps); reconnects always force an immediate keyframe. Changed content is tracked in 64×64 tiles: sparse, fragmented changes use a packed multi-tile payload instead of one wasteful bounding rectangle. The receiver preserves those tiles through its render handoff and performs separate GPU texture uploads when they cover less than half of their bounding rectangle and require no more than 64 calls; otherwise it uses one bounding upload. Updates covering at least 75% of the image adaptively become full frames. Set --damage-full-threshold 0 to disable that adaptive cutoff. Capture is paced by the headless output's refresh rather than a recorder-side FPS filter, avoiding accumulated frames and catch-up bursts. The receiver's fixed device port 8888 is tunneled to host port 18888.

Override settings with DAYLIGHT_WIDTH, DAYLIGHT_HEIGHT, DAYLIGHT_FPS, DAYLIGHT_BRIGHTNESS (0–255; zero disables the backlight), DAYLIGHT_MAX_INFLIGHT, DAYLIGHT_KEYFRAME_INTERVAL, DAYLIGHT_DAMAGE_FULL_THRESHOLD, DAYLIGHT_PORT, DAYLIGHT_WORKSPACE, or DAYLIGHT_SERIAL. To restore the sharper mode, use DAYLIGHT_WIDTH=1600 DAYLIGHT_HEIGHT=1200 bin/daylight-display restart. If a workload produces too much motion or CPU use, use DAYLIGHT_FPS=30 bin/daylight-display restart.

The launcher runs as a transient user systemd service so it survives the terminal that started it. The output and ADB tunnel are created at runtime and removed on stop. No file under ~/.config/hypr or /usr/share/omarchy is modified.

Performance timing

bin/daylight-display logs reports host averages for capture-to-ACK frame age, pixel processing, LZ4 compression, ACK backpressure waits, socket writes, and receiver ACK round trips. The sender waits for transport capacity before selecting the newest captured generation, preventing an already-processed frame from becoming stale behind an in-flight frame. The Android DaylightMirror log tag reports payload reads, decompression, delta application, GPU texture upload, buffer swap, decode-to-presentation time, tiled-upload frame and call counts, and uploaded pixels as a percentage of the equivalent bounding rectangles. Use the profile receiver when collecting traces and the release receiver when comparing normal performance.

The native sender is built with -march=native and link-time optimization. Rebuild it on the host where it will run; override CFLAGS if a portable binary is required.

Testing

Run all host protocol tests with:

make test

The tests open a loopback receiver, validate resolution/brightness control packets, decompress payloads, acknowledge frames, and reconstruct the expected images. The tiled test specifically covers variable-length tile headers, bounds, packed tile payloads, multi-tile reconstruction, and adaptive full-frame fallback. Android builds also run Gradle's lint checks as part of profile/release assembly.

Release artifacts

Build all versioned artifacts and SHA-256 checksums with:

export ANDROID_SDK_ROOT=/path/to/Android/Sdk
export JAVA_HOME=/path/to/jdk-21
make release

Outputs under dist/ are:

  • daylight-display-VERSION-linux-x86_64.tar.gz: portable x86-64-v2 sender, helpers, README, and APK
  • daylight-display-VERSION-source.tar.gz: reproducible source archive
  • daylight-mirror-VERSION.apk: standalone optimized receiver
  • SHA256SUMS: artifact checksums

The archives use sorted paths, normalized ownership, SOURCE_DATE_EPOCH, and timestamp-free gzip output. Override SOURCE_DATE_EPOCH to reproduce a specific build timestamp.

For a distribution-signed APK, provide all four values before make release:

export DAYLIGHT_KEYSTORE_FILE=/secure/path/release.jks
export DAYLIGHT_KEYSTORE_PASSWORD='...'
export DAYLIGHT_KEY_ALIAS='...'
export DAYLIGHT_KEY_PASSWORD='...'
make release

Never commit the keystore or passwords. Without these values the artifact is development-signed and suitable for local installation, not an official public release.

License

The project is available under the MIT License. Bundled LZ4 source retains its own upstream license notices.

Troubleshooting

adb devices -l shows unauthorized or no device:

  • Unlock the DC-1 and accept its USB-debugging authorization prompt.
  • Confirm the cable supports data, reconnect it, then run adb kill-server && adb start-server.
  • Select USB debugging again in Android developer options if authorization was revoked.

The receiver remains on “waiting”:

  • Run bin/daylight-display status and inspect bin/daylight-display logs.
  • Verify adb reverse --list contains device port 8888 mapped to the configured host port.
  • Restart with bin/daylight-display restart; reconnects force a recovery keyframe.

The headless output is missing or applications open on the wrong monitor:

  • Run hyprctl monitors all and confirm DAYLIGHT-1 exists.
  • Confirm the requested workspace is not reserved by another monitor rule.
  • Stop and restart the helper. It creates only runtime state and does not edit Hyprland configuration files.

Installation fails with INSTALL_FAILED_UPDATE_INCOMPATIBLE:

  • The installed APK uses a different signing key. Uninstall com.daylight.mirror, then reinstall. Uninstalling removes local app data.

Performance is poor during full-screen animation:

  • Try --resolution 1024x768 or --refresh-rate 30.
  • Sparse editor/cursor changes use tiled transport; genuinely full-screen motion must still send large frames.
  • Use sender logs and the Android DaylightMirror log tag to distinguish capture, transport, decode, upload, and display synchronization costs.

Stability testing

Run a one-hour background stability session while using the display normally:

bin/daylight-stability start
bin/daylight-stability status

The monitor samples sender and receiver memory, CPU counters, connection state, and DC-1 temperatures every ten seconds. Results and a generated REPORT.md are written beneath stability-runs/, which is ignored by Git. Use bin/daylight-stability stop to finish early; stopping the monitor does not stop the display. Duration and cadence can be changed with --duration and --interval.

About

Use your Daylight DC1 as an external screen with Linux

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages