From 24ec74a441524df8ce1aec42e861d1178467f467 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 09:53:39 +0000 Subject: [PATCH 01/12] fix: stop video stream before enabling sleep mode --- internal/native/cgo/video.c | 1 + internal/native/video.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 857acbbb9..090f2bab6 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -539,6 +539,7 @@ void *run_video_stream(void *arg) if (r == 0) { log_info("select timeout"); + ensure_sleep_mode_disabled(); break; } if (r == -1) diff --git a/internal/native/video.go b/internal/native/video.go index c556a9383..29a361730 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -35,7 +35,13 @@ func (n *Native) setSleepMode(enabled bool) error { bEnabled := "0" if enabled { bEnabled = "1" + if err := n.VideoStop(); err != nil { + return fmt.Errorf("video stop failed, won't enable sleep mode: %w", err) + } + // wait few seconds to ensure the video stream is stopped + time.Sleep(3 * time.Second) } + return os.WriteFile(sleepModeFile, []byte(bEnabled), 0644) } From dc611870c7f1330aee06a41f1e318b93feb56c54 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 10:06:03 +0000 Subject: [PATCH 02/12] feat: stop video stream only if it's active --- internal/native/cgo/ctrl.c | 4 ++++ internal/native/cgo/ctrl.h | 1 + internal/native/cgo/video.c | 7 +++++++ internal/native/cgo/video.h | 7 +++++++ internal/native/cgo_linux.go | 9 +++++++++ internal/native/cgo_notlinux.go | 5 +++++ internal/native/video.go | 18 ++++++++++++++++++ 7 files changed, 51 insertions(+) diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index 547d5694b..9a5ce2ddf 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -367,6 +367,10 @@ void jetkvm_video_stop() { video_stop_streaming(); } +uint8_t jetkvm_is_video_streaming() { + return video_is_streaming(); +} + int jetkvm_video_set_quality_factor(float quality_factor) { if (quality_factor <= 0 || quality_factor > 1) { return -1; diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index 774ee1473..7df6ac435 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -56,6 +56,7 @@ int jetkvm_video_init(float quality_factor); void jetkvm_video_shutdown(); void jetkvm_video_start(); void jetkvm_video_stop(); +uint8_t jetkvm_is_video_streaming(); int jetkvm_video_set_quality_factor(float quality_factor); float jetkvm_video_get_quality_factor(); int jetkvm_video_set_edid(const char *edid_hex); diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 090f2bab6..ce9491740 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -727,6 +727,13 @@ void video_stop_streaming() log_info("video streaming stopped"); } +uint8_t video_is_streaming() { + // streaming flag can be false when stopping streaming + if (get_streaming_flag() == true) return 1; + if (streaming_stopped == false) return 2; + return 0; +} + void video_restart_streaming() { if (get_streaming_flag() == true) diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index 6fa00ca42..edc32b6de 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -31,6 +31,13 @@ void video_start_streaming(); */ void video_stop_streaming(); +/** + * @brief Check if the video streaming is active + * + * @return uint8_t 1 if the video streaming is active, 2 if the video streaming is stopping, 0 otherwise + */ +uint8_t video_is_streaming(); + /** * @brief Set the quality factor of the video * diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index b33eb5347..abf962ead 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -168,6 +168,15 @@ func videoStop() { C.jetkvm_video_stop() } +func videoIsStreaming() (bool, error) { + cgoLock.Lock() + defer cgoLock.Unlock() + + isStreaming := C.jetkvm_is_video_streaming() + + return isStreaming == 1, nil +} + func videoLogStatus() string { cgoLock.Lock() defer cgoLock.Unlock() diff --git a/internal/native/cgo_notlinux.go b/internal/native/cgo_notlinux.go index 4602f7133..3d28a106a 100644 --- a/internal/native/cgo_notlinux.go +++ b/internal/native/cgo_notlinux.go @@ -123,6 +123,11 @@ func videoSetEDID(edid string) error { return nil } +func videoIsStreaming() (bool, error) { + panicPlatformNotSupported() + return false, nil +} + func crash() { panicPlatformNotSupported() } diff --git a/internal/native/video.go b/internal/native/video.go index 29a361730..ea70e65ed 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -33,11 +33,21 @@ func (n *Native) setSleepMode(enabled bool) error { } bEnabled := "0" + shouldStopVideo := false if enabled { bEnabled = "1" + + isStreaming, err := n.VideoIsStreaming() + if isStreaming || err != nil { + shouldStopVideo = true + } + } + + if shouldStopVideo { if err := n.VideoStop(); err != nil { return fmt.Errorf("video stop failed, won't enable sleep mode: %w", err) } + // wait few seconds to ensure the video stream is stopped time.Sleep(3 * time.Second) } @@ -165,3 +175,11 @@ func (n *Native) VideoStart() error { videoStart() return nil } + +// VideoIsStreaming checks if the video stream is active. +func (n *Native) VideoIsStreaming() (bool, error) { + n.videoLock.Lock() + defer n.videoLock.Unlock() + + return videoIsStreaming() +} From ae742e3c8f34c6258e9c577d37a2fb66c571f56c Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 10:18:41 +0000 Subject: [PATCH 03/12] apply changes based on Copilot suggestions --- internal/native/cgo/ctrl.c | 4 ++-- internal/native/cgo/ctrl.h | 2 +- internal/native/cgo/video.c | 3 ++- internal/native/cgo/video.h | 6 +++--- internal/native/cgo_linux.go | 6 +++--- internal/native/cgo_notlinux.go | 4 ++-- internal/native/native.go | 8 ++++++++ internal/native/video.go | 33 +++++++++++++++++---------------- 8 files changed, 38 insertions(+), 28 deletions(-) diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index 9a5ce2ddf..ef5eb32a5 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -367,8 +367,8 @@ void jetkvm_video_stop() { video_stop_streaming(); } -uint8_t jetkvm_is_video_streaming() { - return video_is_streaming(); +uint8_t jetkvm_video_get_streaming_status() { + return video_get_streaming_status(); } int jetkvm_video_set_quality_factor(float quality_factor) { diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index 7df6ac435..62009efc0 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -56,7 +56,7 @@ int jetkvm_video_init(float quality_factor); void jetkvm_video_shutdown(); void jetkvm_video_start(); void jetkvm_video_stop(); -uint8_t jetkvm_is_video_streaming(); +uint8_t jetkvm_video_get_streaming_status(); int jetkvm_video_set_quality_factor(float quality_factor); float jetkvm_video_get_quality_factor(); int jetkvm_video_set_edid(const char *edid_hex); diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index ce9491740..5a367377d 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -727,9 +727,10 @@ void video_stop_streaming() log_info("video streaming stopped"); } -uint8_t video_is_streaming() { +uint8_t video_get_streaming_status() { // streaming flag can be false when stopping streaming if (get_streaming_flag() == true) return 1; + // streaming_stopped isn't protected by a mutex, but we won't care about race conditions here if (streaming_stopped == false) return 2; return 0; } diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index edc32b6de..bbfedf40c 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -32,11 +32,11 @@ void video_start_streaming(); void video_stop_streaming(); /** - * @brief Check if the video streaming is active + * @brief Get the streaming status of the video * - * @return uint8_t 1 if the video streaming is active, 2 if the video streaming is stopping, 0 otherwise + * @return VideoStreamingStatus 1 if the video streaming is active, 2 if the video streaming is stopping, 0 otherwise */ -uint8_t video_is_streaming(); +uint8_t video_get_streaming_status(); /** * @brief Set the quality factor of the video diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index abf962ead..b020e745b 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -168,13 +168,13 @@ func videoStop() { C.jetkvm_video_stop() } -func videoIsStreaming() (bool, error) { +func videoGetStreamingStatus() VideoStreamingStatus { cgoLock.Lock() defer cgoLock.Unlock() - isStreaming := C.jetkvm_is_video_streaming() + isStreaming := C.jetkvm_video_get_streaming_status() - return isStreaming == 1, nil + return VideoStreamingStatus(isStreaming) } func videoLogStatus() string { diff --git a/internal/native/cgo_notlinux.go b/internal/native/cgo_notlinux.go index 3d28a106a..9bc77806d 100644 --- a/internal/native/cgo_notlinux.go +++ b/internal/native/cgo_notlinux.go @@ -123,9 +123,9 @@ func videoSetEDID(edid string) error { return nil } -func videoIsStreaming() (bool, error) { +func videoGetStreamingStatus() VideoStreamingStatus { panicPlatformNotSupported() - return false, nil + return VideoStreamingStatusInactive } func crash() { diff --git a/internal/native/native.go b/internal/native/native.go index 61c4b0ac7..3750c0e33 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -40,6 +40,14 @@ type NativeOptions struct { OnNativeRestart func() } +type VideoStreamingStatus uint8 + +const ( + VideoStreamingStatusActive VideoStreamingStatus = 1 + VideoStreamingStatusStopping VideoStreamingStatus = 2 // video is stopping, but not yet stopped + VideoStreamingStatusInactive VideoStreamingStatus = 0 +) + func NewNative(opts NativeOptions) *Native { pid := os.Getpid() nativeSubLogger := nativeLogger.With().Int("pid", pid).Str("scope", "native").Logger() diff --git a/internal/native/video.go b/internal/native/video.go index ea70e65ed..7c277ba3b 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -27,31 +27,32 @@ func isSleepModeSupported() bool { return err == nil } +const sleepModeWaitTimeout = 3 * time.Second + func (n *Native) setSleepMode(enabled bool) error { if !n.sleepModeSupported { return nil } bEnabled := "0" - shouldStopVideo := false if enabled { bEnabled = "1" - isStreaming, err := n.VideoIsStreaming() - if isStreaming || err != nil { - shouldStopVideo = true + switch n.VideoGetStreamingStatus() { + case VideoStreamingStatusActive: + n.l.Info().Msg("stopping video stream to enable sleep mode") + if err := n.VideoStop(); err != nil { + return fmt.Errorf("video stop failed, won't enable sleep mode: %w", err) + } + // wait a few seconds to ensure the video stream is stopped + time.Sleep(sleepModeWaitTimeout) + case VideoStreamingStatusStopping: + n.l.Info().Msg("video stream is stopping, will enable sleep mode in a few seconds") + // wait a few seconds to ensure the video stream is stopped + time.Sleep(sleepModeWaitTimeout) } } - if shouldStopVideo { - if err := n.VideoStop(); err != nil { - return fmt.Errorf("video stop failed, won't enable sleep mode: %w", err) - } - - // wait few seconds to ensure the video stream is stopped - time.Sleep(3 * time.Second) - } - return os.WriteFile(sleepModeFile, []byte(bEnabled), 0644) } @@ -176,10 +177,10 @@ func (n *Native) VideoStart() error { return nil } -// VideoIsStreaming checks if the video stream is active. -func (n *Native) VideoIsStreaming() (bool, error) { +// VideoGetStreamingStatus gets the streaming status of the video. +func (n *Native) VideoGetStreamingStatus() VideoStreamingStatus { n.videoLock.Lock() defer n.videoLock.Unlock() - return videoIsStreaming() + return videoGetStreamingStatus() } From 4244c84a2abf31a4bc9cf27873565a2110670a53 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 10:30:34 +0000 Subject: [PATCH 04/12] fix: wait for video stream to stop before enabling sleep mode --- internal/native/cgo/video.h | 2 +- internal/native/video.go | 27 +++++++++++++++++++-------- webrtc.go | 1 + 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index bbfedf40c..391f7dddb 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -34,7 +34,7 @@ void video_stop_streaming(); /** * @brief Get the streaming status of the video * - * @return VideoStreamingStatus 1 if the video streaming is active, 2 if the video streaming is stopping, 0 otherwise + * @return uint8_t 1 if the video streaming is active, 2 if the video streaming is stopping, 0 otherwise */ uint8_t video_get_streaming_status(); diff --git a/internal/native/video.go b/internal/native/video.go index 7c277ba3b..d65ecc4c6 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -35,21 +35,32 @@ func (n *Native) setSleepMode(enabled bool) error { } bEnabled := "0" + shouldWait := false if enabled { bEnabled = "1" - switch n.VideoGetStreamingStatus() { + switch videoGetStreamingStatus() { case VideoStreamingStatusActive: n.l.Info().Msg("stopping video stream to enable sleep mode") - if err := n.VideoStop(); err != nil { - return fmt.Errorf("video stop failed, won't enable sleep mode: %w", err) - } - // wait a few seconds to ensure the video stream is stopped - time.Sleep(sleepModeWaitTimeout) + videoStop() + shouldWait = true case VideoStreamingStatusStopping: n.l.Info().Msg("video stream is stopping, will enable sleep mode in a few seconds") - // wait a few seconds to ensure the video stream is stopped - time.Sleep(sleepModeWaitTimeout) + shouldWait = true + } + } + + if shouldWait { + start := time.Now() + for { + if n.VideoGetStreamingStatus() == VideoStreamingStatusInactive { + break + } + if time.Since(start) >= sleepModeWaitTimeout { + n.l.Warn().Msg("timed out waiting for video stream to stop") + break + } + time.Sleep(100 * time.Millisecond) } } diff --git a/webrtc.go b/webrtc.go index 76de29145..10c43ddf1 100644 --- a/webrtc.go +++ b/webrtc.go @@ -386,6 +386,7 @@ func newSession(config SessionConfig) (*Session, error) { isConnected = false onActiveSessionsChanged() if decrActiveSessions() == 0 { + scopedLogger.Info().Msg("last session disconnected, stopping video stream") onLastSessionDisconnected() } } From 26872ee0b631928d0cccd6214955bbbb9ea034dc Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 10:34:11 +0000 Subject: [PATCH 05/12] refactor: rewrite waitForVideoStreamingStatus to use ticker instead of time.Sleep --- internal/native/native.go | 12 ++++++++++++ internal/native/video.go | 29 +++++++++++++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/internal/native/native.go b/internal/native/native.go index 3750c0e33..87eebf185 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -48,6 +48,18 @@ const ( VideoStreamingStatusInactive VideoStreamingStatus = 0 ) +func (s VideoStreamingStatus) String() string { + switch s { + case VideoStreamingStatusActive: + return "active" + case VideoStreamingStatusStopping: + return "stopping" + case VideoStreamingStatusInactive: + return "inactive" + } + return "unknown" +} + func NewNative(opts NativeOptions) *Native { pid := os.Getpid() nativeSubLogger := nativeLogger.With().Int("pid", pid).Str("scope", "native").Logger() diff --git a/internal/native/video.go b/internal/native/video.go index d65ecc4c6..b46f2b68c 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -29,6 +29,23 @@ func isSleepModeSupported() bool { const sleepModeWaitTimeout = 3 * time.Second +func (n *Native) waitForVideoStreamingStatus(status VideoStreamingStatus) error { + timeout := time.After(sleepModeWaitTimeout) + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + if videoGetStreamingStatus() == status { + return nil + } + select { + case <-timeout: + return fmt.Errorf("timed out waiting for video streaming status to be %s", status.String()) + case <-ticker.C: + } + } +} + func (n *Native) setSleepMode(enabled bool) error { if !n.sleepModeSupported { return nil @@ -51,16 +68,8 @@ func (n *Native) setSleepMode(enabled bool) error { } if shouldWait { - start := time.Now() - for { - if n.VideoGetStreamingStatus() == VideoStreamingStatusInactive { - break - } - if time.Since(start) >= sleepModeWaitTimeout { - n.l.Warn().Msg("timed out waiting for video stream to stop") - break - } - time.Sleep(100 * time.Millisecond) + if err := n.waitForVideoStreamingStatus(VideoStreamingStatusInactive); err != nil { + return err } } From 5cafbe9fd9da6751d2b7a272729dc235f98609e4 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 11:22:19 +0000 Subject: [PATCH 06/12] fix: do not restart video streaming if it's already stopped --- internal/native/cgo/video.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 5a367377d..ce2ce2d77 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -700,6 +700,16 @@ void video_start_streaming() streaming_thread = new_thread; } +bool wait_for_streaming_stopped() +{ + int attempts = 0; + while (!streaming_stopped && attempts < 30) { + usleep(100000); // 100ms + attempts++; + } + return streaming_stopped; +} + void video_stop_streaming() { if (streaming_thread == NULL) { @@ -711,12 +721,7 @@ void video_stop_streaming() set_streaming_flag(false); log_info("waiting for video streaming thread to exit"); - int attempts = 0; - while (!streaming_stopped && attempts < 30) { - usleep(100000); // 100ms - attempts++; - } - if (!streaming_stopped) { + if (!wait_for_streaming_stopped()) { log_error("video streaming thread did not exit after 30s"); } @@ -737,11 +742,22 @@ uint8_t video_get_streaming_status() { void video_restart_streaming() { - if (get_streaming_flag() == true) + uint8_t streaming_status = video_get_streaming_status(); + if (streaming_status == 0) { - log_info("restarting video streaming"); + log_info("will not restart video streaming because it's stopped"); + return; + } + + if (streaming_status == 2) { video_stop_streaming(); } + + if (!wait_for_streaming_stopped()) { + log_error("video streaming did not stop after 30s"); + return ; + } + video_start_streaming(); } @@ -812,7 +828,7 @@ void *run_detect_format(void *arg) if (should_restart) { log_info("restarting video streaming due to format change"); - video_restart_streaming(); + video_restart_streaming(false); } } From ed553884d084c6e69cc709252d923e549212deda Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 11:27:27 +0000 Subject: [PATCH 07/12] fix: use mutex to protect streaming_stopped variable --- internal/native/cgo/video.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index ce2ce2d77..d3c373100 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -349,7 +349,10 @@ static void *venc_read_stream(void *arg) } uint32_t detected_width, detected_height; -bool detected_signal = false, streaming_flag = false, streaming_stopped = true; +bool detected_signal = false, streaming_flag = false; + +bool streaming_stopped = true; +pthread_mutex_t streaming_stopped_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t *streaming_thread = NULL; pthread_mutex_t streaming_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -372,6 +375,21 @@ void set_streaming_flag(bool flag) pthread_mutex_unlock(&streaming_mutex); } +void set_streaming_stopped(bool stopped) +{ + pthread_mutex_lock(&streaming_stopped_mutex); + streaming_stopped = stopped; + pthread_mutex_unlock(&streaming_stopped_mutex); +} + +bool get_streaming_stopped() +{ + pthread_mutex_lock(&streaming_stopped_mutex); + bool stopped = streaming_stopped; + pthread_mutex_unlock(&streaming_stopped_mutex); + return stopped; +} + void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename) { FILE *file = fopen(filename, "wb"); @@ -385,8 +403,7 @@ void *run_video_stream(void *arg) log_info("running video stream"); - streaming_stopped = false; - + set_streaming_stopped(false); while (streaming_flag) { if (detected_signal == false) @@ -635,7 +652,7 @@ void *run_video_stream(void *arg) log_info("video stream thread exiting"); - streaming_stopped = true; + set_streaming_stopped(true); return NULL; } @@ -671,9 +688,10 @@ void video_start_streaming() log_info("starting video streaming"); if (streaming_thread != NULL) { - if (streaming_stopped == true) { + bool stopped = get_streaming_stopped(); + if (stopped == true) { log_error("video streaming already stopped but streaming_thread is not NULL"); - assert(streaming_stopped == true); + assert(stopped == true); } log_warn("video streaming already started"); return; @@ -703,11 +721,14 @@ void video_start_streaming() bool wait_for_streaming_stopped() { int attempts = 0; - while (!streaming_stopped && attempts < 30) { + while (attempts < 30) { + if (get_streaming_stopped() == true) { + return true; + } usleep(100000); // 100ms attempts++; } - return streaming_stopped; + return false; } void video_stop_streaming() From 06dbead5cbe11cef79126fe6c46001ec839eb8bd Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 11:50:40 +0000 Subject: [PATCH 08/12] fix(cgo): update process title when video state changes --- internal/native/cgo/ctrl.c | 8 ++++++++ internal/native/cgo/ctrl.h | 2 ++ internal/native/cgo/video.c | 6 ++++++ internal/native/cgo_linux.go | 1 + internal/native/chan.go | 1 + internal/native/grpc_servermethods.go | 6 ------ internal/native/server.go | 20 ++++++++++++++++++++ internal/native/video.go | 11 ++++++----- 8 files changed, 44 insertions(+), 11 deletions(-) diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index ef5eb32a5..62d2c8b92 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -59,6 +59,7 @@ const char *jetkvm_ui_event_code_to_name(int code) { void video_report_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second) { + state.streaming = video_get_streaming_status(); state.ready = ready; state.error = error; state.width = width; @@ -69,6 +70,13 @@ void video_report_format(bool ready, const char *error, u_int16_t width, u_int16 } } +void video_send_format_report() { + state.streaming = video_get_streaming_status(); + if (video_state_handler != NULL) { + (*video_state_handler)(&state); + } +} + int video_send_frame(const uint8_t *frame, ssize_t len) { if (video_handler != NULL) { diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index 62009efc0..59f9e4cdf 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -8,6 +8,7 @@ typedef struct { bool ready; + uint8_t streaming; const char *error; u_int16_t width; u_int16_t height; @@ -65,6 +66,7 @@ char *jetkvm_video_log_status(); jetkvm_video_state_t *jetkvm_video_get_status(); void video_report_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second); +void video_send_format_report(); int video_send_frame(const uint8_t *frame, ssize_t len); diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index d3c373100..e9d694407 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -654,6 +654,8 @@ void *run_video_stream(void *arg) set_streaming_stopped(true); + video_send_format_report(); + return NULL; } @@ -716,6 +718,8 @@ void video_start_streaming() // Only set streaming_thread after successful creation streaming_thread = new_thread; + + video_send_format_report(); } bool wait_for_streaming_stopped() @@ -751,6 +755,8 @@ void video_stop_streaming() streaming_thread = NULL; log_info("video streaming stopped"); + + video_send_format_report(); } uint8_t video_get_streaming_status() { diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index b020e745b..dcd25e42a 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -57,6 +57,7 @@ var ( func jetkvm_go_video_state_handler(state *C.jetkvm_video_state_t) { videoState := VideoState{ Ready: bool(state.ready), + Streaming: VideoStreamingStatus(state.streaming), Error: C.GoString(state.error), Width: int(state.width), Height: int(state.height), diff --git a/internal/native/chan.go b/internal/native/chan.go index 4162f2605..cd6d07af1 100644 --- a/internal/native/chan.go +++ b/internal/native/chan.go @@ -28,6 +28,7 @@ func (n *Native) handleVideoFrameChan() { func (n *Native) handleVideoStateChan() { for { state := <-videoStateChan + n.onVideoStateChange(state) } } diff --git a/internal/native/grpc_servermethods.go b/internal/native/grpc_servermethods.go index cc16dfd10..c1dea54fc 100644 --- a/internal/native/grpc_servermethods.go +++ b/internal/native/grpc_servermethods.go @@ -70,9 +70,6 @@ func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.Vid } func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { - procPrefix = "jetkvm: [native]" - setProcTitle(lastProcTitle) - if err := s.native.VideoStop(); err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -80,9 +77,6 @@ func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, e } func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { - procPrefix = "jetkvm: [native+video]" - setProcTitle(lastProcTitle) - if err := s.native.VideoStart(); err != nil { return nil, status.Error(codes.Internal, err.Error()) } diff --git a/internal/native/server.go b/internal/native/server.go index ae983159d..7fa6c9879 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -54,6 +54,23 @@ func monitorCrashSignal(ctx context.Context, logger *zerolog.Logger, nativeInsta } } +func updateProcessTitle(state *VideoState) { + if state == nil { + procPrefix = "jetkvm: [native]" + } else { + status := "active" + if !state.Ready { + status = "starting" + } else if state.Error != "" { + status = state.Error + } else { + status = fmt.Sprintf("%s,%dx%d,%.1ffps", state.Streaming.String(), state.Width, state.Height, state.FramePerSecond) + } + procPrefix = fmt.Sprintf("jetkvm: [native+video{%s}]", status) + } + setProcTitle(lastProcTitle) +} + // RunNativeProcess runs the native process mode func RunNativeProcess(binaryName string) { appCtx, appCtxCancel := context.WithCancel(context.Background()) @@ -82,6 +99,9 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to write frame to video stream socket") } } + nativeOptions.OnVideoStateChange = func(state VideoState) { + updateProcessTitle(&state) + } // Create native instance nativeInstance := NewNative(*nativeOptions) diff --git a/internal/native/video.go b/internal/native/video.go index b46f2b68c..c360441b2 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -15,11 +15,12 @@ var extraLockTimeout = 5 * time.Second // VideoState is the state of the video stream. type VideoState struct { - Ready bool `json:"ready"` - Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range - Width int `json:"width"` - Height int `json:"height"` - FramePerSecond float64 `json:"fps"` + Ready bool `json:"ready"` + Streaming VideoStreamingStatus `json:"streaming"` + Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range + Width int `json:"width"` + Height int `json:"height"` + FramePerSecond float64 `json:"fps"` } func isSleepModeSupported() bool { From 1a3582c8770ccb383ad9b29bffe54c4b8c625a48 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 12:03:01 +0000 Subject: [PATCH 09/12] fix: update process title when video state changes --- internal/native/cgo/video.c | 12 ++++++------ internal/native/server.go | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index e9d694407..e0f550fa6 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -373,6 +373,8 @@ void set_streaming_flag(bool flag) pthread_mutex_lock(&streaming_mutex); streaming_flag = flag; pthread_mutex_unlock(&streaming_mutex); + + video_send_format_report(); } void set_streaming_stopped(bool stopped) @@ -380,6 +382,8 @@ void set_streaming_stopped(bool stopped) pthread_mutex_lock(&streaming_stopped_mutex); streaming_stopped = stopped; pthread_mutex_unlock(&streaming_stopped_mutex); + + video_send_format_report(); } bool get_streaming_stopped() @@ -545,6 +549,8 @@ void *run_video_stream(void *arg) uint32_t num = 0; VIDEO_FRAME_INFO_S stFrame; + + while (streaming_flag) { FD_ZERO(&fds); @@ -654,8 +660,6 @@ void *run_video_stream(void *arg) set_streaming_stopped(true); - video_send_format_report(); - return NULL; } @@ -718,8 +722,6 @@ void video_start_streaming() // Only set streaming_thread after successful creation streaming_thread = new_thread; - - video_send_format_report(); } bool wait_for_streaming_stopped() @@ -755,8 +757,6 @@ void video_stop_streaming() streaming_thread = NULL; log_info("video streaming stopped"); - - video_send_format_report(); } uint8_t video_get_streaming_status() { diff --git a/internal/native/server.go b/internal/native/server.go index 7fa6c9879..62c2a74fc 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -59,8 +59,10 @@ func updateProcessTitle(state *VideoState) { procPrefix = "jetkvm: [native]" } else { status := "active" - if !state.Ready { - status = "starting" + if state.Streaming == VideoStreamingStatusInactive { + status = "inactive" + } else if !state.Ready { + status = "not ready" } else if state.Error != "" { status = state.Error } else { From 3f9280cec5f41a5e0586d812703fb2b92dac1e91 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 12:04:21 +0000 Subject: [PATCH 10/12] fix: ineffectual assignment to status (ineffassign) --- internal/native/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/native/server.go b/internal/native/server.go index 62c2a74fc..f52289e8c 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -58,7 +58,7 @@ func updateProcessTitle(state *VideoState) { if state == nil { procPrefix = "jetkvm: [native]" } else { - status := "active" + var status string if state.Streaming == VideoStreamingStatusInactive { status = "inactive" } else if !state.Ready { From 0fac7f4116b687fe373c161f6b6c640d536499ca Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 12:13:01 +0000 Subject: [PATCH 11/12] fix based on copilot suggestions --- internal/native/cgo/video.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index e0f550fa6..92cd6f032 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -729,11 +729,13 @@ bool wait_for_streaming_stopped() int attempts = 0; while (attempts < 30) { if (get_streaming_stopped() == true) { + log_info("video streaming stopped after %d attempts", attempts); return true; } usleep(100000); // 100ms attempts++; } + log_error("video streaming did not stop after 3s"); return false; } @@ -748,9 +750,7 @@ void video_stop_streaming() set_streaming_flag(false); log_info("waiting for video streaming thread to exit"); - if (!wait_for_streaming_stopped()) { - log_error("video streaming thread did not exit after 30s"); - } + wait_for_streaming_stopped(); pthread_join(*streaming_thread, NULL); free(streaming_thread); @@ -763,7 +763,7 @@ uint8_t video_get_streaming_status() { // streaming flag can be false when stopping streaming if (get_streaming_flag() == true) return 1; // streaming_stopped isn't protected by a mutex, but we won't care about race conditions here - if (streaming_stopped == false) return 2; + if (get_streaming_stopped() == false) return 2; return 0; } @@ -781,7 +781,6 @@ void video_restart_streaming() } if (!wait_for_streaming_stopped()) { - log_error("video streaming did not stop after 30s"); return ; } @@ -855,7 +854,7 @@ void *run_detect_format(void *arg) if (should_restart) { log_info("restarting video streaming due to format change"); - video_restart_streaming(false); + video_restart_streaming(); } } From 5174eb94b844deb0dc3a34cd18d54b71b3dd0730 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 21 Nov 2025 12:18:14 +0000 Subject: [PATCH 12/12] fix based on copilot suggestions --- internal/native/cgo/video.c | 3 +-- internal/native/video.go | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 92cd6f032..9107c70ef 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -762,7 +762,6 @@ void video_stop_streaming() uint8_t video_get_streaming_status() { // streaming flag can be false when stopping streaming if (get_streaming_flag() == true) return 1; - // streaming_stopped isn't protected by a mutex, but we won't care about race conditions here if (get_streaming_stopped() == false) return 2; return 0; } @@ -781,7 +780,7 @@ void video_restart_streaming() } if (!wait_for_streaming_stopped()) { - return ; + return; } video_start_streaming(); diff --git a/internal/native/video.go b/internal/native/video.go index c360441b2..176511c69 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -47,6 +47,7 @@ func (n *Native) waitForVideoStreamingStatus(status VideoStreamingStatus) error } } +// before calling this function, make sure to lock n.videoLock func (n *Native) setSleepMode(enabled bool) error { if !n.sleepModeSupported { return nil