Files
Warp/apps/desktop/README.md
T
tajniak81andClaude Opus 5.5 829b236efe
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
CI / Desktop client (ubuntu-24.04) (push) Canceled after 0s
CI / Desktop client (windows-latest) (push) Canceled after 0s
Desktop: notifications, tray, auto-update, installer; web: sliding sync, voice messages, notifications
Web client (also what the desktop app runs):

- Simplified Sliding Sync (MSC4186) when the homeserver advertises
  org.matrix.simplified_msc3575. src/matrix/sliding.ts answers the SDK's
  GET /sync with a sliding sync request and rewrites the response into
  the /sync shape, as the Android app does; the SDK's own sliding sync
  mode doesn't sort thread replies into threads. 20 rooms first, then
  every room (up to 1000); to_device, e2ee, account data, receipts and
  typing extensions; M_UNKNOWN_POS starts a new connection. The SDK's
  /members?at=<sync token> drops the token, which here is ours. Settings
  can switch back to /sync.
- Notifications for new messages and incoming calls while Warp isn't in
  front, by the account's push rules, evaluated after decryption. The
  browser's Notification API once turned on in Settings, or the desktop
  app's bridge (window.warpDesktop). One per room; a click opens it.
- Voice messages: record from the composer, sent as m.audio with
  MSC3245's voice marker and MSC1767's duration and waveform (encrypted
  in encrypted rooms); a waveform player in the timeline.
- @warp/ui: a slot for extra composer buttons; bell-off, play and pause
  icons.

Desktop app:

- System notifications through Wails' notifications service, fed by
  /_desktop/bridge.js (/_desktop/notify, JSON only); clicking one shows
  the window and opens the room.
- Tray icon with Open and Quit; closing the window hides it (a one-time
  notification says so), File > Quit ends it. The page title goes to
  the window and the tray tooltip, the unread count to the taskbar or
  Dock badge.
- Auto-update from a Wails update manifest at DESKTOP_UPDATE_URL: a quiet
  check a minute after start and every six hours, a notification when a
  newer version exists, Help > Check for updates opens the update window.
  Optional signature key (DESKTOP_UPDATE_KEY). Linux package installs
  leave updates to the package manager. build/manifest writes the
  manifest.
- Per-user NSIS installer (make desktop-windows-installer), the macOS
  bundle zipped for the updater, a desktop CI job, and a Desktop release
  workflow that builds all three platforms and the manifest.

Tested on Windows against a local homeserver: sliding sync with E2EE,
threads and a server restart; notifications (toast history), close to
tray, relaunch restoring the window; voice messages recorded, sent and
played, encrypted too; the update check finding a newer version. Not
tested: installing an update, the NSIS installer, CI, macOS and Linux.
Code signing needs certificates and isn't done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:01:02 +02:00

