Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
cargo test --locked
cargo build --locked
- name: Qt manager dependencies
run: sudo apt-get update && sudo apt-get install -y cmake qt6-base-dev qt6-declarative-dev qml6-module-qtqml qml6-module-qtqml-models qml6-module-qtquick qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-templates qml6-module-qtquick-window qml6-module-qtqml-workerscript
run: sudo apt-get update && sudo apt-get install -y cmake qt6-base-dev qt6-declarative-dev qml6-module-qtqml qml6-module-qtqml-models qml6-module-qtquick qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-templates qml6-module-qtquick-window qml6-module-qtqml-workerscript libqt6svg6
- name: Qt manager build and isolated tests
run: |
cmake -S ui -B build/ui -DCMAKE_BUILD_TYPE=Release
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ cargo build --locked --release
./target/release/remote-desktops connect macbook
./target/release/remote-desktops status --json
./target/release/remote-desktops disconnect macbook
./target/release/remote-desktops start
./target/release/remote-desktops settings remove macbook
```

Configure and pair your computers first; see [backend usage](docs/BACKEND.md).
Expand Down Expand Up @@ -84,9 +86,10 @@ Moonlight, or Sunshine application.

## Graphical manager

A separate Qt 6/QML manager is available on `develop` for existing configured
computers. It provides connection controls, profile selection, display recovery,
and per-computer launchers. Closing it leaves connections running.
A separate Qt 6/QML manager is available on `develop`. It adds, edits, and
removes computers paired in Moonlight, and provides connection controls,
profile selection, display recovery, and per-computer launchers, following the
active Omarchy theme and desktop font. Closing it leaves connections running.

See [UI build, preview, and interaction guide](docs/UI.md). Pairing and computer
configuration still use Moonlight and the backend setup guide; no installable
Expand Down
13 changes: 13 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Moonlight retains hardware decoding and its own rendering/frame pacing.
- Session intent and recovery use separate files. Rust writes `session.json`;
the Python helper alone writes `recovery.json` under an operation lock. Both
use private atomic file replacement and fsync.
- A `forget` command drops a session record only when it is settled: not
desired, no live client, phase idle or attention, and no pending recovery
journal. Removing the record also drops its wake channel, which ends the
session worker before it can touch a record a later connect creates.
- Disconnect intent is durable immediately. An already-started host operation
may finish, but its journal is retained and restored before any client launch.
Cancelling a task must never erase an uncertain host write.
Expand Down Expand Up @@ -108,6 +112,15 @@ implementation. Configuration listing runs the Rust CLI once at startup and on
explicit refresh, exposing only computer ID/name, host, platform, default
profile, and profile names. Pairing material is not part of this listing.
Commands use an absolute executable and an argv array, never a shell string.
Remove runs `settings remove`; Start service runs `start`. The only other
process the manager starts is the installed Moonlight executable, on an explicit
Open Moonlight request during setup, detached and without arguments. Window
size and the selected computer persist through QSettings; the preview never
writes them.

The status reply also carries the session's launch time, Moonlight version,
retry attempt count, and next retry time so the manager can show elapsed
session time and retry countdowns without inventing figures.

Status uses asynchronous QLocalSocket request/reply framing, a 1.5-second
request timeout, a 2 MB reply limit, and at most one outstanding request. The
Expand Down
11 changes: 11 additions & 0 deletions docs/BACKEND.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,19 @@ cargo build --locked --release
./target/release/remote-desktops reconnect macbook
./target/release/remote-desktops disconnect macbook
./target/release/remote-desktops restore macbook
./target/release/remote-desktops start
./target/release/remote-desktops settings remove macbook
```

`start` starts the daemon if it is not running and reports status without
connecting anything. `settings remove` deletes a computer's configuration and
its generated launcher entry, and forgets its session record only when nothing
owns it: it refuses while the session is desired, a client is alive, the
session is still finishing, or a recovery journal is pending. With the daemon
running, the record is dropped through the daemon's `forget` command;
otherwise the CLI holds the daemon's writer lock while deleting the directory.
A computer removed this way can be added again from the same pairing.

