mirror of
https://github.com/jetkvm/kvm.git
synced 2026-06-20 05:24:32 +00:00
dev
99 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
800c0cbb53 |
Add USB audio gadget permission setting (#1479)
* fix disabled audio gadget * add usb audio device setting * polish usb audio disabled hint * stabilize disconnect key release test * clear audio when usb audio is disabled * guard audio config with usb gadget |
||
|
|
f332ceefbf |
feat: add idle host display setting (#1476)
* feat: hide host display while idle * feat: add idle display setting * fix: handle host display advertisement errors * fix: serialize host display advertisement decisions * fix: suppress transient HDMI startup error * docs: explain HDMI startup grace period * fix: avoid redundant EDID reapply on sessions * fix: reset video playing state on reconnect |
||
|
|
90750bf609 |
Experimental Audio Support (#1475)
* WIP: USB audio capture via UAC1 gadget with G.722 + PCMU encoding
Adds ALSA capture from the USB UAC1 gadget, G.722/PCMU encoding, a WebRTC
audio track, and an e2e remote-agent flow that plays a tone on the remote
host and verifies it reaches the browser.
Snapshot of codex-driven implementation before simplification.
* Simplify USB audio path: drop residual scaffolding from long session
Backend:
- audio.go: drop source rotation + "no-data reopen with next device" loop.
One source (UAC1Gadget; falls back to hw:1,0 only if sysfs lookup fails).
- internal/audio: remove unused Reader interface and unavailableCapture stub.
Stub now returns the concrete type with an error.
- webrtc.go: inline single-use resolveAudioCodec helper; DRY video/audio RTCP
drain into drainRTCP; fold startSessionAudio into the connect callback.
Frontend:
- devices.$id.tsx: drop remoteMediaStreamRef track-merging. Backend tracks
share stream ID "kvm", so pion delivers them in one MediaStream — just
assign event.streams[0].
- WebRTCVideo.tsx: replace dynamic per-track <audio> creation + ref array
with a single hidden <audio> bound to mediaStream.
Remote agent:
- Drop PipeWire/wpctl detection path; plughw: works directly.
- Drop killStaleAudioToneProcesses pkill workaround; the (cmd, cancel, done)
trio collapses to a single *exec.Cmd field with Start/Kill/Wait.
E2E:
- ra-audio.spec.ts: drop attachAudioDiagnostics scaffold and openReadyPage
duplicate. Spec is now linear: setup → wait for track → diff stats → tone.
Net: ~355 LOC removed.
* Fix onSessionConnected race; replace onFirst/CurrentSessionConnected split
The previous codex split routed audio start through onCurrentSessionConnected,
gated on session == currentSession. But currentSession is assigned by the
caller (web.go, cloud.go) AFTER ExchangeOffer returns, while
OnICEConnectionStateChange can fire from inside ExchangeOffer or shortly
after — racing the assignment. When the race hits, the equality check fails,
the callback is skipped, and audio never starts.
Pass session into the callback directly so the per-session setup uses the
session in hand, not whatever currentSession happens to point to at that
instant. Keep stopVideoSleepModeTicker on the count-edge (still only on
first-session) and let onSessionConnected handle the rest unconditionally.
* Pin WebRTC playout-delay to zero so receivers don't ratchet under stress
Chrome's adaptive receive-side jitter buffer grows under stress (e.g.
playing a video on the controlled machine) and does not reliably shrink
back; the Connection Stats "Playback Delay" graph used to climb to
~300 ms and stay there until the page was reloaded.
The trigger is the USB UAC1 audio path, not video motion per se — once
real audio starts flowing, Chrome's AV-sync layer pulls the video
jitter buffer up to whatever the audio path settles at, and the ratchet
locks in. Receiver-side hints (jitterBufferTarget, playoutDelayHint,
setMinimumJitterBufferDelay) cap the steady state but don't release a
buffer that has already grown.
Fix: register the WebRTC playout-delay RTP header extension on both
audio and video and stamp min=max=0 on every outgoing packet via a
pion interceptor. Chrome treats this as an authoritative override of
its adaptive logic and keeps both buffers at the decoder floor through
and after stress, with no peer-connection rebuild needed.
Test: drive the host display with a real audio+video file via
gst-launch playbin (audio routed through PipeWire to the USB UAC1
sink) and assert receive-side video delay stays bounded both during
and after playback.
* Simplify audio capture loop and ALSA reader
- Drop short-read zero-fill in ALSA reader; return ErrNoAudioData so the
capture loop emits no frame for the cycle instead of half-silent audio.
- Replace ErrNoAudioData = io.ErrNoProgress (wrong semantic) with a domain
sentinel and remove the unused idleReads debug counter.
- Encoders sum all source samples before one divide — better precision,
fewer ops; clampS16 and sampleS16 helpers gone.
- Resolve audio codec inline in runAudioCapture; drop the audioCodecForTrack
wrapper. Caller checks AudioTrack != nil so startAudio no longer accepts
nil as a stop signal.
- Use C.GoString instead of hand-rolled cString helper.
- Add a why-comment on the separate <audio> element (video stays muted).
* Trim playoutdelay per-symbol comments
Keep the package-level "why" (Chrome's one-way jitter buffer); drop
restate-the-signature comments on Factory, NewFactory, NewInterceptor,
and BindLocalStream.
* e2e: extract shared audio helpers and drop dev-only override
- Lift ensureNoPasswordViaAPI and waitForAudioStream into helpers.ts (the
audio spec was inlining both, the latter as a copy of waitForVideoStream).
- ra-audio.spec.ts shrinks from 78 to 55 lines.
- Remove the JETKVM_AUDIO_DEVICE override in the remote agent: it fabricated
an AudioDeviceInfo with is_jetkvm=true regardless of what device the env
var pointed at, silently lying to the spec's assertion. Audio device
discovery via aplay + /proc/asound/.../usbid is reliable; if no JetKVM
device is present the spec already skips.
* Reopen ALSA capture on persistent read errors
The C-side recovers EPIPE/ESTRPIPE via snd_pcm_recover; the errors that
surface to Go (EBADFD, ENODEV, …) usually mean the handle is dead —
typically a USB gadget rebuild or host reattach mid-session, which used
to leave audio silent until the session disconnected.
After 5 consecutive non-idle read errors, close and reopen the capture
with exponential backoff (100 ms → 2 s cap). Initial open uses the same
helper so we keep retrying instead of giving up if the gadget isn't ready
yet. Re-resolves the card each attempt so a USB re-enumeration that
shifts the card number is picked up automatically.
* Add Audio settings page with Enable Audio toggle (experimental)
Audio is opt-in via device config. New Audio nav entry in Settings sits
next to Video, with a single "Enable Audio" item marked Experimental
(mirrors HTTPS Mode in Access).
Backend:
- Config.AudioEnabled (default false), persisted to /userdata/kvm_config.json
- getAudioConfig / setAudioConfig JSON-RPC handlers
- webrtc.go: extract attachAudioTrack helper; skip track creation when
disabled or when the offer advertises no supported codec. The SDP
answer leaves the audio m-line inactive, so flipping the toggle
requires a fresh connection (page reload).
Frontend:
- New devices.$id.settings.audio.tsx — fetches state via getAudioConfig,
saves via setAudioConfig, optimistic UI with rollback on error.
- devices.$id.tsx always offers audio in the SDP; backend decides.
- en.json + 13 locale files: 5 keys each (audio_*, settings_audio) with
proper translations honoring per-language formality.
E2E:
- ra-audio.spec.ts: connect, enable via RPC, reload, verify audio energy.
Restores disabled state in a finally block so other specs aren't
affected. 9 s on kvm-2 + .180.
* Reload page after enabling audio so the browser prompts for autoplay
Re-negotiation only happens on a fresh WebRTC session, and the autoplay
overlay needs a user gesture to play the new audio track. A simple reload
covers both — the toggle's user click acts as the gesture, the new offer
includes audio, and the overlay surfaces normally.
Disable path is unchanged (audio stops naturally on the next connect).
* Reload page on disable too so audio stops immediately
* Trim audio_* copy across all 14 locales
Page header and item description previously said roughly the same thing
in long form. Now: page-level describes the topic ("Stream audio from
the host to your browser"); item-level is a terse one-liner ("Stream
HDMI audio from the host."). Drops the "Requires a fresh connection"
clause — the page auto-reloads on toggle, so it's no longer accurate.
Per-language tone follows I18N_BEST_PRACTICES.md: formal Sie/vous/usted/
вы/chi (de/fr/es/ru/cy), informal du (sv/nb/da), polite です/ます (ja),
infinitive (it), European Portuguese (pt).
* Fold audio e2e into the standard remote-agent project
Drop the dedicated test_audio_e2e Makefile target and the separate
remote-agent-audio Playwright project. The remote-agent project now
matches every ra-*.spec.ts under e2e/remote-agent, so make test_e2e
runs ra-audio.spec.ts alongside ra-all.spec.ts in the same worker.
* Fall back to bare-track MediaStream when ontrack streams[] is empty
Hit a state where pc.getReceivers() showed live video and audio tracks
but useRTCStore.mediaStream stayed undefined — the SDP answer arrived
without a=msid, so event.streams[0] was undefined and
setMediaStream(undefined) left the store empty even though RTP was
flowing. Only a hard reload recovered.
Now: when the event carries a stream, use it as before. When it doesn't,
get-or-create a MediaStream and append the track. Re-using the existing
store value across both ontrack invocations keeps audio + video on the
same MediaStream so the autoplay/video pipeline downstream is unchanged.
* Close peer connection before reload-on-toggle
Firefox's soft reload doesn't always tear down the RTCPeerConnection,
which leaves the post-reload page in a half-renegotiated state: tracks
arrive on receivers but never attach to a MediaStream, so video stays
stuck on "Loading…" (or the page falls back to the pre-connect blue
background) until a hard refresh. Closing the PC explicitly before
reload guarantees a clean start.
* Aggregate ontrack into one canonical MediaStream
The answer SDP from pion omits a=msid for the audio track in some
configurations (visible on Firefox: video keeps its msid, audio doesn't).
The previous handler called setMediaStream(event.streams[0]) on each
ontrack, so:
1. video ontrack → setMediaStream(streamA) [has video]
2. audio ontrack → setMediaStream(streamB) [synthetic, audio only]
streamB replaces streamA, video disappears. Hard refresh only "fixed" it
incidentally — the same SDP would break the next negotiation too.
Now: ignore event.streams[0], maintain one canonical MediaStream in the
store, and addTrack into it on every ontrack. Browsers render tracks
added to a live MediaStream that's already attached to srcObject, so
both audio and video stay attached regardless of which order ontrack
fires or whether the SDP carried msid.
* Only render <audio> element when device audio is enabled
The backend keeps the m=audio section in the SDP even when audio is
disabled (just inactive direction), so Firefox still attaches a muted
audio track to the MediaStream. The autoplay <audio> element then
triggers Firefox's "block audio" policy on a stream that will never
actually play any sound.
Fetch getAudioConfig once the RPC channel is up, then conditionally
render the <audio> element. No autoplay prompt when audio is off.
* Drop stale mediaStream on reconnect; release audio capture when owner session ends
* Back off briefly on idle ALSA reads to avoid CPU spin
* Re-wire <audio> when track arrives late; gate VideoStart to first session
|
||
|
|
e3595cdea2 | Add explicit USB wake control (#1471) | ||
|
|
51e7a95f19 |
feat(video): Add 120hz Support (#1452)
* feat(video): experimental 120 Hz low-latency mode Add an opt-in setting that swaps the JetKVM default EDID for one advertising 848x480@120 (preferred) and 1280x720@120. At 120 fps the per-frame display→encode delay drops from ~16.7 ms to ~8.3 ms, halving source-side video latency vs the standard 1080p60 path. The TC358743 capture chip on JetKVM v1 has a hard ~120 Hz vrefresh ceiling (Toshiba spec is 1080p@60; everything above 60 Hz is undocumented territory). 144/240 Hz were tested and do not lock — the chip's internal blocks above the TMDS PHY were never validated past 60 Hz. 120 Hz works reliably across both 480p and 720p; that's what this EDID advertises. Wiring: - internal/native/video.go: new LowLatency120HzEDID constant (CVT-RB, EDID 1.4, single base block, no CEA extension) - config.go: VideoLowLatencyMode bool, with reconciliation on load — toggling only swaps EdidString when it currently holds one of the well-known JetKVM defaults; user-supplied custom EDIDs are preserved - jsonrpc.go: getVideoLowLatencyMode / setVideoLowLatencyMode RPCs - UI: experimental Checkbox in Settings → Video and an extra entry in the EDID preset dropdown Source-side note: enabling the toggle does not switch the source PC's display mode. The user must manually pick 1280x720@120 or 848x480@120 in their OS display settings; the EDID alone just tells the source what's allowed. scripts/edid_gen.py is the generator used to produce the EDID hex. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): address PR review on 120 Hz toggle Single source of truth for the toggle is now `EdidString`. The separate `VideoLowLatencyMode` config bool is gone, along with the load-time reconciliation logic that could silently revert the EDID dropdown's choice on reboot. Addresses: - cursor[bot] HIGH "Config reconciliation reverts explicit EDID dropdown choices on reboot" — drop `VideoLowLatencyMode` field; `rpcGetVideoLowLatencyMode` now derives state from `EdidString`, `rpcSetVideoLowLatencyMode` only writes `EdidString`. UI toggle is derived from `edid` state. Dropdown ↔ toggle can no longer drift. - Copilot ui/src/routes/.../video.tsx:30 — same root cause; same fix. - Copilot ui/src/routes/.../video.tsx:199 — drop the spurious `.toUpperCase()` on the matched-EDID value so SelectMenuBasic strict equality matches the option's actual `value`. - Copilot jsonrpc.go:276 + config.go:311 — case-insensitive EDID comparisons via `strings.EqualFold`. - Copilot scripts/edid_gen.py:13 — drop unused `import struct`. - Copilot scripts/edid_gen.py:63 — `pclk_khz` was holding Hz; rename to `pclk_hz` and use a `raw_pclk_hz` intermediate for the pre-quantized value. Generator output is byte-identical. - cursor[bot] LOW en.json:1041 "wrong advice when disabling" — split the single `video_low_latency_set_success` (which always told users to switch to 120 Hz) into `video_low_latency_enabled` and `video_low_latency_disabled`; the disabled message tells the user to switch their source back to its usual resolution. Also: small UX cleanup — extracted `applyEDID(...)` helper so the toggle and the dropdown don't double-fire success notifications. Verified locally: `go vet` clean, `tsc --noEmit` clean, `oxlint` 0 errors, `python3 -c "import py_compile; py_compile.compile(...)"` OK, and the regenerated EDID hex matches `LowLatency120HzEDID` byte for byte. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video): replace 120 Hz toggle with four single-mode EDIDs in the dropdown Drops the experimental "Low Latency 120 Hz Mode" toggle and the LowLatency120HzEDID bundle (which packed both 848x480@120 and 1280x720@120 DTDs into a single EDID) in favor of four standalone single-mode EDIDs added directly to the existing EDID dropdown: - JetKVM 1280x720 @ 120 Hz (low latency) - JetKVM 1280x720 @ 60 Hz - JetKVM 848x480 @ 120 Hz (low latency) - JetKVM 848x480 @ 60 Hz Each EDID advertises exactly one DTD, the monitor range descriptor, and the model name — no CEA extension. Generated by scripts/edid_gen.py. Why the redesign The toggle was special-casing a single EDID bundle and trying to keep two pieces of state (the toggle and the EDID dropdown) in agreement. The reviewer flagged that the load-time reconciliation could revert explicit EDID-dropdown choices on reboot, and even the simpler derive-toggle-state-from-EdidString version was carrying: - a separate i18n string set for toggle-on / toggle-off, - a Checkbox plus an extra dropdown row labeling the same EDID, - special-cased applyEDID plumbing distinct from setEDID, - a config-load reconciliation path. Treating the 120 Hz modes as ordinary EDID choices removes all of that. Picking a 120 Hz EDID writes through setEDID like every other entry; the dropdown is the only source of truth. Bundling 480p120 and 720p120 into one EDID also forced the source PC to choose between two preferred modes. With separate EDIDs the source sees exactly one preferred timing per choice. Changes internal/native/video.go: add EDID720p120, EDID720p60, EDID480p120, EDID480p60. Drop LowLatency120HzEDID. jsonrpc.go: drop rpcGetVideoLowLatencyMode / rpcSetVideoLowLatencyMode and their handler registrations. Drop the now-unused native and strings imports. ui/src/routes/devices.\$id.settings.video.tsx: drop the Checkbox, the derived lowLatencyMode flag, the handleLowLatencyChange handler, the applyEDID helper, and the warning paragraph. Add four new entries to the EDID dropdown. ui/localization/messages/en.json: drop the five video_low_latency_* keys and the single-bundle video_edid_jetkvm_120hz key. Add four new per-mode keys; update video_edid_jetkvm_default to spell out the resolution so the dropdown is internally consistent. Verified locally: go vet clean on all packages, tsc --noEmit clean, oxlint 0 errors, EDID hex round-trips byte-for-byte through scripts/edid_gen.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop unused EDID constants; fix CVT-RB v1 vertical timing in edid_gen.py internal/native/video.go: the four EDID720p120 / EDID720p60 / EDID480p120 / EDID480p60 constants were never read — the dropdown carries the hex inline. Remove them; nothing else in the Go side referenced them. scripts/edid_gen.py: CVT-RB v1 specifies a fixed 3-line vertical front porch and an aspect-dependent vsync; the back porch absorbs the remainder of v_blank. The previous implementation had it backwards (back porch fixed at RB_V_BACK_PORCH, front porch = remainder), which produced a multi-hundred-line front porch and a 6-line back porch — technically a valid frame, but not CVT-RB v1. If the recomputed back porch falls below the spec minimum, bump v_blank so it does, and let v_total follow. ui/src/routes/devices.\$id.settings.video.tsx: regenerate the three affected EDID hex strings (720p120, 720p60, 480p120). 480p60 is unchanged because its v_blank already left front porch = 3 in the old code path. All four still lock end-to-end on TC358743 hardware. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Set sRGB color space + 640x480@60 established-timing in generated EDIDs scripts/edid_gen.py: - Feature-support byte (offset 24): 0x0A -> 0x0E. Adds the sRGB color-space bit while keeping the existing flags (digital, RGB 4:4:4, YCbCr 4:2:2, preferred timing in DTD0). EDID 1.4 already requires DTD0 to be the preferred timing; tagging sRGB lets the source treat the chromaticity block as authoritative instead of guessing. - Established-timings byte (offset 35): 0x00 -> 0x20. Bit 5 advertises 640x480@60 as a VGA-fallback mode some sources fall back to during early-boot / BIOS. The source still prefers DTD0 for the active desktop, so this is harmless for the 120 Hz / 60 Hz advertised modes. ui/src/routes/devices.\$id.settings.video.tsx: regenerate all four dropdown EDIDs with the new flag bytes and recomputed checksums. Strings are now uppercase to match the rest of the dropdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): plumb source vrefresh into MPP encoder rate control The MPP H.264/H.265 rate-control structure has Src/Dst FrameRateNum/Den fields the firmware never set, so MPP defaulted to fps_fix [30/1]. That made the encoder size its bitrate budget for 30 fps and behave unpredictably when 120 fps arrived from the capture chip. Now run_detect_format rounds the v4l2 dv-timings vrefresh to an integer (stored in detected_fps), and run_video_stream passes that value through venc_start -> populate_venc_attr where it's written into both Src and Dst FrameRate fields. GOP is sized to fps/2, which keeps the IDR cadence at ~0.5s for any source refresh — same WebRTC recovery latency at 60 Hz and 120 Hz. run_detect_format now also restarts the streaming pipeline when the rounded fps changes by more than ±1 fps, so an EDID swap that keeps resolution but changes refresh (e.g. 720p60 -> 720p120) actually reconfigures the encoder. The ±1 tolerance absorbs CVT-RB rounding (119.91 fps and 119.87 fps both round to 120). Verified on device: before: fps fix [30/1] -> fix [30/1] gop i [30] after: fps fix [120/1] -> fix [120/1] gop i [60] WebRTC inbound-rtp framesPerSecond now sustains ~120 with 0 dropped packets at 720p120, 19 ms jitter buffer, glass-to-glass delay halved on a 120 Hz panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Collapse 4 single-mode EDIDs into one combined 720p entry Drop the four single-mode 480p/720p × 60/120 EDIDs and replace them with one multi-mode JetKVM 720p EDID that advertises both 1280x720@120 (DTD0, preferred) and 1280x720@60 (DTD1). Drop 480p entirely — 848x480 is non-standard and the source PC's display panel UI usually doesn't expose it. The source picks 60 Hz vs 120 Hz via OS-side display settings (`xrandr --rate 60/120` on Linux, Display Settings on Windows) without needing to swap EDIDs. The encoder-fps plumbing from |
||
|
|
c2e29cd3c8 |
fix(video): signal limited-range BT.709/BT.601 colorimetry in encoder VUI (#1460)
Annotate the H.264/H.265 bitstream with proper VUI colour_primaries, transfer_characteristics, and matrix_coefficients (BT.709 for >576p, SMPTE 170M otherwise) plus video_full_range_flag=0. Without this, browser WebRTC decoders fall back to BT.601/full-range guesses, producing tinted output on HD sources. Also set the matching V4L2 capture colorimetry hints and hoist constant per-stream fields out of the per-frame SendFrame loop. |
||
|
|
5806c80e6a |
fix(websecure): cap serial numbers at 128 bits for Apple TLS clients (#1453)
The 4096-bit limit produced ~500-byte serials that violate RFC 5280 §4.1.2.2's 20-octet cap. Apple's DER parser enforces this strictly, so URLSession, NWConnection, AVFoundation and every other client on macOS/iOS/tvOS rejected the cert with "Unknown format in import" before any trust evaluation ran. Lower the limit to 128 bits (matching Go stdlib's generate_cert.go example) and add a one-shot migration that drops any already-baked oversized CA, plus the leaves it issued, on startup so existing devices recover without manual SSH cleanup. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d266805097 |
Fix tzdata generator to emit go fmt formatted file (#1442)
Regeneration added "America/Coyhaique" |
||
|
|
df5dbea431 |
fix(keyboard): keep modifiers out of auto-release (#1438)
* fix(keyboard): keep modifiers out of auto-release Prevent per-key auto-release from dropping held modifiers during jitter while keeping explicit cleanup paths covered by E2E tests. * fix(keyboard): keep modifiers out of auto-release Prevent per-key auto-release from dropping held modifiers during jitter while keeping explicit cleanup paths covered by E2E tests. * chore(keyboard): trim autorelease comments Keep comments focused on keyboard behavior and remove branch-specific narrative from the tests. * fix(keyboard): reset keepalive timing on key state changes Reset session keepalive timing on every keyboard state change so stale gaps do not poison later holds under modifiers. |
||
|
|
eb56468d25 |
feat(ota): include device SKU in update requests (#1429)
* feat(ota): include device SKU in update requests Read /etc/jetkvm-sku at startup and pass the value as a sku query parameter when checking for OTA updates, enabling the cloud API to serve variant-specific firmware for jetkvm-v2 (eMMC) and jetkvm-v2-sdmmc hardware. Falls back to "jetkvm-v2" when the file is absent for backwards compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ota): include SKU in auto-update loop The background auto-update loop in main.go also constructs UpdateParams and was missing the SKU field, so periodic updates would not transmit the device SKU to the cloud API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2614db6b89 |
feat(ui): add log level selector in Troubleshooting Mode (#1395)
* feat(ui): add log level selector in Troubleshooting Mode Add a UI dropdown in Advanced > Troubleshooting Mode that lets users set the system log verbosity (Error, Warning, Info, Debug, Trace). Changes take effect immediately without restart via new getDefaultLogLevel/setDefaultLogLevel JSON-RPC endpoints. Also downgrades the noisy wakeup_on_write permission denied warning from Warn to Debug level, and removes the INFO→WARN config migration so users can actually select INFO. Localized for all 14 languages. * chore(ui): disable no-floating-promises in oxlint The rule is not actionable for the current codebase; turn it off explicitly. * refactor(ui): drop void prefixes on JSON-RPC send in advanced settings no-floating-promises is disabled in oxlint; match the rest of the codebase. * fix(ui): localize log level dropdown and fix optimistic update - Replace hardcoded English dropdown labels with localized m.*() calls - Replace hardcoded error string with m.advanced_error_set_log_level() - Optimistically update dropdown on change and revert on RPC failure - Add 6 new i18n keys across all 14 locales * chore: add remote-agent to .gitignore and auto-sort i18n in pre-commit - Ignore the compiled e2e/remote-agent/remote-agent binary - Add lint-staged rule to run i18n:resort on message JSON changes * copy(ui): improve log level setting description Apply outcome-oriented copy: explain what the setting does for the user and when to change it, rather than restating the control's mechanics. Updated across all 14 locales. * fix(logging): scope loggers not rebuilt when config level matches base default UpdateLogLevel compared the new config level against the base default (ErrorLevel) instead of the previous config level. When switching from WARN back to ERROR, the comparison was equal so scope loggers kept their old WarnLevel filter — WRN messages continued appearing despite the user selecting Error. Compare against the previous defaultLogLevelFromConfig instead. * test(logging): add RPC probe for log level filtering Add a dedicated emitTestLog JSON-RPC method and a focused e2e spec that verifies live TRACE/DEBUG/INFO/WARN/ERROR filtering against last.log. * chore(ui): update .gitignore to exclude screenshot.png file |
||
|
|
73a67e88ab |
fix(mouse): prevent double wheel scroll by sending to only one HID device (#1390)
rpcWheelReport was unconditionally sending wheel events to both the absolute and relative mouse HID devices. The host OS sees two separate USB HID devices each producing a scroll event, so it processes both, doubling the effective scroll distance (e.g. 6 lines instead of 3 on Windows). Send wheel events to the absolute mouse device when enabled, falling back to the relative mouse device otherwise. This mirrors how pointer reports already work (absMouseReport vs relMouseReport are separate). Add an E2E test that verifies exactly one wheel event is received per wheelReport RPC call when both mouse devices are enabled. |
||
|
|
f2fac87b17 |
feat(video): tune encoder for better quality and faster recovery (#1372)
* feat(video): tune encoder for better quality and faster recovery - Reduce GOP from 60 to 30 for faster keyframe recovery on screen changes - Set u32MinBitRate to half the target to prevent static-screen bitrate collapse - Reduce u32StatTime from 3s to 2s for tighter rate control adaptation - Raise minimum bitrate floor from 100 to 200 kbps * feat(ui): show live bitrate in debug info bar Poll WebRTC inbound-rtp stats every second and display the current receive bitrate next to the codec indicator when debug mode is enabled. * feat(ui): add loading spinner to video quality dropdown Disable the select and show a spinner while fetching or applying the stream quality factor, matching the existing EDID selector pattern. * fix(i18n): remove redundant prefixes from video settings titles Strip "Video"/"Stream" prefixes from settings headings that are already under the Video settings page: Stream Quality → Quality, Video Codec → Codec, Video Enhancement → Enhancement. |
||
|
|
cb7746fb78 |
feat(video): add H.265 codec support with auto-negotiation (#1371)
* feat(video): add H.265 codec support with auto-negotiation Add H.265 (HEVC) encoding support to the RV1106 hardware encoder alongside existing H.264. The codec is negotiated per-WebRTC session based on browser capabilities. - Add codec preference setting (Auto/H.265/H.264) to config, RPC, and UI - Auto mode inspects the browser's SDP offer and prefers H.265 when supported, with graceful fallback to H.264 for browsers without H.265 (e.g. Firefox) - Move WebRTC video track creation from newSession() to ExchangeOffer() so the codec can be resolved after seeing the browser's offer - Set encoder codec type in onFirstSessionConnected() before VideoStart() - Show active codec in the status bar when troubleshooting mode is enabled - Remove quality factor >1.0 ceiling from ctrl.c to allow bitrate testing - Fix Go wrapper to check return value from C quality factor setter - Add e2e tests: video quality bitrate measurement, codec negotiation, codec preference persistence, and a quality factor sweep benchmark - Add visual noise helpers (remote host terminal) to e2e test infrastructure * chore(e2e): remove video quality benchmark tests and helpers Remove video-quality-sweep and video-quality spec files — these are benchmarking tools, not regression tests. Also removes the visual noise helpers and hardcoded developer SSH address from helpers.ts. * feat(video): bump bitrate cap to 4000 kbps and tighten VBR ceiling - Increase base_bitrate_high from 2000 to 4000 kbps, giving users better image quality at every quality factor setting. - Tighten VBR max_bitrate from 2x to 1.5x target, reducing encoder overshoot while still allowing headroom for dynamic content. - Add frames dropped, decode time, freeze count to WebRTC test hooks for pipeline health monitoring. - Move bitrate sweep benchmark to ui/benchmarks/ with its own playwright config, separate from the e2e test suite. Sweep results (visual noise, H.264, 1080p): factor=0.1: 3082 kbps, 60fps, 0 dropped, 2.9ms decode factor=0.5: 6357 kbps, 60fps, 0 dropped, 3.6ms decode factor=1.0: 9445 kbps, 59fps, 0 dropped, 4.3ms decode |
||
|
|
5cd265ae52 |
feat(network): add custom NTP/HTTP time sync configuration UI (#1289)
* feat(network): add custom NTP/HTTP time sync configuration UI Closes #516, #645, #59 The backend supports custom NTP servers, HTTP URLs, source ordering, parallel queries, and fallback control for time synchronization, but the frontend only exposes three presets (NTP only, NTP and HTTP, HTTP only). Users who need to specify their own NTP server — the core ask in all three linked issues — have no way to do so through the UI. Add a "Custom" option to the time sync dropdown. When selected, a card appears with input fields for NTP servers and HTTP URLs, following the same list-with-add/remove pattern used by the static IPv4 DNS fields. This is a simplified alternative to #1102 which exposed every backend field (source ordering, parallel queries, disable fallback) as direct UI controls. That PR stalled for 3 months due to complexity concerns and UX debate. This PR ships the functionality users actually requested — custom NTP servers — with a minimal UI surface: #1102: 753 additions, 15 files, new Combobox modifications This: ~120 additions, 18 files (13 are localization) The advanced fields (TimeSyncOrdering, TimeSyncParallel, TimeSyncDisableFallback) retain their backend defaults and can be surfaced in a follow-up if there is demand. Backend changes: confparser.go — add hostname_or_ipv4_or_ipv6 validation type so NTP server fields accept hostnames like pool.ntp.org, not just raw IPs. config.go — change TimeSyncNTPServers validation from ipv4_or_ipv6 to hostname_or_ipv4_or_ipv6. Frontend changes: CustomTimeSyncCard.tsx — new component with NTP server list and HTTP URL list, field validation, add/remove controls. stores.ts — add optional time_sync_ordering, time_sync_ntp_servers, time_sync_http_urls, time_sync_disable_fallback, time_sync_parallel to NetworkSettings interface. network settings page — uncomment Custom option, render card when time_sync_mode is custom. Translations added for all 13 supported languages. * fix(timesync): address review feedback on custom NTP UI 1. filterNTPServers: pass hostnames through instead of dropping them. net.ParseIP() returns nil for hostnames like pool.ntp.org, causing them to be silently skipped. The NTP library handles DNS resolution itself, so hostnames are valid entries. 2. getSyncMode: when TimeSyncMode is "custom", default the ordering to [ntp_user_provided, http_user_provided, ntp_dhcp, ntp, http] so user-provided servers are actually queried. The previous hardcoded default never included *_user_provided entries, rendering custom servers unreachable. 3. Stale config pointer: add SetNetworkConfig() on TimeSync and call it from rpcSetNetworkSettings after config.NetworkConfig is replaced. Without this, TimeSync holds a stale pointer and ignores runtime config changes until restart. 4. DNS vacuous truth: guard .every() calls on ipv4/ipv6 DNS dirty arrays with .length > 0 checks. [].every() returns true in JS, causing empty DNS arrays to falsely appear in the confirmation dialog. Signed-off-by: Alex Howells <alex@howells.me> * fix(timesync): ensure custom mode uses user-provided servers and re-syncs on settings change Move TimeSyncOrdering override before the mode switch so "custom" mode always sets the correct ordering with ntp_user_provided first, preventing stale ordering values from overriding it. Trigger an immediate time sync when network settings are saved so users don't have to wait for the hourly cycle or reboot. --------- Signed-off-by: Alex Howells <alex@howells.me> Co-authored-by: Adam Shiervani <adam@jetkvm.com> |
||
|
|
2167272ef1 |
fix(keyboard): serialise keyboard state mutations to eliminate race (#1369)
* fix(keyboard): serialise keyboard state mutations to eliminate race keypressReport performed a read-modify-write on keysDownState across three separate lock acquisitions, allowing concurrent callers (e.g. two auto-release timers, or an auto-release racing with a session-disconnect clear) to interleave and produce lost updates — leaving stale keys in the device state after disconnect. Replace the per-operation keyboardWriteHidFileLock with a keyboardMutex that covers the entire read-compute-write-update sequence in both keypressReport and KeyboardReport. This makes all keyboard state mutations serialisable, eliminating the race by construction rather than patching individual call sites. Also cancel pending auto-release timers on session close (avoids unnecessary HID writes) and query device state directly via JSON-RPC in the E2E test (removes Zustand store timing dependency). * fix(lint): align goimports formatting in config.go |
||
|
|
009a22e14c |
feat(usb): enable remote wakeup for HID devices (#1235)
* usb: enable remote wakeup for HID devices Set bmAttributes to 0xa0 (bus-powered + remote wakeup) in the USB configuration descriptor, and enable wakeup_on_write on all HID functions (keyboard, absolute mouse, relative mouse). Together with the corresponding kernel f_hid patch in rv1106-system, this allows the JetKVM to wake a sleeping host by sending keyboard or mouse input through the web UI or API. Tested on JetKVM v2 waking a Windows 11 host from S3 sleep. Requires: jetkvm/rv1106-system kernel patch (f_hid wakeup_on_write) Closes: #120 Closes: #674 * fix: ignore errors writing wakeup_on_write for backward compatibility The wakeup_on_write configfs attribute only exists on kernels with the rv1106-system#57 patch. On unpatched kernels the attribute is absent, and writing to it fails, which would break the entire USB gadget initialization. Set IgnoreErrors for wakeup_on_write so it is silently skipped on kernels that lack the attribute. |
||
|
|
203c6ae6fd |
fix: recover HID chardev after DWC3 rebind race on RV1106 (#1366)
* fix: reset USB gadget when virtual media unmount fails with EBUSY (#834) When unmountImageLocked() gets EBUSY from the kernel (host OS still accessing the virtual disk via PREVENT MEDIUM REMOVAL), fall back to gadget.RebindUsb(true) to force-disconnect the host, then retry the unmount. Uses RebindUsb directly instead of UpdateGadgetConfig to avoid hitting the same EBUSY when writing configfs attributes before rebind. After rebind, properly reopen keyboard HID file (ResetHIDFiles + sleep + OpenKeyboardHidFile) matching the pattern in setMassStorageMode(). Also propagate unmount errors to RPC callers and only clear currentVirtualMediaState after the unmount actually succeeds. Adds E2E test that mounts an ISO on the remote host to trigger PREVENT MEDIUM REMOVAL, then verifies unmount succeeds and keyboard recovers. * fix: recover HID chardev after DWC3 rebind race on RV1106 The DWC3 USB controller on the RV1106 has a race condition where rapid unbind→bind of the UDC can permanently corrupt HID chardev state — /dev/hidg0 returns ENXIO even though the device node exists and the UDC shows "configured". This can be triggered by UpdateGadgetConfig's transaction rebind and by host-initiated USB device resets during mass storage media changes. Three-part fix: 1. rebindUsb(): after binding, verify /dev/hidg0 is openable. If not, unbind again with a 100ms pause for kernel cleanup, then rebind. 2. setMassStorageMode(): pre-set recovery timer before UpdateGadgetConfig to prevent the poller from interfering. After the 1s sleep, if OpenKeyboardHidFile fails, do a corrective RebindUsb + retry. 3. checkUSBState() poller: when a state transition occurs and OpenKeyboardHidFile fails, trigger a corrective rebind to recover from host-initiated USB resets that corrupt the chardev. * fix: suppress USB recovery poller before rebind in unmount and mode-change paths The auto-recovery poller could see transient "not attached" UDC state during RebindUsb and trigger a competing rebind, corrupting HID chardev state. Add setUSBRecoveryTimer calls before the rebind in unmountImageLocked and before the corrective rebind in setMassStorageMode. * test: replace blind sleep with two-phase wait in factory-reset e2e test Wait for device to become unreachable before polling for it to come back, preventing false passes from stale pre-reset responses. * refactor: simplify branch — extract helpers, remove duplication, fix flaky tests - Extract rebindAndRecoverHID() in Go to deduplicate USB recovery sequences - Remove redundant setUSBRecoveryTimer() call after UpdateGadgetConfig() - Extract waitForKeyboardReady() helper replacing 5 duplicate retry loops - Consolidate 3 duplicate remoteExec definitions into single remoteHostExec() - Use shared SSH_OPTS from helpers.ts instead of hardcoded SSH options - Fix remote agent omitempty on mouse X/Y causing undefined in TypeScript - Poll keys-down state in disconnect test to avoid race condition * fix: remove dead IsHidgChardevHealthy export, reset HID files before rebind - Remove unused exported IsHidgChardevHealthy wrapper (only the unexported isHidgChardevHealthy is called, inside rebindUsb) - Move ResetHIDFiles() before RebindUsb in checkUSBState so stale file handles are closed even if the rebind fails — prevents silent mouse write failures on dead inodes after a successful unbind + failed bind |
||
|
|
1809e271e6 |
fix: swap menu screen padding on display rotation (#637) (#1354)
The physical LCD panel (ST7789V, 300 rows on a 320-row controller) and touch digitizer are misaligned by ~20px in the Y axis. LVGL's rotation transform inverts the Y-to-X mapping between 90° and 270°, so the touch calibration offset must be applied only at 90°. Changes: - Swap flex_screen_menu_style padding at 90°/270° (centering fix) - Apply +20px touch Y calibration via lv_evdev_set_calibration() at 90° - Recalibrate on rotation change in lvgl_set_rotation() - Remove fragile framebuffer-based rotation centering e2e test |
||
|
|
76e748a820 |
fix: USB HID startup recovery, unreliable channel fallback, and e2e test stability (#1364)
* fix: USB HID startup recovery and e2e test stability - fix(usb): always rebind UDC on Init() to guarantee clean HID function driver state. After factory reset + reboot, the configfs entries may exist from the previous boot but the kernel's internal HID function attachment is broken (/dev/hidg0 returns ENXIO). The changeset resolver skipped the bind because the UDC file content matched — but content match != working. Rebinding on every startup is cheap (brief USB re-enumeration) and guarantees a clean state. - fix(ui): fall back to reliable HID channel when unreliable WebRTC data channel is not yet established. Prevents silent mouse event drops during the brief window after page reload. - fix(e2e): suppress SSH known-hosts warnings with LogLevel=ERROR, replace zsh-incompatible glob patterns with find(1), fix nested SSH quoting, increase USB rebind timeouts, add keyboard warmup after EDID changes, reorder tests for stability. * test: remove Polish diacritics, WoL broadcast, and factory reset UI tests * fix(test): assert keyboard recovery after EDID restore instead of silently passing The retry loop captured no result and had no assertion, so a timeout would let the test pass without verifying HID actually recovered. |
||
|
|
edaa86c0d3 |
feat: add USB CDC-ACM serial console gadget (#726) (#1352)
* fix: add USB serial console toggle to hardware settings (#726) * fix: add USB CDC-ACM serial console gadget function (#726) Add serial_console.go with acm.usb0 gadget config item following the mass_storage pattern. Add SerialConsole bool to Devices struct and wire it through config.go enable check and jsonrpc.go setUsbDeviceState. The existing UI toggle in UsbDeviceSetting.tsx (with localization messages) now calls through to the backend correctly. When enabled, the KVM device creates /dev/ttyGS0 and the target host sees a CDC-ACM serial device (/dev/ttyACM*). When disabled, the symlink is removed from the USB gadget config and the host no longer enumerates the ACM interface. * fix: add CDC-ACM Console terminal UI for USB serial gadget (#726) * fix: merge terminal buttons into split button and rename CDC-ACM to USB Serial Console (#726) Combine KVM Terminal and USB Serial Console into a split button when both are present, make USB serial console state reactive via zustand store so the action bar updates without a page refresh, and fix the split button chevron not respecting the disabled state. |
||
|
|
711158ab18 |
fix: modifier key auto-release and keyboard reset on disconnect (#641) (#1339)
* fix: modifier key auto-release and keyboard reset on disconnect (#641) - Fix performAutoRelease() to check state.Modifier bitmask for modifier keys (0xE0-0xE7) instead of only checking state.Keys array, which never contained modifiers - Release all keys (send all-keys-up HID report) when WebRTC session disconnects to prevent stuck keys - Add keyboard state reset in onLastSessionDisconnected() as safety net * test(e2e): add modifier auto-release and disconnect key-release tests - Add test verifying modifier keys (Ctrl, Shift, Alt) auto-release after timeout using direct JSON-RPC to bypass browser keepalive - Add test verifying all held keys are released when WebRTC session disconnects, checking both host-side events and device-side state - Add getKeysDownState helper to e2e helpers |
||
|
|
e735f7d228 |
fix(video): align VENC virtual width/height to 16 bytes for non-standard resolutions (#699) (#1347)
The RV1106 hardware H.264 encoder (VENC) requires virtual width and height to be aligned to 16-pixel boundaries. Resolutions like 1366x768 (where 1366 is not divisible by 16) caused the encoder to fail silently, producing no video output and triggering an infinite retry loop. Changed RK_ALIGN_2 to RK_ALIGN_16 for u32VirWidth and u32VirHeight in both the VENC attribute initialization and frame submission paths. |
||
|
|
e0211302c0 |
fix: replace default EDID with JetKVM v1 EDID (#1341)
The old default EDID used Toshiba's manufacturer ID (TSB) from the TC358743 capture chip, had no CEA-861 extension block, and declared a very narrow frequency range (1-29Hz vertical). This caused compatibility issues with NVIDIA proprietary drivers and other strict EDID validators. The new EDID uses a JetKVM manufacturer ID (JKV), includes a CEA-861 extension with HDMI vendor specific block and audio support, declares proper standard timings (1920x1200, 1280x720, 640x480), and has corrected frequency range descriptors. Existing devices with the old default EDID are automatically migrated on config load. |
||
|
|
71db3b91ab |
fix: add settling delay when waking HDMI capture chip from sleep (#519) (#1356)
* fix: add settling delay in VideoStart() when waking from sleep mode (#519) * test: improve e2e test reliability and reduce flakiness - Replace fire-sleep-assert with polling in wheel scroll tests - Add retry loops for macro test and RPC setup after USB re-enumeration - Add waitForRpcReady helper to handle session dialogs and stale pages - Remove flaky paste modal UI test (redundant with keyboard scan tests) - Preserve SSH keys and dev mode across config resets in setup/teardown - Add saveSSHDevState/restoreSSHDevState helpers * fix: trim sysfs whitespace in getSleepMode() so sleep detection works Linux sysfs attributes include a trailing newline, so comparing raw content with "1" always returned false. Use strings.TrimSpace to match the convention used elsewhere in the codebase. |
||
|
|
fc64d6d2f3 | feat: add 4th and 5th button support for absolute mouse (#416) (#1357) | ||
|
|
cf7215411d |
feat: add horizontal mouse wheel scrolling support (#415) (#1358)
* fix: add horizontal mouse wheel (AC Pan) scroll support (#415) - HID descriptors: add AC Pan (Usage 0x0238, Consumer Page) to both absolute and relative mouse descriptors for horizontal scroll - Backend: extend AbsMouseWheelReport to accept wheelX, add RelMouseWheelReport with both axes, update report_length - RPC: add wheelX parameter to wheelReport binding - Frontend: read deltaX in wheel handler with same clamping/inversion and throttling as vertical scroll - E2E: add wheel scroll test verifying both vertical (REL_WHEEL) and horizontal (REL_HWHEEL) events reach the remote host * style: fix goimports alignment in RelMouseReport * fix: don't negate horizontal scroll direction in wheelReport The clampWheel helper was negating the result for both axes, but only vertical scrolling needs inversion (browser deltaY and HID Wheel use opposite sign conventions). Horizontal scrolling (deltaX / AC Pan) shares the same convention (positive = right), so negation reversed the direction on the target machine. * fix: wire RelMouseWheelReport into RPC and add wheel scroll e2e tests rpcWheelReport only called AbsMouseWheelReport, so wheel scrolling was silently broken in relative-only mouse mode. Now calls both Abs and Rel wheel report methods (each guards on its own enabledDevices flag). Adds e2e tests for vertical/horizontal wheel scroll in default mode and relative-only mode. Bumps beforeAll waitForInputDevices timeout to 30s and keyboard LED test expectKeyPress timeouts to 5s to reduce flakiness. |
||
|
|
9f2f1a20bf |
test(ota): add GPG signing subkey verification tests (#1325)
Add four tests covering the subkey lifecycle that mirrors production key setups (root key + signing subkeys on YubiKeys): - Signing with a subkey verifies successfully - Subkeys survive parseAndValidateKeyring entity filtering - Rotated subkeys (old and new) both verify - Revoked subkey is rejected by SigningKeyById These complement the rogue-entity filtering test from #1316 by verifying that subkey operations within a trusted entity work correctly through the full verification pipeline. |
||
|
|
ea5f0e5f4a |
fix(ota): return only the matched entity from parseAndValidateKeyring (#1316)
parseAndValidateKeyring validates that at least one entity in a
fetched keyring matches the pinned root key fingerprint
(rootKeyFingerprint, gpg.go:21). On match, it returns the entire
keyring — including any additional entities the keyserver included
in its response.
This is a problem because openpgp.CheckDetachedSignature iterates
every key in the provided keyring and accepts a signature from any
of them. A compromised or malicious keyserver could return a
response containing the legitimate JetKVM release key (satisfying
the fingerprint check) alongside an attacker-controlled key. A
binary signed with the attacker key would then pass verification
in both VerifySignature and VerifySignatureFromFile, since both
pass the cached keyring directly to CheckDetachedSignature.
The fix is a single-line change: return openpgp.EntityList{entity}
instead of the full keyring when the fingerprint matches. This
ensures only the trusted key is ever used for signature verification
regardless of what a keyserver returns.
TestParseAndValidateKeyring_FiltersRogueKeys exercises this by
constructing a two-entity armored keyring (trusted + rogue),
passing it through parseAndValidateKeyring, asserting the returned
keyring contains exactly one entity with the correct fingerprint,
and confirming that CheckDetachedSignature rejects a signature
produced by the rogue key.
Reported-by: equinox0815
Signed-off-by: Alex Howells <alex@howells.me>
|
||
|
|
5b143578d6 |
refactor: move tailscale logic into internal/tailscale package (#1318)
Move types, parsing, exec, and control-URL logic from the root kvm package into internal/tailscale/ so go test ./... no longer tries to link ARM-only CGO libraries on x86_64 hosts. |
||
|
|
e56304b7f5 |
refactor(cmd): replace fmt.Printf with zerolog in supervisor (#1302)
The supervisor process in cmd/main.go used fmt.Printf for operational messages (error dump handling, version mismatch fatal), while the KVM application uses zerolog throughout. The plain-text output mixed with zerolog-formatted output on the same stdout stream, breaking log parsability for anyone collecting logs via journald or similar. Replace the five fmt.Printf calls in createErrorDump() and the version mismatch fatal in main() with a supervisorLogger using the existing logging.GetSubsystemLogger infrastructure. Add "supervisor" to subsystemDefaultLevels at InfoLevel so the "error dump saved" message is not suppressed by the default ErrorLevel threshold. Intentionally left unchanged: - Version flag output (fmt.Println) — CLI output, not logging - Crash info written to log file (fmt.Fprintf) — file I/O - fmt.Sprintf calls — string formatting, not logging - setProcTitle — process title, not logging Signed-off-by: Alex Howells <alex@howells.me> |
||
|
|
bc8fda8aac |
fix(timesync): remove pool.ntp.org from default NTP servers (#1301)
pool.ntp.org requires vendor zone registration for use as a default in software or appliances. JetKVM has no vendor zone, so including it as a hardcoded fallback violates the NTP Pool's usage policy (https://www.ntppool.org/en/vendors.html). The remaining five hostname entries (time.apple.com, time.aws.com, time.windows.com, time.google.com, time.cloudflare.com) are vendor-operated services that explicitly permit public use. Combined with the 12 static IP entries for Cloudflare and Google, there is more than enough redundancy. Also removes a stale comment referencing a GitHub list of public NTP servers, since the fallback list is already curated. Closes #698 Signed-off-by: Alex Howells <alex@howells.me> |
||
|
|
15dc380062 |
fix: auto-recover USB gadget when host power-cycles (#1297)
* fix: auto-recover USB gadget when host reconnects (#128) When the USB host reboots or disconnects, the UDC state becomes "not attached" and never recovers. Add automatic recovery that detects this state and rebinds the USB gadget, with rate limiting to avoid thrashing. Also refactor keyboard HID file handling to support force-reopen after rebind. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move USB recovery logic to internal/usbgadget package Extract ShouldAttemptUSBRecovery and its retry interval constant into the internal/usbgadget package so the logic is testable without importing the top-level kvm package. Reset the recovery timer in updateUsbRelatedConfig to prevent auto-recovery from interfering during the host's USB re-enumeration window after a deliberate config change. Made-with: Cursor * feat(e2e): add JSON-RPC data channel support to test hooks Expose the WebRTC RPC data channel through test hooks and add a sendJsonRpc helper that sends a JSON-RPC request over the channel and resolves via callback with timeout handling. This enables e2e tests to invoke backend RPC methods directly. Made-with: Cursor * refactor(e2e): restructure tests with remote-agent suite Move device-level e2e tests (config-reset, EDID, HTTPS, HDMI sleep, LED, mouse, USB attach timing, USB device) into a consolidated remote-agent suite that runs through a Go-based agent binary. Add a separate Playwright project for remote-agent tests. - Remove standalone spec files now covered by ra-all.spec.ts - Rename login-rate-limit to zz-login-rate-limit so it runs last (avoids needing a post-test reboot to clear rate-limit state) - Reorder welcome-password tests so validation runs first, reusing the onboarding state and saving an SSH reset cycle Made-with: Cursor * refactor(e2e): clean up test helpers and reduce duplication Consolidate all test helpers into a single helpers.ts file, removing the separate ota-helpers.ts. This gives every test file a single import source and eliminates duplicated code across the test suite. Key changes: - Merge ota-helpers.ts into helpers.ts (mock server, binary deployment, device config, env var validation, triggerUpdate, withTempSignature) - Remove duplicated rpc/restartAppViaSSH/waitForDeviceReady functions from ra-all.spec.ts in favour of shared imports - Extract loginAndOpenSettings helper in settings-local-auth tests - Extract getOTAEnvVars, toPreReleaseVersion, triggerUpdate, and withTempSignature to reduce boilerplate across OTA tests - Remove unused verifyMouseWorks function and dead variables - Strip redundant JSDoc that just restated type signatures - Remove duplicated per-project use config from playwright.config.ts (already inherited from top-level) - Convert dynamic imports in sshExec to top-level imports Made-with: Cursor * refactor(e2e): move binary deployment into Playwright globalSetup Replace the shell-script deployment logic with Playwright's globalSetup/globalTeardown hooks. When BASELINE_BINARY_PATH is set, globalSetup deploys the binary, resets device config, reboots, and captures pre-test logs. globalTeardown captures post-test logs. This keeps the deployment lifecycle inside Playwright where it belongs, and reduces test_core_e2e.sh to a thin wrapper that sets env vars. Made-with: Cursor * fix: retry HID file reopen after USB gadget rebind After rebinding the DWC3 USB controller, the kernel needs a moment to create the /dev/hidg* device nodes. The previous code attempted to reopen the keyboard HID file immediately after rebind, which raced with the kernel and failed with "no such device or address". Add a retry loop (up to 10 attempts, 200ms apart) to wait for the device nodes to appear before reopening the keyboard HID file. Made-with: Cursor * fix: harden USB gadget recovery after UDC unbind Reset stale HID gadget handles after rebind, suppress transient HID-open errors during detach windows, and fall back to full gadget reconfiguration when simple UDC rebind does not restore keyboard HID promptly. Strengthen the remote-agent USB recovery E2E to verify both keyboard and mouse input recover after unbind with retry tolerance for host-side input node churn. Made-with: Cursor * refactor: simplify USB HID error handling and reduce hot-path overhead - Use errors.Is with syscall.Errno instead of string matching in IsHIDTemporarilyUnavailableError (robust, zero-alloc) - Cache USB state in usbReadyForHidReports instead of reading sysfs on every HID report - Extract rpcHidReport wrapper to deduplicate 5 rpc*Report functions - Fix openWithTimeout goroutine/fd leak on timeout - Add USBStateNotAttached/USBStateUnknown constants, replace literals - Deduplicate discoverJetKVMDevices by delegating to listInputDevices Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: run remote-agent e2e tests when JETKVM_REMOTE_HOST is provided Made-with: Cursor --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
cc23da692b |
fix(hid): skip mouse reports when mouse device is disabled (#1283)
AbsMouseReport, AbsMouseWheelReport, and RelMouseReport attempt to open and write to the HID device file on every call regardless of whether the mouse USB device is enabled. When the user disables the mouse in USB device settings, /dev/hidg1 does not exist and every mouse movement event produces an open/write error that is returned to the client, wasting CPU on both sides. Add an early return at the top of each function when the corresponding device (AbsoluteMouse or RelativeMouse) is disabled in enabledDevices. The check is outside the mutex lock since enabledDevices is set at construction and not mutated. Fixes #546 |
||
|
|
8e7b6ce4e3 |
Fix/ota signature bypass (#1254)
* fix(ota): reject empty signature and require prerelease opt-in for bypass Two OTA signature verification vulnerabilities: 1. Empty signature bypass: downloadSignature accepted a 0-byte response, which caused verifyFile to skip GPG verification entirely via the len(signature) > 0 guard. An attacker serving an empty .sig file could bypass signature checks on any stable release. 2. Prerelease bypass without opt-in: shouldBypassSignatureCheck only checked whether the remote version had a prerelease suffix, not whether the device had opted into the dev channel. A compromised server could push a version like "99.0.0-dev.1" to any device and skip signature verification regardless of include_pre_release setting. Fixes: - downloadSignature now returns an error when signature bytes are empty - shouldBypassSignatureCheck takes includePreRelease param and requires it to be true before allowing prerelease bypass Unit tests added for: empty signature, hash mismatch, non-200 sig download, valid signature happy path, and prerelease opt-in table tests. Made-with: Cursor * test(e2e): add OTA signature edge case and prerelease rejection tests Add wrong-key signature, empty signature, and prerelease-without-opt-in E2E tests to catch signature bypass vulnerabilities on real devices. Made-with: Cursor * refactor(e2e): use Playwright projects and remove OTA shell scripts Organize E2E tests into named Playwright projects (core, ota-signed, ota-prerelease-unsigned, etc.) so each test suite can be run with --project=<name>. Remove 5 OTA wrapper scripts that were just boilerplate env-var setup, and inline them into the Makefile via a shared OTA_ENV macro. Rename z-ota-* specs to ota-* now that ordering is controlled by project selection, not alphabetical filename sorting. Made-with: Cursor * fix(ci): use Go 1.25 for golangci-lint to match build workflow golangci-lint v2.1.6 (built with Go 1.24) panics when type-checking code that requires Go 1.25. Align the lint workflow with build.yml by using go-version: ^1.25.1 instead of oldstable. Made-with: Cursor |
||
|
|
11b99d59fd |
Signed Releases (#1188)
* Implement GPG signature verification for OTA updates - Added GPG signature verification to the OTA update process, ensuring that updates requiring signatures cannot be applied without them. - Introduced a new GPGVerifier struct to handle fetching and verifying signatures. - Updated the updateApp and updateSystem methods to check for signature URLs and download signatures as needed. - Enhanced error handling for missing signatures and verification failures. - Removed the old release.sh script as its functionality has been integrated into the Makefile for better release management. * Add tests for GPG signature verification in OTA updates * Refactor error message for missing GPG signature URL in OTA updates * Refactor OTA update process to improve signature handling - Introduced a new method for downloading component signatures, ensuring that updates requiring signatures cannot proceed without them. - Updated the Makefile to allow E2E tests to optionally include OTA tests based on the SKIP_OTA_E2E variable. - Enhanced the test_local_update.sh script to support signature file verification and inclusion during tests. - Improved error handling for missing signature URLs and added context cancellation checks in GPG key fetching. * Refactor GPG key caching to validate keyring before storing * Update Makefile to enhance E2E test process with optional OTA signature verification * Comment out non-working keyservers and update root key fingerprint * Add Ubunutu keyserver * Update root key fingerprint for GPG signature verification in OTA updates * Add signed OTA E2E test and full E2E test suite to Makefile - Introduced `test_e2e_signed` target for testing signed OTA updates with GPG signature verification. - Added `test_e2e_full` target to run both regular and signed OTA tests, requiring a signing key fingerprint. - Enhanced error handling for missing parameters in both test targets. * Update IP address extraction in test_local_update.sh to exclude all localhost addresses * Add GPG public key fetching tests with caching and error handling * Enhance build and testing scripts for signed OTA updates * Add fingerprint extraction and validation for GPG keys * Simplify bypass mechanism of OTA signature checks * Refactor E2E testing and release workflows * Enhance OTA testing framework and scripts * Improve local network IP detection in OTA helpers by implementing route-based detection as a primary method, falling back to interface scanning if necessary. * Add support for unsigned OTA version testing - Introduced a new script to test unsigned OTA updates with specific version checks. - Updated Makefile to include the new test script for unsigned OTA. - Enhanced existing E2E tests to validate version differences and ensure proper OTA behavior. - Improved error handling for required environment variables in the testing framework. * Update Makefile to include core E2E tests and enhance dev release validation - Added `test_core_e2e.sh` script execution to both production and development release workflows. - Improved user confirmation prompt before proceeding with the dev release. - Added completion messages to indicate successful test execution and readiness for release. * Enhance Makefile and testing scripts for improved OTA validation - Added a new script execution for testing unsigned OTA updates in the Makefile. - Updated E2E test configurations to exclude specific OTA tests and improve retry logic for video stream dimension retrieval. - Refactored mouse round-trip tests to remove unnecessary settle time parameters. * Final release confirmation of prod releases * Cleanup OTA code: eliminate redundant parsing, TOCTOU, and duplication - Remove double parseAndValidateKeyring call by threading validated keyring through fetchFromSingleKeyserver → fetchFromKeyservers → updateMemoryCache - Extract getKeyring() helper to deduplicate VerifySignature and VerifySignatureFromFile preamble - Replace os.Stat+os.Remove TOCTOU pattern with direct os.Remove ignoring os.ErrNotExist in downloadFile - Remove unnecessary fs.existsSync in mock server handler; check signaturePath variable directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * E2E: fix flaky tests, add unsigned OTA to dev test lane - Fix mouse roundtrip flakiness by increasing MOUSE_SETTLE_MS (50→150ms) - Export sshExec from helpers for ota-helpers.ts - Reduce overly conservative delays (polling, animations, reconnects) - Add waitForVideoDimensions helper with proper polling - Improve ensureLocalAuthMode to try known passwords before SSH reset - Add unsigned specific-version OTA test to `make test_e2e` target - Build baseline + dev binary with pinned VERSION_DEV to avoid timestamp drift Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove stale dev_release checklist item from PR templates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
d1027206bc |
Enhance synctrace logging (#980)
* Enhance synctrace logging. Switched the maps to be indexed by the .Pointer (not a string) Grouped the lockCount, unlockCount ,and lastLock in an trackingEntry so we can detect unlocks of something that wasn't ever locked and excessive unlocks and also tracks the first time locked and the last unlock time. Added LogDangledLocks for debugging use. Added a panic handler to the Main so we can log out panics * Switch to traceable sync for most everything * More documentation * Update internal/sync/log.go * Update DEVELOPMENT.md * Resolve merge issue. * Applied review comments * Restore --enable-sync-trace option. * Use WithLevel so we can re-panic as desired |
||
|
|
d84ff1b12a |
Enhance diagnostics logging with context-aware timeouts (#1138)
* Enhance diagnostics logging with context-aware timeouts * Add full system diagnostics logging on supervisor crash * Clear diagnostics context after logging to prevent memory leaks * remove pr.txt |
||
|
|
4cf6b08964 |
Improve failsafe handling (#1135)
* Refactor crash log handling to improve large log handling. - Introduced `readFileTail` function to read only the last specified bytes from log files, preventing memory issues with large files. - Updated `checkFailsafeReason` and `rpcGetDiagnostics` functions to utilize `readFileTail` for reading crash logs and application logs, respectively. - Adjusted log retrieval logic to handle different byte limits for recent and older crash logs. * Remove the cause of the large logs. No need to log our mdn * Move to HTTP for diagnostics * Fix linter |
||
|
|
81808b11ec |
Fix: Timeout Diagnostics (#1132)
* Enhance command execution with context timeout in diagnostics helper - Updated `runCmdLog` and `runShellLog` methods to use `exec.CommandContext` with a timeout, improving command execution reliability. - Introduced a default command timeout constant to manage execution duration effectively. * Refactor diagnostics log retrieval to use async JSON-RPC calls - Replaced the synchronous JSON-RPC call in `FailSafeModeOverlay` and `SettingsAdvancedRoute` with an asynchronous approach using `callJsonRpc`. - Improved error handling for diagnostics retrieval, ensuring user notifications are displayed for both success and failure cases. - Updated the `rpcDataChannel` closure handling in `KvmIdRoute` to set the state correctly upon closure. * Fix lint * Fix linter |
||
|
|
94fb5d1e8a | Enhance diagnostics logging: add application log section, improve crash dump handling, and allow custom log output destination (#1081) | ||
|
|
55a17dd16b | Implement diagnostics logging and download feature (#1078) | ||
|
|
a851e019e0 | Fix crash when touchscreen device missing (#1077) | ||
|
|
c085b3ec51 | Enhance logging configuration in dev_deploy.sh (#1071) | ||
|
|
2ebc7c808d | feat: Add Japanese 106/109 key minimum mappings and extend HID usage range (#1036) | ||
|
|
29f7fe46cf | fix(video): quality switching not taking effect (#1042) | ||
|
|
512dc5dc9c | fix(video-stream): switch to SOCK_STREAM between main and video (#1041) | ||
|
|
d24ce1c76f | fix: stop video stream before enabling sleep mode (#999) | ||
|
|
79cb2aa5ef |
fix(ota): set updating to false when no updates are available (#996)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
d34d01c4b3 | feat: add failsafe reason for video max restart attempts reached (#991) |