208 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Warp desktop client
The [web client](../web/README.md) in a native window, built with
[Wails v3](https://v3alpha.wails.io). Every screen is the web client's:
`warp-desktop` embeds the same build `warp-web` does and serves it through
the same Go package (`apps/web/host`), so web and desktop don't drift
apart. It runs on Windows (WebView2), macOS (WKWebView) and Linux
(WebKitGTK).
```
window ──► wails.localhost ──► shell (this app) ── the web client, /config.json
│ first-run page, links
└──────────────────────────────────────────────► homeserver /_matrix/*
```
Unlike `warp-web`, the desktop app isn't installed next to one homeserver,
so it proxies nothing. The client calls the homeserver directly, the way
any Matrix client does; homeservers answer CORS for every origin. The
Content-Security-Policy allows the homeserver plus any `https:` and `wss:`
origin, because the call backend (the LiveKit JWT service and SFU) is
learned from the homeserver at run time and can't be listed in advance.
## What it adds to the web client
- **First run asks for the homeserver.** A server name (`example.org`) or
an address. Discovery follows `/.well-known/matrix/client` and checks
`/_matrix/client/versions`. If the well-known document names an address
that doesn't answer, the typed address is used when that one works.
**File → Change homeserver…** asks again. A session already signed in
keeps the server it signed in to until you sign out, so changing the
homeserver never sends one server's access token to another.
- **Links to other sites open in the system browser.** That covers a URL in
a message, `target="_blank"` and `window.open`. Without this they would
replace the app in its window or open a bare webview window.
`/_desktop/links.js` does it; the shell adds a `<script>` tag for it to
`index.html`. Only `http`, `https` and `mailto` URLs are opened.
- **One instance at a time.** Sessions and encryption keys live in the
webview's storage, and two instances on one store would both sync and
use the same one-time keys. Starting the app again brings the open window
forward.
- **System notifications.** New messages and incoming calls show as the
system's own notifications (Windows toasts, Notification Center, the
freedesktop daemon), one per room; clicking one opens the room. The
client decides what notifies, by the account's push rules
(`apps/web/src/stores/notifications.ts`), and hands it to the shell
through `window.warpDesktop`, which `/_desktop/bridge.js` adds; the shell
shows it with Wails' notifications service. On macOS they need the
bundle (`make desktop-macos`), not the bare binary.
- **The tray and the unread count.** A tray icon (menu-bar icon on macOS)
with Open and Quit. Closing the window hides it and the app keeps
running there, so notifications still arrive; the first time, a
notification says so. **File → Quit** (Ctrl+Q) ends it. The window
title and the tray's tooltip follow the page title, and the number of
unread rooms goes on the taskbar button (Windows) or the Dock icon. On a
Linux desktop with no tray (GNOME without an AppIndicator extension),
starting the app again brings the hidden window back.
- **Updates.** A build that knows its update manifest (see
[Updates](#updates)) checks a minute after it starts and every six hours,
quietly; a newer version brings a notification, and clicking it, or
**Help → Check for updates…**, opens Wails' update window, which
downloads the new binary, checks its SHA-256 (and signature, when the
build pins a key), swaps it in and restarts.
## Where things are kept
| What | Where |
|---|---|
| The chosen homeserver, whether the tray hint was shown | `desktop.json` in the user config directory, under `Warp` (`%AppData%\Warp` on Windows, `~/Library/Application Support/Warp` on macOS, `~/.config/Warp` on Linux) |
| Sessions, encryption keys, cache | the webview's storage: `%LocalAppData%\Warp\WebView2` on Windows; the platform webview's default location elsewhere |
Signing out deletes a session's keys, the same as in the browser (see
[Encryption keys in the browser](../web/README.md#encryption-keys-in-the-browser)).
## Build and run
Requires Go 1.26, Node 22 and pnpm. On Windows the WebView2 runtime, which
Windows 11 ships with, and no C compiler. On macOS, Xcode's command line
tools. On Linux, a C compiler with `libgtk-4-dev` and `libwebkitgtk-6.0-dev`
(Wails needs cgo there, and builds against GTK 4 by default).
```bash
pnpm install
make desktop # builds the web client, then bin/warp-desktop
./bin/warp-desktop
```
| Flag | Environment | |
|---|---|---|
| `-homeserver` | `WARP_DESKTOP_HOMESERVER` | use this homeserver for this run without saving it |
| `-version` | | print the version |
| Command | What |
|---|---|
| `make desktop` | a release build: `-tags production` (no developer tools), and on Windows a GUI program with no console |
| `make desktop-run` | build the web client and run the app with the developer tools (View → Open Developer Tools) |
| `make desktop-test` | vet and test the Go code |
| `make desktop-macos` | on macOS: `bin/Warp.app` |
| `make desktop-linux` | on Linux: `bin/warp-desktop.deb`, `.rpm` and `.pkg.tar.zst` |
| `make desktop-resources` | regenerate the icons and the Windows `.syso` files |
| `make desktop-windows-installer` | on Windows, with NSIS: `bin/warp-desktop-setup.exe` |
| `make desktop-manifest` | the update manifest, `bin/warp-desktop.json` |
A development build (one without `-tags production`) also opens the Chrome
DevTools Protocol when `WARP_DESKTOP_REMOTE_DEBUGGING_PORT` is set, on
Windows. A script or Playwright can then drive the real window:
```bash
WARP_DESKTOP_REMOTE_DEBUGGING_PORT=9229 go run ./cmd/warp-desktop
```
## Packaging
The inputs are in `build/`. The `wails3` CLI does the generating and the
Linux packaging; the Makefile runs it with `go run` at the version
`go.mod` pins (`v3.0.0-beta.27`), so it needs no install. `DESKTOP_VERSION`
in the Makefile (default `0.1.0`) is the version the OS sees in each
package; `-version` prints the build's git version.
- **Windows installer.** `make desktop-windows-installer` turns the
binary into `bin/warp-desktop-setup.exe` with NSIS
(`build/windows/installer.nsi`). It installs for the current user, in
`%LocalAppData%\Programs\Warp` with a Start menu entry and an entry in
Installed apps, so it needs no administrator rights and the updater can
replace the executable. It points at the WebView2 download when the
runtime is missing. Uninstalling keeps your settings and the webview's
storage, so a reinstall stays signed in. `DESKTOP_SIGN` signs the
installer and uninstaller, given a signing command with `%1` for the file.
- **Windows.** `cmd/warp-desktop/rsrc_windows_{amd64,arm64}.syso` carry the
icon, the version info and the application manifest (per-monitor DPI,
common controls 6, `asInvoker`). They are committed, so `make desktop`
(and a plain `go build`) links them in with no extra step. The icon,
`build/windows/icon.ico`, takes 16–48 px from the brand favicon (the
symbol's small cut) and 64–256 px from the app icon tile;
`build/windows/mkico.go` merges the two. After changing the icon,
`build/windows/info.json`, the manifest or `DESKTOP_VERSION`, run
`make desktop-resources` and commit the `.syso` files.
- **macOS.** `make desktop-macos`, on a Mac, builds the binary and puts it
in `bin/Warp.app` with `build/darwin/Info.plist` and `icons.icns`, then
signs the bundle ad hoc, and zips it as `bin/Warp.app.zip` for the
updater. The `Info.plist` has `NSCameraUsageDescription`
and `NSMicrophoneUsageDescription`: without them macOS ends the app the
moment a call or the QR scanner asks for the camera or microphone. The
bundle is for the CPU it was built on, and ad-hoc signed it opens only on
the machine that built it; to hand it out, sign it with a Developer ID
and notarize it.
- **Linux.** `make desktop-linux`, on Linux, builds the binary and turns it
into `.deb`, `.rpm` and Arch packages with nfpm (through `wails3 tool
package`, configured by `build/linux/nfpm.yaml`). They install
`/usr/bin/warp-desktop`, a menu entry and the icon, and depend on GTK 4
and WebKitGTK 6.0 (Ubuntu 24.04+, Debian 13+, Fedora 40+).
`DESKTOP_FORMATS=deb` builds one format; `DESKTOP_MAINTAINER` defaults
to your git name and email.
`build/appicon.png` and `build/windows/favicon.ico` are copies from the
brand kit (`DESIGN/warp-brand/logo-kit`, not in git):
`symbol/warp-symbol-app-icon-1024.png` and `web/favicon.ico`.
## Updates
The app updates itself from a Wails update manifest: one JSON document
with the latest version and, per platform, the file to download and its
SHA-256. Any static web server can host it, next to the files it lists.
- Build with `DESKTOP_UPDATE_URL` (the manifest's URL) and
`DESKTOP_VERSION` (the version this build is, `1.2.3`). A build without
an update URL never checks, and has no **Help** menu.
- `make desktop-manifest DESKTOP_VERSION=1.2.3
DESKTOP_ARTIFACTS="windows/amd64=bin/warp-desktop.exe ..."` writes
`bin/warp-desktop.json`; the artifact URLs are the files' names,
relative to the manifest.
- Artifacts are the bare binaries for Windows and Linux, and
`Warp.app.zip` for macOS. The Windows one is `warp-desktop.exe`, not the
installer: the updater swaps the executable in place, which the per-user
install allows. A Linux install from a package (in `/usr`) doesn't
update itself; the package manager does that.
- `DESKTOP_UPDATE_KEY`, a PEM public key (Ed25519 or ECDSA P-256), makes
the build insist on releases signed with its private key. Without one, a
release is checked by its digest only, so the manifest's host is trusted.
The **Desktop release** workflow (`.github/workflows/desktop-release.yml`,
on a `desktop-v1.2.3` tag or by hand) builds Windows, Linux and macOS,
the installer and the Linux packages, and writes the manifest; set the
`DESKTOP_UPDATE_URL` repository variable to where you will publish them.
It keeps everything as one workflow artifact; publishing is yours to do.
## Layout
| Path | What |
|---|---|
| `cmd/warp-desktop/` | the binary: the Wails app, window, menu and single-instance lock; notifications, the tray and the badge (`desktop.go`); updates (`update.go`) |
| `internal/shell/` | the webview's `http.Handler`: the client through `apps/web/host`, the first-run page and discovery, the settings file, links, the bridge the client's notifications and title go through |
| `build/` | packaging inputs: icons, the Windows manifest and version info, the NSIS installer script, `Info.plist`, the nfpm config; `build/manifest`, the update manifest writer |
## Not yet
These follow from [PLAN.md §8.3](../../PLAN.md):
- Signing, which needs certificates: an Authenticode-signed executable
and installer (without it SmartScreen warns on first run), a Developer
ID signed and notarized macOS `.app` in a `.dmg` (ad-hoc signed, it
opens elsewhere only after "Open anyway"), and a signing key for update
releases. The hooks are there: `DESKTOP_SIGN`, `DESKTOP_UPDATE_KEY`.
- A universal (arm64 + amd64) macOS build, an AppImage or Flatpak.
- Sync and encryption in Go (`mautrix-go`) behind the same Pinia stores,
with the access token in the OS keychain. Today the desktop app runs
matrix-js-sdk in the webview exactly as the browser does. Whether to move
this to Go is open question 5 in the plan.