From 19e1f8bb710fd9ef98b2819ff23e3d385ec5f0e6 Mon Sep 17 00:00:00 2001 From: Nitish Agarwal <1592163+nitishagar@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:54:20 +0530 Subject: [PATCH] fix: surface out_of_range error for unsupported HDMI resolution When the HDMI source exceeds 1920x1080, the native layer reports real Width/Height but no Error, so the UI hangs on "Loading video stream...". Classify such states as out_of_range (Ready=false) in OnVideoStateChange before storing/emitting, feeding the existing HDMIErrorOverlay pipeline so the user sees an incompatible-resolution message. rpcGetVideoState reflects the same state for automation. Fixes #1482 --- native.go | 1 + video.go | 8 ++++++++ video_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 video_test.go diff --git a/native.go b/native.go index 529ab4a57..ffef1db3c 100644 --- a/native.go +++ b/native.go @@ -37,6 +37,7 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) { _ = reapplyHostDisplayAdvertisement("native_restarted") }, OnVideoStateChange: func(state native.VideoState) { + state = unsupportedResolutionState(state) lastVideoState = state triggerVideoStateUpdate() requestDisplayUpdate(false, "video_state_changed") diff --git a/video.go b/video.go index e981979d6..38e13f3c4 100644 --- a/video.go +++ b/video.go @@ -37,6 +37,14 @@ func rpcGetVideoState() (native.VideoState, error) { return lastVideoState, nil } +func unsupportedResolutionState(state native.VideoState) native.VideoState { + if state.Error == "" && (state.Width > 1920 || state.Height > 1080) { + state.Error = "out_of_range" + state.Ready = false + } + return state +} + var ( hostDisplayAdvertiseLock = sync.Mutex{} hostDisplayAdvertised bool diff --git a/video_test.go b/video_test.go new file mode 100644 index 000000000..b78e0e6b5 --- /dev/null +++ b/video_test.go @@ -0,0 +1,47 @@ +//go:build linux && arm + +package kvm + +import ( + "testing" + + "github.com/jetkvm/kvm/internal/native" +) + +func TestUnsupportedResolutionState(t *testing.T) { + tests := []struct { + name string + input native.VideoState + want native.VideoState + }{ + { + name: "1920x1080 no error unchanged", + input: native.VideoState{Ready: true, Width: 1920, Height: 1080}, + want: native.VideoState{Ready: true, Width: 1920, Height: 1080}, + }, + { + name: "3840x2160 no error becomes out_of_range", + input: native.VideoState{Ready: true, Width: 3840, Height: 2160}, + want: native.VideoState{Ready: false, Error: "out_of_range", Width: 3840, Height: 2160}, + }, + { + name: "1920x1080 no_signal unchanged", + input: native.VideoState{Ready: false, Error: "no_signal", Width: 1920, Height: 1080}, + want: native.VideoState{Ready: false, Error: "no_signal", Width: 1920, Height: 1080}, + }, + { + name: "3840x2160 no_lock keeps no_lock", + input: native.VideoState{Ready: false, Error: "no_lock", Width: 3840, Height: 2160}, + want: native.VideoState{Ready: false, Error: "no_lock", Width: 3840, Height: 2160}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := unsupportedResolutionState(tt.input) + if got != tt.want { + t.Errorf("unsupportedResolutionState(%+v) = %+v, want %+v", tt.input, got, tt.want) + } + }) + } +}