From a1594d6e111a51941d8992eb2e4368d0a4ba7b3f Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Tue, 12 May 2026 17:20:57 +0000 Subject: [PATCH 01/10] initial commit - works! --- .devcontainer/docker/devcontainer-lock.json | 9 + internal/native/cgo/ctrl.c | 4 + internal/native/cgo/ctrl.h | 10 + internal/native/cgo/video.c | 152 ++++++ internal/native/cgo/video.h | 14 + internal/native/cgo_linux.go | 17 + internal/native/cgo_notlinux.go | 5 + internal/native/empty.go | 6 + internal/native/grpc_clientmethods.go | 8 + internal/native/grpc_servermethods.go | 8 + internal/native/interface.go | 1 + internal/native/proto/native.pb.go | 540 +++++++++++--------- internal/native/proto/native.proto | 5 + internal/native/proto/native_grpc.pb.go | 42 +- internal/native/proxy.go | 11 + internal/native/video.go | 8 + mqtt.go | 61 ++- mqtt_commands.go | 15 + mqtt_discovery.go | 36 ++ mqtt_publish.go | 18 + ui/localization/messages/en.json | 2 + ui/src/routes/devices.$id.settings.mqtt.tsx | 17 + 22 files changed, 731 insertions(+), 258 deletions(-) create mode 100644 .devcontainer/docker/devcontainer-lock.json diff --git a/.devcontainer/docker/devcontainer-lock.json b/.devcontainer/docker/devcontainer-lock.json new file mode 100644 index 000000000..96d86886e --- /dev/null +++ b/.devcontainer/docker/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "1.7.1", + "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6", + "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6" + } + } +} diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index 7d64ab83a..b1a97753c 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -437,6 +437,10 @@ void jetkvm_video_shutdown() { video_shutdown(); } +int jetkvm_video_capture_jpeg(uint8_t **out_buf, size_t *out_len) { + return video_capture_jpeg(out_buf, out_len); +} + void jetkvm_crash() { // let's call a function that will crash the program int* p = 0; diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index ab0a08818..5e3a274e6 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -71,6 +71,16 @@ void video_report_format(bool ready, const char *error, u_int16_t width, u_int16 void video_send_format_report(); int video_send_frame(const uint8_t *frame, ssize_t len); +/** + * @brief Capture a single JPEG frame from the live video stream. + * + * On success, *out_buf is set to a malloc'd buffer containing JPEG bytes and + * *out_len to its length. The caller must free *out_buf with free(). + * + * @return 0 on success, non-zero on error. + */ +int jetkvm_video_capture_jpeg(uint8_t **out_buf, size_t *out_len); + #endif //VIDEO_DAEMON_CTRL_H diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 0be09df56..99138a36a 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -38,6 +38,7 @@ int sub_dev_fd = -1; #define VENC_CHANNEL 0 +#define JPEG_CHANNEL 1 MB_POOL memPool = MB_INVALID_POOLID; bool sleep_mode_available = false; @@ -45,6 +46,15 @@ bool should_exit = false; float quality_factor = 1.0f; int codec_type = 0; +// --- JPEG snapshot state --- +static pthread_mutex_t jpeg_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t jpeg_cond = PTHREAD_COND_INITIALIZER; +static volatile bool jpeg_requested = false; +static bool jpeg_done = false; +static int jpeg_result = 0; +static uint8_t *jpeg_buf = NULL; +static size_t jpeg_buf_len = 0; + static void *venc_read_stream(void *arg); RK_U64 get_us() @@ -411,6 +421,143 @@ bool get_streaming_stopped() return stopped; } +// do_jpeg_capture – called from the streaming loop when jpeg_requested is set. +// Creates VENC JPEG_CHANNEL, encodes one frame, stores result, signals waiter. +static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t height) +{ + int ret; + + VENC_CHN_ATTR_S attr; + memset(&attr, 0, sizeof(attr)); + attr.stRcAttr.enRcMode = VENC_RC_MODE_MJPEGFIXQP; + attr.stRcAttr.stMjpegFixQp.u32Qfactor = 90; + attr.stVencAttr.enType = RK_VIDEO_ID_MJPEG; + attr.stVencAttr.enPixelFormat = RK_FMT_YUV422_YUYV; + attr.stVencAttr.u32PicWidth = width; + attr.stVencAttr.u32PicHeight = height; + attr.stVencAttr.u32VirWidth = RK_ALIGN_16(width); + attr.stVencAttr.u32VirHeight = RK_ALIGN_16(height); + attr.stVencAttr.u32StreamBufCnt = 2; + attr.stVencAttr.u32BufSize = width * height * 2; + + ret = RK_MPI_VENC_CreateChn(JPEG_CHANNEL, &attr); + if (ret != RK_SUCCESS) { + log_error("JPEG capture: RK_MPI_VENC_CreateChn failed: %d", ret); + goto signal_error; + } + + VENC_RECV_PIC_PARAM_S recv; + memset(&recv, 0, sizeof(recv)); + recv.s32RecvPicNum = 1; + ret = RK_MPI_VENC_StartRecvFrame(JPEG_CHANNEL, &recv); + if (ret != RK_SUCCESS) { + log_error("JPEG capture: RK_MPI_VENC_StartRecvFrame failed: %d", ret); + RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); + goto signal_error; + } + + ret = RK_MPI_VENC_SendFrame(JPEG_CHANNEL, frame, 2000); + if (ret != RK_SUCCESS) { + log_error("JPEG capture: RK_MPI_VENC_SendFrame failed: %d", ret); + RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); + RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); + goto signal_error; + } + + VENC_STREAM_S stream; + stream.pstPack = malloc(sizeof(VENC_PACK_S)); + if (!stream.pstPack) { + log_error("JPEG capture: malloc failed"); + RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); + RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); + ret = -ENOMEM; + goto signal_error; + } + + ret = RK_MPI_VENC_GetStream(JPEG_CHANNEL, &stream, 2000); + if (ret == RK_SUCCESS) { + void *data = RK_MPI_MB_Handle2VirAddr(stream.pstPack->pMbBlk); + size_t len = stream.pstPack->u32Len; + uint8_t *buf = malloc(len); + if (buf) { + memcpy(buf, data, len); + pthread_mutex_lock(&jpeg_mutex); + jpeg_buf = buf; + jpeg_buf_len = len; + jpeg_result = 0; + jpeg_done = true; + jpeg_requested = false; + pthread_cond_signal(&jpeg_cond); + pthread_mutex_unlock(&jpeg_mutex); + } else { + log_error("JPEG capture: malloc for output failed"); + ret = -ENOMEM; + } + RK_MPI_VENC_ReleaseStream(JPEG_CHANNEL, &stream); + } else { + log_error("JPEG capture: RK_MPI_VENC_GetStream failed: %d", ret); + } + free(stream.pstPack); + RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); + RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); + + if (ret != 0 && !(ret == RK_SUCCESS && jpeg_done)) { + goto signal_error; + } + return; + +signal_error: + pthread_mutex_lock(&jpeg_mutex); + jpeg_result = (ret != 0) ? ret : -1; + jpeg_done = true; + jpeg_requested = false; + pthread_cond_signal(&jpeg_cond); + pthread_mutex_unlock(&jpeg_mutex); +} + +int video_capture_jpeg(uint8_t **out_buf, size_t *out_len) +{ + if (!detected_signal) + return -ENODEV; + if (!get_streaming_flag()) + return -ENODATA; + + pthread_mutex_lock(&jpeg_mutex); + + if (jpeg_requested) { + pthread_mutex_unlock(&jpeg_mutex); + return -EBUSY; + } + + jpeg_requested = true; + jpeg_done = false; + jpeg_result = 0; + jpeg_buf = NULL; + jpeg_buf_len = 0; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 5; + + while (!jpeg_done) { + int err = pthread_cond_timedwait(&jpeg_cond, &jpeg_mutex, &ts); + if (err == ETIMEDOUT) { + jpeg_requested = false; + pthread_mutex_unlock(&jpeg_mutex); + return -ETIMEDOUT; + } + } + + int result = jpeg_result; + if (result == 0) { + *out_buf = jpeg_buf; + *out_len = jpeg_buf_len; + jpeg_buf = NULL; + } + pthread_mutex_unlock(&jpeg_mutex); + return result; +} + void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename) { FILE *file = fopen(filename, "wb"); @@ -635,6 +782,11 @@ void *run_video_stream(void *arg) num++; + // Capture JPEG if requested (before returning the V4L2 buffer) + if (jpeg_requested) { + do_jpeg_capture(&stFrame, width, height); + } + if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0) log_error("failure VIDIOC_QBUF: %s", strerror(errno)); } diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index c3b382660..68169faa2 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -62,4 +62,18 @@ void video_set_codec_type(int type); */ int video_get_codec_type(); +/** + * @brief Capture a single JPEG frame from the live video stream. + * + * On success, *out_buf is set to a malloc'd buffer containing the JPEG bytes + * and *out_len is set to its length. The caller is responsible for freeing + * *out_buf with free(). + * + * @param out_buf Output pointer for the JPEG buffer (caller must free). + * @param out_len Output pointer for the buffer length. + * @return 0 on success, non-zero on error (ENODEV = no signal, ENODATA = not + * streaming, EBUSY = capture already in progress, ETIMEDOUT = timeout). + */ +int video_capture_jpeg(uint8_t **out_buf, size_t *out_len); + #endif //VIDEO_DAEMON_VIDEO_H diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index 0a41e747d..61ea6d9c6 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -431,3 +431,20 @@ func videoSetEDID(edid string) error { func crash() { C.jetkvm_crash() } + +func videoCaptureJPEG() ([]byte, error) { + cgoLock.Lock() + defer cgoLock.Unlock() + + var outBuf *C.uint8_t + var outLen C.size_t + + ret := C.jetkvm_video_capture_jpeg(&outBuf, &outLen) + if ret != 0 { + return nil, fmt.Errorf("video_capture_jpeg failed with code %d", int(ret)) + } + defer C.free(unsafe.Pointer(outBuf)) + + buf := C.GoBytes(unsafe.Pointer(outBuf), C.int(outLen)) + return buf, nil +} diff --git a/internal/native/cgo_notlinux.go b/internal/native/cgo_notlinux.go index 383f99297..e185ba1b3 100644 --- a/internal/native/cgo_notlinux.go +++ b/internal/native/cgo_notlinux.go @@ -141,3 +141,8 @@ func videoGetStreamingStatus() VideoStreamingStatus { func crash() { panicPlatformNotSupported() } + +func videoCaptureJPEG() ([]byte, error) { + panicPlatformNotSupported() + return nil, nil +} diff --git a/internal/native/empty.go b/internal/native/empty.go index 25b3e3dfa..9e9b8d039 100644 --- a/internal/native/empty.go +++ b/internal/native/empty.go @@ -1,5 +1,7 @@ package native +import "fmt" + type EmptyNativeInterface struct { } @@ -117,3 +119,7 @@ func (e *EmptyNativeInterface) SwitchToScreenIf(screenName string, shouldSwitch func (e *EmptyNativeInterface) SwitchToScreenIfDifferent(screenName string) {} func (e *EmptyNativeInterface) DoNotUseThisIsForCrashTestingOnly() {} + +func (e *EmptyNativeInterface) VideoCaptureJPEG() ([]byte, error) { + return nil, fmt.Errorf("not supported in failsafe mode") +} diff --git a/internal/native/grpc_clientmethods.go b/internal/native/grpc_clientmethods.go index bfd00c54c..6b48bea5d 100644 --- a/internal/native/grpc_clientmethods.go +++ b/internal/native/grpc_clientmethods.go @@ -223,3 +223,11 @@ func (c *GRPCClient) SwitchToScreenIfDifferent(screenName string) { func (c *GRPCClient) DoNotUseThisIsForCrashTestingOnly() { _, _ = c.client.DoNotUseThisIsForCrashTestingOnly(context.Background(), &pb.Empty{}) } + +func (c *GRPCClient) VideoCaptureJPEG() ([]byte, error) { + resp, err := c.client.VideoGetJPEG(context.Background(), &pb.Empty{}) + if err != nil { + return nil, err + } + return resp.Data, nil +} diff --git a/internal/native/grpc_servermethods.go b/internal/native/grpc_servermethods.go index 439ab0038..80549352e 100644 --- a/internal/native/grpc_servermethods.go +++ b/internal/native/grpc_servermethods.go @@ -237,3 +237,11 @@ func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req s.native.DoNotUseThisIsForCrashTestingOnly() return &pb.Empty{}, nil } + +func (s *grpcServer) VideoGetJPEG(ctx context.Context, req *pb.Empty) (*pb.VideoGetJPEGResponse, error) { + data, err := s.native.VideoCaptureJPEG() + if err != nil { + return nil, err + } + return &pb.VideoGetJPEGResponse{Data: data}, nil +} diff --git a/internal/native/interface.go b/internal/native/interface.go index 2f3392001..ee7d44351 100644 --- a/internal/native/interface.go +++ b/internal/native/interface.go @@ -35,4 +35,5 @@ type NativeInterface interface { SwitchToScreenIf(screenName string, shouldSwitch []string) SwitchToScreenIfDifferent(screenName string) DoNotUseThisIsForCrashTestingOnly() + VideoCaptureJPEG() ([]byte, error) } diff --git a/internal/native/proto/native.pb.go b/internal/native/proto/native.pb.go index af2bc57ae..9f29b63e9 100644 --- a/internal/native/proto/native.pb.go +++ b/internal/native/proto/native.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v3.21.12 +// protoc v5.28.3 // source: internal/native/proto/native.proto package proto @@ -230,6 +230,50 @@ func (x *VideoState) GetFramePerSecond() float64 { return 0 } +type VideoGetJPEGResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoGetJPEGResponse) Reset() { + *x = VideoGetJPEGResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoGetJPEGResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoGetJPEGResponse) ProtoMessage() {} + +func (x *VideoGetJPEGResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoGetJPEGResponse.ProtoReflect.Descriptor instead. +func (*VideoGetJPEGResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{4} +} + +func (x *VideoGetJPEGResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + type VideoSetSleepModeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` @@ -239,7 +283,7 @@ type VideoSetSleepModeRequest struct { func (x *VideoSetSleepModeRequest) Reset() { *x = VideoSetSleepModeRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[4] + mi := &file_internal_native_proto_native_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -251,7 +295,7 @@ func (x *VideoSetSleepModeRequest) String() string { func (*VideoSetSleepModeRequest) ProtoMessage() {} func (x *VideoSetSleepModeRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[4] + mi := &file_internal_native_proto_native_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -264,7 +308,7 @@ func (x *VideoSetSleepModeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSetSleepModeRequest.ProtoReflect.Descriptor instead. func (*VideoSetSleepModeRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{4} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{5} } func (x *VideoSetSleepModeRequest) GetEnabled() bool { @@ -283,7 +327,7 @@ type VideoGetSleepModeResponse struct { func (x *VideoGetSleepModeResponse) Reset() { *x = VideoGetSleepModeResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[5] + mi := &file_internal_native_proto_native_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -295,7 +339,7 @@ func (x *VideoGetSleepModeResponse) String() string { func (*VideoGetSleepModeResponse) ProtoMessage() {} func (x *VideoGetSleepModeResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[5] + mi := &file_internal_native_proto_native_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -308,7 +352,7 @@ func (x *VideoGetSleepModeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoGetSleepModeResponse.ProtoReflect.Descriptor instead. func (*VideoGetSleepModeResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{5} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{6} } func (x *VideoGetSleepModeResponse) GetEnabled() bool { @@ -327,7 +371,7 @@ type VideoSleepModeSupportedResponse struct { func (x *VideoSleepModeSupportedResponse) Reset() { *x = VideoSleepModeSupportedResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[6] + mi := &file_internal_native_proto_native_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -339,7 +383,7 @@ func (x *VideoSleepModeSupportedResponse) String() string { func (*VideoSleepModeSupportedResponse) ProtoMessage() {} func (x *VideoSleepModeSupportedResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[6] + mi := &file_internal_native_proto_native_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -352,7 +396,7 @@ func (x *VideoSleepModeSupportedResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSleepModeSupportedResponse.ProtoReflect.Descriptor instead. func (*VideoSleepModeSupportedResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{6} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{7} } func (x *VideoSleepModeSupportedResponse) GetSupported() bool { @@ -371,7 +415,7 @@ type VideoSetQualityFactorRequest struct { func (x *VideoSetQualityFactorRequest) Reset() { *x = VideoSetQualityFactorRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[7] + mi := &file_internal_native_proto_native_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -383,7 +427,7 @@ func (x *VideoSetQualityFactorRequest) String() string { func (*VideoSetQualityFactorRequest) ProtoMessage() {} func (x *VideoSetQualityFactorRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[7] + mi := &file_internal_native_proto_native_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -396,7 +440,7 @@ func (x *VideoSetQualityFactorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSetQualityFactorRequest.ProtoReflect.Descriptor instead. func (*VideoSetQualityFactorRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{7} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{8} } func (x *VideoSetQualityFactorRequest) GetFactor() float64 { @@ -415,7 +459,7 @@ type VideoGetQualityFactorResponse struct { func (x *VideoGetQualityFactorResponse) Reset() { *x = VideoGetQualityFactorResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[8] + mi := &file_internal_native_proto_native_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +471,7 @@ func (x *VideoGetQualityFactorResponse) String() string { func (*VideoGetQualityFactorResponse) ProtoMessage() {} func (x *VideoGetQualityFactorResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[8] + mi := &file_internal_native_proto_native_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -440,7 +484,7 @@ func (x *VideoGetQualityFactorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoGetQualityFactorResponse.ProtoReflect.Descriptor instead. func (*VideoGetQualityFactorResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{8} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{9} } func (x *VideoGetQualityFactorResponse) GetFactor() float64 { @@ -459,7 +503,7 @@ type VideoSetCodecTypeRequest struct { func (x *VideoSetCodecTypeRequest) Reset() { *x = VideoSetCodecTypeRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[9] + mi := &file_internal_native_proto_native_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -471,7 +515,7 @@ func (x *VideoSetCodecTypeRequest) String() string { func (*VideoSetCodecTypeRequest) ProtoMessage() {} func (x *VideoSetCodecTypeRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[9] + mi := &file_internal_native_proto_native_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -484,7 +528,7 @@ func (x *VideoSetCodecTypeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSetCodecTypeRequest.ProtoReflect.Descriptor instead. func (*VideoSetCodecTypeRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{9} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{10} } func (x *VideoSetCodecTypeRequest) GetCodecType() int32 { @@ -503,7 +547,7 @@ type VideoGetCodecTypeResponse struct { func (x *VideoGetCodecTypeResponse) Reset() { *x = VideoGetCodecTypeResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[10] + mi := &file_internal_native_proto_native_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -515,7 +559,7 @@ func (x *VideoGetCodecTypeResponse) String() string { func (*VideoGetCodecTypeResponse) ProtoMessage() {} func (x *VideoGetCodecTypeResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[10] + mi := &file_internal_native_proto_native_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -528,7 +572,7 @@ func (x *VideoGetCodecTypeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoGetCodecTypeResponse.ProtoReflect.Descriptor instead. func (*VideoGetCodecTypeResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{10} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{11} } func (x *VideoGetCodecTypeResponse) GetCodecType() int32 { @@ -547,7 +591,7 @@ type VideoSetEDIDRequest struct { func (x *VideoSetEDIDRequest) Reset() { *x = VideoSetEDIDRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[11] + mi := &file_internal_native_proto_native_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -559,7 +603,7 @@ func (x *VideoSetEDIDRequest) String() string { func (*VideoSetEDIDRequest) ProtoMessage() {} func (x *VideoSetEDIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[11] + mi := &file_internal_native_proto_native_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -572,7 +616,7 @@ func (x *VideoSetEDIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSetEDIDRequest.ProtoReflect.Descriptor instead. func (*VideoSetEDIDRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{11} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{12} } func (x *VideoSetEDIDRequest) GetEdid() string { @@ -591,7 +635,7 @@ type VideoGetEDIDResponse struct { func (x *VideoGetEDIDResponse) Reset() { *x = VideoGetEDIDResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[12] + mi := &file_internal_native_proto_native_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -603,7 +647,7 @@ func (x *VideoGetEDIDResponse) String() string { func (*VideoGetEDIDResponse) ProtoMessage() {} func (x *VideoGetEDIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[12] + mi := &file_internal_native_proto_native_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -616,7 +660,7 @@ func (x *VideoGetEDIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoGetEDIDResponse.ProtoReflect.Descriptor instead. func (*VideoGetEDIDResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{12} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{13} } func (x *VideoGetEDIDResponse) GetEdid() string { @@ -635,7 +679,7 @@ type VideoLogStatusResponse struct { func (x *VideoLogStatusResponse) Reset() { *x = VideoLogStatusResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[13] + mi := &file_internal_native_proto_native_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -647,7 +691,7 @@ func (x *VideoLogStatusResponse) String() string { func (*VideoLogStatusResponse) ProtoMessage() {} func (x *VideoLogStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[13] + mi := &file_internal_native_proto_native_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -660,7 +704,7 @@ func (x *VideoLogStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoLogStatusResponse.ProtoReflect.Descriptor instead. func (*VideoLogStatusResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{13} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{14} } func (x *VideoLogStatusResponse) GetStatus() string { @@ -679,7 +723,7 @@ type GetLVGLVersionResponse struct { func (x *GetLVGLVersionResponse) Reset() { *x = GetLVGLVersionResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[14] + mi := &file_internal_native_proto_native_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -691,7 +735,7 @@ func (x *GetLVGLVersionResponse) String() string { func (*GetLVGLVersionResponse) ProtoMessage() {} func (x *GetLVGLVersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[14] + mi := &file_internal_native_proto_native_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -704,7 +748,7 @@ func (x *GetLVGLVersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLVGLVersionResponse.ProtoReflect.Descriptor instead. func (*GetLVGLVersionResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{14} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{15} } func (x *GetLVGLVersionResponse) GetVersion() string { @@ -723,7 +767,7 @@ type UIObjHideRequest struct { func (x *UIObjHideRequest) Reset() { *x = UIObjHideRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[15] + mi := &file_internal_native_proto_native_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -735,7 +779,7 @@ func (x *UIObjHideRequest) String() string { func (*UIObjHideRequest) ProtoMessage() {} func (x *UIObjHideRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[15] + mi := &file_internal_native_proto_native_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -748,7 +792,7 @@ func (x *UIObjHideRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjHideRequest.ProtoReflect.Descriptor instead. func (*UIObjHideRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{15} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{16} } func (x *UIObjHideRequest) GetObjName() string { @@ -767,7 +811,7 @@ type UIObjHideResponse struct { func (x *UIObjHideResponse) Reset() { *x = UIObjHideResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[16] + mi := &file_internal_native_proto_native_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -779,7 +823,7 @@ func (x *UIObjHideResponse) String() string { func (*UIObjHideResponse) ProtoMessage() {} func (x *UIObjHideResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[16] + mi := &file_internal_native_proto_native_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -792,7 +836,7 @@ func (x *UIObjHideResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjHideResponse.ProtoReflect.Descriptor instead. func (*UIObjHideResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{16} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{17} } func (x *UIObjHideResponse) GetSuccess() bool { @@ -811,7 +855,7 @@ type UIObjShowRequest struct { func (x *UIObjShowRequest) Reset() { *x = UIObjShowRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[17] + mi := &file_internal_native_proto_native_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -823,7 +867,7 @@ func (x *UIObjShowRequest) String() string { func (*UIObjShowRequest) ProtoMessage() {} func (x *UIObjShowRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[17] + mi := &file_internal_native_proto_native_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -836,7 +880,7 @@ func (x *UIObjShowRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjShowRequest.ProtoReflect.Descriptor instead. func (*UIObjShowRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{17} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{18} } func (x *UIObjShowRequest) GetObjName() string { @@ -855,7 +899,7 @@ type UIObjShowResponse struct { func (x *UIObjShowResponse) Reset() { *x = UIObjShowResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[18] + mi := &file_internal_native_proto_native_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -867,7 +911,7 @@ func (x *UIObjShowResponse) String() string { func (*UIObjShowResponse) ProtoMessage() {} func (x *UIObjShowResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[18] + mi := &file_internal_native_proto_native_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -880,7 +924,7 @@ func (x *UIObjShowResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjShowResponse.ProtoReflect.Descriptor instead. func (*UIObjShowResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{18} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{19} } func (x *UIObjShowResponse) GetSuccess() bool { @@ -900,7 +944,7 @@ type UISetVarRequest struct { func (x *UISetVarRequest) Reset() { *x = UISetVarRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[19] + mi := &file_internal_native_proto_native_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -912,7 +956,7 @@ func (x *UISetVarRequest) String() string { func (*UISetVarRequest) ProtoMessage() {} func (x *UISetVarRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[19] + mi := &file_internal_native_proto_native_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -925,7 +969,7 @@ func (x *UISetVarRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UISetVarRequest.ProtoReflect.Descriptor instead. func (*UISetVarRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{19} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{20} } func (x *UISetVarRequest) GetName() string { @@ -951,7 +995,7 @@ type UIGetVarRequest struct { func (x *UIGetVarRequest) Reset() { *x = UIGetVarRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[20] + mi := &file_internal_native_proto_native_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -963,7 +1007,7 @@ func (x *UIGetVarRequest) String() string { func (*UIGetVarRequest) ProtoMessage() {} func (x *UIGetVarRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[20] + mi := &file_internal_native_proto_native_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -976,7 +1020,7 @@ func (x *UIGetVarRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIGetVarRequest.ProtoReflect.Descriptor instead. func (*UIGetVarRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{20} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{21} } func (x *UIGetVarRequest) GetName() string { @@ -995,7 +1039,7 @@ type UIGetVarResponse struct { func (x *UIGetVarResponse) Reset() { *x = UIGetVarResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[21] + mi := &file_internal_native_proto_native_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1007,7 +1051,7 @@ func (x *UIGetVarResponse) String() string { func (*UIGetVarResponse) ProtoMessage() {} func (x *UIGetVarResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[21] + mi := &file_internal_native_proto_native_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1020,7 +1064,7 @@ func (x *UIGetVarResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIGetVarResponse.ProtoReflect.Descriptor instead. func (*UIGetVarResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{21} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{22} } func (x *UIGetVarResponse) GetValue() string { @@ -1040,7 +1084,7 @@ type UIObjAddStateRequest struct { func (x *UIObjAddStateRequest) Reset() { *x = UIObjAddStateRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[22] + mi := &file_internal_native_proto_native_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1052,7 +1096,7 @@ func (x *UIObjAddStateRequest) String() string { func (*UIObjAddStateRequest) ProtoMessage() {} func (x *UIObjAddStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[22] + mi := &file_internal_native_proto_native_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1065,7 +1109,7 @@ func (x *UIObjAddStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjAddStateRequest.ProtoReflect.Descriptor instead. func (*UIObjAddStateRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{22} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{23} } func (x *UIObjAddStateRequest) GetObjName() string { @@ -1091,7 +1135,7 @@ type UIObjAddStateResponse struct { func (x *UIObjAddStateResponse) Reset() { *x = UIObjAddStateResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[23] + mi := &file_internal_native_proto_native_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1103,7 +1147,7 @@ func (x *UIObjAddStateResponse) String() string { func (*UIObjAddStateResponse) ProtoMessage() {} func (x *UIObjAddStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[23] + mi := &file_internal_native_proto_native_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1116,7 +1160,7 @@ func (x *UIObjAddStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjAddStateResponse.ProtoReflect.Descriptor instead. func (*UIObjAddStateResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{23} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{24} } func (x *UIObjAddStateResponse) GetSuccess() bool { @@ -1136,7 +1180,7 @@ type UIObjClearStateRequest struct { func (x *UIObjClearStateRequest) Reset() { *x = UIObjClearStateRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[24] + mi := &file_internal_native_proto_native_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1148,7 +1192,7 @@ func (x *UIObjClearStateRequest) String() string { func (*UIObjClearStateRequest) ProtoMessage() {} func (x *UIObjClearStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[24] + mi := &file_internal_native_proto_native_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1161,7 +1205,7 @@ func (x *UIObjClearStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjClearStateRequest.ProtoReflect.Descriptor instead. func (*UIObjClearStateRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{24} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{25} } func (x *UIObjClearStateRequest) GetObjName() string { @@ -1187,7 +1231,7 @@ type UIObjClearStateResponse struct { func (x *UIObjClearStateResponse) Reset() { *x = UIObjClearStateResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[25] + mi := &file_internal_native_proto_native_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1199,7 +1243,7 @@ func (x *UIObjClearStateResponse) String() string { func (*UIObjClearStateResponse) ProtoMessage() {} func (x *UIObjClearStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[25] + mi := &file_internal_native_proto_native_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1212,7 +1256,7 @@ func (x *UIObjClearStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjClearStateResponse.ProtoReflect.Descriptor instead. func (*UIObjClearStateResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{25} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{26} } func (x *UIObjClearStateResponse) GetSuccess() bool { @@ -1232,7 +1276,7 @@ type UIObjAddFlagRequest struct { func (x *UIObjAddFlagRequest) Reset() { *x = UIObjAddFlagRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[26] + mi := &file_internal_native_proto_native_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1244,7 +1288,7 @@ func (x *UIObjAddFlagRequest) String() string { func (*UIObjAddFlagRequest) ProtoMessage() {} func (x *UIObjAddFlagRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[26] + mi := &file_internal_native_proto_native_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1257,7 +1301,7 @@ func (x *UIObjAddFlagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjAddFlagRequest.ProtoReflect.Descriptor instead. func (*UIObjAddFlagRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{26} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{27} } func (x *UIObjAddFlagRequest) GetObjName() string { @@ -1283,7 +1327,7 @@ type UIObjAddFlagResponse struct { func (x *UIObjAddFlagResponse) Reset() { *x = UIObjAddFlagResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[27] + mi := &file_internal_native_proto_native_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1295,7 +1339,7 @@ func (x *UIObjAddFlagResponse) String() string { func (*UIObjAddFlagResponse) ProtoMessage() {} func (x *UIObjAddFlagResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[27] + mi := &file_internal_native_proto_native_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1308,7 +1352,7 @@ func (x *UIObjAddFlagResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjAddFlagResponse.ProtoReflect.Descriptor instead. func (*UIObjAddFlagResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{27} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{28} } func (x *UIObjAddFlagResponse) GetSuccess() bool { @@ -1328,7 +1372,7 @@ type UIObjClearFlagRequest struct { func (x *UIObjClearFlagRequest) Reset() { *x = UIObjClearFlagRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[28] + mi := &file_internal_native_proto_native_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1340,7 +1384,7 @@ func (x *UIObjClearFlagRequest) String() string { func (*UIObjClearFlagRequest) ProtoMessage() {} func (x *UIObjClearFlagRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[28] + mi := &file_internal_native_proto_native_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1353,7 +1397,7 @@ func (x *UIObjClearFlagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjClearFlagRequest.ProtoReflect.Descriptor instead. func (*UIObjClearFlagRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{28} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{29} } func (x *UIObjClearFlagRequest) GetObjName() string { @@ -1379,7 +1423,7 @@ type UIObjClearFlagResponse struct { func (x *UIObjClearFlagResponse) Reset() { *x = UIObjClearFlagResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[29] + mi := &file_internal_native_proto_native_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1391,7 +1435,7 @@ func (x *UIObjClearFlagResponse) String() string { func (*UIObjClearFlagResponse) ProtoMessage() {} func (x *UIObjClearFlagResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[29] + mi := &file_internal_native_proto_native_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1404,7 +1448,7 @@ func (x *UIObjClearFlagResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjClearFlagResponse.ProtoReflect.Descriptor instead. func (*UIObjClearFlagResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{29} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{30} } func (x *UIObjClearFlagResponse) GetSuccess() bool { @@ -1424,7 +1468,7 @@ type UIObjSetOpacityRequest struct { func (x *UIObjSetOpacityRequest) Reset() { *x = UIObjSetOpacityRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[30] + mi := &file_internal_native_proto_native_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1436,7 +1480,7 @@ func (x *UIObjSetOpacityRequest) String() string { func (*UIObjSetOpacityRequest) ProtoMessage() {} func (x *UIObjSetOpacityRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[30] + mi := &file_internal_native_proto_native_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1449,7 +1493,7 @@ func (x *UIObjSetOpacityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetOpacityRequest.ProtoReflect.Descriptor instead. func (*UIObjSetOpacityRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{30} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{31} } func (x *UIObjSetOpacityRequest) GetObjName() string { @@ -1475,7 +1519,7 @@ type UIObjSetOpacityResponse struct { func (x *UIObjSetOpacityResponse) Reset() { *x = UIObjSetOpacityResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[31] + mi := &file_internal_native_proto_native_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1487,7 +1531,7 @@ func (x *UIObjSetOpacityResponse) String() string { func (*UIObjSetOpacityResponse) ProtoMessage() {} func (x *UIObjSetOpacityResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[31] + mi := &file_internal_native_proto_native_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1500,7 +1544,7 @@ func (x *UIObjSetOpacityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetOpacityResponse.ProtoReflect.Descriptor instead. func (*UIObjSetOpacityResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{31} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{32} } func (x *UIObjSetOpacityResponse) GetSuccess() bool { @@ -1520,7 +1564,7 @@ type UIObjFadeInRequest struct { func (x *UIObjFadeInRequest) Reset() { *x = UIObjFadeInRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[32] + mi := &file_internal_native_proto_native_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1532,7 +1576,7 @@ func (x *UIObjFadeInRequest) String() string { func (*UIObjFadeInRequest) ProtoMessage() {} func (x *UIObjFadeInRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[32] + mi := &file_internal_native_proto_native_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1545,7 +1589,7 @@ func (x *UIObjFadeInRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjFadeInRequest.ProtoReflect.Descriptor instead. func (*UIObjFadeInRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{32} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{33} } func (x *UIObjFadeInRequest) GetObjName() string { @@ -1571,7 +1615,7 @@ type UIObjFadeInResponse struct { func (x *UIObjFadeInResponse) Reset() { *x = UIObjFadeInResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[33] + mi := &file_internal_native_proto_native_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1583,7 +1627,7 @@ func (x *UIObjFadeInResponse) String() string { func (*UIObjFadeInResponse) ProtoMessage() {} func (x *UIObjFadeInResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[33] + mi := &file_internal_native_proto_native_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1596,7 +1640,7 @@ func (x *UIObjFadeInResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjFadeInResponse.ProtoReflect.Descriptor instead. func (*UIObjFadeInResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{33} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{34} } func (x *UIObjFadeInResponse) GetSuccess() bool { @@ -1616,7 +1660,7 @@ type UIObjFadeOutRequest struct { func (x *UIObjFadeOutRequest) Reset() { *x = UIObjFadeOutRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[34] + mi := &file_internal_native_proto_native_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1628,7 +1672,7 @@ func (x *UIObjFadeOutRequest) String() string { func (*UIObjFadeOutRequest) ProtoMessage() {} func (x *UIObjFadeOutRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[34] + mi := &file_internal_native_proto_native_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1641,7 +1685,7 @@ func (x *UIObjFadeOutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjFadeOutRequest.ProtoReflect.Descriptor instead. func (*UIObjFadeOutRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{34} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{35} } func (x *UIObjFadeOutRequest) GetObjName() string { @@ -1667,7 +1711,7 @@ type UIObjFadeOutResponse struct { func (x *UIObjFadeOutResponse) Reset() { *x = UIObjFadeOutResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[35] + mi := &file_internal_native_proto_native_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1679,7 +1723,7 @@ func (x *UIObjFadeOutResponse) String() string { func (*UIObjFadeOutResponse) ProtoMessage() {} func (x *UIObjFadeOutResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[35] + mi := &file_internal_native_proto_native_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1692,7 +1736,7 @@ func (x *UIObjFadeOutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjFadeOutResponse.ProtoReflect.Descriptor instead. func (*UIObjFadeOutResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{35} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{36} } func (x *UIObjFadeOutResponse) GetSuccess() bool { @@ -1712,7 +1756,7 @@ type UIObjSetLabelTextRequest struct { func (x *UIObjSetLabelTextRequest) Reset() { *x = UIObjSetLabelTextRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[36] + mi := &file_internal_native_proto_native_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1724,7 +1768,7 @@ func (x *UIObjSetLabelTextRequest) String() string { func (*UIObjSetLabelTextRequest) ProtoMessage() {} func (x *UIObjSetLabelTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[36] + mi := &file_internal_native_proto_native_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1737,7 +1781,7 @@ func (x *UIObjSetLabelTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetLabelTextRequest.ProtoReflect.Descriptor instead. func (*UIObjSetLabelTextRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{36} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{37} } func (x *UIObjSetLabelTextRequest) GetObjName() string { @@ -1763,7 +1807,7 @@ type UIObjSetLabelTextResponse struct { func (x *UIObjSetLabelTextResponse) Reset() { *x = UIObjSetLabelTextResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[37] + mi := &file_internal_native_proto_native_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1775,7 +1819,7 @@ func (x *UIObjSetLabelTextResponse) String() string { func (*UIObjSetLabelTextResponse) ProtoMessage() {} func (x *UIObjSetLabelTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[37] + mi := &file_internal_native_proto_native_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1788,7 +1832,7 @@ func (x *UIObjSetLabelTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetLabelTextResponse.ProtoReflect.Descriptor instead. func (*UIObjSetLabelTextResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{37} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{38} } func (x *UIObjSetLabelTextResponse) GetSuccess() bool { @@ -1808,7 +1852,7 @@ type UIObjSetImageSrcRequest struct { func (x *UIObjSetImageSrcRequest) Reset() { *x = UIObjSetImageSrcRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[38] + mi := &file_internal_native_proto_native_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1820,7 +1864,7 @@ func (x *UIObjSetImageSrcRequest) String() string { func (*UIObjSetImageSrcRequest) ProtoMessage() {} func (x *UIObjSetImageSrcRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[38] + mi := &file_internal_native_proto_native_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1833,7 +1877,7 @@ func (x *UIObjSetImageSrcRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetImageSrcRequest.ProtoReflect.Descriptor instead. func (*UIObjSetImageSrcRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{38} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{39} } func (x *UIObjSetImageSrcRequest) GetObjName() string { @@ -1859,7 +1903,7 @@ type UIObjSetImageSrcResponse struct { func (x *UIObjSetImageSrcResponse) Reset() { *x = UIObjSetImageSrcResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[39] + mi := &file_internal_native_proto_native_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1871,7 +1915,7 @@ func (x *UIObjSetImageSrcResponse) String() string { func (*UIObjSetImageSrcResponse) ProtoMessage() {} func (x *UIObjSetImageSrcResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[39] + mi := &file_internal_native_proto_native_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1884,7 +1928,7 @@ func (x *UIObjSetImageSrcResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UIObjSetImageSrcResponse.ProtoReflect.Descriptor instead. func (*UIObjSetImageSrcResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{39} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{40} } func (x *UIObjSetImageSrcResponse) GetSuccess() bool { @@ -1903,7 +1947,7 @@ type DisplaySetRotationRequest struct { func (x *DisplaySetRotationRequest) Reset() { *x = DisplaySetRotationRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[40] + mi := &file_internal_native_proto_native_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1915,7 +1959,7 @@ func (x *DisplaySetRotationRequest) String() string { func (*DisplaySetRotationRequest) ProtoMessage() {} func (x *DisplaySetRotationRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[40] + mi := &file_internal_native_proto_native_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1928,7 +1972,7 @@ func (x *DisplaySetRotationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DisplaySetRotationRequest.ProtoReflect.Descriptor instead. func (*DisplaySetRotationRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{40} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{41} } func (x *DisplaySetRotationRequest) GetRotation() uint32 { @@ -1947,7 +1991,7 @@ type DisplaySetRotationResponse struct { func (x *DisplaySetRotationResponse) Reset() { *x = DisplaySetRotationResponse{} - mi := &file_internal_native_proto_native_proto_msgTypes[41] + mi := &file_internal_native_proto_native_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1959,7 +2003,7 @@ func (x *DisplaySetRotationResponse) String() string { func (*DisplaySetRotationResponse) ProtoMessage() {} func (x *DisplaySetRotationResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[41] + mi := &file_internal_native_proto_native_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1972,7 +2016,7 @@ func (x *DisplaySetRotationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DisplaySetRotationResponse.ProtoReflect.Descriptor instead. func (*DisplaySetRotationResponse) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{41} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{42} } func (x *DisplaySetRotationResponse) GetSuccess() bool { @@ -1992,7 +2036,7 @@ type UpdateLabelIfChangedRequest struct { func (x *UpdateLabelIfChangedRequest) Reset() { *x = UpdateLabelIfChangedRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[42] + mi := &file_internal_native_proto_native_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2004,7 +2048,7 @@ func (x *UpdateLabelIfChangedRequest) String() string { func (*UpdateLabelIfChangedRequest) ProtoMessage() {} func (x *UpdateLabelIfChangedRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[42] + mi := &file_internal_native_proto_native_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2017,7 +2061,7 @@ func (x *UpdateLabelIfChangedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateLabelIfChangedRequest.ProtoReflect.Descriptor instead. func (*UpdateLabelIfChangedRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{42} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{43} } func (x *UpdateLabelIfChangedRequest) GetObjName() string { @@ -2044,7 +2088,7 @@ type UpdateLabelAndChangeVisibilityRequest struct { func (x *UpdateLabelAndChangeVisibilityRequest) Reset() { *x = UpdateLabelAndChangeVisibilityRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[43] + mi := &file_internal_native_proto_native_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2056,7 +2100,7 @@ func (x *UpdateLabelAndChangeVisibilityRequest) String() string { func (*UpdateLabelAndChangeVisibilityRequest) ProtoMessage() {} func (x *UpdateLabelAndChangeVisibilityRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[43] + mi := &file_internal_native_proto_native_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2069,7 +2113,7 @@ func (x *UpdateLabelAndChangeVisibilityRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use UpdateLabelAndChangeVisibilityRequest.ProtoReflect.Descriptor instead. func (*UpdateLabelAndChangeVisibilityRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{43} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{44} } func (x *UpdateLabelAndChangeVisibilityRequest) GetObjName() string { @@ -2096,7 +2140,7 @@ type SwitchToScreenIfRequest struct { func (x *SwitchToScreenIfRequest) Reset() { *x = SwitchToScreenIfRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[44] + mi := &file_internal_native_proto_native_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2108,7 +2152,7 @@ func (x *SwitchToScreenIfRequest) String() string { func (*SwitchToScreenIfRequest) ProtoMessage() {} func (x *SwitchToScreenIfRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[44] + mi := &file_internal_native_proto_native_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2121,7 +2165,7 @@ func (x *SwitchToScreenIfRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SwitchToScreenIfRequest.ProtoReflect.Descriptor instead. func (*SwitchToScreenIfRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{44} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{45} } func (x *SwitchToScreenIfRequest) GetScreenName() string { @@ -2147,7 +2191,7 @@ type SwitchToScreenIfDifferentRequest struct { func (x *SwitchToScreenIfDifferentRequest) Reset() { *x = SwitchToScreenIfDifferentRequest{} - mi := &file_internal_native_proto_native_proto_msgTypes[45] + mi := &file_internal_native_proto_native_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2159,7 +2203,7 @@ func (x *SwitchToScreenIfDifferentRequest) String() string { func (*SwitchToScreenIfDifferentRequest) ProtoMessage() {} func (x *SwitchToScreenIfDifferentRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[45] + mi := &file_internal_native_proto_native_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2172,7 +2216,7 @@ func (x *SwitchToScreenIfDifferentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SwitchToScreenIfDifferentRequest.ProtoReflect.Descriptor instead. func (*SwitchToScreenIfDifferentRequest) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{45} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{46} } func (x *SwitchToScreenIfDifferentRequest) GetScreenName() string { @@ -2198,7 +2242,7 @@ type Event struct { func (x *Event) Reset() { *x = Event{} - mi := &file_internal_native_proto_native_proto_msgTypes[46] + mi := &file_internal_native_proto_native_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2210,7 +2254,7 @@ func (x *Event) String() string { func (*Event) ProtoMessage() {} func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[46] + mi := &file_internal_native_proto_native_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2223,7 +2267,7 @@ func (x *Event) ProtoReflect() protoreflect.Message { // Deprecated: Use Event.ProtoReflect.Descriptor instead. func (*Event) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{46} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{47} } func (x *Event) GetType() string { @@ -2314,7 +2358,7 @@ type VideoFrame struct { func (x *VideoFrame) Reset() { *x = VideoFrame{} - mi := &file_internal_native_proto_native_proto_msgTypes[47] + mi := &file_internal_native_proto_native_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2326,7 +2370,7 @@ func (x *VideoFrame) String() string { func (*VideoFrame) ProtoMessage() {} func (x *VideoFrame) ProtoReflect() protoreflect.Message { - mi := &file_internal_native_proto_native_proto_msgTypes[47] + mi := &file_internal_native_proto_native_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2339,7 +2383,7 @@ func (x *VideoFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoFrame.ProtoReflect.Descriptor instead. func (*VideoFrame) Descriptor() ([]byte, []int) { - return file_internal_native_proto_native_proto_rawDescGZIP(), []int{47} + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{48} } func (x *VideoFrame) GetFrame() []byte { @@ -2374,7 +2418,9 @@ const file_internal_native_proto_native_proto_rawDesc = "" + "\x05error\x18\x02 \x01(\tR\x05error\x12\x14\n" + "\x05width\x18\x03 \x01(\x05R\x05width\x12\x16\n" + "\x06height\x18\x04 \x01(\x05R\x06height\x12(\n" + - "\x10frame_per_second\x18\x05 \x01(\x01R\x0eframePerSecond\"4\n" + + "\x10frame_per_second\x18\x05 \x01(\x01R\x0eframePerSecond\"*\n" + + "\x14VideoGetJPEGResponse\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"4\n" + "\x18VideoSetSleepModeRequest\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\"5\n" + "\x19VideoGetSleepModeResponse\x12\x18\n" + @@ -2490,7 +2536,7 @@ const file_internal_native_proto_native_proto_rawDesc = "" + "VideoFrame\x12\x14\n" + "\x05frame\x18\x01 \x01(\fR\x05frame\x12\x1f\n" + "\vduration_ns\x18\x02 \x01(\x03R\n" + - "durationNs2\x8a\x13\n" + + "durationNs2\xc7\x13\n" + "\rNativeService\x12:\n" + "\aIsReady\x12\x16.native.IsReadyRequest\x1a\x17.native.IsReadyResponse\x12D\n" + "\x11VideoSetSleepMode\x12 .native.VideoSetSleepModeRequest\x1a\r.native.Empty\x12E\n" + @@ -2505,7 +2551,8 @@ const file_internal_native_proto_native_proto_rawDesc = "" + "\x0eVideoLogStatus\x12\r.native.Empty\x1a\x1e.native.VideoLogStatusResponse\x12)\n" + "\tVideoStop\x12\r.native.Empty\x1a\r.native.Empty\x12*\n" + "\n" + - "VideoStart\x12\r.native.Empty\x1a\r.native.Empty\x12?\n" + + "VideoStart\x12\r.native.Empty\x1a\r.native.Empty\x12;\n" + + "\fVideoGetJPEG\x12\r.native.Empty\x1a\x1c.native.VideoGetJPEGResponse\x12?\n" + "\x0eGetLVGLVersion\x12\r.native.Empty\x1a\x1e.native.GetLVGLVersionResponse\x12@\n" + "\tUIObjHide\x12\x18.native.UIObjHideRequest\x1a\x19.native.UIObjHideResponse\x12@\n" + "\tUIObjShow\x12\x18.native.UIObjShowRequest\x1a\x19.native.UIObjShowResponse\x122\n" + @@ -2540,130 +2587,133 @@ func file_internal_native_proto_native_proto_rawDescGZIP() []byte { return file_internal_native_proto_native_proto_rawDescData } -var file_internal_native_proto_native_proto_msgTypes = make([]protoimpl.MessageInfo, 48) +var file_internal_native_proto_native_proto_msgTypes = make([]protoimpl.MessageInfo, 49) var file_internal_native_proto_native_proto_goTypes = []any{ (*Empty)(nil), // 0: native.Empty (*IsReadyRequest)(nil), // 1: native.IsReadyRequest (*IsReadyResponse)(nil), // 2: native.IsReadyResponse (*VideoState)(nil), // 3: native.VideoState - (*VideoSetSleepModeRequest)(nil), // 4: native.VideoSetSleepModeRequest - (*VideoGetSleepModeResponse)(nil), // 5: native.VideoGetSleepModeResponse - (*VideoSleepModeSupportedResponse)(nil), // 6: native.VideoSleepModeSupportedResponse - (*VideoSetQualityFactorRequest)(nil), // 7: native.VideoSetQualityFactorRequest - (*VideoGetQualityFactorResponse)(nil), // 8: native.VideoGetQualityFactorResponse - (*VideoSetCodecTypeRequest)(nil), // 9: native.VideoSetCodecTypeRequest - (*VideoGetCodecTypeResponse)(nil), // 10: native.VideoGetCodecTypeResponse - (*VideoSetEDIDRequest)(nil), // 11: native.VideoSetEDIDRequest - (*VideoGetEDIDResponse)(nil), // 12: native.VideoGetEDIDResponse - (*VideoLogStatusResponse)(nil), // 13: native.VideoLogStatusResponse - (*GetLVGLVersionResponse)(nil), // 14: native.GetLVGLVersionResponse - (*UIObjHideRequest)(nil), // 15: native.UIObjHideRequest - (*UIObjHideResponse)(nil), // 16: native.UIObjHideResponse - (*UIObjShowRequest)(nil), // 17: native.UIObjShowRequest - (*UIObjShowResponse)(nil), // 18: native.UIObjShowResponse - (*UISetVarRequest)(nil), // 19: native.UISetVarRequest - (*UIGetVarRequest)(nil), // 20: native.UIGetVarRequest - (*UIGetVarResponse)(nil), // 21: native.UIGetVarResponse - (*UIObjAddStateRequest)(nil), // 22: native.UIObjAddStateRequest - (*UIObjAddStateResponse)(nil), // 23: native.UIObjAddStateResponse - (*UIObjClearStateRequest)(nil), // 24: native.UIObjClearStateRequest - (*UIObjClearStateResponse)(nil), // 25: native.UIObjClearStateResponse - (*UIObjAddFlagRequest)(nil), // 26: native.UIObjAddFlagRequest - (*UIObjAddFlagResponse)(nil), // 27: native.UIObjAddFlagResponse - (*UIObjClearFlagRequest)(nil), // 28: native.UIObjClearFlagRequest - (*UIObjClearFlagResponse)(nil), // 29: native.UIObjClearFlagResponse - (*UIObjSetOpacityRequest)(nil), // 30: native.UIObjSetOpacityRequest - (*UIObjSetOpacityResponse)(nil), // 31: native.UIObjSetOpacityResponse - (*UIObjFadeInRequest)(nil), // 32: native.UIObjFadeInRequest - (*UIObjFadeInResponse)(nil), // 33: native.UIObjFadeInResponse - (*UIObjFadeOutRequest)(nil), // 34: native.UIObjFadeOutRequest - (*UIObjFadeOutResponse)(nil), // 35: native.UIObjFadeOutResponse - (*UIObjSetLabelTextRequest)(nil), // 36: native.UIObjSetLabelTextRequest - (*UIObjSetLabelTextResponse)(nil), // 37: native.UIObjSetLabelTextResponse - (*UIObjSetImageSrcRequest)(nil), // 38: native.UIObjSetImageSrcRequest - (*UIObjSetImageSrcResponse)(nil), // 39: native.UIObjSetImageSrcResponse - (*DisplaySetRotationRequest)(nil), // 40: native.DisplaySetRotationRequest - (*DisplaySetRotationResponse)(nil), // 41: native.DisplaySetRotationResponse - (*UpdateLabelIfChangedRequest)(nil), // 42: native.UpdateLabelIfChangedRequest - (*UpdateLabelAndChangeVisibilityRequest)(nil), // 43: native.UpdateLabelAndChangeVisibilityRequest - (*SwitchToScreenIfRequest)(nil), // 44: native.SwitchToScreenIfRequest - (*SwitchToScreenIfDifferentRequest)(nil), // 45: native.SwitchToScreenIfDifferentRequest - (*Event)(nil), // 46: native.Event - (*VideoFrame)(nil), // 47: native.VideoFrame + (*VideoGetJPEGResponse)(nil), // 4: native.VideoGetJPEGResponse + (*VideoSetSleepModeRequest)(nil), // 5: native.VideoSetSleepModeRequest + (*VideoGetSleepModeResponse)(nil), // 6: native.VideoGetSleepModeResponse + (*VideoSleepModeSupportedResponse)(nil), // 7: native.VideoSleepModeSupportedResponse + (*VideoSetQualityFactorRequest)(nil), // 8: native.VideoSetQualityFactorRequest + (*VideoGetQualityFactorResponse)(nil), // 9: native.VideoGetQualityFactorResponse + (*VideoSetCodecTypeRequest)(nil), // 10: native.VideoSetCodecTypeRequest + (*VideoGetCodecTypeResponse)(nil), // 11: native.VideoGetCodecTypeResponse + (*VideoSetEDIDRequest)(nil), // 12: native.VideoSetEDIDRequest + (*VideoGetEDIDResponse)(nil), // 13: native.VideoGetEDIDResponse + (*VideoLogStatusResponse)(nil), // 14: native.VideoLogStatusResponse + (*GetLVGLVersionResponse)(nil), // 15: native.GetLVGLVersionResponse + (*UIObjHideRequest)(nil), // 16: native.UIObjHideRequest + (*UIObjHideResponse)(nil), // 17: native.UIObjHideResponse + (*UIObjShowRequest)(nil), // 18: native.UIObjShowRequest + (*UIObjShowResponse)(nil), // 19: native.UIObjShowResponse + (*UISetVarRequest)(nil), // 20: native.UISetVarRequest + (*UIGetVarRequest)(nil), // 21: native.UIGetVarRequest + (*UIGetVarResponse)(nil), // 22: native.UIGetVarResponse + (*UIObjAddStateRequest)(nil), // 23: native.UIObjAddStateRequest + (*UIObjAddStateResponse)(nil), // 24: native.UIObjAddStateResponse + (*UIObjClearStateRequest)(nil), // 25: native.UIObjClearStateRequest + (*UIObjClearStateResponse)(nil), // 26: native.UIObjClearStateResponse + (*UIObjAddFlagRequest)(nil), // 27: native.UIObjAddFlagRequest + (*UIObjAddFlagResponse)(nil), // 28: native.UIObjAddFlagResponse + (*UIObjClearFlagRequest)(nil), // 29: native.UIObjClearFlagRequest + (*UIObjClearFlagResponse)(nil), // 30: native.UIObjClearFlagResponse + (*UIObjSetOpacityRequest)(nil), // 31: native.UIObjSetOpacityRequest + (*UIObjSetOpacityResponse)(nil), // 32: native.UIObjSetOpacityResponse + (*UIObjFadeInRequest)(nil), // 33: native.UIObjFadeInRequest + (*UIObjFadeInResponse)(nil), // 34: native.UIObjFadeInResponse + (*UIObjFadeOutRequest)(nil), // 35: native.UIObjFadeOutRequest + (*UIObjFadeOutResponse)(nil), // 36: native.UIObjFadeOutResponse + (*UIObjSetLabelTextRequest)(nil), // 37: native.UIObjSetLabelTextRequest + (*UIObjSetLabelTextResponse)(nil), // 38: native.UIObjSetLabelTextResponse + (*UIObjSetImageSrcRequest)(nil), // 39: native.UIObjSetImageSrcRequest + (*UIObjSetImageSrcResponse)(nil), // 40: native.UIObjSetImageSrcResponse + (*DisplaySetRotationRequest)(nil), // 41: native.DisplaySetRotationRequest + (*DisplaySetRotationResponse)(nil), // 42: native.DisplaySetRotationResponse + (*UpdateLabelIfChangedRequest)(nil), // 43: native.UpdateLabelIfChangedRequest + (*UpdateLabelAndChangeVisibilityRequest)(nil), // 44: native.UpdateLabelAndChangeVisibilityRequest + (*SwitchToScreenIfRequest)(nil), // 45: native.SwitchToScreenIfRequest + (*SwitchToScreenIfDifferentRequest)(nil), // 46: native.SwitchToScreenIfDifferentRequest + (*Event)(nil), // 47: native.Event + (*VideoFrame)(nil), // 48: native.VideoFrame } var file_internal_native_proto_native_proto_depIdxs = []int32{ 3, // 0: native.Event.video_state:type_name -> native.VideoState - 47, // 1: native.Event.video_frame:type_name -> native.VideoFrame + 48, // 1: native.Event.video_frame:type_name -> native.VideoFrame 1, // 2: native.NativeService.IsReady:input_type -> native.IsReadyRequest - 4, // 3: native.NativeService.VideoSetSleepMode:input_type -> native.VideoSetSleepModeRequest + 5, // 3: native.NativeService.VideoSetSleepMode:input_type -> native.VideoSetSleepModeRequest 0, // 4: native.NativeService.VideoGetSleepMode:input_type -> native.Empty 0, // 5: native.NativeService.VideoSleepModeSupported:input_type -> native.Empty - 7, // 6: native.NativeService.VideoSetQualityFactor:input_type -> native.VideoSetQualityFactorRequest + 8, // 6: native.NativeService.VideoSetQualityFactor:input_type -> native.VideoSetQualityFactorRequest 0, // 7: native.NativeService.VideoGetQualityFactor:input_type -> native.Empty - 9, // 8: native.NativeService.VideoSetCodecType:input_type -> native.VideoSetCodecTypeRequest + 10, // 8: native.NativeService.VideoSetCodecType:input_type -> native.VideoSetCodecTypeRequest 0, // 9: native.NativeService.VideoGetCodecType:input_type -> native.Empty - 11, // 10: native.NativeService.VideoSetEDID:input_type -> native.VideoSetEDIDRequest + 12, // 10: native.NativeService.VideoSetEDID:input_type -> native.VideoSetEDIDRequest 0, // 11: native.NativeService.VideoGetEDID:input_type -> native.Empty 0, // 12: native.NativeService.VideoLogStatus:input_type -> native.Empty 0, // 13: native.NativeService.VideoStop:input_type -> native.Empty 0, // 14: native.NativeService.VideoStart:input_type -> native.Empty - 0, // 15: native.NativeService.GetLVGLVersion:input_type -> native.Empty - 15, // 16: native.NativeService.UIObjHide:input_type -> native.UIObjHideRequest - 17, // 17: native.NativeService.UIObjShow:input_type -> native.UIObjShowRequest - 19, // 18: native.NativeService.UISetVar:input_type -> native.UISetVarRequest - 20, // 19: native.NativeService.UIGetVar:input_type -> native.UIGetVarRequest - 22, // 20: native.NativeService.UIObjAddState:input_type -> native.UIObjAddStateRequest - 24, // 21: native.NativeService.UIObjClearState:input_type -> native.UIObjClearStateRequest - 26, // 22: native.NativeService.UIObjAddFlag:input_type -> native.UIObjAddFlagRequest - 28, // 23: native.NativeService.UIObjClearFlag:input_type -> native.UIObjClearFlagRequest - 30, // 24: native.NativeService.UIObjSetOpacity:input_type -> native.UIObjSetOpacityRequest - 32, // 25: native.NativeService.UIObjFadeIn:input_type -> native.UIObjFadeInRequest - 34, // 26: native.NativeService.UIObjFadeOut:input_type -> native.UIObjFadeOutRequest - 36, // 27: native.NativeService.UIObjSetLabelText:input_type -> native.UIObjSetLabelTextRequest - 38, // 28: native.NativeService.UIObjSetImageSrc:input_type -> native.UIObjSetImageSrcRequest - 40, // 29: native.NativeService.DisplaySetRotation:input_type -> native.DisplaySetRotationRequest - 42, // 30: native.NativeService.UpdateLabelIfChanged:input_type -> native.UpdateLabelIfChangedRequest - 43, // 31: native.NativeService.UpdateLabelAndChangeVisibility:input_type -> native.UpdateLabelAndChangeVisibilityRequest - 44, // 32: native.NativeService.SwitchToScreenIf:input_type -> native.SwitchToScreenIfRequest - 45, // 33: native.NativeService.SwitchToScreenIfDifferent:input_type -> native.SwitchToScreenIfDifferentRequest - 0, // 34: native.NativeService.DoNotUseThisIsForCrashTestingOnly:input_type -> native.Empty - 0, // 35: native.NativeService.StreamEvents:input_type -> native.Empty - 2, // 36: native.NativeService.IsReady:output_type -> native.IsReadyResponse - 0, // 37: native.NativeService.VideoSetSleepMode:output_type -> native.Empty - 5, // 38: native.NativeService.VideoGetSleepMode:output_type -> native.VideoGetSleepModeResponse - 6, // 39: native.NativeService.VideoSleepModeSupported:output_type -> native.VideoSleepModeSupportedResponse - 0, // 40: native.NativeService.VideoSetQualityFactor:output_type -> native.Empty - 8, // 41: native.NativeService.VideoGetQualityFactor:output_type -> native.VideoGetQualityFactorResponse - 0, // 42: native.NativeService.VideoSetCodecType:output_type -> native.Empty - 10, // 43: native.NativeService.VideoGetCodecType:output_type -> native.VideoGetCodecTypeResponse - 0, // 44: native.NativeService.VideoSetEDID:output_type -> native.Empty - 12, // 45: native.NativeService.VideoGetEDID:output_type -> native.VideoGetEDIDResponse - 13, // 46: native.NativeService.VideoLogStatus:output_type -> native.VideoLogStatusResponse - 0, // 47: native.NativeService.VideoStop:output_type -> native.Empty - 0, // 48: native.NativeService.VideoStart:output_type -> native.Empty - 14, // 49: native.NativeService.GetLVGLVersion:output_type -> native.GetLVGLVersionResponse - 16, // 50: native.NativeService.UIObjHide:output_type -> native.UIObjHideResponse - 18, // 51: native.NativeService.UIObjShow:output_type -> native.UIObjShowResponse - 0, // 52: native.NativeService.UISetVar:output_type -> native.Empty - 21, // 53: native.NativeService.UIGetVar:output_type -> native.UIGetVarResponse - 23, // 54: native.NativeService.UIObjAddState:output_type -> native.UIObjAddStateResponse - 25, // 55: native.NativeService.UIObjClearState:output_type -> native.UIObjClearStateResponse - 27, // 56: native.NativeService.UIObjAddFlag:output_type -> native.UIObjAddFlagResponse - 29, // 57: native.NativeService.UIObjClearFlag:output_type -> native.UIObjClearFlagResponse - 31, // 58: native.NativeService.UIObjSetOpacity:output_type -> native.UIObjSetOpacityResponse - 33, // 59: native.NativeService.UIObjFadeIn:output_type -> native.UIObjFadeInResponse - 35, // 60: native.NativeService.UIObjFadeOut:output_type -> native.UIObjFadeOutResponse - 37, // 61: native.NativeService.UIObjSetLabelText:output_type -> native.UIObjSetLabelTextResponse - 39, // 62: native.NativeService.UIObjSetImageSrc:output_type -> native.UIObjSetImageSrcResponse - 41, // 63: native.NativeService.DisplaySetRotation:output_type -> native.DisplaySetRotationResponse - 0, // 64: native.NativeService.UpdateLabelIfChanged:output_type -> native.Empty - 0, // 65: native.NativeService.UpdateLabelAndChangeVisibility:output_type -> native.Empty - 0, // 66: native.NativeService.SwitchToScreenIf:output_type -> native.Empty - 0, // 67: native.NativeService.SwitchToScreenIfDifferent:output_type -> native.Empty - 0, // 68: native.NativeService.DoNotUseThisIsForCrashTestingOnly:output_type -> native.Empty - 46, // 69: native.NativeService.StreamEvents:output_type -> native.Event - 36, // [36:70] is the sub-list for method output_type - 2, // [2:36] is the sub-list for method input_type + 0, // 15: native.NativeService.VideoGetJPEG:input_type -> native.Empty + 0, // 16: native.NativeService.GetLVGLVersion:input_type -> native.Empty + 16, // 17: native.NativeService.UIObjHide:input_type -> native.UIObjHideRequest + 18, // 18: native.NativeService.UIObjShow:input_type -> native.UIObjShowRequest + 20, // 19: native.NativeService.UISetVar:input_type -> native.UISetVarRequest + 21, // 20: native.NativeService.UIGetVar:input_type -> native.UIGetVarRequest + 23, // 21: native.NativeService.UIObjAddState:input_type -> native.UIObjAddStateRequest + 25, // 22: native.NativeService.UIObjClearState:input_type -> native.UIObjClearStateRequest + 27, // 23: native.NativeService.UIObjAddFlag:input_type -> native.UIObjAddFlagRequest + 29, // 24: native.NativeService.UIObjClearFlag:input_type -> native.UIObjClearFlagRequest + 31, // 25: native.NativeService.UIObjSetOpacity:input_type -> native.UIObjSetOpacityRequest + 33, // 26: native.NativeService.UIObjFadeIn:input_type -> native.UIObjFadeInRequest + 35, // 27: native.NativeService.UIObjFadeOut:input_type -> native.UIObjFadeOutRequest + 37, // 28: native.NativeService.UIObjSetLabelText:input_type -> native.UIObjSetLabelTextRequest + 39, // 29: native.NativeService.UIObjSetImageSrc:input_type -> native.UIObjSetImageSrcRequest + 41, // 30: native.NativeService.DisplaySetRotation:input_type -> native.DisplaySetRotationRequest + 43, // 31: native.NativeService.UpdateLabelIfChanged:input_type -> native.UpdateLabelIfChangedRequest + 44, // 32: native.NativeService.UpdateLabelAndChangeVisibility:input_type -> native.UpdateLabelAndChangeVisibilityRequest + 45, // 33: native.NativeService.SwitchToScreenIf:input_type -> native.SwitchToScreenIfRequest + 46, // 34: native.NativeService.SwitchToScreenIfDifferent:input_type -> native.SwitchToScreenIfDifferentRequest + 0, // 35: native.NativeService.DoNotUseThisIsForCrashTestingOnly:input_type -> native.Empty + 0, // 36: native.NativeService.StreamEvents:input_type -> native.Empty + 2, // 37: native.NativeService.IsReady:output_type -> native.IsReadyResponse + 0, // 38: native.NativeService.VideoSetSleepMode:output_type -> native.Empty + 6, // 39: native.NativeService.VideoGetSleepMode:output_type -> native.VideoGetSleepModeResponse + 7, // 40: native.NativeService.VideoSleepModeSupported:output_type -> native.VideoSleepModeSupportedResponse + 0, // 41: native.NativeService.VideoSetQualityFactor:output_type -> native.Empty + 9, // 42: native.NativeService.VideoGetQualityFactor:output_type -> native.VideoGetQualityFactorResponse + 0, // 43: native.NativeService.VideoSetCodecType:output_type -> native.Empty + 11, // 44: native.NativeService.VideoGetCodecType:output_type -> native.VideoGetCodecTypeResponse + 0, // 45: native.NativeService.VideoSetEDID:output_type -> native.Empty + 13, // 46: native.NativeService.VideoGetEDID:output_type -> native.VideoGetEDIDResponse + 14, // 47: native.NativeService.VideoLogStatus:output_type -> native.VideoLogStatusResponse + 0, // 48: native.NativeService.VideoStop:output_type -> native.Empty + 0, // 49: native.NativeService.VideoStart:output_type -> native.Empty + 4, // 50: native.NativeService.VideoGetJPEG:output_type -> native.VideoGetJPEGResponse + 15, // 51: native.NativeService.GetLVGLVersion:output_type -> native.GetLVGLVersionResponse + 17, // 52: native.NativeService.UIObjHide:output_type -> native.UIObjHideResponse + 19, // 53: native.NativeService.UIObjShow:output_type -> native.UIObjShowResponse + 0, // 54: native.NativeService.UISetVar:output_type -> native.Empty + 22, // 55: native.NativeService.UIGetVar:output_type -> native.UIGetVarResponse + 24, // 56: native.NativeService.UIObjAddState:output_type -> native.UIObjAddStateResponse + 26, // 57: native.NativeService.UIObjClearState:output_type -> native.UIObjClearStateResponse + 28, // 58: native.NativeService.UIObjAddFlag:output_type -> native.UIObjAddFlagResponse + 30, // 59: native.NativeService.UIObjClearFlag:output_type -> native.UIObjClearFlagResponse + 32, // 60: native.NativeService.UIObjSetOpacity:output_type -> native.UIObjSetOpacityResponse + 34, // 61: native.NativeService.UIObjFadeIn:output_type -> native.UIObjFadeInResponse + 36, // 62: native.NativeService.UIObjFadeOut:output_type -> native.UIObjFadeOutResponse + 38, // 63: native.NativeService.UIObjSetLabelText:output_type -> native.UIObjSetLabelTextResponse + 40, // 64: native.NativeService.UIObjSetImageSrc:output_type -> native.UIObjSetImageSrcResponse + 42, // 65: native.NativeService.DisplaySetRotation:output_type -> native.DisplaySetRotationResponse + 0, // 66: native.NativeService.UpdateLabelIfChanged:output_type -> native.Empty + 0, // 67: native.NativeService.UpdateLabelAndChangeVisibility:output_type -> native.Empty + 0, // 68: native.NativeService.SwitchToScreenIf:output_type -> native.Empty + 0, // 69: native.NativeService.SwitchToScreenIfDifferent:output_type -> native.Empty + 0, // 70: native.NativeService.DoNotUseThisIsForCrashTestingOnly:output_type -> native.Empty + 47, // 71: native.NativeService.StreamEvents:output_type -> native.Event + 37, // [37:72] is the sub-list for method output_type + 2, // [2:37] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name @@ -2674,7 +2724,7 @@ func file_internal_native_proto_native_proto_init() { if File_internal_native_proto_native_proto != nil { return } - file_internal_native_proto_native_proto_msgTypes[46].OneofWrappers = []any{ + file_internal_native_proto_native_proto_msgTypes[47].OneofWrappers = []any{ (*Event_VideoState)(nil), (*Event_IndevEvent)(nil), (*Event_RpcEvent)(nil), @@ -2686,7 +2736,7 @@ func file_internal_native_proto_native_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_internal_native_proto_native_proto_rawDesc), len(file_internal_native_proto_native_proto_rawDesc)), NumEnums: 0, - NumMessages: 48, + NumMessages: 49, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/native/proto/native.proto b/internal/native/proto/native.proto index 8186af14d..960e8877b 100644 --- a/internal/native/proto/native.proto +++ b/internal/native/proto/native.proto @@ -22,6 +22,7 @@ service NativeService { rpc VideoLogStatus(Empty) returns (VideoLogStatusResponse); rpc VideoStop(Empty) returns (Empty); rpc VideoStart(Empty) returns (Empty); + rpc VideoGetJPEG(Empty) returns (VideoGetJPEGResponse); // UI methods rpc GetLVGLVersion(Empty) returns (GetLVGLVersionResponse); @@ -70,6 +71,10 @@ message VideoState { double frame_per_second = 5; } +message VideoGetJPEGResponse { + bytes data = 1; +} + message VideoSetSleepModeRequest { bool enabled = 1; } diff --git a/internal/native/proto/native_grpc.pb.go b/internal/native/proto/native_grpc.pb.go index 9a11de683..89efbf5fe 100644 --- a/internal/native/proto/native_grpc.pb.go +++ b/internal/native/proto/native_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.1 -// - protoc v3.21.12 +// - protoc-gen-go-grpc v1.6.2 +// - protoc v5.28.3 // source: internal/native/proto/native.proto package proto @@ -32,6 +32,7 @@ const ( NativeService_VideoLogStatus_FullMethodName = "/native.NativeService/VideoLogStatus" NativeService_VideoStop_FullMethodName = "/native.NativeService/VideoStop" NativeService_VideoStart_FullMethodName = "/native.NativeService/VideoStart" + NativeService_VideoGetJPEG_FullMethodName = "/native.NativeService/VideoGetJPEG" NativeService_GetLVGLVersion_FullMethodName = "/native.NativeService/GetLVGLVersion" NativeService_UIObjHide_FullMethodName = "/native.NativeService/UIObjHide" NativeService_UIObjShow_FullMethodName = "/native.NativeService/UIObjShow" @@ -76,6 +77,7 @@ type NativeServiceClient interface { VideoLogStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoLogStatusResponse, error) VideoStop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) VideoStart(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) + VideoGetJPEG(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetJPEGResponse, error) // UI methods GetLVGLVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetLVGLVersionResponse, error) UIObjHide(ctx context.Context, in *UIObjHideRequest, opts ...grpc.CallOption) (*UIObjHideResponse, error) @@ -240,6 +242,16 @@ func (c *nativeServiceClient) VideoStart(ctx context.Context, in *Empty, opts .. return out, nil } +func (c *nativeServiceClient) VideoGetJPEG(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetJPEGResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(VideoGetJPEGResponse) + err := c.cc.Invoke(ctx, NativeService_VideoGetJPEG_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nativeServiceClient) GetLVGLVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetLVGLVersionResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetLVGLVersionResponse) @@ -480,6 +492,7 @@ type NativeServiceServer interface { VideoLogStatus(context.Context, *Empty) (*VideoLogStatusResponse, error) VideoStop(context.Context, *Empty) (*Empty, error) VideoStart(context.Context, *Empty) (*Empty, error) + VideoGetJPEG(context.Context, *Empty) (*VideoGetJPEGResponse, error) // UI methods GetLVGLVersion(context.Context, *Empty) (*GetLVGLVersionResponse, error) UIObjHide(context.Context, *UIObjHideRequest) (*UIObjHideResponse, error) @@ -553,6 +566,9 @@ func (UnimplementedNativeServiceServer) VideoStop(context.Context, *Empty) (*Emp func (UnimplementedNativeServiceServer) VideoStart(context.Context, *Empty) (*Empty, error) { return nil, status.Error(codes.Unimplemented, "method VideoStart not implemented") } +func (UnimplementedNativeServiceServer) VideoGetJPEG(context.Context, *Empty) (*VideoGetJPEGResponse, error) { + return nil, status.Error(codes.Unimplemented, "method VideoGetJPEG not implemented") +} func (UnimplementedNativeServiceServer) GetLVGLVersion(context.Context, *Empty) (*GetLVGLVersionResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetLVGLVersion not implemented") } @@ -871,6 +887,24 @@ func _NativeService_VideoStart_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _NativeService_VideoGetJPEG_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).VideoGetJPEG(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoGetJPEG_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoGetJPEG(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _NativeService_GetLVGLVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { @@ -1301,6 +1335,10 @@ var NativeService_ServiceDesc = grpc.ServiceDesc{ MethodName: "VideoStart", Handler: _NativeService_VideoStart_Handler, }, + { + MethodName: "VideoGetJPEG", + Handler: _NativeService_VideoGetJPEG_Handler, + }, { MethodName: "GetLVGLVersion", Handler: _NativeService_GetLVGLVersion_Handler, diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 551625e70..e4190aba4 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -734,3 +734,14 @@ func (p *NativeProxy) DoNotUseThisIsForCrashTestingOnly() { return nil }) } + +func (p *NativeProxy) VideoCaptureJPEG() ([]byte, error) { + p.clientMu.RLock() + defer p.clientMu.RUnlock() + + if p.client == nil { + return nil, fmt.Errorf("gRPC client not initialized") + } + + return p.client.VideoCaptureJPEG() +} diff --git a/internal/native/video.go b/internal/native/video.go index fec90de18..b96dbd9af 100644 --- a/internal/native/video.go +++ b/internal/native/video.go @@ -233,3 +233,11 @@ func (n *Native) VideoGetStreamingStatus() VideoStreamingStatus { return videoGetStreamingStatus() } + +// VideoCaptureJPEG captures a single JPEG frame from the live video stream. +func (n *Native) VideoCaptureJPEG() ([]byte, error) { + n.videoLock.Lock() + defer n.videoLock.Unlock() + + return videoCaptureJPEG() +} diff --git a/mqtt.go b/mqtt.go index 9c9e2f199..6b0bbc208 100644 --- a/mqtt.go +++ b/mqtt.go @@ -21,17 +21,18 @@ var mqttLogger = logging.GetSubsystemLogger("mqtt") const publishTimeout = 5 * time.Second type MQTTConfig struct { - Enabled bool `json:"enabled"` - Broker string `json:"broker"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - BaseTopic string `json:"base_topic"` - UseTLS bool `json:"use_tls"` - TLSInsecure bool `json:"tls_insecure"` - EnableHADiscovery bool `json:"enable_ha_discovery"` - EnableActions bool `json:"enable_actions"` - DebounceMs int `json:"debounce_ms"` + Enabled bool `json:"enabled"` + Broker string `json:"broker"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + BaseTopic string `json:"base_topic"` + UseTLS bool `json:"use_tls"` + TLSInsecure bool `json:"tls_insecure"` + EnableHADiscovery bool `json:"enable_ha_discovery"` + EnableActions bool `json:"enable_actions"` + DebounceMs int `json:"debounce_ms"` + ScreenshotIntervalSec int `json:"screenshot_interval_sec"` } var mqttManager *MQTTManager @@ -212,6 +213,11 @@ func (m *MQTTManager) onConnect(client mqtt.Client) { m.publishDCState(getDCState()) } m.publishExtendedStates() + + // Start periodic screenshot publishing if configured + if config.MqttConfig != nil && config.MqttConfig.ScreenshotIntervalSec > 0 { + m.startScreenshotTicker() + } } func (m *MQTTManager) onConnectionLost(client mqtt.Client, err error) { @@ -270,6 +276,39 @@ func (m *MQTTManager) publishString(topic string, payload string, retained bool) } } +// publishBinary publishes a raw binary payload. +func (m *MQTTManager) publishBinary(topic string, payload []byte, retained bool) { + token := m.client.Publish(topic, 1, retained, payload) + if !token.WaitTimeout(publishTimeout) { + mqttLogger.Warn().Str("topic", topic).Msg("MQTT publish timed out") + return + } + if token.Error() != nil { + mqttLogger.Error().Err(token.Error()).Str("topic", topic).Msg("failed to publish MQTT message") + } +} + +// startScreenshotTicker starts a background goroutine that periodically +// captures and publishes a JPEG screenshot if ScreenshotIntervalSec > 0. +func (m *MQTTManager) startScreenshotTicker() { + interval := config.MqttConfig.ScreenshotIntervalSec + if interval <= 0 { + return + } + go func() { + ticker := time.NewTicker(time.Duration(interval) * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + m.publishScreenshot() + case <-m.done: + return + } + } + }() +} + // actionsAllowed checks if MQTT actions are enabled in the config. func (m *MQTTManager) actionsAllowed() bool { return config.MqttConfig != nil && config.MqttConfig.EnableActions diff --git a/mqtt_commands.go b/mqtt_commands.go index 7dbb90bff..50b4fe49d 100644 --- a/mqtt_commands.go +++ b/mqtt_commands.go @@ -20,6 +20,7 @@ func (m *MQTTManager) subscribeCommands() { m.topic("reboot", "set"): m.handleRebootCommand, m.topic("update", "install"): m.handleUpdateInstallCommand, m.topic("virtual_media", "set"): m.handleVirtualMediaCommand, + m.topic("screenshot", "set"): m.handleScreenshotCommand, } for topic, handler := range commands { @@ -223,3 +224,17 @@ func (m *MQTTManager) handleVirtualMediaCommand(client mqtt.Client, msg mqtt.Mes // Publish updated state immediately m.publishVirtualMediaState() } + +func (m *MQTTManager) handleScreenshotCommand(client mqtt.Client, msg mqtt.Message) { + if !m.actionsAllowed() { + mqttLogger.Warn().Msg("screenshot command rejected: actions are disabled") + return + } + payload := strings.TrimSpace(string(msg.Payload())) + if strings.ToUpper(payload) != "CAPTURE" { + mqttLogger.Warn().Str("payload", payload).Msg("unknown screenshot command") + return + } + mqttLogger.Info().Msg("received screenshot capture command") + go m.publishScreenshot() +} diff --git a/mqtt_discovery.go b/mqtt_discovery.go index e86df4760..6a5981908 100644 --- a/mqtt_discovery.go +++ b/mqtt_discovery.go @@ -53,6 +53,10 @@ type haDiscoveryPayload struct { LatestVersionTemplate string `json:"latest_version_template,omitempty"` PayloadInstall string `json:"payload_install,omitempty"` ReleaseURL string `json:"release_url,omitempty"` + + // Image-specific + ImageTopic string `json:"image_topic,omitempty"` + ContentType string `json:"content_type,omitempty"` } func (m *MQTTManager) haDeviceInfo() *haDevice { @@ -412,6 +416,35 @@ func (m *MQTTManager) publishHADiscovery() { m.removeDiscovery("button", "reboot") } + // Screenshot Image: always published (disabled by default to avoid large retained payloads). + m.publishDiscovery("image", "screenshot", haDiscoveryPayload{ + Name: "Screenshot", + UniqueID: fmt.Sprintf("jetkvm_%s_screenshot", m.deviceID), + ImageTopic: m.topic("screenshot", "image"), + ContentType: "image/jpeg", + EnabledByDefault: boolPtr(false), + Icon: "mdi:monitor-screenshot", + AvailabilityTopic: availTopic, + AvailTemplate: availTemplate, + Device: device, + }) + + // Screenshot Capture Button: only when actions enabled. + if actionsEnabled { + m.publishDiscovery("button", "screenshot", haDiscoveryPayload{ + Name: "Capture Screenshot", + UniqueID: fmt.Sprintf("jetkvm_%s_screenshot_button", m.deviceID), + CommandTopic: m.topic("screenshot", "set"), + PayloadPress: "CAPTURE", + Icon: "mdi:camera", + AvailabilityTopic: availTopic, + AvailTemplate: availTemplate, + Device: device, + }) + } else { + m.removeDiscovery("button", "screenshot") + } + // Firmware Update: always published, but command_topic only when actions enabled. // NOTE: Do NOT use value_template/latest_version_template here — HA needs to parse // the full JSON directly to recognize in_progress and update_percentage fields. @@ -691,6 +724,9 @@ func (m *MQTTManager) removeAllDiscovery() { m.removeDiscovery("binary_sensor", "jiggler") m.removeDiscovery("button", "reboot") m.removeDiscovery("update", "firmware") + // Screenshot entities + m.removeDiscovery("image", "screenshot") + m.removeDiscovery("button", "screenshot") // Extension-specific entities (both switch and binary_sensor variants) m.removeATXDiscovery() diff --git a/mqtt_publish.go b/mqtt_publish.go index fc2818c40..c7744afca 100644 --- a/mqtt_publish.go +++ b/mqtt_publish.go @@ -579,3 +579,21 @@ func (m *MQTTManager) startPeriodicStatusUpdates(interval time.Duration) { } }() } + +// publishScreenshot captures a single JPEG frame from the video stream and +// publishes it to the screenshot image topic. +func (m *MQTTManager) publishScreenshot() { + if nativeInstance == nil { + return + } + if !lastVideoState.Ready { + mqttLogger.Debug().Msg("skipping screenshot: video not ready") + return + } + data, err := nativeInstance.VideoCaptureJPEG() + if err != nil { + mqttLogger.Warn().Err(err).Msg("failed to capture JPEG for MQTT screenshot") + return + } + m.publishBinary(m.topic("screenshot", "image"), data, false) +} diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index 219bbd3cb..a44f48453 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -684,6 +684,8 @@ "mqtt_save_button": "Save & Reconnect", "mqtt_saved_error": "Failed to save MQTT settings: {error}", "mqtt_saved_success": "MQTT settings saved. Reconnecting to broker…", + "mqtt_screenshot_interval_description": "Publish a JPEG screenshot to MQTT every N seconds (0 = disabled). When Remote Control is enabled, a capture button is also available in Home Assistant.", + "mqtt_screenshot_interval_title": "Screenshot Interval", "mqtt_section_advanced": "Advanced", "mqtt_section_advanced_description": "Fine-tune MQTT behavior and permissions", "mqtt_section_auth": "Authentication", diff --git a/ui/src/routes/devices.$id.settings.mqtt.tsx b/ui/src/routes/devices.$id.settings.mqtt.tsx index 6b78245f2..e374fb532 100644 --- a/ui/src/routes/devices.$id.settings.mqtt.tsx +++ b/ui/src/routes/devices.$id.settings.mqtt.tsx @@ -23,6 +23,7 @@ interface MQTTSettings { use_tls: boolean; tls_insecure: boolean; enable_ha_discovery: boolean; + screenshot_interval_sec: number; enable_actions: boolean; debounce_ms: number; } @@ -73,6 +74,7 @@ export default function SettingsMqttRoute() { use_tls: false, tls_insecure: false, enable_ha_discovery: true, + screenshot_interval_sec: 0, enable_actions: true, debounce_ms: 500, }); @@ -441,6 +443,21 @@ export default function SettingsMqttRoute() { /> )} + + + + updateField("screenshot_interval_sec", parseInt(e.target.value) || 0) + } + /> + {/* --- Advanced (only when ATX extension is active) --- */} From d7fc2a0e750d4c4807154288383936a8289818c2 Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Tue, 12 May 2026 19:56:06 +0000 Subject: [PATCH 02/10] fix(mqtt): fix screenshot capture when no WebRTC client is connected Previously, MQTT screenshot publishing would silently fail whenever no WebRTC client was streaming, because video_capture_jpeg() required the V4L2/VENC streaming loop to be active to supply a frame. Changes: - Refactor do_jpeg_capture into a synchronous do_jpeg_encode_frame helper that handles VENC JPEG_CHANNEL setup/teardown and returns raw bytes without touching the mutex/cond signaling machinery. - Add video_capture_jpeg_standalone() which opens /dev/video0 independently, allocates DMA buffers from the existing memPool, warms up the capture pipeline by discarding one stale frame, then encodes the second frame via do_jpeg_encode_frame and tears everything down cleanly. - Update video_capture_jpeg() to dispatch to the standalone path when streaming_flag is false, instead of returning -ENODATA. - Prevent HDMI sleep mode from engaging when MQTT screenshot publishing is active (ScreenshotIntervalSec > 0), matching the existing pattern that blocks sleep when WebRTC sessions are active. This avoids capture timeouts that would otherwise occur once the capture chip powers down. - Add an amber warning card in the MQTT settings UI below the Screenshot Interval field (shown only when interval > 0) informing the user that HDMI Sleep Mode has no effect while screenshot publishing is active. - Add corresponding i18n string mqtt_screenshot_interval_sleep_warning. --- internal/native/cgo/video.c | 241 +++++++++++++++++--- ui/localization/messages/en.json | 1 + ui/src/routes/devices.$id.settings.mqtt.tsx | 22 ++ video.go | 5 + 4 files changed, 240 insertions(+), 29 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 99138a36a..e3851eeee 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -421,9 +421,12 @@ bool get_streaming_stopped() return stopped; } -// do_jpeg_capture – called from the streaming loop when jpeg_requested is set. -// Creates VENC JPEG_CHANNEL, encodes one frame, stores result, signals waiter. -static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t height) +// do_jpeg_encode_frame – synchronous JPEG encode of a single video frame. +// Creates and destroys JPEG_CHANNEL internally. Returns 0 on success; +// on success *out_buf is a malloc'd JPEG buffer (caller must free). +// Does NOT touch jpeg_mutex/jpeg_cond – safe to call from any context. +static int do_jpeg_encode_frame(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t height, + uint8_t **out_buf, size_t *out_len) { int ret; @@ -442,8 +445,8 @@ static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t ret = RK_MPI_VENC_CreateChn(JPEG_CHANNEL, &attr); if (ret != RK_SUCCESS) { - log_error("JPEG capture: RK_MPI_VENC_CreateChn failed: %d", ret); - goto signal_error; + log_error("JPEG encode: RK_MPI_VENC_CreateChn failed: %d", ret); + return ret; } VENC_RECV_PIC_PARAM_S recv; @@ -451,27 +454,26 @@ static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t recv.s32RecvPicNum = 1; ret = RK_MPI_VENC_StartRecvFrame(JPEG_CHANNEL, &recv); if (ret != RK_SUCCESS) { - log_error("JPEG capture: RK_MPI_VENC_StartRecvFrame failed: %d", ret); + log_error("JPEG encode: RK_MPI_VENC_StartRecvFrame failed: %d", ret); RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); - goto signal_error; + return ret; } ret = RK_MPI_VENC_SendFrame(JPEG_CHANNEL, frame, 2000); if (ret != RK_SUCCESS) { - log_error("JPEG capture: RK_MPI_VENC_SendFrame failed: %d", ret); + log_error("JPEG encode: RK_MPI_VENC_SendFrame failed: %d", ret); RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); - goto signal_error; + return ret; } VENC_STREAM_S stream; stream.pstPack = malloc(sizeof(VENC_PACK_S)); if (!stream.pstPack) { - log_error("JPEG capture: malloc failed"); + log_error("JPEG encode: malloc failed"); RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); - ret = -ENOMEM; - goto signal_error; + return -ENOMEM; } ret = RK_MPI_VENC_GetStream(JPEG_CHANNEL, &stream, 2000); @@ -481,46 +483,227 @@ static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t uint8_t *buf = malloc(len); if (buf) { memcpy(buf, data, len); - pthread_mutex_lock(&jpeg_mutex); - jpeg_buf = buf; - jpeg_buf_len = len; - jpeg_result = 0; - jpeg_done = true; - jpeg_requested = false; - pthread_cond_signal(&jpeg_cond); - pthread_mutex_unlock(&jpeg_mutex); + *out_buf = buf; + *out_len = len; + ret = 0; } else { - log_error("JPEG capture: malloc for output failed"); + log_error("JPEG encode: malloc for output failed"); ret = -ENOMEM; } RK_MPI_VENC_ReleaseStream(JPEG_CHANNEL, &stream); } else { - log_error("JPEG capture: RK_MPI_VENC_GetStream failed: %d", ret); + log_error("JPEG encode: RK_MPI_VENC_GetStream failed: %d", ret); } free(stream.pstPack); RK_MPI_VENC_StopRecvFrame(JPEG_CHANNEL); RK_MPI_VENC_DestroyChn(JPEG_CHANNEL); + return ret; +} - if (ret != 0 && !(ret == RK_SUCCESS && jpeg_done)) { - goto signal_error; - } - return; +// do_jpeg_capture – called from the streaming loop when jpeg_requested is set. +// Encodes one frame and signals the video_capture_jpeg() waiter. +static void do_jpeg_capture(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint32_t height) +{ + uint8_t *buf = NULL; + size_t len = 0; + int ret = do_jpeg_encode_frame(frame, width, height, &buf, &len); -signal_error: pthread_mutex_lock(&jpeg_mutex); - jpeg_result = (ret != 0) ? ret : -1; + if (ret == 0 && buf != NULL) { + jpeg_buf = buf; + jpeg_buf_len = len; + jpeg_result = 0; + } else { + jpeg_result = (ret != 0) ? ret : -1; + free(buf); + } jpeg_done = true; jpeg_requested = false; pthread_cond_signal(&jpeg_cond); pthread_mutex_unlock(&jpeg_mutex); } +// video_capture_jpeg_standalone – captures a JPEG without an active streaming loop. +// Opens the V4L2 device independently, grabs one frame (skipping one stale buffer), +// encodes it as JPEG, and tears everything down cleanly. +static int video_capture_jpeg_standalone(uint8_t **out_buf, size_t *out_len) +{ + uint32_t width = detected_width; + uint32_t height = detected_height; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + int ret = -1; + + int fd = open(VIDEO_DEV, O_RDWR); + if (fd < 0) { + log_error("JPEG standalone: failed to open %s: %s", VIDEO_DEV, strerror(errno)); + return -errno; + } + + struct v4l2_format fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = type; + fmt.fmt.pix_mp.width = width; + fmt.fmt.pix_mp.height = height; + fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUYV; + fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; + if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { + log_error("JPEG standalone: VIDIOC_S_FMT failed: %s", strerror(errno)); + close(fd); + return -errno; + } + + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof(req)); + req.count = input_buffer_count; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + req.memory = V4L2_MEMORY_DMABUF; + if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) { + log_error("JPEG standalone: VIDIOC_REQBUFS failed: %s", strerror(errno)); + close(fd); + return -errno; + } + + struct buffer bufs[3]; + memset(bufs, 0, sizeof(bufs)); + int bufs_allocated = 0; + + for (int i = 0; i < input_buffer_count; i++) { + struct v4l2_plane *plane = &bufs[i].plane_buffer; + memset(plane, 0, sizeof(*plane)); + + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = type; + buf.memory = V4L2_MEMORY_DMABUF; + buf.m.planes = plane; + buf.length = 1; + buf.index = i; + if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { + log_error("JPEG standalone: VIDIOC_QUERYBUF failed: %s", strerror(errno)); + goto cleanup; + } + + MB_BLK blk = RK_MPI_MB_GetMB(memPool, plane->length, RK_TRUE); + if (blk == NULL) { + log_error("JPEG standalone: RK_MPI_MB_GetMB failed"); + ret = -ENOMEM; + goto cleanup; + } + bufs[i].mb_blk = blk; + bufs_allocated++; + plane->m.fd = RK_MPI_MB_Handle2Fd(blk); + } + + for (int i = 0; i < input_buffer_count; i++) { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = type; + buf.memory = V4L2_MEMORY_DMABUF; + buf.length = 1; + buf.index = i; + buf.m.planes = &bufs[i].plane_buffer; + if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { + log_error("JPEG standalone: VIDIOC_QBUF failed: %s", strerror(errno)); + goto cleanup; + } + } + + if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) { + log_error("JPEG standalone: VIDIOC_STREAMON failed: %s", strerror(errno)); + goto cleanup; + } + + { + struct v4l2_plane tmp_plane; + struct v4l2_buffer dqbuf; + + for (int f = 0; f < 2; f++) { + fd_set fds; + struct timeval tv; + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 2; + tv.tv_usec = 0; + int r = select(fd + 1, &fds, NULL, NULL, &tv); + if (r <= 0) { + log_error("JPEG standalone: select timeout/error on frame %d", f); + ret = -ETIMEDOUT; + break; + } + + memset(&dqbuf, 0, sizeof(dqbuf)); + memset(&tmp_plane, 0, sizeof(tmp_plane)); + dqbuf.type = type; + dqbuf.memory = V4L2_MEMORY_DMABUF; + dqbuf.m.planes = &tmp_plane; + dqbuf.length = 1; + if (ioctl(fd, VIDIOC_DQBUF, &dqbuf) < 0) { + log_error("JPEG standalone: VIDIOC_DQBUF failed: %s", strerror(errno)); + ret = -errno; + break; + } + + if (f == 0) { + // Discard the first frame to flush any stale buffer, then re-queue. + if (ioctl(fd, VIDIOC_QBUF, &dqbuf) < 0) { + log_error("JPEG standalone: VIDIOC_QBUF (warmup) failed: %s", strerror(errno)); + ret = -errno; + break; + } + continue; + } + + // f == 1: encode this frame as JPEG. + MB_BLK blk = RK_MPI_MMZ_Fd2Handle(tmp_plane.m.fd); + VIDEO_FRAME_INFO_S stFrame; + memset(&stFrame, 0, sizeof(stFrame)); + stFrame.stVFrame.pMbBlk = blk; + stFrame.stVFrame.u32Width = width; + stFrame.stVFrame.u32Height = height; + stFrame.stVFrame.u32VirWidth = RK_ALIGN_16(width); + stFrame.stVFrame.u32VirHeight = RK_ALIGN_16(height); + stFrame.stVFrame.u32TimeRef = f; + stFrame.stVFrame.u64PTS = get_us(); + stFrame.stVFrame.enPixelFormat = RK_FMT_YUV422_YUYV; + stFrame.stVFrame.enCompressMode = COMPRESS_MODE_NONE; + + ret = do_jpeg_encode_frame(&stFrame, width, height, out_buf, out_len); + + // Re-queue the buffer before tearing down. + ioctl(fd, VIDIOC_QBUF, &dqbuf); + } + } + + ioctl(fd, VIDIOC_STREAMOFF, &type); + +cleanup: + { + struct v4l2_requestbuffers req_free; + memset(&req_free, 0, sizeof(req_free)); + req_free.count = 0; + req_free.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + req_free.memory = V4L2_MEMORY_DMABUF; + ioctl(fd, VIDIOC_REQBUFS, &req_free); + } + + for (int i = 0; i < bufs_allocated; i++) { + if (bufs[i].mb_blk != NULL) + RK_MPI_MB_ReleaseMB(bufs[i].mb_blk); + } + + close(fd); + return ret; +} + int video_capture_jpeg(uint8_t **out_buf, size_t *out_len) { if (!detected_signal) return -ENODEV; + + // When no WebRTC client is streaming, run a self-contained V4L2 capture + // so that MQTT screenshot publishing works without an active connection. if (!get_streaming_flag()) - return -ENODATA; + return video_capture_jpeg_standalone(out_buf, out_len); pthread_mutex_lock(&jpeg_mutex); diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index a44f48453..a5091e0b0 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -685,6 +685,7 @@ "mqtt_saved_error": "Failed to save MQTT settings: {error}", "mqtt_saved_success": "MQTT settings saved. Reconnecting to broker…", "mqtt_screenshot_interval_description": "Publish a JPEG screenshot to MQTT every N seconds (0 = disabled). When Remote Control is enabled, a capture button is also available in Home Assistant.", + "mqtt_screenshot_interval_sleep_warning": "HDMI Sleep Mode (Power Saving) is disabled while screenshot publishing is active.", "mqtt_screenshot_interval_title": "Screenshot Interval", "mqtt_section_advanced": "Advanced", "mqtt_section_advanced_description": "Fine-tune MQTT behavior and permissions", diff --git a/ui/src/routes/devices.$id.settings.mqtt.tsx b/ui/src/routes/devices.$id.settings.mqtt.tsx index e374fb532..8dd715ff7 100644 --- a/ui/src/routes/devices.$id.settings.mqtt.tsx +++ b/ui/src/routes/devices.$id.settings.mqtt.tsx @@ -12,6 +12,7 @@ import { Button } from "@components/Button"; import LoadingSpinner from "@components/LoadingSpinner"; import notifications from "@/notifications"; import { m } from "@localizations/messages"; +import { GridCard } from "@components/Card"; interface MQTTSettings { enabled: boolean; @@ -458,6 +459,27 @@ export default function SettingsMqttRoute() { } /> + {settings.screenshot_interval_sec > 0 && ( + +
+ + + +

+ {m.mqtt_screenshot_interval_sleep_warning()} +

+
+
+ )} {/* --- Advanced (only when ATX extension is active) --- */} diff --git a/video.go b/video.go index 8084f3749..4c250840e 100644 --- a/video.go +++ b/video.go @@ -116,6 +116,11 @@ func doVideoSleepModeTicker(ctx context.Context, duration time.Duration) { continue } + if config.MqttConfig != nil && config.MqttConfig.ScreenshotIntervalSec > 0 { + nativeLogger.Info().Msg("not going to enter HDMI sleep mode because MQTT screenshot publishing is active") + continue + } + nativeLogger.Trace().Msg("entering HDMI sleep mode") _ = nativeInstance.VideoSetSleepMode(true) case <-ctx.Done(): From b0ad116c4a9c469fbbfdeb39652f06f910a0c036 Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Tue, 12 May 2026 20:05:50 +0000 Subject: [PATCH 03/10] fix(mqtt): wake HDMI capture chip from sleep for on-demand screenshots When screenshot_interval_sec is 0, the periodic publish is disabled but the HA 'Capture Screenshot' button is still available. In that case the HDMI sleep mode can engage after the idle timeout, causing the standalone V4L2 capture to time out when the button is pressed. publishScreenshot() now: - Checks whether the capture chip is in sleep mode before attempting capture. - If sleeping, disables sleep mode and waits 5 s for the HDMI signal to re-lock (matching the same delay used in VideoStart). - Defers a call to startVideoSleepModeTicker() so the chip returns to sleep after its normal idle timeout once the capture is complete. Since publishScreenshot is always invoked in its own goroutine the 5-second wait does not block any other path. --- mqtt_publish.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mqtt_publish.go b/mqtt_publish.go index c7744afca..347cf52d6 100644 --- a/mqtt_publish.go +++ b/mqtt_publish.go @@ -590,6 +590,24 @@ func (m *MQTTManager) publishScreenshot() { mqttLogger.Debug().Msg("skipping screenshot: video not ready") return } + + // If the capture chip is in HDMI sleep mode, wake it and wait for the + // HDMI signal to re-lock before attempting capture. The sleep mode ticker + // is restarted afterward so the chip returns to sleep after its normal + // idle timeout. + sleeping, _ := nativeInstance.VideoGetSleepMode() + if sleeping { + mqttLogger.Info().Msg("waking capture chip from sleep mode for screenshot") + if err := nativeInstance.VideoSetSleepMode(false); err != nil { + mqttLogger.Warn().Err(err).Msg("failed to wake capture chip for screenshot") + return + } + // Allow the HDMI capture chip time to re-lock the signal. + // This mirrors the delay used in VideoStart() for the same reason. + time.Sleep(5 * time.Second) + defer startVideoSleepModeTicker() + } + data, err := nativeInstance.VideoCaptureJPEG() if err != nil { mqttLogger.Warn().Err(err).Msg("failed to capture JPEG for MQTT screenshot") From 209353f4b0882f6d4e34ff14f31575e651c452c6 Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Tue, 12 May 2026 20:34:43 +0000 Subject: [PATCH 04/10] fix(mqtt): always reset sleep ticker after on-demand screenshot capture Previously, startVideoSleepModeTicker() was only called when the chip was woken from sleep. If the button was pressed while the chip was already awake (e.g. shortly after a previous capture), the idle countdown was not reset and the chip could sleep sooner than expected. Move the defer startVideoSleepModeTicker() call outside the sleeping block so the countdown is reset on every capture attempt, regardless of whether the chip needed waking. Repeated button presses will continuously push back the sleep deadline, which is the expected behaviour when the user is actively requesting screenshots. --- mqtt_publish.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mqtt_publish.go b/mqtt_publish.go index 347cf52d6..fa227312c 100644 --- a/mqtt_publish.go +++ b/mqtt_publish.go @@ -592,9 +592,7 @@ func (m *MQTTManager) publishScreenshot() { } // If the capture chip is in HDMI sleep mode, wake it and wait for the - // HDMI signal to re-lock before attempting capture. The sleep mode ticker - // is restarted afterward so the chip returns to sleep after its normal - // idle timeout. + // HDMI signal to re-lock before attempting capture. sleeping, _ := nativeInstance.VideoGetSleepMode() if sleeping { mqttLogger.Info().Msg("waking capture chip from sleep mode for screenshot") @@ -605,9 +603,15 @@ func (m *MQTTManager) publishScreenshot() { // Allow the HDMI capture chip time to re-lock the signal. // This mirrors the delay used in VideoStart() for the same reason. time.Sleep(5 * time.Second) - defer startVideoSleepModeTicker() } + // Always restart the sleep mode ticker after a capture attempt so that + // repeated button presses reset the idle countdown. This means the chip + // will not sleep until the full idle timeout has elapsed since the last + // capture, which is the expected behaviour when the user is actively + // requesting screenshots. + defer startVideoSleepModeTicker() + data, err := nativeInstance.VideoCaptureJPEG() if err != nil { mqttLogger.Warn().Err(err).Msg("failed to capture JPEG for MQTT screenshot") From 8db5ddb5ed65d2c5d79d20a104098bca6efdd94c Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Thu, 14 May 2026 16:06:59 +0000 Subject: [PATCH 05/10] fix(ui): update UI, detach from controls --- config.go | 14 +-- internal/native/cgo/video.c | 2 +- mqtt.go | 33 +++--- mqtt_commands.go | 4 +- mqtt_discovery.go | 4 +- ui/localization/messages/en.json | 11 +- ui/src/routes/devices.$id.settings.mqtt.tsx | 108 ++++++++++++++------ video.go | 2 +- 8 files changed, 118 insertions(+), 60 deletions(-) diff --git a/config.go b/config.go index ce7d14cc0..6cd219314 100644 --- a/config.go +++ b/config.go @@ -205,12 +205,14 @@ func getDefaultConfig() Config { VideoQualityFactor: 1.0, VideoCodecPreference: "auto", MqttConfig: &MQTTConfig{ - Enabled: false, - Port: 1883, - BaseTopic: "jetkvm", - EnableHADiscovery: false, - EnableActions: true, - DebounceMs: 500, + Enabled: false, + Port: 1883, + BaseTopic: "jetkvm", + EnableHADiscovery: false, + EnableActions: true, + DebounceMs: 500, + PublishScreenshot: false, + PublishScreenshotButton: false, }, } } diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 7ab359da8..94d242132 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -528,7 +528,7 @@ static int do_jpeg_encode_frame(VIDEO_FRAME_INFO_S *frame, uint32_t width, uint3 VENC_CHN_ATTR_S attr; memset(&attr, 0, sizeof(attr)); attr.stRcAttr.enRcMode = VENC_RC_MODE_MJPEGFIXQP; - attr.stRcAttr.stMjpegFixQp.u32Qfactor = 90; + attr.stRcAttr.stMjpegFixQp.u32Qfactor = 85; attr.stVencAttr.enType = RK_VIDEO_ID_MJPEG; attr.stVencAttr.enPixelFormat = RK_FMT_YUV422_YUYV; attr.stVencAttr.u32PicWidth = width; diff --git a/mqtt.go b/mqtt.go index 6b0bbc208..b75da675c 100644 --- a/mqtt.go +++ b/mqtt.go @@ -21,18 +21,20 @@ var mqttLogger = logging.GetSubsystemLogger("mqtt") const publishTimeout = 5 * time.Second type MQTTConfig struct { - Enabled bool `json:"enabled"` - Broker string `json:"broker"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - BaseTopic string `json:"base_topic"` - UseTLS bool `json:"use_tls"` - TLSInsecure bool `json:"tls_insecure"` - EnableHADiscovery bool `json:"enable_ha_discovery"` - EnableActions bool `json:"enable_actions"` - DebounceMs int `json:"debounce_ms"` - ScreenshotIntervalSec int `json:"screenshot_interval_sec"` + Enabled bool `json:"enabled"` + Broker string `json:"broker"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + BaseTopic string `json:"base_topic"` + UseTLS bool `json:"use_tls"` + TLSInsecure bool `json:"tls_insecure"` + EnableHADiscovery bool `json:"enable_ha_discovery"` + EnableActions bool `json:"enable_actions"` + DebounceMs int `json:"debounce_ms"` + PublishScreenshot bool `json:"publish_screenshot"` + ScreenshotIntervalSec int `json:"screenshot_interval_sec"` + PublishScreenshotButton bool `json:"publish_screenshot_button"` } var mqttManager *MQTTManager @@ -215,7 +217,7 @@ func (m *MQTTManager) onConnect(client mqtt.Client) { m.publishExtendedStates() // Start periodic screenshot publishing if configured - if config.MqttConfig != nil && config.MqttConfig.ScreenshotIntervalSec > 0 { + if config.MqttConfig != nil && config.MqttConfig.PublishScreenshot && config.MqttConfig.ScreenshotIntervalSec >= 10 { m.startScreenshotTicker() } } @@ -289,10 +291,11 @@ func (m *MQTTManager) publishBinary(topic string, payload []byte, retained bool) } // startScreenshotTicker starts a background goroutine that periodically -// captures and publishes a JPEG screenshot if ScreenshotIntervalSec > 0. +// captures and publishes a JPEG screenshot if PublishScreenshot is enabled +// and ScreenshotIntervalSec is at least 10. func (m *MQTTManager) startScreenshotTicker() { interval := config.MqttConfig.ScreenshotIntervalSec - if interval <= 0 { + if !config.MqttConfig.PublishScreenshot || interval < 10 { return } go func() { diff --git a/mqtt_commands.go b/mqtt_commands.go index 50b4fe49d..0edb2d212 100644 --- a/mqtt_commands.go +++ b/mqtt_commands.go @@ -226,8 +226,8 @@ func (m *MQTTManager) handleVirtualMediaCommand(client mqtt.Client, msg mqtt.Mes } func (m *MQTTManager) handleScreenshotCommand(client mqtt.Client, msg mqtt.Message) { - if !m.actionsAllowed() { - mqttLogger.Warn().Msg("screenshot command rejected: actions are disabled") + if config.MqttConfig == nil || !config.MqttConfig.PublishScreenshotButton { + mqttLogger.Warn().Msg("screenshot command rejected: screenshot button is disabled") return } payload := strings.TrimSpace(string(msg.Payload())) diff --git a/mqtt_discovery.go b/mqtt_discovery.go index 6a5981908..46d2124b6 100644 --- a/mqtt_discovery.go +++ b/mqtt_discovery.go @@ -429,8 +429,8 @@ func (m *MQTTManager) publishHADiscovery() { Device: device, }) - // Screenshot Capture Button: only when actions enabled. - if actionsEnabled { + // Screenshot Capture Button: published when publish_screenshot_button is enabled. + if config.MqttConfig != nil && config.MqttConfig.PublishScreenshotButton { m.publishDiscovery("button", "screenshot", haDiscoveryPayload{ Name: "Capture Screenshot", UniqueID: fmt.Sprintf("jetkvm_%s_screenshot_button", m.deviceID), diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index a5091e0b0..f7ba0c1a2 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -679,14 +679,19 @@ "mqtt_port_custom": "Custom", "mqtt_port_description": "Uses 1883 by default, or 8883 when TLS is enabled", "mqtt_port_label": "Port", + "mqtt_publish_screenshot_button_description": "Register a \"Capture Screenshot\" button in Home Assistant for on-demand screenshots.", + "mqtt_publish_screenshot_button_title": "Register Button", + "mqtt_publish_screenshot_description": "Automatically capture and publish a screenshot to MQTT on a set schedule.", + "mqtt_publish_screenshot_title": "Scheduled Screenshots", "mqtt_remote_control_description": "Allow MQTT clients to trigger power control, reboot, and other actions", "mqtt_remote_control_title": "Remote Control", "mqtt_save_button": "Save & Reconnect", "mqtt_saved_error": "Failed to save MQTT settings: {error}", "mqtt_saved_success": "MQTT settings saved. Reconnecting to broker…", - "mqtt_screenshot_interval_description": "Publish a JPEG screenshot to MQTT every N seconds (0 = disabled). When Remote Control is enabled, a capture button is also available in Home Assistant.", + "mqtt_screenshot_interval_description": "Capture and publish a screenshot every N seconds. Minimum 10, maximum 3600.", + "mqtt_screenshot_interval_range_error": "Interval must be between 10 and 3600 seconds.", "mqtt_screenshot_interval_sleep_warning": "HDMI Sleep Mode (Power Saving) is disabled while screenshot publishing is active.", - "mqtt_screenshot_interval_title": "Screenshot Interval", + "mqtt_screenshot_interval_title": "Interval (seconds)", "mqtt_section_advanced": "Advanced", "mqtt_section_advanced_description": "Fine-tune MQTT behavior and permissions", "mqtt_section_auth": "Authentication", @@ -695,6 +700,8 @@ "mqtt_section_connection_description": "MQTT broker connection settings", "mqtt_section_homeassistant": "Home Assistant", "mqtt_section_homeassistant_description": "Automatic device registration and topic settings", + "mqtt_section_screenshot": "Screenshot", + "mqtt_section_screenshot_description": "Capture and automatically publish screenshots to your MQTT broker.", "mqtt_status_connected": "Connected", "mqtt_status_connecting": "Connecting…", "mqtt_status_disconnected": "Disconnected", diff --git a/ui/src/routes/devices.$id.settings.mqtt.tsx b/ui/src/routes/devices.$id.settings.mqtt.tsx index 8dd715ff7..72bf14b08 100644 --- a/ui/src/routes/devices.$id.settings.mqtt.tsx +++ b/ui/src/routes/devices.$id.settings.mqtt.tsx @@ -24,7 +24,9 @@ interface MQTTSettings { use_tls: boolean; tls_insecure: boolean; enable_ha_discovery: boolean; + publish_screenshot: boolean; screenshot_interval_sec: number; + publish_screenshot_button: boolean; enable_actions: boolean; debounce_ms: number; } @@ -75,7 +77,9 @@ export default function SettingsMqttRoute() { use_tls: false, tls_insecure: false, enable_ha_discovery: true, - screenshot_interval_sec: 0, + publish_screenshot: false, + screenshot_interval_sec: 60, + publish_screenshot_button: false, enable_actions: true, debounce_ms: 500, }); @@ -149,6 +153,11 @@ export default function SettingsMqttRoute() { if (settings.enabled && !settings.broker.trim()) { errs.broker = m.mqtt_error_broker_required(); } + if (settings.publish_screenshot) { + if (settings.screenshot_interval_sec < 10 || settings.screenshot_interval_sec > 3600) { + errs.screenshot_interval_sec = m.mqtt_screenshot_interval_range_error(); + } + } setFieldErrors(errs); return Object.keys(errs).length === 0; }, [settings]); @@ -444,42 +453,79 @@ export default function SettingsMqttRoute() { /> )} + + {/* --- Screenshot --- */} +
+ + - - updateField("screenshot_interval_sec", parseInt(e.target.value) || 0) - } + updateField("publish_screenshot", e.target.checked)} /> - {settings.screenshot_interval_sec > 0 && ( - -
- - - -

- {m.mqtt_screenshot_interval_sleep_warning()} -

-
-
+ + {settings.publish_screenshot && ( + + + { + updateField("screenshot_interval_sec", parseInt(e.target.value) || 60); + if (fieldErrors.screenshot_interval_sec) + setFieldErrors(prev => ({ ...prev, screenshot_interval_sec: "" })); + }} + /> + + {settings.screenshot_interval_sec > 0 && ( + +
+ + + +

+ {m.mqtt_screenshot_interval_sleep_warning()} +

+
+
+ )} +
)} + + + updateField("publish_screenshot_button", e.target.checked)} + /> +
{/* --- Advanced (only when ATX extension is active) --- */} diff --git a/video.go b/video.go index 4c250840e..997befabf 100644 --- a/video.go +++ b/video.go @@ -116,7 +116,7 @@ func doVideoSleepModeTicker(ctx context.Context, duration time.Duration) { continue } - if config.MqttConfig != nil && config.MqttConfig.ScreenshotIntervalSec > 0 { + if config.MqttConfig != nil && config.MqttConfig.PublishScreenshot && config.MqttConfig.ScreenshotIntervalSec >= 10 { nativeLogger.Info().Msg("not going to enter HDMI sleep mode because MQTT screenshot publishing is active") continue } From fe73753c1f4c44dd412704d98b7efea361e92f36 Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Fri, 15 May 2026 14:39:48 +0000 Subject: [PATCH 06/10] fix(video): replace input_buffer_count literal with INPUT_BUFFER_COUNT macro Using a const local variable as an array size causes a VLA (variable-length array) in C, which is a compile-time ambiguity and can lead to out-of-bounds access if the compiler does not treat it as a compile-time constant. Replace with a #define so that all buffer and loop declarations that reference the buffer count are unambiguously fixed-size constants. --- internal/native/cgo/video.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 7a077bfd0..d10f29769 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -344,14 +344,14 @@ struct buffer MB_BLK mb_blk; }; -const int input_buffer_count = 3; +#define INPUT_BUFFER_COUNT 3 static int32_t buf_init() { MB_POOL_CONFIG_S stMbPoolCfg; memset(&stMbPoolCfg, 0, sizeof(MB_POOL_CONFIG_S)); stMbPoolCfg.u64MBSize = 1920 * 1080 * 3; // max resolution - stMbPoolCfg.u32MBCnt = input_buffer_count; + stMbPoolCfg.u32MBCnt = INPUT_BUFFER_COUNT; stMbPoolCfg.enAllocType = MB_ALLOC_TYPE_DMA; stMbPoolCfg.bPreAlloc = RK_TRUE; memPool = RK_MPI_MB_CreatePool(&stMbPoolCfg); @@ -665,7 +665,7 @@ static int video_capture_jpeg_standalone(uint8_t **out_buf, size_t *out_len) struct v4l2_requestbuffers req; memset(&req, 0, sizeof(req)); - req.count = input_buffer_count; + req.count = INPUT_BUFFER_COUNT; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; req.memory = V4L2_MEMORY_DMABUF; if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) { @@ -674,11 +674,11 @@ static int video_capture_jpeg_standalone(uint8_t **out_buf, size_t *out_len) return -errno; } - struct buffer bufs[3]; + struct buffer bufs[INPUT_BUFFER_COUNT]; memset(bufs, 0, sizeof(bufs)); int bufs_allocated = 0; - for (int i = 0; i < input_buffer_count; i++) { + for (int i = 0; i < INPUT_BUFFER_COUNT; i++) { struct v4l2_plane *plane = &bufs[i].plane_buffer; memset(plane, 0, sizeof(*plane)); @@ -705,7 +705,7 @@ static int video_capture_jpeg_standalone(uint8_t **out_buf, size_t *out_len) plane->m.fd = RK_MPI_MB_Handle2Fd(blk); } - for (int i = 0; i < input_buffer_count; i++) { + for (int i = 0; i < INPUT_BUFFER_COUNT; i++) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = type; @@ -909,7 +909,7 @@ void *run_video_stream(void *arg) struct v4l2_buffer buf; struct v4l2_requestbuffers req; - req.count = input_buffer_count; + req.count = INPUT_BUFFER_COUNT; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; req.memory = V4L2_MEMORY_DMABUF; @@ -921,10 +921,10 @@ void *run_video_stream(void *arg) } log_info("VIDIOC_REQBUFS successful"); - struct buffer buffers[3] = {}; + struct buffer buffers[INPUT_BUFFER_COUNT] = {}; log_info("allocated buffers"); - for (int i = 0; i < input_buffer_count; i++) + for (int i = 0; i < INPUT_BUFFER_COUNT; i++) { struct v4l2_plane *planes_buffer = &buffers[i].plane_buffer; memset(planes_buffer, 0, sizeof(struct v4l2_plane)); @@ -970,7 +970,7 @@ void *run_video_stream(void *arg) planes_buffer->m.fd = buf_fd; } - for (int i = 0; i < input_buffer_count; ++i) + for (int i = 0; i < INPUT_BUFFER_COUNT; ++i) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); @@ -1110,7 +1110,7 @@ void *run_video_stream(void *arg) venc_stop(); - for (int i = 0; i < input_buffer_count; i++) + for (int i = 0; i < INPUT_BUFFER_COUNT; i++) { if (buffers[i].mb_blk != NULL) { From 5add90e2fbd52f5ab2286a77246198f6c2fc24aa Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Fri, 15 May 2026 14:41:38 +0000 Subject: [PATCH 07/10] fix(video): guard jpeg_requested read with mutex to prevent data race jpeg_requested is written by video_capture_jpeg() under jpeg_mutex from the caller's thread, but was read in run_video_stream()'s per-frame loop without holding the mutex. This is a data race: the compiler and CPU are free to cache or reorder the read, producing incorrect behaviour. Fix by reading jpeg_requested under the same mutex that all writers already hold. The lock is uncontested in the common case (no JPEG capture pending), so the per-frame overhead is negligible. --- internal/native/cgo/video.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index d10f29769..1ca0285dc 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -1081,9 +1081,16 @@ void *run_video_stream(void *arg) num++; - // Capture JPEG if requested (before returning the V4L2 buffer) - if (jpeg_requested) { - do_jpeg_capture(&stFrame, width, height); + // Capture JPEG if requested (before returning the V4L2 buffer). + // Read jpeg_requested under the mutex to avoid a data race with + // video_capture_jpeg() which writes it from another thread. + { + pthread_mutex_lock(&jpeg_mutex); + bool do_capture = jpeg_requested; + pthread_mutex_unlock(&jpeg_mutex); + if (do_capture) { + do_jpeg_capture(&stFrame, width, height); + } } if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0) From 5fea33f858415bbf53f89ebe18552532d6b1e628 Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Fri, 15 May 2026 14:41:52 +0000 Subject: [PATCH 08/10] fix(mqtt): prevent duplicate screenshot ticker goroutines on reconnect startScreenshotTicker() is called from the reconnect handler, so every reconnection to the broker would spawn an additional ticker goroutine. Over time this causes screenshot publishes to pile up and wastes resources. Add a screenshotRunning atomic.Bool guard: if a ticker goroutine is already running the function returns early; the goroutine clears the flag when it exits so a fresh goroutine can start after a genuine restart. --- mqtt.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mqtt.go b/mqtt.go index b75da675c..616804673 100644 --- a/mqtt.go +++ b/mqtt.go @@ -65,6 +65,10 @@ type MQTTManager struct { // Cached update state to avoid calling getUpdateStatus on every tick. lastUpdateCheck time.Time lastUpdatePayload *mqttUpdateState + + // screenshotRunning is set when the screenshot ticker goroutine is active. + // Prevents spawning duplicate publishers on MQTT reconnects. + screenshotRunning atomic.Bool } type mqttStatusPayload struct { @@ -293,12 +297,18 @@ func (m *MQTTManager) publishBinary(topic string, payload []byte, retained bool) // startScreenshotTicker starts a background goroutine that periodically // captures and publishes a JPEG screenshot if PublishScreenshot is enabled // and ScreenshotIntervalSec is at least 10. +// Safe to call on every reconnect: returns immediately if a ticker is already running. func (m *MQTTManager) startScreenshotTicker() { interval := config.MqttConfig.ScreenshotIntervalSec if !config.MqttConfig.PublishScreenshot || interval < 10 { return } + // Swap true; if it was already true a goroutine is still running — skip. + if m.screenshotRunning.Swap(true) { + return + } go func() { + defer m.screenshotRunning.Store(false) ticker := time.NewTicker(time.Duration(interval) * time.Second) defer ticker.Stop() for { From 79a454ccf40c171438a99e26667110421af2800f Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Fri, 15 May 2026 14:42:04 +0000 Subject: [PATCH 09/10] fix(config): add missing default value for ScreenshotIntervalSec getDefaultConfig() initialised MqttConfig with PublishScreenshot and PublishScreenshotButton but omitted ScreenshotIntervalSec, leaving it as 0. A zero interval causes the screenshot ticker to fire as fast as possible, flooding the broker on first run before the user saves a configuration. Set the default to 60 seconds to match the documented behaviour. --- config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config.go b/config.go index c0141c28c..27930a00f 100644 --- a/config.go +++ b/config.go @@ -212,6 +212,7 @@ func getDefaultConfig() Config { EnableActions: true, DebounceMs: 500, PublishScreenshot: false, + ScreenshotIntervalSec: 60, PublishScreenshotButton: false, }, } From b197de53ca9c07e5626627328d51dfd7de06f50a Mon Sep 17 00:00:00 2001 From: Lewis Roberts Date: Fri, 15 May 2026 14:42:25 +0000 Subject: [PATCH 10/10] fix(ui): specify radix in parseInt calls and handle NaN input safely All three parseInt() calls in the MQTT settings form (port, screenshot interval, debounce ms) were missing the required radix argument. Without an explicit radix, some environments may parse a leading-zero value as octal, producing a silently wrong number. The fallback pattern `parseInt(...) || default` also silently replaces the value 0 with the default, so entering 0 for debounce_ms was impossible. Fix all three handlers: - Pass radix 10 to parseInt(). - Use isNaN() to detect invalid input and fall back to the default only in that case, allowing 0 to be stored as a valid value. --- ui/src/routes/devices.$id.settings.mqtt.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/src/routes/devices.$id.settings.mqtt.tsx b/ui/src/routes/devices.$id.settings.mqtt.tsx index 72bf14b08..358b0d064 100644 --- a/ui/src/routes/devices.$id.settings.mqtt.tsx +++ b/ui/src/routes/devices.$id.settings.mqtt.tsx @@ -341,7 +341,10 @@ export default function SettingsMqttRoute() { type="number" placeholder={defaultPortForTLS(settings.use_tls).toString()} value={settings.port.toString()} - onChange={e => updateField("port", parseInt(e.target.value) || DEFAULT_PORT)} + onChange={e => { + const parsed = parseInt(e.target.value, 10); + updateField("port", isNaN(parsed) ? DEFAULT_PORT : parsed); + }} /> )} @@ -487,7 +490,8 @@ export default function SettingsMqttRoute() { value={settings.screenshot_interval_sec.toString()} error={fieldErrors.screenshot_interval_sec} onChange={e => { - updateField("screenshot_interval_sec", parseInt(e.target.value) || 60); + const parsed = parseInt(e.target.value, 10); + updateField("screenshot_interval_sec", isNaN(parsed) ? 60 : parsed); if (fieldErrors.screenshot_interval_sec) setFieldErrors(prev => ({ ...prev, screenshot_interval_sec: "" })); }} @@ -546,7 +550,10 @@ export default function SettingsMqttRoute() { type="number" placeholder="500" value={settings.debounce_ms.toString()} - onChange={e => updateField("debounce_ms", parseInt(e.target.value) || 0)} + onChange={e => { + const parsed = parseInt(e.target.value, 10); + updateField("debounce_ms", isNaN(parsed) ? 0 : parsed); + }} />