Connect/disconnect/restore/reconnect start the daemon on demand. To run it in
the foreground for development, use `remote-desktops daemon`. A CLI command
returning successfully means the intent was accepted; inspect `status` for
Expand Down
186 changes: 118 additions & 68 deletions docs/UI.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Desktop manager

The Qt 6/QML manager is a separate application for setting up and connecting computers. It
shows connection state, selects an existing profile, connects, focuses,
disconnects, reconnects, restores a pending host display, and installs a
per-computer application launcher. It has no video renderer and owns no host
recovery. Closing it leaves the daemon and Moonlight sessions running.
The Qt 6/QML manager is a separate application for setting up and connecting
computers. It shows connection state, selects an existing profile, connects,
focuses, disconnects, reconnects, restores a pending host display, installs a
per-computer application launcher, and removes computers. It has no video
renderer and owns no host recovery. Closing it leaves the daemon and Moonlight
sessions running.

## Run from a checkout

Expand All @@ -17,98 +18,147 @@ cmake --build build/ui --parallel 2
build/ui/remote-desktops-manager --backend "$PWD/target/debug/remote-desktops"
```

The UI requires Qt 6.4 or later: Quick, Quick Controls 2, Network, and the Qt
Test development module for the test target. No Qt libraries are linked into
the Rust backend. The manager accepts an absolute backend path; otherwise it
looks beside itself and then on PATH. Configuration and socket paths follow
the backend's XDG conventions.
The UI requires Qt 6.4 or later: Quick, Quick Controls 2, Network, the SVG
image format plugin for its icons, and the Qt Test development module for the
test target. No Qt libraries are linked into the Rust backend. The manager
accepts an absolute backend path; otherwise it looks beside itself and then on
PATH. Configuration and socket paths follow the backend's XDG conventions.

Opening the manager reads configured computers and status. It does not start a
connection automatically. An explicit Connect starts the daemon when necessary.
Do not use a development binary to take over active production sessions merely
to test the UI. Use the isolated preview instead:
connection automatically. An explicit Connect, or the Start service action shown
when the service is not answering, starts the daemon. Window size and the last
selected computer are kept in the application's QSettings file; the preview
never writes them. Do not use a development binary to take over active
production sessions merely to test the UI. Use the isolated preview instead:

```sh
build/ui/remote-desktops-manager --demo
build/ui/remote-desktops-manager --demo --state restore-pending
build/ui/remote-desktops-manager --demo --state many --compact
build/ui/remote-desktops-manager --demo --dialog help
ctest --test-dir build/ui --output-on-failure
```

Demo actions never spawn the backend or access its socket. The three example
computers are synthetic. `--state` also supports `idle`, `preflight`, `empty`,
and `unavailable`; `--compact` exercises the minimum window size. With an
offscreen platform, `--screenshot /tmp/manager.png` exports the rendered demo.
These preview and screenshot options require `--demo`.
Demo actions never spawn the backend or access its socket; they walk through
the same intermediate phases a real session reports. The example computers are
synthetic. `--state` supports `idle`, `connecting`, `preflight`, `running`,
`attention`, `restore-pending`, `empty`, `unavailable`, `many` (twelve
computers), and `unconfigured` (a removed computer with a pending restore).
`--dialog` opens `help`, `details`, `remove`, `notice`, or `error`. `--compact`
renders the minimum window size. With an offscreen platform,
`--screenshot /tmp/manager.png` exports the rendered demo. These preview and
screenshot options require `--demo`.

## Layout

- The header carries the application identity, the background service
indicator (running, not responding, or starting; click it to check again),
refresh, and help.
- The sidebar lists computers with a status dot and label per row, scrolls
with a visible bar, and holds the one global primary action, Add computer.
- The main pane shows the selected computer's name, platform, and address,
then a status card, the action row, the profile, and tertiary actions (Edit,
Add to app launcher, Details, Remove). Nothing else competes for the space.
- When the service is not answering, one banner names the cause and offers
Start service and Check again. Rows keep their last known label with a
hollow dot, and the card says which state was last known.
- Transient messages appear as a toast over the main pane. Informational
messages fade after five seconds; errors stay until dismissed or replaced.
Accepted connection commands produce no message because status shows them.

## Status card

The card states what is happening, what it means, and what to do next, with a
tone that follows the state: neutral, connected, or warning. It lists only
values the daemon reports: time since the client launched, the profile,
whether an owned window was detected, the negotiated video size and rate from
Moonlight's own log, the Moonlight version, the next retry with its attempt
count, and the last error message inline. Nothing is invented: no thumbnails,
latency, or quality figures. Window detection is an identity match, not proof
of a rendered frame.

## Interaction design

- A stable computer list sits beside the selected connection. Selection is
retained by computer ID, rather than by a changing row index.
- Selection is retained by computer ID rather than a row index. Tab reaches
the list; Up, Down, Home, and End move through it; typing a letter jumps to
the next matching name; Enter or a double-click runs the primary action.
Ctrl+Enter runs the primary action, Ctrl+R refreshes, Ctrl+N adds a
computer, and Escape closes a dialog.
- One primary action follows state: Connect, Open desktop, or Restore display.
Repeat actions are suppressed while an acknowledgement is pending. A request
acknowledgement is not presented as a successful connection.
- Profiles cannot change during a desired session; disconnect first. Reconnect
restarts the selected client. Disconnect cancels pending connection intent
and asks the daemon to restore its owned host settings.
- Host recovery errors remain visible, with technical detail available on
demand. Recovery records for removed computers remain in the list. There is
deliberately no one-click abandonment of the original host settings.
- Status loss preserves last-known records and labels them unavailable; it
does not pretend that a running remote session disconnected. Refresh retries
observation. Connect/Restore use the CLI's existing daemon-start behavior.
- No thumbnails or performance figures are invented. The device drawing is an
illustration. Window-ready is an identity match, not proof of a rendered
video frame or measured latency.
- Tab navigates controls, arrow keys navigate the focused computer list,
Ctrl+Enter invokes the primary action, Ctrl+R refreshes, and Escape closes
a dialog. Focus rings, accessible names, textual statuses, and restrained
hover transitions complement color cues.

## Current scope
While a transition runs, the primary action is disabled and labeled with the
phase, and Cancel sits beside it, never in the position Disconnect uses. A
running client without an observed window disables Open desktop; Reconnect
and Disconnect remain. Repeat actions are suppressed while a request is
pending. A request acknowledgement is not presented as a connection.
- Profiles cannot change during a desired session; disconnect first. A
computer with a single profile shows it as text rather than a control.
- Host recovery errors stay visible inline in the card, with a details dialog
for identity, state, window, client, and copyable text. Recovery records for
removed computers remain listed until restored. There is deliberately no
one-click abandonment of the original host settings.
- Remove asks for confirmation, then calls `settings remove`. The backend
refuses while the computer is connected, still finishing a session, or has
a pending display restore, and the reason is shown as an error message.
- Focus rings, accessible names and roles, textual statuses, and restrained
animation on state changes complement color cues.

## Guided setup

Choose **Add computer** to select a computer already paired in Moonlight,
confirm its name and address, choose desktop quality, then test and save. The
check authenticates through Moonlight and verifies the Desktop app is listed.
It does not start video, capture input, or change a host display. Pairing and
certificate storage stay in Moonlight. If no paired computers appear, complete
pairing there and refresh the setup list.

New profiles use the host鈥檚 existing display and automatic decoder selection.
Advanced controls expose resolution, frame rate, bitrate, codec, mouse mode,
and audio policy. **Edit** changes an existing computer and its default profile;
confirm its name and address, choose desktop quality, then check and save.
Steps are shown with completed, current, and upcoming markers; editing an
existing computer skips the first step and shows two. If Moonlight has no
paired computers, the dialog explains pairing and offers Open Moonlight, which
launches the installed `moonlight` executable, and Refresh list. Pairing and
certificate storage stay in Moonlight.

Continue validates the name, address, and resolution and shows the rule under
each field that needs attention. The check runs automatically when the last
step opens and can be repeated with Check again. It authenticates through
Moonlight and verifies the Desktop app is listed; it does not start video,
capture input, or change a host display. A revision conflict offers Reload,
which refreshes the saved revision for a new computer or reloads the saved
settings for an existing one. The summary lists name, address, operating
system, quality, mouse, and audio in plain language. Save requires a passing
check of the current draft; editing any field invalidates it. Escape or Cancel
with unsaved changes asks before discarding.

New profiles use the host's existing display and automatic decoder selection.
Advanced controls expose resolution, frame rate, bitrate in Mbit/s, codec,
mouse mode (direct or relative pointer), and audio (play here and mute when
unfocused, always play here, or keep audio on the host), each with a one-line
explanation. **Edit** changes an existing computer and its default profile;
other profiles, SSH configuration, host display adapters, and window identity
are preserved. Changes apply after disconnecting and starting a new connection;
Reconnect continues using the active session鈥檚 snapshot.

The save button requires a successful check of the current draft. Editing any
field invalidates that check. Validation or reachability failures leave the
saved configuration untouched. A revision conflict asks you to reopen setup,
so another editor鈥檚 changes are not silently overwritten. Cancel discards the
draft. Saving and optional launcher installation are separate operations: a
launcher failure leaves the computer saved and can be retried from the main
screen. Existing launcher entries can be updated from the final setup step.
are preserved. Changes apply after disconnecting and starting a new connection.

Use `--demo --setup-preview computer` (or `preferences`, `advanced`, `check`)
to preview each setup page with synthetic data. Demo settings stay in memory.

The manager does not install itself, register a system service, migrate legacy
configuration, or change Hypertile. Packaging remains a separate delivery step.

## Omarchy themes
## Omarchy themes and type

The manager automatically reads the active Omarchy palette from
`$XDG_STATE_HOME/omarchy/current/theme/colors.toml` (normally
`~/.local/state/omarchy/current/theme/colors.toml`). Background, foreground,
accent, surfaces, and warning/destructive colors follow that theme. Derived
colors support light and dark palettes, with contrast-adjusted text for
buttons and recovery messages.
`~/.local/state/omarchy/current/theme/colors.toml`). It uses `mode`,
`background`, `foreground`, `accent`, `lighter_background`,
`dark_background`, `selection`, `muted`, `red`, `yellow`, and `green`; only
the first three are required. Every shared foreground, including disabled
text and status colors, is adjusted until it reads at WCAG AA contrast on
every surface it is drawn on, in light and dark palettes alike.

Text uses the desktop's application font as provided by the platform theme;
no family is hardcoded. A five-step type scale is derived from that font's
size, with the smallest step never below nine points.

Filesystem notifications update the palette without restarting the manager or
its connections, including when Omarchy replaces the entire theme directory.
There are no installed hooks, theme-file writes, subprocesses, or periodic
palette polling. Outside Omarchy, a built-in palette is used. Partial, malformed,
or temporarily missing theme files retain the last complete palette.
There are no installed hooks, theme-file writes, or periodic palette polling.
Outside Omarchy, a built-in palette is used. Partial, malformed, or
temporarily missing theme files retain the last complete palette.

For isolated visual checks, pass `--theme-file /path/to/colors.toml`; the light
fixture is `ui/tests/light.toml`. The reader accepts flat quoted `#RRGGBB` color
assignments and ignores non-color metadata. It never executes theme code.
assignments and the `mode` key and ignores other metadata. It never executes
theme code.
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ enum Action {
Status {
computer: Option<String>,
},
/// Start the background service if needed and report status; no connection is made.
Start,
Computers,
/// Guided computer setup and editing.
Settings {
Expand Down Expand Up @@ -166,6 +168,7 @@ async fn run(cli: Cli) -> Result<()> {
}
Action::Focus { computer } => json!({"command":"focus","computer":computer}),
Action::Status { computer } => json!({"command":"status","computer":computer}),
Action::Start => json!({"command":"status"}),
_ => unreachable!(),
};
if matches!(
Expand All @@ -175,6 +178,7 @@ async fn run(cli: Cli) -> Result<()> {
| Action::Disconnect { .. }
| Action::Restore { .. }
| Action::Reconnect { .. }
| Action::Start
) && tokio::net::UnixStream::connect(paths.socket())
.await
.is_err()
Expand Down
Loading