* 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 fa36843 already
reconfigures MPP on each v4l2 source-change event, so rate swaps work
end-to-end against this single EDID.
Validated at edidcraft.com: 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fold low-latency 720p modes into the JetKVM default EDID
The JetKVM v1 default EDID had two empty CTA-extension DTD slots after
its existing data blocks (audio, YCbCr 4:2:2, vendor-specific). Use the
first slot to advertise 1280x720@120 alongside the existing 1080p60
(DTD0, preferred) and 1280x720@60 (DTD1) base-block timings.
Source picks rate via OS display settings (`xrandr --rate 120`, Windows
Display Settings, etc.) — no separate "low latency" EDID needed in the
dropdown. The encoder-fps plumbing from fa36843 already reconfigures
MPP on every v4l2 source-change event, so OS-side rate swaps work
end-to-end against this single combined EDID.
Migration: config.LoadConfig now also rewrites EdidString to the new
default when the user is currently on the previous JetKVM v1 EDID
(without the 720p120 DTD), so existing devices auto-pick up the
high-refresh option on next boot.
Validated at edidcraft.com: 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop scripts/edid_gen.py — exploration tool, not a build dependency
The Python EDID generator was only used during this PR's development
phase to probe the TC358743 chip's max vrefresh and produce candidate
EDIDs. Now that the only EDID change is one DTD added to the JetKVM
default (a fully-validated 18-byte hex string in
internal/native/video.go and ui/src/routes/devices.\$id.settings.video.tsx),
the generator is no longer referenced by any code path. Drop it from
the PR to keep the diff focused on the runtime change. Available in
git history if anyone wants to extend the EDID set later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "fix(video): plumb source vrefresh into MPP encoder rate control"
This reverts commit fa36843. Empirically the MPP encoder forwards every
input frame whether or not Src/DstFrameRateNum/Den are set in the rate
control struct — those fields appear to only size the bitrate budget,
not gate frame submission. With this code in, dmesg shows
fps fix [120/1] -> fix [120/1] gop i [60]; with it reverted, dmesg
shows fps fix [30/1] -> fix [30/1] gop i [30]; in both cases WebRTC
inbound-rtp framesPerSecond sustains ~120 at the receiver. Drop the
plumbing to keep the PR's surface area minimal — the EDID-side change
alone is what unlocks 120 Hz end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Move 720p120 DTD from CTA extension to base block
NVIDIA's display driver enumerates base-block DTDs reliably but ignores
DTDs in the CTA extension that don't carry a CTA-861 VIC. 1280x720@120
isn't a CTA VIC, so the previous layout (720p120 in CTA-extension first
DTD slot) silently dropped the 120 Hz mode on GeForce hosts — `xrandr`
listed 1080p60 / 720p60 / 640p variants only.
Swap base-block DTD1 from 720p60 to 720p120. 720p60 stays advertised
through the Standard Timings block (0x81C0 at offset 40), which every
driver respects. Also drop the now-redundant 720p120 DTD from the CTA
extension and add the prior CTA-only EDID to the migration list so
existing devices auto-upgrade on next boot.
Validated at edidcraft.com: 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Reapply "fix(video): plumb source vrefresh into MPP encoder rate control"
This reverts commit 3cb61dfc8f.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* fix(logging): reset troubleshooting log level after reboot
Restore WARN on boot so temporary troubleshooting verbosity cannot silently fill device storage before rolling logs land. Update the settings copy to describe the reboot reset, add an e2e regression test for the INFO-to-WARN behavior, and make the i18n resort helper accept lint-staged file arguments.
* fix(i18n): avoid formatter churn in locale files
Keep localization message JSON on the repo's existing resort_messages.py formatting so translation-only changes do not explode into full-file diffs. Remove JSON from the generic oxfmt lint-staged rule and restage the locale files in their canonical format.
* 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
* 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
* fix: add custom broadcast IP option to Wake-on-LAN (#1238)
Add support for specifying a custom subnet broadcast IP when sending
WOL magic packets, enabling wake across different subnets.
Backend:
- Add broadcastIP optional parameter to rpcSendWOLMagicPacket
- Add OptionalParams support to RPCHandler for params with zero defaults
- Pass broadcastIP query param through HTTP handler
UI:
- Add broadcast address dropdown (Auto/Custom) to WOL dialog
- Show subnet broadcast IP input when Custom is selected
- Pass broadcastIP to RPC call when custom mode is active
* fix: move broadcast address field to add form only, default to Auto (#1238)
* fix(ui): simplify WoL broadcast dropdown and indent custom field
- Rename "Auto (global broadcast)" to "Auto" in the broadcast address
dropdown
- Wrap the custom subnet IP input in a nested indent with left border,
matching the settings page pattern (NestedSettingsGroup style)
* fix(i18n): use localization system for WoL broadcast address labels
Replace hardcoded English strings with m.xxx() calls in the broadcast
address UI and add the 4 new keys to all 14 locale files.
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.
* feat(tailscale): add custom control URL configuration & handling w/ tests
- Introduced TailscaleControlURL in the Config struct to allow configuration of the Tailscale control server.
- Added RPC handlers for getting and setting the Tailscale control URL.
- Updated TailscaleStatus to include controlURL, ensuring it reflects the configured or default value.
- Enhanced parsing and normalization of the control URL to enforce valid formats.
- Updated TailscaleCard component to manage and display the control server URL, allowing users to save changes.
* docs: update README to include optional Tailscale networking feature
Added a new section highlighting the built-in Tailscale status and control-server configuration, including support for custom Headscale-compatible endpoints.
* docs: Extend DEVELOPMENT.md with Tailscale control server details
* fix(tailscale): enhance error handling in rpcSetTailscaleControlURL
- Updated the rpcSetTailscaleControlURL function to revert the TailscaleControlURL to its previous value if saving or applying the new URL fails.
- Added a new test to ensure that the configuration is not saved when the apply command fails, verifying that the previous URL remains intact.
- Adjusted existing tests to validate the order of operations during the URL setting process.
Related to Review: https://github.com/jetkvm/kvm/pull/1312#pullrequestreview-3984856379
* refactor(tailscale): simplify control URL application logic and enhance error handling
- Renamed the test function to better reflect its purpose and updated the test cases to ensure correct command execution.
- Removed fallback logic for applying the Tailscale control URL, streamlining the error handling to return a clear error message when the "set" command fails.
- Added a new test to verify behavior when the "set" command fails, ensuring proper error reporting.
Related to Review https://github.com/jetkvm/kvm/pull/1312#pullrequestreview-3984856379
* refactor(tailscale): update control server configuration in UI & documentation
- Updated the TailscaleCard component to allow users to select between default and custom control server URLs.
- Improved state management for control server URL input based on the selected mode.
- Revised DEVELOPMENT.md to clarify control server application logic and error handling.
- Removed outdated example JSON-RPC payloads for clarity.
Related to Review: https://github.com/jetkvm/kvm/pull/1312#discussion_r2980284611
* refactor(ui): use built-in components and i18n for TailscaleCard
- Replace raw <select> with SelectMenuBasic component
- Use SettingsItem with new SM size for control server setting
- Use NestedSettingsGroup for indented custom URL input
- Add tailscale_* i18n keys to all 14 locale files with translations
- Add size prop (SM/MD) to SettingsItem for compact contexts
---------
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* MQTT support
* feat(mqtt): redesign MQTT settings UI with improved UX
Restructure the MQTT settings page for clarity and usability:
UI Structure:
- Organize settings into logical sections (Auth, Home Assistant, Advanced)
- Use progressive disclosure for port (Auto/Custom) and base topic (Default/Custom)
- Move connection status badge into page header
- Conditionally show HDD debounce only when ATX extension is active
- Add inline validation for required broker field
Connection & Error Handling:
- Add test-then-save flow: Save & Reconnect validates connectivity before persisting
- Add standalone Test Connection button for dry-run validation
- Add testMqttConnection RPC with 5s timeout (no retry, no side effects)
- Surface friendly i18n-ready error messages for common failures (auth, timeout, TLS, DNS)
- Track last connection error on MQTTManager for status reporting
Copy:
- Rewrite all descriptions for clarity and brevity
- Use benefit-oriented, active-voice microcopy throughout
---------
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* 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
Ensure the jiggler config loads the defaults so they can be saved.
Ensure the file.Sync occurs before acknowledging save.
Also fixup the old KeyboardLayout to use en-US not en_US
Remove LED sync source option and add keypress reporting while still working with devices that haven't been upgraded
We return the modifiers as the valid bitmask so that the VirtualKeyboard and InfoBar can represent the correct keys as down. This is important when we have strokes like Left-Control + Right-Control + Keypad-1 (used in switching KVMs and such).
Fix handling of modifier keys in client and also removed the extraneous resetKeyboardState.
Manage state to eliminate rerenders by judicious use of useMemo.
Centralized keyboard layout and localized display maps
Move keyboardOptions to useKeyboardLayouts
Added translations for display maps.
Add documentation on the legacy support.
Return the KeysDownState from keyboardReport
Clear out the hidErrorRollOver once sent to reset the keyboard to nothing down.
Handles the returned KeysDownState from keyboardReport
Now passes all logic through handleKeyPress.
If we get a state back from a keyboardReport, use it and also enable keypressReport because we now know it's an upgraded device.
Added exposition on isoCode management
Fix de-DE chars to reflect German E2 keyboard.
https://kbdlayout.info/kbdgre2/overview+virtualkeys
Ran go modernize
Morphs Interface{} to any
Ranges over SplitSeq and FieldSeq for iterating splits
Used min for end calculation remote_mount.Read
Used range 16 in wol.createMagicPacket
DID NOT apply the Omitempty cleanup.
Strong typed in the typescript realm.
Cleanup react state management to enable upgrading Zustand
* feat: add timezone support to jiggler and fix custom settings persistence
- Add timezone field to JigglerConfig with comprehensive IANA timezone list
- Fix custom settings not loading current values
- Remove business hours preset, add as examples in custom settings
- Improve error handling for invalid cron expressions
* fix: format jiggler.go with gofmt
* fix: add embedded timezone data and validation
- Import time/tzdata to embed timezone database in binary
- Add timezone validation in runJigglerCronTab() to gracefully fallback to UTC
- Add timezone to debug logging in rpcSetJigglerConfig
- Fixes 'unknown time zone' errors when system lacks timezone data
* refactor: add timezone field comments from jiggler options
* chore: move tzdata to backend
* refactor: fix JigglerSetting linting
- Adjusted useEffect dependency to include send function for better data fetching
- Modified layout classes for improved responsiveness and consistency
- Cleaned up code formatting for better readability
---------
Co-authored-by: Siyuan Miao <i@xswan.net>
* feat: add local web server loopback mode configuration
- Introduced a new configuration option `LocalWebServerLoopbackOnly` to restrict the web server to listen only on the loopback interface.
- Added RPC methods `rpcGetLocalWebServerLoopbackOnly` and `rpcSetLocalWebServerLoopbackOnly` for retrieving and updating this setting.
- Updated the web server startup logic to bind to the appropriate address based on the new configuration.
- Modified the `LocalDevice` struct to include the loopback setting in the response.
* remove extra logs
* chore: add VSCode extensions for improved development environment
* refactor: rename LocalWebServerLoopbackOnly to LocalLoopbackOnly
- Updated the configuration struct and related RPC methods to use the new name `LocalLoopbackOnly` for clarity.
- Adjusted the web server binding logic and device response structure to reflect this change.
* feat: add loopback-only mode functionality to UI
- Implemented a new setting for enabling loopback-only mode, restricting web interface access to localhost.
- Added a confirmation dialog to warn users before enabling this feature.
- Updated the ConfirmDialog component to accept React nodes for the description prop.
- Refactored imports and adjusted component structure for clarity.
* refactor: optimize device settings handlers for better performance
- Refactored the `handleDevChannelChange` and `handleLoopbackOnlyModeChange` functions to use `useCallback` for improved performance and to prevent unnecessary re-renders.
- Consolidated the logic for applying loopback-only mode into a separate `applyLoopbackOnlyMode` function, enhancing code clarity and maintainability.
- Updated the confirmation flow for enabling loopback-only mode to ensure user warnings are displayed appropriately.
* Enable multiple keyboard layouts for paste text from host
* Trema is the more robust method for capital umlauts
* Improve error handling and pre-loading
* Improve accent handling
* Remove obscure Alt-Gr keys, unsure if they are supported everywhere
* Add Swiss French
* Change line ordering
* Fix whitespace
* Add French (France)
* Add English (UK)
* Add Swedish
* Add Spanish
* Fix fr_FR special characters
* Add more keys to Spanish
* Remove default value shift: false
* Add Norwegian
* Operator precedence 🤦
* Add Italian
* Add Czech
* Move guard statements outside of loop
* Move language name definitions into the keyboard layout files
* Change the locale names to their native language
German->Deutsch et. al.
* Move hold key handling into Go backend analogous to https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
* Remove trailing whitespace
* Fix
* Add Belgisch Nederlands
* Add JSONRPC handling
* Use useSettingsStore
* Revert "Move hold key handling into Go backend analogous to https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt"
This reverts commit 146cee9309.
* Move FeatureFlag to navigation
* Fix: flip Y/Z
* Add useEffect dependencies
* Embolden language
* Add to useCallback dependencies
---------
Co-authored-by: Marc Brooks <IDisposable@gmail.com>
* feat(ui): Add other session handling route and modal
* feat(ui): Add dedicated update route and refactor update dialog state management
* feat(ui): Add local authentication route
* refactor(ui): Remove LocalAuthPasswordDialog component and clean up related code
* refactor(ui): Remove OtherSessionConnectedModal component
* feat(ui): Add dedicated mount route and refactor mount media dialog
* refactor(ui): Simplify Escape key navigation in device route
* refactor(ui): Add TODO comments for future URL-based state migration
* refactor(ui): Migrate settings and update routes to dedicated routes
This commit introduces a comprehensive refactoring of the UI routing and state management:
- Removed sidebar-based settings view
- Replaced global modal state with URL-based routing
- Added dedicated routes for settings, including general, security, and update sections
- Simplified modal and sidebar interactions
- Improved animation and transition handling using motion library
- Removed deprecated components and simplified route structure
* fix(ui): Add TODO comment for modal session interaction
* refactor(ui): Move USB configuration to new settings setup
This commit introduces several improvements to the USB configuration workflow:
- Refactored USB configuration dialog component
- Simplified USB config state management
- Moved USB configuration to hardware settings route
- Updated JSON-RPC type definitions
- Cleaned up unused imports and components
- Improved error handling and notifications
* refactor(ui): Replace react-router-dom navigation with custom navigation hook
This commit introduces a new custom navigation hook `useDeviceUiNavigation` to replace direct usage of `useNavigate` across multiple components:
- Removed direct `useNavigate` imports in various components
- Added `navigateTo` method from new navigation hook
- Updated navigation calls in ActionBar, MountPopover, UpdateInProgressStatusCard, and other routes
- Simplified navigation logic and prepared for potential future navigation enhancements
- Removed console logs and unnecessary comments
* refactor(ui): Remove unused react-router-dom import
Clean up unnecessary import of `useNavigate` from react-router-dom in device settings route
* feat(ui): Improve mobile navigation and scrolling in device settings
* refactor(ui): Reorganize device access and security settings
This commit introduces several changes to the device access and security settings:
- Renamed "Security" section to "Access" in settings navigation
- Moved local authentication routes from security to access
- Removed deprecated security settings route
- Added new route for device access settings with cloud and local authentication management
- Updated cloud URL and adoption logic to be part of the access settings
- Simplified routing and component structure for better user experience
* fix(ui): Update logout button hover state color
* fix(ui): Adjust device de-registration button size to small
* fix(ui): Update appearance settings section header and description
* refactor(ui): Replace SectionHeader with new SettingsPageHeader and SettingsSectionHeader components
This commit introduces two new header components for settings pages:
- Created SettingsPageHeader for main page headers
- Created SettingsSectionHeader for subsection headers
- Replaced all existing SectionHeader imports with new components
- Updated styling and type definitions to support more flexible header rendering
* feat(ui): Add dev channel toggle to advanced settings
Move dev channel update option from general settings to advanced settings
- Introduced new state and handler for dev channel toggle
- Removed dev channel option from general settings route
- Added dev channel toggle in advanced settings with error handling
* rebasing on dev branch
* fixed formatting
* fixed formatting
* removed query params
* moved usb settings to hardware setting
* swapped from error to log
* added fix for any change to product name now resulting in show the spinner as custom on page reload
* formatting
---------
Co-authored-by: JackTheRooster <adrian@rydeas.com>
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* feat(display.go): impl setDisplayBrightness()
Implements setDisplayBrightness(brightness int) which allows setting the
backlight brightness on JetKVM's hardware.
Needs to be implemented into the RPC, config and frontend.
* feat(config): add backlight control settings
* feat(display): add automatic dimming & switch off to display
WIP, dims the display to 50% of the BacklightMaxBrightness after
BacklightDimAfterMS expires. Turns the display off after
BacklightOffAfterMS
* feat(rpc): add methods to get and set BacklightSettings
* WIP: feat(settings): add Max backlight setting
* chore: use constant for backlight control file
* fix: only attempt to wake the display if it's off
* feat(display): wake on touch
* fix: re-use buffer between reads
* fix: wakeDisplay() on start to fix warm start issue
If the application had turned off the display before exiting, it
wouldn't be brought on when the application restarted without a device
reboot.
* chore: various comment & string updates
* fix: newline on set brightness log
Noticed by @eric
https://github.com/jetkvm/kvm/pull/17#discussion_r1903338705
* fix: set default value for display
Set the DisplayMaxBrightness to the default brightness used
out-of-the-box by JetKVM. Also sets the dim/timeout to 2 minutes and 30
mintes respectively.
* feat(display.go): use tickers to countdown to dim/off
As suggested by tutman in https://github.com/jetkvm/kvm/pull/17, use
tickers set to the duration of dim/off to avoid a loop running every
second. The tickers are reset to the dim/off times whenever
wakeDisplay() is called.
* chore: update config
Changed Dim & Off values to seconds instead of milliseconds, there's no
need for it to be that precise.
* feat(display.go): wakeDisplay() force
Adds the force boolean to wakedisplay() which allows skipping the
backlightState == 0 check, this means you can force a ticker reset, even
if the display is currently in the "full bright" state
* feat(display.go): move tickers into their own method
This allows them to only be started if necessary. If the user has chosen
to keep the display on and not-dimmed all the time, the tickers can't
start as their tick value must be a positive integer.
* feat(display.go): stop tickers when auto-dim/auto-off is disabled
* feat(rpc): implement display backlight control methods
* feat(ui): implement display backlight control
* chore: update variable names
As part of @joshuasing's review on #17, updated variables & constants to
match the Go best practices.
Signed-off-by: Cameron Fleming <cameron@nevexo.space>
* fix(display): move backlightTicker setup into screen setup goroutine
Signed-off-by: Cameron Fleming <cameron@nevexo.space>
* chore: fix some start-up timing issues
* fix(display): Don't attempt to start the tickers if the display is disabled
If max_brightness is zero, then there's no point in trying to dim it (or
turn it off...)
* fix: wakeDisplay() doesn't need to stop the tickers
The tickers only need to be reset, if they're disabled, they won't have
been started.
* fix: Don't wake up the display if it's turned off
---------
Signed-off-by: Cameron Fleming <cameron@nevexo.space>
This commit adds the config entry "EdidString" and saves the EDID string
when it's modified via the RPC.
The EDID is restored when the jetkvm_native control socket connects
(usually at boot)
Signed-off-by: Cameron Fleming <cameron@nevexo.space>