Commit Graph

33 Commits

Author SHA1 Message Date
pletch 73e0b6ddf2 fix: reports in local time, jump-host tunnels, and resizes that change no pixels
Unrelated fixes carried by the fork: session times and date filters in local
time, no local resolution of the target when a jump host is set, no resize
request that changes no pixels, and rustfmt on three hunks left unformatted.

(cherry picked from commit d9b361db6eb216d871f3fdac41c423a586e2d559)

Upstream changes on pick: the client.html resize skip is kept but
without the native-resolution sizing (displayScale/deviceSize), which
belongs to the HiDPI commit not taken here, so sizes stay in CSS pixels.
The oidc.rs hunk was formatting only and is superseded.
2026-10-03 11:57:23 +10:00
pletch f16eed73ab fix(h264): size aux snapshots from the render, keep the combine watchdog
Review notes from the upstream issue thread:

- The auxiliary-view snapshot was sized to the auxiliary frame's own
  dimensions. The v1 chroma layout pads that view to a multiple of 16
  rows, so the snapshot could be taller than the picture the renderer
  draws; with no rects the whole thing is blitted, painting a blank
  strip below the image. Size it from the rendered canvas instead.

- The per-frame watchdog was cleared before combineFrame() ran, so the
  4:4:4 path had no timeout despite the comment saying otherwise: a
  copyTo() that never settled would hold the ordered display queue with
  nothing able to release it. Clear it once the frame is done with
  instead -- in the finally for the synchronous paths, and after the
  copy settles for the combine.

- Drop the unreachable view != 0 branch after the combine hand-off.

- '0' no longer coerces to false in override(), so ?h264ChromaFilter=0
  sets threshold zero rather than switching the filter off. Boolean
  overrides are unaffected: they coerce, and 0 is falsy.

- Drop the stale stats() reference from client.html; the decoder has no
  such method.
2026-08-26 19:27:33 -04:00
pletch 5b5661888b feat(rdp): negotiate AVC444, skip the server-side H.264 decode
H.264 passthrough had two independent defects. On Windows hosts it
negotiated no H.264 at all, and where it did engage it delivered about
half the saving it should.

GfxAVC444 = FALSE was set to make the client advertise AVC420-only,
which is not what the combination does. FreeRDP gates the entire RDPGFX
V10+ capability block on

    !GfxH264 || GfxAVC444

(rdpgfx_main.c:254, rdpgfx_send_caps_advertise_pdu), so with GfxH264 set
and GfxAVC444 clear no V10 capability set is sent at all -- the client
advertises only V8 and V8.1. Windows will not offer H.264 at V8.1: it
falls back to CLEARCODEC and CAPROGRESSIVE, which guacd then decodes and
re-encodes as JPEG/WebP, exactly the cost the feature exists to avoid.
The display stays correct, so the regression is invisible without
measuring CPU or counting codecs on the wire. Over a minute of 1080p
video on Windows 11 Pro: 4050 CLEARCODEC + 1447 CAPROGRESSIVE + zero
H.264 frames before, all frames codec 15 after. xrdp is unaffected,
which is why this went unnoticed -- it accepts AVC420 at V8.1 and never
needed the V10 capsets.

AVC444 was disabled because forwarding it was broken, not because
AVC420 was wanted: only bitstream[0] reached the client, rendering as a
green/magenta split. Forwarding one view cannot work -- both bitstreams
decode through the same H264_CONTEXT in FreeRDP's avc444_decompress, so
they are one H.264 sequence with alternating views. Dropping either
breaks reference frames for the other, and the decoder reports no error
because what arrives is well formed. Both views are now forwarded,
tagged with a view field on the h264 instruction; the client decodes
both, draws only view 0, and combines the auxiliary view's chroma into
full 4:4:4 in a WebGL2 pass, falling back to 4:2:0 if it cannot.

The GDI decode is now skipped for captured H.264 commands. Previously
the wrapper always called through to the original handler, so only the
re-encode was saved. RemoteFX, planar and progressive still decode
normally, so a server mixing codecs within a frame stays correct.

guacd CPU with 1080p video playing, sampled per-thread over 30s:

    xrdp (AVC420)        ~100% of a core  ->  2.0%
    Windows 11 (AVC444)   90.6% of a core ->  2.1%

