Skip to content

Latch v4.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 01:56
· 118 commits to main since this release
  • On macOS and Linux the background service keeps its login item
    registered without restarting itself: it only writes the launch agent or
    user unit, and a copy started while another already answers on the port
    exits quietly instead of failing, so launchd no longer respawns a
    duplicate every ten seconds for the rest of the session and a service
    launchd started at login is no longer killed by its own registration.
    Turning the login item off in the panel leaves the running service alone.

  • Only a Windows PC accepts a pushed brolink-host.exe; a Mac or a
    container answers 400 instead of trying to swap it in, and the Mac only
    sends one to Windows machines.

  • Elevated Windows setup approved with another administrator's password
    reads and writes the launching user's Latch folder, so the engine login
    it configures is that user's.

  • The updater takes the window's locks in the window's order; the reverse
    could hang the app when an update was ready just as a stream ended.

  • Docker desktops now publish a valid streaming app catalog. The missing
    environment object made a healthy-looking VPS report "offers nothing to
    stream"; node self-tests now check the catalog and persisted login setup.
    Startup waits for the engine before launching the control service, which
    prevents two engine processes racing to bind the same ports.

  • On Windows, the streaming engine runs as the signed-in user and the
    only active speakers stay the default. The engine's own startup was
    clearing Steam Streaming Speakers on a PC with no other playback
    device, which left Couldn't get default audio endpoint and a silent
    stream. Startup probe errors are no longer reported as a missing sound
    device.

  • Software encode (a VPS with no GPU) uses every CPU core and the
    ultrafast preset so 1080p60 holds instead of collapsing to ~30 fps and a
    trickle of bitrate. AMD encode prefers speed, with preanalysis off, so
    the target rate does not sit at ~18 Mbps on a still desktop.

  • The stream protocol always treats Tailscale as a LAN: no 1024-byte
    packets, no extra 500 kbps tax, and the bitrate is sent at launch as
    well as over RTSP.

  • The streaming engine is not a second app. Status, logs, the Dock and
    the app list say Latch; only Desktop is launched.

  • Latch is a mesh. Every install lists every other machine on the
    Tailscale account — Windows, Mac, Linux, a VPS container — and Connect
    opens that desktop. The same app shares this machine: Mac and Linux
    install the streaming engine; Windows still does. There is no designated
    host or client role.

  • deploy/node/ is a Docker kit for a VPS or any container: a virtual
    desktop, the streaming engine, Latch's control service, and optional
    Tailscale userspace so the container is its own machine. Several copies
    on one host are several machines. The packet relay stays deploy/relay/.

  • Stream bitrate holds the target instead of swinging with the scene.
    Sunshine is set to constant bitrate (AMD CBR+HRD, NVENC VBV, VA-API CBR),
    Tailscale is treated as a LAN so moonlight does not tax 500 kbps or shrink
    packets to 1024, packets stay 1184 bytes for WireGuard's MTU, and the
    moonlight cap is 150 Mbps. Recommended quality is 50 Mbps; Sharp is 100.

  • Settings and pairing no longer wipe each other. The window, discovery and
    pairing used to rewrite all of client.toml from a stale copy, so a
    bitrate change could drop the PC's certificate and its wake details, and
    a reconnect right after the first pair could ask for a PIN again. Every
    write is now one read-modify-write under a lock: the window overlays its
    preferences, discovery and pairing edit only what they learned, and the
    host panel does the same with host.toml. Config files are written 0600
    in a 0700 folder, and a damaged file is kept beside the new one as
    .bad-* instead of being replaced.

  • An update no longer installs while a connect is waking, pairing or
    launching, a connect refuses to start while an install is under way, and
    a cancelled connect cannot finish into the next one. Cancel takes effect
    while the PC is still being asked for the PIN. The update cache keeps
    this release and the previous one only.

  • Connecting no longer overwrites the Mac clipboard with whatever the PC
    already held. Ctrl+Alt (the host key) is never sent to the PC, and it
    works even when a control has focus; while the mouse is captured, keys go
    to the PC rather than to a focused control. Captured mouse movement is
    scaled to the stream's pixels, so a hand movement crosses the same share
    of the PC's desktop as of the picture.

  • Every Tailscale CLI call is bounded (10 s; the per-request whois 2 s),
    and its output is drained while it runs, so a slow or chatty CLI cannot
    hang the window or the control service.

  • Host setup runs brolink-host --setup-elevated after UAC, writes its
    script under %SystemRoot%\Temp (administrators only) and passes the
    engine take-over helper as an encoded command, instead of running a
    user-writable setup.ps1. Repair setup registers the engine service when
    files exist but the service is down, and stops a leftover service before
    deleting it. Firewall program= paths are single-quoted so a $ in the
    username is not expanded. The logon task for the take-over helper runs
    with limited rights.

  • The engine login is written as the engine's own hashed credentials file
    (brolink-web.json, credentials_file = in sunshine.conf) instead of
    sunshine --creds on a command line, and the engine's web API is called
    with the password on stdin (curl --config -), so neither the setup log
    nor the process list shows it. The Docker node does the same through
    deploy/node/bootstrap.py, which also keeps an existing host.toml
    (preferences, pairing) intact across restarts.

  • Auto-update refuses a GitHub asset with no SHA-256. The Mac installer
    checks that digest, verifies the signature, and swaps /Applications
    without deleting the live app first. git credential is killed if its
    stdin closes early.

  • Stream teardown waits for in-flight input and keeps the session alive
    until moonlight's detached termination thread has nowhere to call; a
    decode unit whose buffer list overruns its declared length is refused.

  • The migration check reads the installed engine's real kind instead of
    labelling every install "Latch". The macOS launch agent and Linux user
    unit escape the executable path, and a failure to register them is
    reported instead of ignored. Spawned engine and service processes are
    reaped. A settings save failure shows in the window.

  • One pointer. Freeing the mouse showed the Mac cursor behind egui's back,
    and it stayed on top of the PC's own cursor in the picture until the
    pointer left the window; the cursor is now hidden the one way egui
    keeps track of.

  • The mouse is captured by default: a click on the picture hides the
    cursor, holds it in place and sends raw movement, which is what games
    read for the camera. A game that ignores the cursor's position (Genshin
    Impact) turned only in the old Captured mode; Honkai: Star Rail happened
    to follow the position too. Free in the Mouse menu is the old
    behaviour, and the choice is remembered.

  • The stream is the whole window, nothing over it. Ctrl+Alt is the
    host key, as in a hypervisor: it frees the mouse and drops the toolbar
    over the picture; a click on the picture, or Ctrl+Alt again, puts it
    away and takes the mouse back. The toolbar no longer sits above the
    picture in a window, which is what left the bars either side, and a
    window takes the stream's proportions when a session starts.

  • 1080p, 1440p and 4K are 1920×1080, 2560×1440 and 3840×2160, not those
    widths at this display's shape (1920×1246 on a 14" MacBook Pro), and the
    list shows the pixel size of each. Match screen is unchanged. On a
    display of another shape the standard sizes leave a bar above and below.

  • Less delay between the PC and the eye. A decoded frame is presented as
    soon as it is drawn instead of waiting for the display's next refresh,
    up to a refresh interval sooner, and the decoder fills the same few
    buffers over and over instead of taking nine fresh megabytes per frame.

  • The Dock icon is the bundle icns, in macOS's rounded app-icon shape.
    eframe was replacing it at runtime with a 64-pixel square of the logo,
    and the icns had been pre-masked with transparent corners, which macOS
    26 draws as a square plate. scripts/make-macos-icon.py now fills the
    canvas and lets the system apply the shape.

  • The Windows host icon (Explorer, taskbar, Alt-Tab, the window, and the
    in-app header) is the same filled tile. The logo's outer pad had left a
    square plate around the mark; icon::render now crops it.