Make config.EdidString the single source of truth for EDID

- Set DefaultEDID in config defaults instead of empty string
- Pass config EDID to Native.Start() to fix initialization race condition
- Update DefaultEDID to MacBook-compatible value (2ch, 48kHz, 16/20/24-bit)
- Add getDefaultEDID RPC endpoint for UI to fetch backend constant
- Update UI to dynamically fetch default EDID instead of hardcoding
- Remove all EDID fallback logic now that config always has a value
- Simplify rpcGetEDID to return config value directly

This ensures the configured EDID is used from startup and eliminates
sync issues between backend constant, config, and UI.
This commit is contained in:
Alex P 2025-11-19 11:08:32 +02:00
parent c88b98c1f0
commit 0168fcbdbd
7 changed files with 48 additions and 45 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/jetkvm/kvm/internal/confparser"
"github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/native"
"github.com/jetkvm/kvm/internal/network/types"
"github.com/jetkvm/kvm/internal/usbgadget"
"github.com/prometheus/client_golang/prometheus"
@ -174,6 +175,7 @@ func getDefaultConfig() Config {
KeyboardMacros: []KeyboardMacro{},
DisplayRotation: "270",
KeyboardLayout: "en-US",
EdidString: native.DefaultEDID,
DisplayMaxBrightness: 64,
DisplayDimAfterSec: 120, // 2 minutes
DisplayOffAfterSec: 1800, // 30 minutes

View File

@ -94,7 +94,7 @@ func NewNative(opts NativeOptions) *Native {
}
}
func (n *Native) Start() {
func (n *Native) Start(initialEDID string) {
if n.disable {
nativeLogger.Warn().Msg("native is disabled, skipping initialization")
setCgoDisabled(true)
@ -105,9 +105,8 @@ func (n *Native) Start() {
setInstance(n)
setUpNativeHandlers()
// set EDID before video init so source sees audio capabilities immediately
if err := videoSetEDID(DefaultEDID); err != nil {
n.l.Warn().Err(err).Msg("failed to set default EDID before video init")
if err := videoSetEDID(initialEDID); err != nil {
n.l.Warn().Err(err).Msg("failed to set EDID before video init")
}
// start the native video

View File

@ -8,9 +8,7 @@ import (
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
// DefaultEDID is the default EDID (identifies as "JetKVM HDMI" with full TC358743 audio/video capabilities).
// Updated with 24-inch display dimensions (52x32cm) and Dell manufacturer ID for compatibility.
const DefaultEDID = "00ffffffffffff0010ac01000100000001230104803420782ec9a05747982712484c00000000d1c081c0a9c0b3000101010101010101083a801871382d40582c450000000000001e011d007251d01e206e28550000000000001e000000fc004a65746b564d2048444d490a20000000fd00187801ff1d000a202020202020016602032e7229097f070d07070f0707509005040302011f132220111214061507831f000068030c0010003021e2050700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047"
const DefaultEDID = "00ffffffffffff0010ac01000100000001230104803420782ec9a05747982712484c00000000d1c081c0a9c0b3000101010101010101083a801871382d40582c450000000000001e011d007251d01e206e28550000000000001e000000fc004a65746b564d2048444d490a20000000fd00187801ff1d000a20202020202001660203287223090407509005040302011f1322201112140615078301000068030c0010003021e20507000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c800000000005c"
var extraLockTimeout = 5 * time.Second
@ -115,10 +113,6 @@ func (n *Native) VideoSetEDID(edid string) error {
n.videoLock.Lock()
defer n.videoLock.Unlock()
if edid == "" {
edid = DefaultEDID
}
return n.useExtraLock(func() error {
return videoSetEDID(edid)
})

View File

@ -19,6 +19,7 @@ import (
"go.bug.st/serial"
"github.com/jetkvm/kvm/internal/hidrpc"
"github.com/jetkvm/kvm/internal/native"
"github.com/jetkvm/kvm/internal/usbgadget"
"github.com/jetkvm/kvm/internal/utils"
)
@ -208,15 +209,16 @@ func rpcSetAutoUpdateState(enabled bool) (bool, error) {
}
func rpcGetEDID() (string, error) {
resp, err := nativeInstance.VideoGetEDID()
if err != nil {
return "", err
}
return resp, nil
return config.EdidString, nil
}
func rpcGetDefaultEDID() (string, error) {
return native.DefaultEDID, nil
}
func rpcSetEDID(edid string) error {
if edid == "" {
edid = native.DefaultEDID
logger.Info().Msg("Restoring EDID to default")
} else {
logger.Info().Str("edid", edid).Msg("Setting EDID")
@ -226,7 +228,6 @@ func rpcSetEDID(edid string) error {
return err
}
// Save EDID to config, allowing it to be restored on reboot.
config.EdidString = edid
_ = SaveConfig()
return nil
@ -1370,6 +1371,7 @@ var rpcHandlers = map[string]RPCHandler{
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
"getEDID": {Func: rpcGetEDID},
"getDefaultEDID": {Func: rpcGetDefaultEDID},
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
"getVideoLogStatus": {Func: rpcGetVideoLogStatus},
"getVideoSleepMode": {Func: rpcGetVideoSleepMode},

View File

@ -64,12 +64,7 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
},
})
nativeInstance.Start()
go func() {
if err := nativeInstance.VideoSetEDID(config.EdidString); err != nil {
nativeLogger.Warn().Err(err).Msg("error setting EDID")
}
}()
nativeInstance.Start(config.EdidString)
if os.Getenv("JETKVM_CRASH_TESTING") == "1" {
nativeInstance.DoNotUseThisIsForCrashTestingOnly()

View File

@ -39,6 +39,7 @@ const blockedMethodsByReason: Record<string, string[]> = {
video: [
'setStreamQualityFactor',
'getEDID',
'getDefaultEDID',
'setEDID',
'getVideoLogStatus',
'setDisplayRotation',

View File

@ -11,13 +11,7 @@ import Fieldset from "@components/Fieldset";
import notifications from "@/notifications";
import { m } from "@localizations/messages.js";
const defaultEdid =
"00ffffffffffff0010ac01000100000001230104803420782ec9a05747982712484c00000000d1c081c0a9c0b3000101010101010101083a801871382d40582c450000000000001e011d007251d01e206e28550000000000001e000000fc004a65746b564d2048444d490a20000000fd00187801ff1d000a202020202020016602032e7229097f070d07070f0707509005040302011f132220111214061507831f000068030c0010003021e2050700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047";
const edids = [
{
value: defaultEdid,
label: m.video_edid_jetkvm_default(),
},
const otherEdids = [
{
value:
"00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096",
@ -52,6 +46,8 @@ export default function SettingsVideoRoute() {
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
const [edid, setEdid] = useState<string | null>(null);
const [edidLoading, setEdidLoading] = useState(true);
const [defaultEdid, setDefaultEdid] = useState<string>("");
const [edids, setEdids] = useState<Array<{value: string, label: string}>>([]);
const { debugMode } = useSettingsStore();
// Video enhancement settings from store
const {
@ -69,28 +65,42 @@ export default function SettingsVideoRoute() {
setStreamQuality(String(resp.result));
});
send("getEDID", {}, (resp: JsonRpcResponse) => {
setEdidLoading(false);
send("getDefaultEDID", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.video_failed_get_edid({ error: resp.error.data || m.unknown_error() }));
return;
}
const receivedEdid = resp.result as string;
const fetchedDefaultEdid = resp.result as string;
setDefaultEdid(fetchedDefaultEdid);
const matchingEdid = edids.find(
x => x.value.toLowerCase() === receivedEdid.toLowerCase(),
);
const allEdids = [
{ value: fetchedDefaultEdid, label: m.video_edid_jetkvm_default() },
...otherEdids
];
setEdids(allEdids);
if (matchingEdid) {
// EDID is stored in uppercase in the UI
setEdid(matchingEdid.value.toUpperCase());
// Reset custom EDID value
setCustomEdidValue(null);
} else {
setEdid("custom");
setCustomEdidValue(receivedEdid);
}
send("getEDID", {}, (resp: JsonRpcResponse) => {
setEdidLoading(false);
if ("error" in resp) {
notifications.error(m.video_failed_get_edid({ error: resp.error.data || m.unknown_error() }));
return;
}
const receivedEdid = resp.result as string;
const matchingEdid = allEdids.find(
x => x.value.toLowerCase() === receivedEdid.toLowerCase(),
);
if (matchingEdid) {
setEdid(matchingEdid.value.toUpperCase());
setCustomEdidValue(null);
} else {
setEdid("custom");
setCustomEdidValue(receivedEdid);
}
});
});
}, [send]);