Configuration is unchanged: the per-connection enable-h264 argument is
the only switch, and there are no environment variables.
2026-08-26 19:27:33 -04:00
Dave Kempe 928c6d4862 fix(client): fit session to window on connect + tear down pop-outs with the session
fit-on-connect: a session opened in a new window (at a fixed size), or otherwise
created at dimensions that differ from the viewing window, now fits the window
without a manual resize. The client previously only sent a size on a
window-resize event. The one-time fit is retried past the point where the resize
path becomes ready (RDP Display Control channel connected / SPICE agent and
display ready) because an early send is silently dropped; guacd coalesces the
retries into a single resize since the requested size is unchanged.

pop-out teardown: a monitor pop-out is only a satellite of the main window (it
blits from that window's canvas and sends input through its client), so it
cannot outlive it. Closing the main window now closes its pop-outs instead of
leaving a frozen orphan, and a session disconnect shows a "Disconnected" overlay
in the pop-out rather than a stale live image.
2026-07-28 09:15:27 +10:00
Dave Kempe 2be5bfd849 feat(multimon): SPICE multi-channel composite + monitor-0 crop + pop-out input
guacd (patch-008): composite multiple SPICE display channels into one
framebuffer. A Windows/PVE guest exposes each head as a separate QXL device
(a separate display channel with its own primary surface); the previous code
tracked a single display channel and shared surface, so a second head
clobbered it and the session showed black or dropped. Now each channel is
tracked by id, tiled left-to-right into the combined default layer, and the
multimon layout enumerates every head with its offset. The SPICE pointer is
mapped from the combined coordinate to the owning head's local coordinate
(clamped to bounds so an edge value maps to the correct head).

client: the main window now crops to monitor 0 instead of showing the whole
combined framebuffer, and rescales to fit on resize. Secondary monitors open
in their own window that fills and rescales; that window's pointer uses native
mouse events mapped from the canvas's live rect, because Guacamole.Mouse does
not track the X axis correctly in a popup window. The per-frame blit clamps to
the source framebuffer bounds so a resize cannot read past it and produce a
corrupt image.

Validated on a Windows PVE console (VMID 300): both heads render composited,
clicks land on target on both monitors, and resizing the second monitor is
stable.
2026-07-27 20:15:57 +10:00
Dave Kempe 24e8aee5d4 feat(multimon): A3 per-monitor windows + entry storage for monitor count
client.html: per-monitor tick-boxes (contiguity-enforced) that open each
secondary monitor in its own window, blitting that monitor's region of the
combined framebuffer (rAF) and forwarding mouse (combined coords) + keyboard;
untick/close releases the monitor. Requests floor to a sane size if a popup
reports zero dimensions.

Entry storage: AddressBookEntry/EntryInfo gain max_monitors, ab_connect_entry
passes it through, and the Proxmox editor gets a Monitors field. Lets a saved
Proxmox/SPICE entry offer multiple monitors (previously only the ad-hoc API
path could).

KNOWN ISSUE: enabling multi-monitor (secondary-monitors>0) intermittently drops
the SPICE connection ~2-6s after connect, independent of the request. Under
investigation; single-monitor is unaffected. A3 end-to-end (tick -> activate ->
render) still needs a real-browser verification (Playwright can't size popups).
2026-07-27 11:52:31 +10:00
Dave Kempe 1d70b2ae24 feat(multimon): A2 controls sidebar with send-keys (Ctrl+Alt+Del)
Adds a Controls side panel (autohide-integrated, mirroring the clipboard/files
tabs) with a Send Keys section: Ctrl+Alt+Del, Ctrl+Alt+Bksp, Alt+F4, Alt+Tab,
Win, Esc, PrtSc, sent via a press-in-order/release-in-reverse key combo. Also a
Monitors section that reflects the server-advertised count (per-monitor tick-box
selection + rendering land in the next increment).

Verified on the canary vs VMID 300: Ctrl+Alt+Del triggers the Windows secure
attention screen; Monitors shows '2 monitors available'.
2026-07-27 11:19:35 +10:00
Dave Kempe c526c2f00d feat(multimon): A1 receive plumbing for multi-monitor (SPICE)
Client library (Client.js): new onmultimonlayout callback + a multimon-layout
layerPropertyHandler (mirrors multi-touch), and sendSize extended to carry the
optional per-monitor x_position/top_offset.

rustguac: SpiceParams.secondary_monitors + a secondary-monitors connect arg so
guacd advertises the allowed monitor count; CreateSessionRequest.max_monitors
(secondary = max-1) wired through both SPICE branches.

client.html: onargv reads the secondary-monitors count and onmultimonlayout
parses the layout JSON (logging for now; per-monitor windows are A2).

Verified on the canary vs VMID 300: client logs 'server allows 2 monitors' and
receives a multimon-layout. The layout is request-driven (guacd activates a
second guest head only when the client sends a size for monitor 1), so the
second monitor appears in A2.
2026-07-27 11:10:52 +10:00
Dave Kempe 7d9350e065 feat(client): accept a ws-ticket in the client URL for headless owner connections
The built-in client could only authenticate the owner WebSocket via an OIDC
session cookie or a sessionStorage API key. Headless API integrations have
neither, so the owner connection was rejected and guacd reported 'User is not
responding'. Now /client/{id}?ticket=<wst> is honoured: a backend mints a
single-use ticket via POST /api/ws-ticket and hands the browser a ready URL,
keeping the durable API key server-side. The metadata fetch is skipped in this
path (it needs its own auth and would consume the one-shot ticket).
2026-07-26 08:06:49 +10:00
Dave Kempe e7c515654e feat(client): per-entry auto-hide for the clipboard/files side tabs
Add an autohide_side_tabs option (Option<bool>, default off) on the address
book entry, threaded through the same path as fullscreen_on_connect
(AddressBookEntry, EntryInfo, CreateSessionRequest, Session, SessionInfo, the
API connect/quick-connect builders, and import defaults). When set, client.html
slides the left-edge Clipboard and Files tabs off screen when idle and brings
them back when the pointer nears the left edge; defaults preserve the current
always-visible behaviour. Checkbox added to the entry editor.
2026-07-20 15:19:35 +10:00
Lindsay Harvey 8f67a668ea fix(client): move fullscreen control into session menu (#156)
The v1.7.2 floating "⛶ Fullscreen" corner button at 0.45 opacity was
still 80px of permanent clutter in the top-right of the remote session
display before fullscreen was entered. This PR moves the manual
fullscreen action into the existing Ctrl+Alt+Shift session-menu panel
(next to the Home button), removing the floating overlay entirely.

The per-entry `fullscreen_on_connect` flag and the in-fullscreen top
bar (entry name + Exit + Disconnect) are unchanged. Esc-key forwarding
via navigator.keyboard.lock still applies.

Also adds an "In-session keyboard shortcuts" section to
docs/web-sessions.md documenting the Ctrl+Alt+Shift panel toggle,
Ctrl+V clipboard paste-sync, Esc behaviour, and the disable_copy /
disable_paste interaction.

Closes #156.
2026-06-09 16:25:25 +10:00
Dave Kempe d6a1adf10f fix(client): auto-hide fullscreen top bar (#154)
The in-fullscreen top bar covered the remote desktop's own menubar
(xfce4 panel, Windows taskbar). Match the mstsc.exe pattern that #154
referenced: show briefly on fullscreen entry, then slide up out of
view. Reveals when the mouse hits the top 4px edge; hides again ~600ms
after the mouse moves below the bar area. Hysteresis between 36 and 48
pixels keeps a jittering pointer from flickering the bar.
2026-06-04 09:30:26 +10:00
Dave Kempe bd1915fe4f feat(client): per-entry fullscreen-on-connect (#154)
Per-entry boolean fullscreen_on_connect flag. When set, the client enters
browser fullscreen on the first user gesture after CONNECTED and locks
the Escape key (Chromium navigator.keyboard.lock API) so it reaches the
remote session instead of exiting fullscreen. Firefox / Safari fall back
to standard fullscreen with a one-time toast explaining Esc will exit.

A small floating "Fullscreen" toggle in the top-right corner lets any
user enter fullscreen at any time once the session is connected. In
fullscreen mode a thin top bar shows the entry name plus Exit and
Disconnect buttons.

Closes #154.
2026-06-03 15:19:57 +10:00
Dave Kempe d9d7b12f12 debug(client): always-on draw-op ring buffer + rustguacDumpDraws() helper
Adds diagnostic infrastructure for #118 (black tile investigation) and
similar protocol-level rendering bugs. The browser is the most honest
observer of what guacd actually emits, so tapping the instruction stream
here lets us see drawing-op-level detail without changing guacd or
chasing the resize-callback theory.

The previous logger printed each opcode + arg count once per session,
which doesn't help locate a black region. This logs the meaningful args
of draw opcodes (rect, cfill, copy, img, dispose, size) into a 2000-entry
ring buffer, decoded into {op, layer, rect, note}. Coverage is zero-cost
in the steady state; the buffer is consulted only when the user calls
the dump helper from the DevTools console.

Two entry points:

  rustguacDumpDraws()       Print all recent draw ops as a console.table.
  rustguacDumpDraws(x, y)   Filter to ops whose rectangle covers that
                            pixel - point at a black tile's top-left to
                            see exactly what painted there.

`cfill` with rgba(0,0,0,255) is annotated `<BLACK>` so guacd-emitted
black fills jump out. `copy` ops carry the source layer + coords in the
note so we can see when guac_display's search_for_copies optimisation
sources a region that was itself black or uninitialised.

For live per-op logging append `?debug=draw` to the client URL; the
ring buffer is recorded regardless.
2026-05-28 13:54:11 +10:00
Dave Kempe 70185ad215 fix(client): release held keys on focus loss to prevent stuck modifiers
Ctrl+V (and any modifier chord) could leave Ctrl/Alt/Shift stuck "down"
on the remote. The Ctrl+V paste path calls navigator.clipboard.readText(),
which shifts focus to a clipboard-permission prompt; the subsequent
modifier keyup then lands off-page and Guacamole.Keyboard never sees it,
so the keyup is never forwarded to the remote. The modifier stays held
until pressed again.

Adds blur + visibilitychange handlers that call keyboard.reset(), which
releases every tracked key (firing onkeyup -> sendKeyEvent(0, ...) for
each). This is the canonical Guacamole guard for focus-loss key sticking
and also covers alt-tab-with-modifier-held and clicking away to another
app. We already reset() on clipboard/file panel close; this extends the
same guard to window focus loss.
2026-05-28 06:25:50 +10:00
Dave Kempe 38943c9d4c Reconnect button: relaunch entry instead of reloading dead session
The previous commit (Fix zombie WebSocket) wired tunnel.onerror /
onstatechange into client.onerror so the disconnected overlay
appears when a mid-path WS drop is detected. That fixes the
"frozen tab" symptom but leaves the Reconnect button doing
window.location.reload(), which re-opens client.html?session=<id>
against a session that no longer exists. By the time the WS
proxy task in src/websocket.rs returns BrowserEnded, disconnect_viewer
has decremented active_connections to 0 and complete_session has
flipped status to Completed; the guacd stream was dropped when the
proxy task ended. So a reload just walks back into the same overlay.

Reconnect now relaunches against the original Connections entry
when one exists, which is the user's actual intent. The fetch at
the top of setupClient already pulls /api/sessions/:id; we now
stash address_book_entry, address_book_folder, and
entry_display_name from that response into a relaunchInfo object.
On Reconnect click:

  - shareToken viewer: reload (unchanged; nothing better to offer).
  - relaunchInfo present: POST to
    /api/addressbook/folders/{scope}/{folder}/entries/{entry}/connect
    with the current window dims and DPI, then navigate to
    data.client_url + ?name=<display>. Fresh session id, fresh
    guacd connection, RDP/xrdp resumes the desktop state on the
    server side.
  - relaunchInfo absent (ad-hoc) or any non-2xx (412 missing creds,
    403 forbidden, 404 entry deleted) or network error: redirect to
    /connections.html so the user can fill in what is missing or
    pick somewhere else to go.

Splitting scope/folder/entry from the composite entry_key relies
on the constraint enforced in vault.rs validate_name: entry names
cannot contain slashes. Folder paths can, but only ever sit
between scope and entry, so first-slash gives scope and last-slash
gives entry.

Bundled fix: client.onerror now clearInterval(_thumbInterval) when
it runs. That closes the secondary leak called out in the prior
commit message (thumbnail XHR uploader continuing to 404 against
an already cleaned-up session) for users who walk away from the
overlay rather than clicking Close or Reconnect immediately.

Web sessions (Chromium + Xvnc) are entry-backed too and will
relaunch via the same path. The new session means a fresh
Chromium process with empty tab state, but that matches what the
user expects from Reconnect on a web session anyway.
2026-04-25 15:41:42 +10:00
Dave Kempe e70aebf81d Fix zombie WebSocket: wire tunnel.onerror/onstatechange in client.html
When a mid-path middlebox silently drops the TCP between the browser
and HAProxy, HAProxy closes the backend which rustguac logs as
"Connection reset without closing handshake" (termination state CD--
in HAProxy's log). Firefox's WS socket then fires onclose on the
browser side, Tunnel.js's close_tunnel() runs, and the tunnel's
internal state transitions to CLOSED.

But upstream Guacamole's Client.js doesn't listen for tunnel.onerror
or tunnel.onstatechange — the Apache webapp's AngularJS wiring does
that externally. Our lean client.html inherited Tunnel.js + Client.js
verbatim from upstream but missed that glue, so tunnel errors fired
into the void and the Guacamole client stayed in CONNECTED forever.

Effect on users: a dead session that looks like a freeze. Mouse moves
locally, clicks don't register (they're being sent into a closed
WebSocket whose send() silently no-ops at the browser layer). The
"Session Ended" overlay never appears. Meanwhile the thumbnail
uploader keeps running on its own XHR stream, getting 404s from the
already-cleaned-up session.

Fix: wire tunnel.onerror to forward into the existing client.onerror
handler, and wire tunnel.onstatechange so CLOSED also triggers the
overlay (and UNSTABLE updates the status text). This reuses the
existing overlay wiring at client.html:895; no new UI, no new state
machine, no new heartbeat. The upstream-inherited 5s nop keepalive
and 15s receiveTimeout in Tunnel.js are already present and
correct — the missing piece was just propagating their output into
the client's state cascade.

Triggered a lot of detective work chasing false leads (H.264 decoder
closed-state hypothesis, h2 bridging bug, HAProxy timeout tuning,
client/server heartbeats). The actual cause was much simpler and
the diff is six lines of JavaScript.
2026-04-25 15:40:08 +10:00
Dave Kempe e791383375 Issue #103: auto-open singleton entry + client Home button + group picker fix
Feature #103: single-entry auto-connect
- New `auto_open_if_singleton: Option<bool>` on AddressBookEntry and
  EntryInfo. Admin ticks it per entry in the Connections modal;
  importer initialises to None.
- After the /api/addressbook batch resolves on the Connections page,
  if the user sees exactly one entry and it has the flag set, the
  page fetches /connect and navigates the current tab to the new
  session. Same-tab navigation (not a popup) because browsers block
  window.open after an async fetch without a user gesture.
- A sessionStorage flag gates the auto-open to once per browser
  session — an accidental refresh of Connections doesn't re-spawn
  sessions. Logout clears the flag so the next login fires again.
- Admins never meet the singleton condition (they have many
  entries) so this is effectively kiosk-only.

Escape hatch: client.html Home button
- Ctrl+Alt+Shift panel header now has a 🏠 Home button next to the
  close ×. Takes the tab back to /connections.html — primary route
  for a kiosk user whose session auto-opened into this tab.

Bug fix: folder-modal group picker silently added a group on save
- addFmGroupFromInput used to fall through to picking the combo's
  highlighted suggestion when the input was empty. fm-save calls
  addFmGroupFromInput to flush any typed-but-not-chipped text; with
  an empty input that meant every Save on an existing folder tacked
  on the first unselected known group (e.g. an operator group) —
  visible only on the next edit. Split the "pick suggestion when
  empty" behaviour out to an explicit helper that only fires from
  Enter-with-empty-input, never from Save.
2026-04-18 21:56:20 +10:00
Dave Kempe 77c7c535f4 Docs cleanup, default theme to aurora, Vault install guidance
- Switch default theme from dark to aurora across server config,
  all 9 static HTML pages, example config, and docs
- Fix theme docs: list all 8 presets (was 6, missing jaguar/aurora),
  add missing type_vdi_bg/type_vdi_fg fields
- Add Vault/address book setup as recommended post-install step in
  installation docs — the address book is the primary user-facing
  feature and requires Vault
- Renumber subsequent install steps
2026-04-16 16:54:25 +10:00
Dave Kempe 175c30b705 H.264: fix unbounded stream lag via sync gating and frame dropping
The H.264 decode path bypassed the Guacamole sync mechanism — the sync
response fired immediately without waiting for WebCodecs to finish
decoding. guacd thought the client was keeping up and sent at full rate,
causing the decode queue to grow without bound (30+ seconds of lag
observed over time).

Fix: gate the sync response on H.264 decode completion so guacd gets
real backpressure. Also drop delta frames when the decode queue exceeds
5 frames as a safety valve for transient overload (tab backgrounding).

- Add pending-decode tracking and per-frame position capture to
  H264Decoder.js (fixes shared mutable state race)
- Add waitForPending() with 1s safety timeout for sync gating
- Add frame dropping when decodeQueueSize > 5 (never drops keyframes)
- Add stats() method for console debugging (__guac_client._h264Decoder.stats())
- Gate sync response in Client.js on H.264 decode completion

Closes #93
2026-04-08 07:21:23 +10:00
Dave Kempe 697b6f7d75 Active Sessions with thumbnails, dormant VDI containers
Client captures display thumbnail every 10s (JPEG, 320px), uploads
to server. Address book shows "Active Sessions" section with thumbnail
grid — click to reconnect via address book connect flow.

- PUT/GET /api/sessions/{id}/thumbnail endpoints
- GET /api/vdi/containers — list running VDI containers for current user
- VDI container labels: rustguac.entry, rustguac.image for reconnect
- Thumbnail copied to container-keyed file on disconnect (persists)
- Dormant VDI containers shown with last thumbnail
- Cache-busting on thumbnail images for live updates
- Moved logout button out of settings dropdown
- Fixed: VDI containers endpoint in authenticated route group
- Fixed: thumbnail click uses address book connect (not stale session URL)
2026-04-05 07:51:00 +10:00
Dave Kempe 8ff256f5df Session ended overlay with Reconnect/Close buttons
Show a centered overlay when the Guacamole client disconnects or
errors, instead of leaving a frozen canvas. Offers Reconnect (reload)
and Close (navigate to home) buttons. Applies to all session types.
2026-04-05 07:18:26 +10:00
Dave Kempe 836db3c9ec Fix window title: entry name persists after site_title loads 2026-04-01 09:39:50 +11:00
Dave Kempe ef1f194838 Show address book entry name in session window title 2026-03-31 15:55:12 +11:00
Dave Kempe 211ff9e99a WebSocket ticket auth: keep API keys out of WS URLs
API key users now exchange their key for a single-use 30-second
ticket via POST /api/ws-ticket before connecting. The ticket is
passed as ?ticket= in the WebSocket URL instead of the raw API key.

Prevents API key exposure in reverse proxy logs, browser history,
and Referer headers. Legacy ?key= still works as fallback.
2026-03-28 22:22:05 +11:00
Dave Kempe b013678e42 H.264 passthrough: guacd patch + WebCodecs browser decoder
Phase 1-3 of H.264 passthrough for premium RDP video performance.

guacamole-server patch (patches/004-h264-passthrough.patch):
- Enable GfxH264 and GfxAVC444 in FreeRDP settings when GFX is on
- Intercept AVC420/AVC444 SurfaceCommand before GDI decode
- Extract raw H.264 NAL units and send as new "h264" instruction
- Keyframe detection via Annex B start code + NAL type parsing
- Original GDI decode path still runs as fallback

Browser-side (static/guac/):
- H264Decoder.js: WebCodecs VideoDecoder, hardware-accelerated decode
- Client.js: "h264" instruction handler, base64→ArrayBuffer→decode
- Feature detection: falls back gracefully if WebCodecs unavailable

rustguac:
- Advertise video/h264 in guacd handshake
2026-03-28 13:02:06 +11:00
Dave Kempe 3bf1762d87 v0.9.0: RDP audio, GFX pipeline, video performance
Audio:
- RDP audio output now works through guacamole. Advertise audio/L16
  and audio/L8 mimetypes in the guacd handshake, and explicitly set
  disable-audio=false. Fixed mimetype mismatch that silently prevented
  guacd from creating audio streams.
- Browser AudioContext auto-resumed on user interaction (click/keydown)
  to comply with autoplay policy.

Video performance:
- Per-entry GFX pipeline toggle (enable_gfx) — enables RemoteFX codec
- Per-entry desktop composition toggle (enable_desktop_composition)
- Per-entry force lossless toggle (force_lossless) — PNG-only mode
- WebSocket proxy buffer increased from 8KB to 64KB
- Video Performance section in address book UI for RDP entries

Documentation:
- RDP Video Performance guide with Windows server tuning (AVC444,
  60fps, GPU encoding) and Linux xrdp setup (Debian 13)
- contrib/setup-xrdp-gfx.sh — automated GFX/H.264 setup for xrdp
- contrib/setup-xrdp-audio.sh — automated PulseAudio module build
2026-03-24 20:25:05 +11:00
Dave Kempe 6bafc79d60 v0.8.0: Credential variables, bug fixes
Credential variables — address book entries reference $domain_username /
$domain_password instead of storing static credentials. Users fill in their
own values via My Credentials (gear menu), stored per-user in Vault KV.
All variables set → silent launch; missing → prompted. Hyphens allowed in
variable names. Docs section added.

Bug fixes:
- Rate limiting disabled by default; opt-in via rate_limit = true (#62)
- Docker: copy FreeRDP guac-common-svc plugins to runtime image (#64)
- Docker/install: add chromium-sandbox package for non-root web sessions (#61)
- Logo: skip redundant JS src= when server-side branding already set (#65)
- Sessions page: hide Open/Share buttons for non-active sessions (#63)
- Drive: expose drive_configured in /api/auth/status, warn in UI when
  [drive] not configured
- install.sh: verify FreeRDP plugin installation

UI polish:
- Nav bar: border separator + spacing between header and nav on all pages
- Address book: password show/hide toggle on all password fields
- Drive diagnostic logging (session.rs, websocket.rs, client.html)

Closes #61, #62, #63, #64, #65
2026-03-13 13:47:11 +11:00
Dave Kempe 2bf34440e6 v0.5.1: RDP resize fix, new themes, Docker config persistence
- Fix RDP display resize for FreeRDP 3.x (patch 004: config.h struct layout)
- Add aurora theme (midnight blue with ambient glow gradients)
- Add jaguar theme (racing green & gold with subtle gradients)
- Add bg_pattern support for CSS gradient backgrounds in themes
- Fix Docker config.toml persistence across rebuilds (#38)
- Add Docker Compose volume mount documentation
- Increase API rate limit to 5/s burst 30 (fix spurious 429s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:08:14 +11:00
Dave Kempe ea72c52a31 v0.5.0: Theme system, ARM64 builds, Docker fixes, dependency updates
Theme system:
- 6 built-in presets (dark, light, high-contrast, terminal, nord, corporate)
- Admin configures preset + per-color overrides in [theme] config section
- Client-side theme switching via localStorage (flash-free)
- All static pages updated with 28 CSS custom properties

Proxy telemetry:
- Track which side terminated connection (guacd/browser/cancelled)
- Timing-based log levels (guacd close <5s = warning)
- Clamp session dimensions to safe ranges (width 640-8192, height 480-8192, DPI 16-384)

Docker fixes (#37):
- Fix port mismatch: Dockerfile now uses 8089 consistently
- Auto-generate admin API key on first run
- Add API key setup docs and recordings volume to compose example

ARM64 support:
- Multi-platform Docker builds (linux/amd64 + linux/arm64)
- Native ARM64 .deb and tarball builds via ubuntu-24.04-arm runner

Dependency updates:
- rustls 0.23.37, chrono 0.4.44, clap 4.5.60, toml 1.0.3
- futures-util 0.3.32, uuid 1.21.0, pulldown-cmark 0.13.1
- actions/upload-artifact v7, actions/download-artifact v8

Also: FreeRDP 3.x NULL deref patch (003), .gitignore for .playwright-mcp/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:05:41 +11:00
Dave Kempe 066a063304 Improve RDP display sizing: match browser dimensions on connect and resize
- Send actual browser window dimensions at session creation (addressbook,
  sessions) instead of defaulting to 1920x1080
- Send initial sendSize() on WebSocket connect so guacd can resize the
  remote display via Display Update channel
- Debounce window resize handler (250ms) to avoid overwhelming RDP server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:55:40 +11:00
Dave Kempe 2187cfc1e4 Security hardening: 5 fixes from audit
- Vault TLS: replace hardcoded danger_accept_invalid_certs(true) with
  configurable tls_skip_verify option (default: false)
- Share tokens: use constant-time SHA-256 hash comparison to prevent
  timing side-channel attacks
- OIDC pending states: add 10-minute TTL, evict stale entries on each
  login to prevent unbounded HashMap growth
- Recording path traversal: add canonical path validation as defense-
  in-depth alongside existing string checks
- Frontend XSS: escape all user-controlled data (filenames, paths) in
  innerHTML via escapeHtml/escapeAttr in client.html and recordings.html

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 11:34:54 +11:00
Dave Kempe 67101e27ce Initial public release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:38:53 +11:00