From ffc26ac4e75eb087e0e32a6992d6f00d01ba79b9 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 11 Nov 2025 15:53:40 +0000 Subject: [PATCH 01/25] wip: move cgo to separate process --- cmd/main.go | 20 +- go.mod | 3 + go.sum | 6 + internal/native/cgo/test.patch | 210 ++++++++ internal/native/grpc_server.go | 387 ++++++++++++++ internal/native/interface.go | 37 ++ internal/native/ipc.go | 293 ++++++++++ internal/native/native.go | 6 +- internal/native/proto/README.md | 33 ++ internal/native/proto/native.pb.go | 596 +++++++++++++++++++++ internal/native/proto/native.proto | 262 +++++++++ internal/native/proto/native_grpc.pb.go | 492 +++++++++++++++++ internal/native/proxy.go | 682 ++++++++++++++++++++++++ internal/supervisor/consts.go | 9 + native.go | 13 +- native_process.go | 528 ++++++++++++++++++ scripts/generate_proto.sh | 44 ++ scripts/release.sh | 134 +++++ 18 files changed, 3748 insertions(+), 7 deletions(-) create mode 100644 internal/native/cgo/test.patch create mode 100644 internal/native/grpc_server.go create mode 100644 internal/native/interface.go create mode 100644 internal/native/ipc.go create mode 100644 internal/native/proto/README.md create mode 100644 internal/native/proto/native.pb.go create mode 100644 internal/native/proto/native.proto create mode 100644 internal/native/proto/native_grpc.pb.go create mode 100644 internal/native/proxy.go create mode 100644 internal/supervisor/consts.go create mode 100644 native_process.go create mode 100755 scripts/generate_proto.sh create mode 100644 scripts/release.sh diff --git a/cmd/main.go b/cmd/main.go index 9a1e18997..c9d48362d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,19 +17,35 @@ import ( const ( envChildID = "JETKVM_CHILD_ID" + envSubcomponent = "JETKVM_SUBCOMPONENT" errorDumpDir = "/userdata/jetkvm/crashdump" errorDumpLastFile = "last-crash.log" errorDumpTemplate = "jetkvm-%s.log" ) +var ( + subcomponent string +) + func program() { - gspt.SetProcTitle(os.Args[0] + " [app]") - kvm.Main() + subcomponentOverride := os.Getenv(envSubcomponent) + if subcomponentOverride != "" { + subcomponent = subcomponentOverride + } + switch subcomponent { + case "native": + gspt.SetProcTitle(os.Args[0] + " [native]") + kvm.RunNativeProcess() + default: + gspt.SetProcTitle(os.Args[0] + " [app]") + kvm.Main() + } } func main() { versionPtr := flag.Bool("version", false, "print version and exit") versionJSONPtr := flag.Bool("version-json", false, "print version as json and exit") + flag.StringVar(&subcomponent, "subcomponent", "", "subcomponent to run") flag.Parse() if *versionPtr || *versionJSONPtr { diff --git a/go.mod b/go.mod index 404215b0f..bcfbf2cc1 100644 --- a/go.mod +++ b/go.mod @@ -97,6 +97,9 @@ require ( golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1cb90138c..45ce4685e 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,12 @@ golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/native/cgo/test.patch b/internal/native/cgo/test.patch new file mode 100644 index 000000000..5f32a357b --- /dev/null +++ b/internal/native/cgo/test.patch @@ -0,0 +1,210 @@ +diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c +index 2a4a034..760621a 100644 +--- a/internal/native/cgo/video.c ++++ b/internal/native/cgo/video.c +@@ -354,6 +354,10 @@ bool detected_signal = false, streaming_flag = false, streaming_stopped = true; + pthread_t *streaming_thread = NULL; + pthread_mutex_t streaming_mutex = PTHREAD_MUTEX_INITIALIZER; + ++// Diagnostic tracking for validation ++static uint64_t last_close_time = 0; ++static int consecutive_failures = 0; ++ + bool get_streaming_flag() + { + log_info("getting streaming flag"); +@@ -395,6 +399,12 @@ void *run_video_stream(void *arg) + continue; + } + ++ // Log attempt to open with timing info ++ RK_U64 time_since_close = last_close_time > 0 ? (get_us() - last_close_time) : 0; ++ log_info("[DIAG] Attempting to open %s (time_since_last_close=%llu us)", ++ VIDEO_DEV, time_since_close); ++ ++ RK_U64 open_start_time = get_us(); + int video_dev_fd = open(VIDEO_DEV, O_RDWR); + if (video_dev_fd < 0) + { +@@ -402,7 +412,9 @@ void *run_video_stream(void *arg) + usleep(1000000); + continue; + } +- log_info("opened video capture device %s", VIDEO_DEV); ++ RK_U64 open_end_time = get_us(); ++ log_info("[DIAG] opened video capture device %s in %llu us", ++ VIDEO_DEV, open_end_time - open_start_time); + + uint32_t width = detected_width; + uint32_t height = detected_height; +@@ -414,14 +426,45 @@ void *run_video_stream(void *arg) + fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUYV; + fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; + ++ // Probe device state before attempting format set ++ struct v4l2_format query_fmt; ++ memset(&query_fmt, 0, sizeof(query_fmt)); ++ query_fmt.type = type; ++ int query_ret = ioctl(video_dev_fd, VIDIOC_G_FMT, &query_fmt); ++ log_info("[DIAG] VIDIOC_G_FMT probe: ret=%d, errno=%d (%s)", ++ query_ret, query_ret < 0 ? errno : 0, ++ query_ret < 0 ? strerror(errno) : "OK"); ++ ++ RK_U64 set_fmt_start_time = get_us(); ++ log_info("[DIAG] Attempting VIDIOC_S_FMT: %ux%u, time_since_open=%llu us", ++ width, height, set_fmt_start_time - open_end_time); ++ + if (ioctl(video_dev_fd, VIDIOC_S_FMT, &fmt) < 0) + { +- log_error("Set format fail: %s", strerror(errno)); ++ RK_U64 failure_time = get_us(); ++ int saved_errno = errno; ++ consecutive_failures++; ++ ++ log_error("[DIAG] Set format fail: errno=%d (%s)", saved_errno, strerror(saved_errno)); ++ log_error("[DIAG] Failure context: consecutive_failures=%d, time_since_open=%llu us, " ++ "time_since_last_close=%llu us, resolution=%ux%u, streaming_flag=%d", ++ consecutive_failures, ++ failure_time - open_end_time, ++ last_close_time > 0 ? (open_start_time - last_close_time) : 0, ++ width, height, ++ streaming_flag); ++ + usleep(100000); // Sleep for 100 milliseconds + close(video_dev_fd); ++ last_close_time = get_us(); ++ log_info("[DIAG] Closed device after format failure at %llu us", last_close_time); + continue; + } + ++ // Success - reset failure counter ++ log_info("[DIAG] VIDIOC_S_FMT succeeded (previous consecutive failures: %d)", consecutive_failures); ++ consecutive_failures = 0; ++ + struct v4l2_buffer buf; + + struct v4l2_requestbuffers req; +@@ -601,9 +644,46 @@ void *run_video_stream(void *arg) + } + cleanup: + log_info("cleaning up video capture device %s", VIDEO_DEV); +- if (ioctl(video_dev_fd, VIDIOC_STREAMOFF, &type) < 0) ++ ++ RK_U64 streamoff_start = get_us(); ++ log_info("[DIAG] Attempting VIDIOC_STREAMOFF"); ++ ++ int streamoff_ret = ioctl(video_dev_fd, VIDIOC_STREAMOFF, &type); ++ RK_U64 streamoff_end = get_us(); ++ ++ if (streamoff_ret < 0) ++ { ++ log_error("[DIAG] VIDIOC_STREAMOFF failed: errno=%d (%s), duration=%llu us", ++ errno, strerror(errno), streamoff_end - streamoff_start); ++ } ++ else ++ { ++ log_info("[DIAG] VIDIOC_STREAMOFF succeeded in %llu us", ++ streamoff_end - streamoff_start); ++ } ++ ++ // VALIDATION TEST: Explicitly free V4L2 buffer queue ++ struct v4l2_requestbuffers req_free; ++ memset(&req_free, 0, sizeof(req_free)); ++ req_free.count = 0; // Tell driver to free all buffers ++ req_free.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ req_free.memory = V4L2_MEMORY_DMABUF; ++ ++ RK_U64 reqbufs_start = get_us(); ++ log_info("[DIAG] VALIDATION: Calling VIDIOC_REQBUFS(count=0) to free buffer queue"); ++ ++ int reqbufs_ret = ioctl(video_dev_fd, VIDIOC_REQBUFS, &req_free); ++ RK_U64 reqbufs_end = get_us(); ++ ++ if (reqbufs_ret < 0) ++ { ++ log_error("[DIAG] VALIDATION: REQBUFS(0) FAILED - errno=%d (%s), duration=%llu us", ++ errno, strerror(errno), reqbufs_end - reqbufs_start); ++ } ++ else + { +- log_error("VIDIOC_STREAMOFF failed: %s", strerror(errno)); ++ log_info("[DIAG] VALIDATION: REQBUFS(0) SUCCEEDED - freed buffers in %llu us", ++ reqbufs_end - reqbufs_start); + } + + venc_stop(); +@@ -617,9 +697,13 @@ void *run_video_stream(void *arg) + } + + log_info("closing video capture device %s", VIDEO_DEV); ++ RK_U64 close_start = get_us(); + close(video_dev_fd); ++ last_close_time = get_us(); ++ log_info("[DIAG] Device closed, took %llu us, timestamp=%llu", ++ last_close_time - close_start, last_close_time); + } +- ++ + log_info("video stream thread exiting"); + + streaming_stopped = true; +@@ -648,7 +732,7 @@ void video_shutdown() + RK_MPI_MB_DestroyPool(memPool); + } + log_info("Destroyed memory pool"); +- ++ + pthread_mutex_destroy(&streaming_mutex); + log_info("Destroyed streaming mutex"); + } +@@ -665,14 +749,14 @@ void video_start_streaming() + log_warn("video streaming already started"); + return; + } +- ++ + pthread_t *new_thread = malloc(sizeof(pthread_t)); + if (new_thread == NULL) + { + log_error("Failed to allocate memory for streaming thread"); + return; + } +- ++ + set_streaming_flag(true); + int result = pthread_create(new_thread, NULL, run_video_stream, NULL); + if (result != 0) +@@ -682,7 +766,7 @@ void video_start_streaming() + free(new_thread); + return; + } +- ++ + // Only set streaming_thread after successful creation + streaming_thread = new_thread; + } +@@ -693,7 +777,7 @@ void video_stop_streaming() + log_info("video streaming already stopped"); + return; + } +- ++ + log_info("stopping video streaming"); + set_streaming_flag(false); + +@@ -711,7 +795,7 @@ void video_stop_streaming() + free(streaming_thread); + streaming_thread = NULL; + +- log_info("video streaming stopped"); ++ log_info("video streaming stopped"); + } + + void video_restart_streaming() +@@ -818,4 +902,4 @@ void video_set_quality_factor(float factor) + + float video_get_quality_factor() { + return quality_factor; +-} +\ No newline at end of file ++} diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go new file mode 100644 index 000000000..b5fcce4ad --- /dev/null +++ b/internal/native/grpc_server.go @@ -0,0 +1,387 @@ +package native + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/jetkvm/kvm/internal/native/proto" +) + +// grpcServer wraps the Native instance and implements the gRPC service +type grpcServer struct { + pb.UnimplementedNativeServiceServer + native *Native + logger *zerolog.Logger + eventChs []chan *pb.Event + eventM sync.Mutex +} + +// NewGRPCServer creates a new gRPC server for the native service +func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { + s := &grpcServer{ + native: n, + logger: logger, + eventChs: make([]chan *pb.Event, 0), + } + + // Store original callbacks and wrap them to also broadcast events + originalVideoStateChange := n.onVideoStateChange + originalIndevEvent := n.onIndevEvent + originalRpcEvent := n.onRpcEvent + originalVideoFrameReceived := n.onVideoFrameReceived + + // Wrap callbacks to both call original and broadcast events + n.onVideoStateChange = func(state VideoState) { + if originalVideoStateChange != nil { + originalVideoStateChange(state) + } + event := &pb.Event{ + Type: "video_state_change", + VideoState: &pb.VideoState{ + Ready: state.Ready, + Error: state.Error, + Width: int32(state.Width), + Height: int32(state.Height), + FramePerSecond: state.FramePerSecond, + }, + } + s.broadcastEvent(event) + } + + n.onIndevEvent = func(event string) { + if originalIndevEvent != nil { + originalIndevEvent(event) + } + s.broadcastEvent(&pb.Event{ + Type: "indev_event", + IndevEvent: event, + }) + } + + n.onRpcEvent = func(event string) { + if originalRpcEvent != nil { + originalRpcEvent(event) + } + s.broadcastEvent(&pb.Event{ + Type: "rpc_event", + RpcEvent: event, + }) + } + + n.onVideoFrameReceived = func(frame []byte, duration time.Duration) { + if originalVideoFrameReceived != nil { + originalVideoFrameReceived(frame, duration) + } + s.broadcastEvent(&pb.Event{ + Type: "video_frame", + VideoFrame: &pb.VideoFrame{ + Frame: frame, + DurationNs: duration.Nanoseconds(), + }, + }) + } + + return s +} + +func (s *grpcServer) broadcastEvent(event *pb.Event) { + s.eventM.Lock() + defer s.eventM.Unlock() + + for _, ch := range s.eventChs { + select { + case ch <- event: + default: + // Channel full, skip + } + } +} + +// Video methods +func (s *grpcServer) VideoSetSleepMode(ctx context.Context, req *pb.VideoSetSleepModeRequest) (*pb.Empty, error) { + if err := s.native.VideoSetSleepMode(req.Enabled); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetSleepMode(ctx context.Context, req *pb.Empty) (*pb.VideoGetSleepModeResponse, error) { + enabled, err := s.native.VideoGetSleepMode() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetSleepModeResponse{Enabled: enabled}, nil +} + +func (s *grpcServer) VideoSleepModeSupported(ctx context.Context, req *pb.Empty) (*pb.VideoSleepModeSupportedResponse, error) { + return &pb.VideoSleepModeSupportedResponse{Supported: s.native.VideoSleepModeSupported()}, nil +} + +func (s *grpcServer) VideoSetQualityFactor(ctx context.Context, req *pb.VideoSetQualityFactorRequest) (*pb.Empty, error) { + if err := s.native.VideoSetQualityFactor(req.Factor); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetQualityFactor(ctx context.Context, req *pb.Empty) (*pb.VideoGetQualityFactorResponse, error) { + factor, err := s.native.VideoGetQualityFactor() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetQualityFactorResponse{Factor: factor}, nil +} + +func (s *grpcServer) VideoSetEDID(ctx context.Context, req *pb.VideoSetEDIDRequest) (*pb.Empty, error) { + if err := s.native.VideoSetEDID(req.Edid); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetEDID(ctx context.Context, req *pb.Empty) (*pb.VideoGetEDIDResponse, error) { + edid, err := s.native.VideoGetEDID() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetEDIDResponse{Edid: edid}, nil +} + +func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.VideoLogStatusResponse, error) { + logStatus, err := s.native.VideoLogStatus() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoLogStatusResponse{Status: logStatus}, nil +} + +func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + if err := s.native.VideoStop(); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + if err := s.native.VideoStart(); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +// UI methods +func (s *grpcServer) GetLVGLVersion(ctx context.Context, req *pb.Empty) (*pb.GetLVGLVersionResponse, error) { + version, err := s.native.GetLVGLVersion() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.GetLVGLVersionResponse{Version: version}, nil +} + +func (s *grpcServer) UIObjHide(ctx context.Context, req *pb.UIObjHideRequest) (*pb.UIObjHideResponse, error) { + success, err := s.native.UIObjHide(req.ObjName) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjHideResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjShow(ctx context.Context, req *pb.UIObjShowRequest) (*pb.UIObjShowResponse, error) { + success, err := s.native.UIObjShow(req.ObjName) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjShowResponse{Success: success}, nil +} + +func (s *grpcServer) UISetVar(ctx context.Context, req *pb.UISetVarRequest) (*pb.Empty, error) { + s.native.UISetVar(req.Name, req.Value) + return &pb.Empty{}, nil +} + +func (s *grpcServer) UIGetVar(ctx context.Context, req *pb.UIGetVarRequest) (*pb.UIGetVarResponse, error) { + value := s.native.UIGetVar(req.Name) + return &pb.UIGetVarResponse{Value: value}, nil +} + +func (s *grpcServer) UIObjAddState(ctx context.Context, req *pb.UIObjAddStateRequest) (*pb.UIObjAddStateResponse, error) { + success, err := s.native.UIObjAddState(req.ObjName, req.State) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjAddStateResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjClearState(ctx context.Context, req *pb.UIObjClearStateRequest) (*pb.UIObjClearStateResponse, error) { + success, err := s.native.UIObjClearState(req.ObjName, req.State) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjClearStateResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjAddFlag(ctx context.Context, req *pb.UIObjAddFlagRequest) (*pb.UIObjAddFlagResponse, error) { + success, err := s.native.UIObjAddFlag(req.ObjName, req.Flag) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjAddFlagResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjClearFlag(ctx context.Context, req *pb.UIObjClearFlagRequest) (*pb.UIObjClearFlagResponse, error) { + success, err := s.native.UIObjClearFlag(req.ObjName, req.Flag) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjClearFlagResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetOpacity(ctx context.Context, req *pb.UIObjSetOpacityRequest) (*pb.UIObjSetOpacityResponse, error) { + success, err := s.native.UIObjSetOpacity(req.ObjName, int(req.Opacity)) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetOpacityResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjFadeIn(ctx context.Context, req *pb.UIObjFadeInRequest) (*pb.UIObjFadeInResponse, error) { + success, err := s.native.UIObjFadeIn(req.ObjName, req.Duration) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjFadeInResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjFadeOut(ctx context.Context, req *pb.UIObjFadeOutRequest) (*pb.UIObjFadeOutResponse, error) { + success, err := s.native.UIObjFadeOut(req.ObjName, req.Duration) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjFadeOutResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetLabelText(ctx context.Context, req *pb.UIObjSetLabelTextRequest) (*pb.UIObjSetLabelTextResponse, error) { + success, err := s.native.UIObjSetLabelText(req.ObjName, req.Text) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetLabelTextResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetImageSrc(ctx context.Context, req *pb.UIObjSetImageSrcRequest) (*pb.UIObjSetImageSrcResponse, error) { + success, err := s.native.UIObjSetImageSrc(req.ObjName, req.Image) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetImageSrcResponse{Success: success}, nil +} + +func (s *grpcServer) DisplaySetRotation(ctx context.Context, req *pb.DisplaySetRotationRequest) (*pb.DisplaySetRotationResponse, error) { + success, err := s.native.DisplaySetRotation(uint16(req.Rotation)) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.DisplaySetRotationResponse{Success: success}, nil +} + +func (s *grpcServer) UpdateLabelIfChanged(ctx context.Context, req *pb.UpdateLabelIfChangedRequest) (*pb.Empty, error) { + s.native.UpdateLabelIfChanged(req.ObjName, req.NewText) + return &pb.Empty{}, nil +} + +func (s *grpcServer) UpdateLabelAndChangeVisibility(ctx context.Context, req *pb.UpdateLabelAndChangeVisibilityRequest) (*pb.Empty, error) { + s.native.UpdateLabelAndChangeVisibility(req.ObjName, req.NewText) + return &pb.Empty{}, nil +} + +func (s *grpcServer) SwitchToScreenIf(ctx context.Context, req *pb.SwitchToScreenIfRequest) (*pb.Empty, error) { + s.native.SwitchToScreenIf(req.ScreenName, req.ShouldSwitch) + return &pb.Empty{}, nil +} + +func (s *grpcServer) SwitchToScreenIfDifferent(ctx context.Context, req *pb.SwitchToScreenIfDifferentRequest) (*pb.Empty, error) { + s.native.SwitchToScreenIfDifferent(req.ScreenName) + return &pb.Empty{}, nil +} + +func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + s.native.DoNotUseThisIsForCrashTestingOnly() + return &pb.Empty{}, nil +} + +// StreamEvents streams events from the native process +func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error { + eventCh := make(chan *pb.Event, 100) + + // Register this channel for events + s.eventM.Lock() + s.eventChs = append(s.eventChs, eventCh) + s.eventM.Unlock() + + // Unregister on exit + defer func() { + s.eventM.Lock() + defer s.eventM.Unlock() + for i, ch := range s.eventChs { + if ch == eventCh { + s.eventChs = append(s.eventChs[:i], s.eventChs[i+1:]...) + break + } + } + close(eventCh) + }() + + // Stream events + for { + select { + case event := <-eventCh: + if err := stream.Send(event); err != nil { + return err + } + case <-stream.Context().Done(): + return stream.Context().Err() + } + } +} + +// StartGRPCServer starts the gRPC server on a Unix domain socket +func StartGRPCServer(server *grpcServer, socketPath string, logger *zerolog.Logger) (*grpc.Server, net.Listener, error) { + // Remove socket if it exists + if _, err := os.Stat(socketPath); err == nil { + if err := os.Remove(socketPath); err != nil { + return nil, nil, fmt.Errorf("failed to remove existing socket: %w", err) + } + } + + // Ensure directory exists + if err := os.MkdirAll(filepath.Dir(socketPath), 0755); err != nil { + return nil, nil, fmt.Errorf("failed to create socket directory: %w", err) + } + + lis, err := net.Listen("unix", socketPath) + if err != nil { + return nil, nil, fmt.Errorf("failed to listen on socket: %w", err) + } + + s := grpc.NewServer() + pb.RegisterNativeServiceServer(s, server) + + go func() { + if err := s.Serve(lis); err != nil { + logger.Error().Err(err).Msg("gRPC server error") + } + }() + + logger.Info().Str("socket", socketPath).Msg("gRPC server started") + return s, lis, nil +} diff --git a/internal/native/interface.go b/internal/native/interface.go new file mode 100644 index 000000000..be4cb960d --- /dev/null +++ b/internal/native/interface.go @@ -0,0 +1,37 @@ +package native + +// NativeInterface defines the interface that both Native and NativeProxy implement +type NativeInterface interface { + Start() error + VideoSetSleepMode(enabled bool) error + VideoGetSleepMode() (bool, error) + VideoSleepModeSupported() bool + VideoSetQualityFactor(factor float64) error + VideoGetQualityFactor() (float64, error) + VideoSetEDID(edid string) error + VideoGetEDID() (string, error) + VideoLogStatus() (string, error) + VideoStop() error + VideoStart() error + GetLVGLVersion() (string, error) + UIObjHide(objName string) (bool, error) + UIObjShow(objName string) (bool, error) + UISetVar(name string, value string) + UIGetVar(name string) string + UIObjAddState(objName string, state string) (bool, error) + UIObjClearState(objName string, state string) (bool, error) + UIObjAddFlag(objName string, flag string) (bool, error) + UIObjClearFlag(objName string, flag string) (bool, error) + UIObjSetOpacity(objName string, opacity int) (bool, error) + UIObjFadeIn(objName string, duration uint32) (bool, error) + UIObjFadeOut(objName string, duration uint32) (bool, error) + UIObjSetLabelText(objName string, text string) (bool, error) + UIObjSetImageSrc(objName string, image string) (bool, error) + DisplaySetRotation(rotation uint16) (bool, error) + UpdateLabelIfChanged(objName string, newText string) + UpdateLabelAndChangeVisibility(objName string, newText string) + SwitchToScreenIf(screenName string, shouldSwitch []string) + SwitchToScreenIfDifferent(screenName string) + DoNotUseThisIsForCrashTestingOnly() +} + diff --git a/internal/native/ipc.go b/internal/native/ipc.go new file mode 100644 index 000000000..4157c96b2 --- /dev/null +++ b/internal/native/ipc.go @@ -0,0 +1,293 @@ +package native + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "sync" + "time" + + "github.com/rs/zerolog" +) + +// Request represents a JSON-RPC request +type Request struct { + JSONRPC string `json:"jsonrpc"` + ID interface{} `json:"id,omitempty"` + Method string `json:"method"` + Params json.RawMessage `json:"params,omitempty"` +} + +// Response represents a JSON-RPC response +type Response struct { + JSONRPC string `json:"jsonrpc"` + ID interface{} `json:"id,omitempty"` + Result interface{} `json:"result,omitempty"` + Error *RPCError `json:"error,omitempty"` +} + +// RPCError represents a JSON-RPC error +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// Event represents a JSON-RPC notification/event +type Event struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// ProcessConfig is the configuration for the native process +type ProcessConfig struct { + Disable bool `json:"disable"` + SystemVersion string `json:"system_version"` // Serialized as string + AppVersion string `json:"app_version"` // Serialized as string + DisplayRotation uint16 `json:"display_rotation"` + DefaultQualityFactor float64 `json:"default_quality_factor"` +} + +// IPCClient handles communication with the native process +type IPCClient struct { + stdin io.WriteCloser + stdout *bufio.Scanner + logger *zerolog.Logger + + requestID int64 + requestIDM sync.Mutex + + pendingRequests map[interface{}]chan *Response + pendingM sync.Mutex + + eventHandlers map[string][]func(data interface{}) + eventM sync.RWMutex + + readyCh chan struct{} + ready bool + + closed bool + closeM sync.Mutex +} + +type processCmd interface { + Start() error + Wait() error + GetProcess() interface { + Kill() error + Signal(sig interface{}) error + } + StdinPipe() (io.WriteCloser, error) + StdoutPipe() (io.ReadCloser, error) + StderrPipe() (io.ReadCloser, error) +} + +func NewIPCClient(cmd processCmd, logger *zerolog.Logger) (*IPCClient, error) { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, fmt.Errorf("failed to get stdin pipe: %w", err) + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + stdin.Close() + return nil, fmt.Errorf("failed to get stdout pipe: %w", err) + } + + client := &IPCClient{ + stdin: stdin, + stdout: bufio.NewScanner(stdout), + logger: logger, + pendingRequests: make(map[interface{}]chan *Response), + eventHandlers: make(map[string][]func(data interface{})), + readyCh: make(chan struct{}), + } + + // Start reading responses + go client.readLoop() + + return client, nil +} + +func (c *IPCClient) readLoop() { + for c.stdout.Scan() { + line := c.stdout.Bytes() + if len(line) == 0 { + continue + } + + // Try to parse as response + var resp Response + if err := json.Unmarshal(line, &resp); err == nil { + // Check if it's a ready signal (result is "ready" and no ID) + if resp.Result == "ready" && resp.ID == nil && !c.ready { + c.ready = true + close(c.readyCh) + continue + } + if resp.Result != nil || resp.Error != nil { + c.handleResponse(&resp) + continue + } + } + + // Try to parse as event + var event Event + if err := json.Unmarshal(line, &event); err == nil && event.Method == "event" { + c.handleEvent(&event) + continue + } + + c.logger.Warn().Bytes("line", line).Msg("unexpected message from native process") + } + + c.closeM.Lock() + if !c.closed { + c.closed = true + c.closeM.Unlock() + c.logger.Warn().Msg("native process stdout closed") + // Cancel all pending requests + c.pendingM.Lock() + for id, ch := range c.pendingRequests { + close(ch) + delete(c.pendingRequests, id) + } + c.pendingM.Unlock() + } else { + c.closeM.Unlock() + } +} + +func (c *IPCClient) handleResponse(resp *Response) { + c.pendingM.Lock() + ch, ok := c.pendingRequests[resp.ID] + if ok { + delete(c.pendingRequests, resp.ID) + } + c.pendingM.Unlock() + + if ok { + select { + case ch <- resp: + default: + } + } +} + +func (c *IPCClient) handleEvent(event *Event) { + if event.Method != "event" || event.Params == nil { + return + } + + eventType, ok := event.Params["type"].(string) + if !ok { + return + } + + data := event.Params["data"] + + c.eventM.RLock() + handlers := c.eventHandlers[eventType] + c.eventM.RUnlock() + + for _, handler := range handlers { + handler(data) + } +} + +func (c *IPCClient) Call(method string, params interface{}) (*Response, error) { + c.closeM.Lock() + if c.closed { + c.closeM.Unlock() + return nil, fmt.Errorf("client is closed") + } + c.closeM.Unlock() + + c.requestIDM.Lock() + c.requestID++ + id := c.requestID + c.requestIDM.Unlock() + + req := Request{ + JSONRPC: "2.0", + ID: id, + Method: method, + } + + if params != nil { + paramsBytes, err := json.Marshal(params) + if err != nil { + return nil, fmt.Errorf("failed to marshal params: %w", err) + } + req.Params = paramsBytes + } + + ch := make(chan *Response, 1) + c.pendingM.Lock() + c.pendingRequests[id] = ch + c.pendingM.Unlock() + + reqBytes, err := json.Marshal(req) + if err != nil { + c.pendingM.Lock() + delete(c.pendingRequests, id) + c.pendingM.Unlock() + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + if _, err := c.stdin.Write(append(reqBytes, '\n')); err != nil { + c.pendingM.Lock() + delete(c.pendingRequests, id) + c.pendingM.Unlock() + return nil, fmt.Errorf("failed to write request: %w", err) + } + + select { + case resp := <-ch: + if resp.Error != nil { + return nil, fmt.Errorf("RPC error: %s (code: %d)", resp.Error.Message, resp.Error.Code) + } + return resp, nil + case <-time.After(30 * time.Second): + c.pendingM.Lock() + delete(c.pendingRequests, id) + c.pendingM.Unlock() + return nil, fmt.Errorf("request timeout") + } +} + +func (c *IPCClient) OnEvent(eventType string, handler func(data interface{})) { + c.eventM.Lock() + defer c.eventM.Unlock() + c.eventHandlers[eventType] = append(c.eventHandlers[eventType], handler) +} + +func (c *IPCClient) WaitReady() error { + select { + case <-c.readyCh: + return nil + case <-time.After(10 * time.Second): + return fmt.Errorf("timeout waiting for ready signal") + } +} + +func (c *IPCClient) Close() error { + c.closeM.Lock() + defer c.closeM.Unlock() + if c.closed { + return nil + } + c.closed = true + + c.pendingM.Lock() + for id, ch := range c.pendingRequests { + close(ch) + delete(c.pendingRequests, id) + } + c.pendingM.Unlock() + + return c.stdin.Close() +} + diff --git a/internal/native/native.go b/internal/native/native.go index cb8761cf1..e772db526 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -94,11 +94,11 @@ func NewNative(opts NativeOptions) *Native { } } -func (n *Native) Start() { +func (n *Native) Start() error { if n.disable { nativeLogger.Warn().Msg("native is disabled, skipping initialization") setCgoDisabled(true) - return + return nil } // set up singleton @@ -117,9 +117,11 @@ func (n *Native) Start() { if err := videoInit(n.defaultQualityFactor); err != nil { n.l.Error().Err(err).Msg("failed to initialize video") + return err } close(n.ready) + return nil } // DoNotUseThisIsForCrashTestingOnly diff --git a/internal/native/proto/README.md b/internal/native/proto/README.md new file mode 100644 index 000000000..aa619f6d9 --- /dev/null +++ b/internal/native/proto/README.md @@ -0,0 +1,33 @@ +# Proto Files + +This directory contains the Protocol Buffer definitions for the native service. + +## Generating Code + +To generate the Go code from the proto files, run: + +```bash +./scripts/generate_proto.sh +``` + +Or manually: + +```bash +protoc \ + --go_out=. \ + --go_opt=paths=source_relative \ + --go-grpc_out=. \ + --go-grpc_opt=paths=source_relative \ + internal/native/proto/native.proto +``` + +## Prerequisites + +- `protoc` - Protocol Buffer compiler +- `protoc-gen-go` - Go plugin for protoc (install with: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`) +- `protoc-gen-go-grpc` - gRPC Go plugin for protoc (install with: `go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest`) + +## Note + +The current `native.pb.go` and `native_grpc.pb.go` files are placeholder/stub files. They should be regenerated from `native.proto` using the commands above. + diff --git a/internal/native/proto/native.pb.go b/internal/native/proto/native.pb.go new file mode 100644 index 000000000..0cb58fffc --- /dev/null +++ b/internal/native/proto/native.pb.go @@ -0,0 +1,596 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// This file should be regenerated from native.proto using: +// protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/native/proto/native.proto +// +// For now, this is a minimal implementation to allow compilation. +// TODO: Regenerate from proto file when protoc is available. + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} +} + +func (x *Empty) String() string { + return "" +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + return nil // Stub +} + +type VideoState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ready bool `protobuf:"varint,1,opt,name=ready,proto3" json:"ready,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` + Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` + FramePerSecond float64 `protobuf:"fixed64,5,opt,name=frame_per_second,json=framePerSecond,proto3" json:"frame_per_second,omitempty"` +} + +func (x *VideoState) Reset() { + *x = VideoState{} +} + +func (x *VideoState) String() string { + return "" +} + +func (*VideoState) ProtoMessage() {} + +func (x *VideoState) ProtoReflect() protoreflect.Message { + return nil // Stub +} + +// Request/Response types - minimal implementations +type VideoSetSleepModeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *VideoSetSleepModeRequest) Reset() { *x = VideoSetSleepModeRequest{} } +func (x *VideoSetSleepModeRequest) String() string { return "" } +func (*VideoSetSleepModeRequest) ProtoMessage() {} +func (x *VideoSetSleepModeRequest) ProtoReflect() protoreflect.Message { return nil } + +type VideoGetSleepModeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *VideoGetSleepModeResponse) Reset() { *x = VideoGetSleepModeResponse{} } +func (x *VideoGetSleepModeResponse) String() string { return "" } +func (*VideoGetSleepModeResponse) ProtoMessage() {} +func (x *VideoGetSleepModeResponse) ProtoReflect() protoreflect.Message { return nil } + +type VideoSleepModeSupportedResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Supported bool `protobuf:"varint,1,opt,name=supported,proto3" json:"supported,omitempty"` +} + +func (x *VideoSleepModeSupportedResponse) Reset() { *x = VideoSleepModeSupportedResponse{} } +func (x *VideoSleepModeSupportedResponse) String() string { return "" } +func (*VideoSleepModeSupportedResponse) ProtoMessage() {} +func (x *VideoSleepModeSupportedResponse) ProtoReflect() protoreflect.Message { return nil } + +type VideoSetQualityFactorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` +} + +func (x *VideoSetQualityFactorRequest) Reset() { *x = VideoSetQualityFactorRequest{} } +func (x *VideoSetQualityFactorRequest) String() string { return "" } +func (*VideoSetQualityFactorRequest) ProtoMessage() {} +func (x *VideoSetQualityFactorRequest) ProtoReflect() protoreflect.Message { return nil } + +type VideoGetQualityFactorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` +} + +func (x *VideoGetQualityFactorResponse) Reset() { *x = VideoGetQualityFactorResponse{} } +func (x *VideoGetQualityFactorResponse) String() string { return "" } +func (*VideoGetQualityFactorResponse) ProtoMessage() {} +func (x *VideoGetQualityFactorResponse) ProtoReflect() protoreflect.Message { return nil } + +type VideoSetEDIDRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` +} + +func (x *VideoSetEDIDRequest) Reset() { *x = VideoSetEDIDRequest{} } +func (x *VideoSetEDIDRequest) String() string { return "" } +func (*VideoSetEDIDRequest) ProtoMessage() {} +func (x *VideoSetEDIDRequest) ProtoReflect() protoreflect.Message { return nil } + +type VideoGetEDIDResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` +} + +func (x *VideoGetEDIDResponse) Reset() { *x = VideoGetEDIDResponse{} } +func (x *VideoGetEDIDResponse) String() string { return "" } +func (*VideoGetEDIDResponse) ProtoMessage() {} +func (x *VideoGetEDIDResponse) ProtoReflect() protoreflect.Message { return nil } + +type VideoLogStatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *VideoLogStatusResponse) Reset() { *x = VideoLogStatusResponse{} } +func (x *VideoLogStatusResponse) String() string { return "" } +func (*VideoLogStatusResponse) ProtoMessage() {} +func (x *VideoLogStatusResponse) ProtoReflect() protoreflect.Message { return nil } + +type GetLVGLVersionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *GetLVGLVersionResponse) Reset() { *x = GetLVGLVersionResponse{} } +func (x *GetLVGLVersionResponse) String() string { return "" } +func (*GetLVGLVersionResponse) ProtoMessage() {} +func (x *GetLVGLVersionResponse) ProtoReflect() protoreflect.Message { return nil } + +// UI message types +type UIObjHideRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` +} + +func (x *UIObjHideRequest) Reset() { *x = UIObjHideRequest{} } +func (x *UIObjHideRequest) String() string { return "" } +func (*UIObjHideRequest) ProtoMessage() {} +func (x *UIObjHideRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjHideResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjHideResponse) Reset() { *x = UIObjHideResponse{} } +func (x *UIObjHideResponse) String() string { return "" } +func (*UIObjHideResponse) ProtoMessage() {} +func (x *UIObjHideResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjShowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` +} + +func (x *UIObjShowRequest) Reset() { *x = UIObjShowRequest{} } +func (x *UIObjShowRequest) String() string { return "" } +func (*UIObjShowRequest) ProtoMessage() {} +func (x *UIObjShowRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjShowResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjShowResponse) Reset() { *x = UIObjShowResponse{} } +func (x *UIObjShowResponse) String() string { return "" } +func (*UIObjShowResponse) ProtoMessage() {} +func (x *UIObjShowResponse) ProtoReflect() protoreflect.Message { return nil } + +type UISetVarRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *UISetVarRequest) Reset() { *x = UISetVarRequest{} } +func (x *UISetVarRequest) String() string { return "" } +func (*UISetVarRequest) ProtoMessage() {} +func (x *UISetVarRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIGetVarRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *UIGetVarRequest) Reset() { *x = UIGetVarRequest{} } +func (x *UIGetVarRequest) String() string { return "" } +func (*UIGetVarRequest) ProtoMessage() {} +func (x *UIGetVarRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIGetVarResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *UIGetVarResponse) Reset() { *x = UIGetVarResponse{} } +func (x *UIGetVarResponse) String() string { return "" } +func (*UIGetVarResponse) ProtoMessage() {} +func (x *UIGetVarResponse) ProtoReflect() protoreflect.Message { return nil } + +// Additional UI types - abbreviated for brevity, follow same pattern +type UIObjAddStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *UIObjAddStateRequest) Reset() { *x = UIObjAddStateRequest{} } +func (x *UIObjAddStateRequest) String() string { return "" } +func (*UIObjAddStateRequest) ProtoMessage() {} +func (x *UIObjAddStateRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjAddStateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjAddStateResponse) Reset() { *x = UIObjAddStateResponse{} } +func (x *UIObjAddStateResponse) String() string { return "" } +func (*UIObjAddStateResponse) ProtoMessage() {} +func (x *UIObjAddStateResponse) ProtoReflect() protoreflect.Message { return nil } + +// Event types +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // oneof data - using separate fields for now + VideoState *VideoState `protobuf:"bytes,2,opt,name=video_state,json=videoState,proto3" json:"video_state,omitempty"` + IndevEvent string `protobuf:"bytes,3,opt,name=indev_event,json=indevEvent,proto3" json:"indev_event,omitempty"` + RpcEvent string `protobuf:"bytes,4,opt,name=rpc_event,json=rpcEvent,proto3" json:"rpc_event,omitempty"` + VideoFrame *VideoFrame `protobuf:"bytes,5,opt,name=video_frame,json=videoFrame,proto3" json:"video_frame,omitempty"` +} + +func (x *Event) Reset() { *x = Event{} } +func (x *Event) String() string { return "" } +func (*Event) ProtoMessage() {} +func (x *Event) ProtoReflect() protoreflect.Message { return nil } + +type VideoFrame struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Frame []byte `protobuf:"bytes,1,opt,name=frame,proto3" json:"frame,omitempty"` + DurationNs int64 `protobuf:"varint,2,opt,name=duration_ns,json=durationNs,proto3" json:"duration_ns,omitempty"` +} + +func (x *VideoFrame) Reset() { *x = VideoFrame{} } +func (x *VideoFrame) String() string { return "" } +func (*VideoFrame) ProtoMessage() {} +func (x *VideoFrame) ProtoReflect() protoreflect.Message { return nil } + +// Additional request/response types - following same pattern +// (Abbreviated - all follow the same structure) +type UIObjClearStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *UIObjClearStateRequest) Reset() { *x = UIObjClearStateRequest{} } +func (x *UIObjClearStateRequest) String() string { return "" } +func (*UIObjClearStateRequest) ProtoMessage() {} +func (x *UIObjClearStateRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjClearStateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjClearStateResponse) Reset() { *x = UIObjClearStateResponse{} } +func (x *UIObjClearStateResponse) String() string { return "" } +func (*UIObjClearStateResponse) ProtoMessage() {} +func (x *UIObjClearStateResponse) ProtoReflect() protoreflect.Message { return nil } + +// Remaining types follow same pattern - creating minimal stubs +// TODO: Regenerate from proto for complete implementation + +type UIObjAddFlagRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` +} + +func (x *UIObjAddFlagRequest) Reset() { *x = UIObjAddFlagRequest{} } +func (x *UIObjAddFlagRequest) String() string { return "" } +func (*UIObjAddFlagRequest) ProtoMessage() {} +func (x *UIObjAddFlagRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjAddFlagResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjAddFlagResponse) Reset() { *x = UIObjAddFlagResponse{} } +func (x *UIObjAddFlagResponse) String() string { return "" } +func (*UIObjAddFlagResponse) ProtoMessage() {} +func (x *UIObjAddFlagResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjClearFlagRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` +} + +func (x *UIObjClearFlagRequest) Reset() { *x = UIObjClearFlagRequest{} } +func (x *UIObjClearFlagRequest) String() string { return "" } +func (*UIObjClearFlagRequest) ProtoMessage() {} +func (x *UIObjClearFlagRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjClearFlagResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjClearFlagResponse) Reset() { *x = UIObjClearFlagResponse{} } +func (x *UIObjClearFlagResponse) String() string { return "" } +func (*UIObjClearFlagResponse) ProtoMessage() {} +func (x *UIObjClearFlagResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetOpacityRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Opacity int32 `protobuf:"varint,2,opt,name=opacity,proto3" json:"opacity,omitempty"` +} + +func (x *UIObjSetOpacityRequest) Reset() { *x = UIObjSetOpacityRequest{} } +func (x *UIObjSetOpacityRequest) String() string { return "" } +func (*UIObjSetOpacityRequest) ProtoMessage() {} +func (x *UIObjSetOpacityRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetOpacityResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjSetOpacityResponse) Reset() { *x = UIObjSetOpacityResponse{} } +func (x *UIObjSetOpacityResponse) String() string { return "" } +func (*UIObjSetOpacityResponse) ProtoMessage() {} +func (x *UIObjSetOpacityResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjFadeInRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` +} + +func (x *UIObjFadeInRequest) Reset() { *x = UIObjFadeInRequest{} } +func (x *UIObjFadeInRequest) String() string { return "" } +func (*UIObjFadeInRequest) ProtoMessage() {} +func (x *UIObjFadeInRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjFadeInResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjFadeInResponse) Reset() { *x = UIObjFadeInResponse{} } +func (x *UIObjFadeInResponse) String() string { return "" } +func (*UIObjFadeInResponse) ProtoMessage() {} +func (x *UIObjFadeInResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjFadeOutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` +} + +func (x *UIObjFadeOutRequest) Reset() { *x = UIObjFadeOutRequest{} } +func (x *UIObjFadeOutRequest) String() string { return "" } +func (*UIObjFadeOutRequest) ProtoMessage() {} +func (x *UIObjFadeOutRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjFadeOutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjFadeOutResponse) Reset() { *x = UIObjFadeOutResponse{} } +func (x *UIObjFadeOutResponse) String() string { return "" } +func (*UIObjFadeOutResponse) ProtoMessage() {} +func (x *UIObjFadeOutResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetLabelTextRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *UIObjSetLabelTextRequest) Reset() { *x = UIObjSetLabelTextRequest{} } +func (x *UIObjSetLabelTextRequest) String() string { return "" } +func (*UIObjSetLabelTextRequest) ProtoMessage() {} +func (x *UIObjSetLabelTextRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetLabelTextResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjSetLabelTextResponse) Reset() { *x = UIObjSetLabelTextResponse{} } +func (x *UIObjSetLabelTextResponse) String() string { return "" } +func (*UIObjSetLabelTextResponse) ProtoMessage() {} +func (x *UIObjSetLabelTextResponse) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetImageSrcRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` +} + +func (x *UIObjSetImageSrcRequest) Reset() { *x = UIObjSetImageSrcRequest{} } +func (x *UIObjSetImageSrcRequest) String() string { return "" } +func (*UIObjSetImageSrcRequest) ProtoMessage() {} +func (x *UIObjSetImageSrcRequest) ProtoReflect() protoreflect.Message { return nil } + +type UIObjSetImageSrcResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *UIObjSetImageSrcResponse) Reset() { *x = UIObjSetImageSrcResponse{} } +func (x *UIObjSetImageSrcResponse) String() string { return "" } +func (*UIObjSetImageSrcResponse) ProtoMessage() {} +func (x *UIObjSetImageSrcResponse) ProtoReflect() protoreflect.Message { return nil } + +type DisplaySetRotationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Rotation uint32 `protobuf:"varint,1,opt,name=rotation,proto3" json:"rotation,omitempty"` +} + +func (x *DisplaySetRotationRequest) Reset() { *x = DisplaySetRotationRequest{} } +func (x *DisplaySetRotationRequest) String() string { return "" } +func (*DisplaySetRotationRequest) ProtoMessage() {} +func (x *DisplaySetRotationRequest) ProtoReflect() protoreflect.Message { return nil } + +type DisplaySetRotationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *DisplaySetRotationResponse) Reset() { *x = DisplaySetRotationResponse{} } +func (x *DisplaySetRotationResponse) String() string { return "" } +func (*DisplaySetRotationResponse) ProtoMessage() {} +func (x *DisplaySetRotationResponse) ProtoReflect() protoreflect.Message { return nil } + +type UpdateLabelIfChangedRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` +} + +func (x *UpdateLabelIfChangedRequest) Reset() { *x = UpdateLabelIfChangedRequest{} } +func (x *UpdateLabelIfChangedRequest) String() string { return "" } +func (*UpdateLabelIfChangedRequest) ProtoMessage() {} +func (x *UpdateLabelIfChangedRequest) ProtoReflect() protoreflect.Message { return nil } + +type UpdateLabelAndChangeVisibilityRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` +} + +func (x *UpdateLabelAndChangeVisibilityRequest) Reset() { *x = UpdateLabelAndChangeVisibilityRequest{} } +func (x *UpdateLabelAndChangeVisibilityRequest) String() string { return "" } +func (*UpdateLabelAndChangeVisibilityRequest) ProtoMessage() {} +func (x *UpdateLabelAndChangeVisibilityRequest) ProtoReflect() protoreflect.Message { return nil } + +type SwitchToScreenIfRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` + ShouldSwitch []string `protobuf:"bytes,2,rep,name=should_switch,json=shouldSwitch,proto3" json:"should_switch,omitempty"` +} + +func (x *SwitchToScreenIfRequest) Reset() { *x = SwitchToScreenIfRequest{} } +func (x *SwitchToScreenIfRequest) String() string { return "" } +func (*SwitchToScreenIfRequest) ProtoMessage() {} +func (x *SwitchToScreenIfRequest) ProtoReflect() protoreflect.Message { return nil } + +type SwitchToScreenIfDifferentRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` +} + +func (x *SwitchToScreenIfDifferentRequest) Reset() { *x = SwitchToScreenIfDifferentRequest{} } +func (x *SwitchToScreenIfDifferentRequest) String() string { return "" } +func (*SwitchToScreenIfDifferentRequest) ProtoMessage() {} +func (x *SwitchToScreenIfDifferentRequest) ProtoReflect() protoreflect.Message { return nil } diff --git a/internal/native/proto/native.proto b/internal/native/proto/native.proto new file mode 100644 index 000000000..c320d81de --- /dev/null +++ b/internal/native/proto/native.proto @@ -0,0 +1,262 @@ +syntax = "proto3"; + +package native; + +option go_package = "github.com/jetkvm/kvm/internal/native/proto"; + +// NativeService provides methods to interact with the native layer +service NativeService { + // Init + rpc Init(InitRequest) returns (InitResponse); + + // Video methods + rpc VideoSetSleepMode(VideoSetSleepModeRequest) returns (Empty); + rpc VideoGetSleepMode(Empty) returns (VideoGetSleepModeResponse); + rpc VideoSleepModeSupported(Empty) returns (VideoSleepModeSupportedResponse); + rpc VideoSetQualityFactor(VideoSetQualityFactorRequest) returns (Empty); + rpc VideoGetQualityFactor(Empty) returns (VideoGetQualityFactorResponse); + rpc VideoSetEDID(VideoSetEDIDRequest) returns (Empty); + rpc VideoGetEDID(Empty) returns (VideoGetEDIDResponse); + rpc VideoLogStatus(Empty) returns (VideoLogStatusResponse); + rpc VideoStop(Empty) returns (Empty); + rpc VideoStart(Empty) returns (Empty); + + // UI methods + rpc GetLVGLVersion(Empty) returns (GetLVGLVersionResponse); + rpc UIObjHide(UIObjHideRequest) returns (UIObjHideResponse); + rpc UIObjShow(UIObjShowRequest) returns (UIObjShowResponse); + rpc UISetVar(UISetVarRequest) returns (Empty); + rpc UIGetVar(UIGetVarRequest) returns (UIGetVarResponse); + rpc UIObjAddState(UIObjAddStateRequest) returns (UIObjAddStateResponse); + rpc UIObjClearState(UIObjClearStateRequest) returns (UIObjClearStateResponse); + rpc UIObjAddFlag(UIObjAddFlagRequest) returns (UIObjAddFlagResponse); + rpc UIObjClearFlag(UIObjClearFlagRequest) returns (UIObjClearFlagResponse); + rpc UIObjSetOpacity(UIObjSetOpacityRequest) returns (UIObjSetOpacityResponse); + rpc UIObjFadeIn(UIObjFadeInRequest) returns (UIObjFadeInResponse); + rpc UIObjFadeOut(UIObjFadeOutRequest) returns (UIObjFadeOutResponse); + rpc UIObjSetLabelText(UIObjSetLabelTextRequest) returns (UIObjSetLabelTextResponse); + rpc UIObjSetImageSrc(UIObjSetImageSrcRequest) returns (UIObjSetImageSrcResponse); + rpc DisplaySetRotation(DisplaySetRotationRequest) returns (DisplaySetRotationResponse); + rpc UpdateLabelIfChanged(UpdateLabelIfChangedRequest) returns (Empty); + rpc UpdateLabelAndChangeVisibility(UpdateLabelAndChangeVisibilityRequest) returns (Empty); + rpc SwitchToScreenIf(SwitchToScreenIfRequest) returns (Empty); + rpc SwitchToScreenIfDifferent(SwitchToScreenIfDifferentRequest) returns (Empty); + + // Testing + rpc DoNotUseThisIsForCrashTestingOnly(Empty) returns (Empty); + + // Events stream + rpc StreamEvents(Empty) returns (stream Event); +} + +// Messages +message Empty {} + +message InitRequest { + string system_version = 1; + string app_version = 2; + uint32 display_rotation = 3; + double default_quality_factor = 4; +} + +message InitResponse { + bool success = 1; + string error = 2; +} + +message VideoState { + bool ready = 1; + string error = 2; + int32 width = 3; + int32 height = 4; + double frame_per_second = 5; +} + +message VideoSetSleepModeRequest { + bool enabled = 1; +} + +message VideoGetSleepModeResponse { + bool enabled = 1; +} + +message VideoSleepModeSupportedResponse { + bool supported = 1; +} + +message VideoSetQualityFactorRequest { + double factor = 1; +} + +message VideoGetQualityFactorResponse { + double factor = 1; +} + +message VideoSetEDIDRequest { + string edid = 1; +} + +message VideoGetEDIDResponse { + string edid = 1; +} + +message VideoLogStatusResponse { + string status = 1; +} + +message GetLVGLVersionResponse { + string version = 1; +} + +message UIObjHideRequest { + string obj_name = 1; +} + +message UIObjHideResponse { + bool success = 1; +} + +message UIObjShowRequest { + string obj_name = 1; +} + +message UIObjShowResponse { + bool success = 1; +} + +message UISetVarRequest { + string name = 1; + string value = 2; +} + +message UIGetVarRequest { + string name = 1; +} + +message UIGetVarResponse { + string value = 1; +} + +message UIObjAddStateRequest { + string obj_name = 1; + string state = 2; +} + +message UIObjAddStateResponse { + bool success = 1; +} + +message UIObjClearStateRequest { + string obj_name = 1; + string state = 2; +} + +message UIObjClearStateResponse { + bool success = 1; +} + +message UIObjAddFlagRequest { + string obj_name = 1; + string flag = 2; +} + +message UIObjAddFlagResponse { + bool success = 1; +} + +message UIObjClearFlagRequest { + string obj_name = 1; + string flag = 2; +} + +message UIObjClearFlagResponse { + bool success = 1; +} + +message UIObjSetOpacityRequest { + string obj_name = 1; + int32 opacity = 2; +} + +message UIObjSetOpacityResponse { + bool success = 1; +} + +message UIObjFadeInRequest { + string obj_name = 1; + uint32 duration = 2; +} + +message UIObjFadeInResponse { + bool success = 1; +} + +message UIObjFadeOutRequest { + string obj_name = 1; + uint32 duration = 2; +} + +message UIObjFadeOutResponse { + bool success = 1; +} + +message UIObjSetLabelTextRequest { + string obj_name = 1; + string text = 2; +} + +message UIObjSetLabelTextResponse { + bool success = 1; +} + +message UIObjSetImageSrcRequest { + string obj_name = 1; + string image = 2; +} + +message UIObjSetImageSrcResponse { + bool success = 1; +} + +message DisplaySetRotationRequest { + uint32 rotation = 1; +} + +message DisplaySetRotationResponse { + bool success = 1; +} + +message UpdateLabelIfChangedRequest { + string obj_name = 1; + string new_text = 2; +} + +message UpdateLabelAndChangeVisibilityRequest { + string obj_name = 1; + string new_text = 2; +} + +message SwitchToScreenIfRequest { + string screen_name = 1; + repeated string should_switch = 2; +} + +message SwitchToScreenIfDifferentRequest { + string screen_name = 1; +} + +message Event { + string type = 1; + oneof data { + VideoState video_state = 2; + string indev_event = 3; + string rpc_event = 4; + VideoFrame video_frame = 5; + } +} + +message VideoFrame { + bytes frame = 1; + int64 duration_ns = 2; +} + diff --git a/internal/native/proto/native_grpc.pb.go b/internal/native/proto/native_grpc.pb.go new file mode 100644 index 000000000..59d2a2394 --- /dev/null +++ b/internal/native/proto/native_grpc.pb.go @@ -0,0 +1,492 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// This is a placeholder file. Run: protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/native/proto/native.proto + +package proto + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion7 + +// NativeServiceClient is the client API for NativeService service. +type NativeServiceClient interface { + // Video methods + VideoSetSleepMode(ctx context.Context, in *VideoSetSleepModeRequest, opts ...grpc.CallOption) (*Empty, error) + VideoGetSleepMode(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetSleepModeResponse, error) + VideoSleepModeSupported(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoSleepModeSupportedResponse, error) + VideoSetQualityFactor(ctx context.Context, in *VideoSetQualityFactorRequest, opts ...grpc.CallOption) (*Empty, error) + VideoGetQualityFactor(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetQualityFactorResponse, error) + VideoSetEDID(ctx context.Context, in *VideoSetEDIDRequest, opts ...grpc.CallOption) (*Empty, error) + VideoGetEDID(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetEDIDResponse, error) + 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) + // UI methods + GetLVGLVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetLVGLVersionResponse, error) + UIObjHide(ctx context.Context, in *UIObjHideRequest, opts ...grpc.CallOption) (*UIObjHideResponse, error) + UIObjShow(ctx context.Context, in *UIObjShowRequest, opts ...grpc.CallOption) (*UIObjShowResponse, error) + UISetVar(ctx context.Context, in *UISetVarRequest, opts ...grpc.CallOption) (*Empty, error) + UIGetVar(ctx context.Context, in *UIGetVarRequest, opts ...grpc.CallOption) (*UIGetVarResponse, error) + UIObjAddState(ctx context.Context, in *UIObjAddStateRequest, opts ...grpc.CallOption) (*UIObjAddStateResponse, error) + UIObjClearState(ctx context.Context, in *UIObjClearStateRequest, opts ...grpc.CallOption) (*UIObjClearStateResponse, error) + UIObjAddFlag(ctx context.Context, in *UIObjAddFlagRequest, opts ...grpc.CallOption) (*UIObjAddFlagResponse, error) + UIObjClearFlag(ctx context.Context, in *UIObjClearFlagRequest, opts ...grpc.CallOption) (*UIObjClearFlagResponse, error) + UIObjSetOpacity(ctx context.Context, in *UIObjSetOpacityRequest, opts ...grpc.CallOption) (*UIObjSetOpacityResponse, error) + UIObjFadeIn(ctx context.Context, in *UIObjFadeInRequest, opts ...grpc.CallOption) (*UIObjFadeInResponse, error) + UIObjFadeOut(ctx context.Context, in *UIObjFadeOutRequest, opts ...grpc.CallOption) (*UIObjFadeOutResponse, error) + UIObjSetLabelText(ctx context.Context, in *UIObjSetLabelTextRequest, opts ...grpc.CallOption) (*UIObjSetLabelTextResponse, error) + UIObjSetImageSrc(ctx context.Context, in *UIObjSetImageSrcRequest, opts ...grpc.CallOption) (*UIObjSetImageSrcResponse, error) + DisplaySetRotation(ctx context.Context, in *DisplaySetRotationRequest, opts ...grpc.CallOption) (*DisplaySetRotationResponse, error) + UpdateLabelIfChanged(ctx context.Context, in *UpdateLabelIfChangedRequest, opts ...grpc.CallOption) (*Empty, error) + UpdateLabelAndChangeVisibility(ctx context.Context, in *UpdateLabelAndChangeVisibilityRequest, opts ...grpc.CallOption) (*Empty, error) + SwitchToScreenIf(ctx context.Context, in *SwitchToScreenIfRequest, opts ...grpc.CallOption) (*Empty, error) + SwitchToScreenIfDifferent(ctx context.Context, in *SwitchToScreenIfDifferentRequest, opts ...grpc.CallOption) (*Empty, error) + // Testing + DoNotUseThisIsForCrashTestingOnly(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) + // Events stream + StreamEvents(ctx context.Context, in *Empty, opts ...grpc.CallOption) (NativeService_StreamEventsClient, error) +} + +type nativeServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewNativeServiceClient(cc grpc.ClientConnInterface) NativeServiceClient { + return &nativeServiceClient{cc} +} + +func (c *nativeServiceClient) VideoSetSleepMode(ctx context.Context, in *VideoSetSleepModeRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetSleepMode", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoGetSleepMode(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetSleepModeResponse, error) { + out := new(VideoGetSleepModeResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetSleepMode", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoSleepModeSupported(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoSleepModeSupportedResponse, error) { + out := new(VideoSleepModeSupportedResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoSleepModeSupported", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoSetQualityFactor(ctx context.Context, in *VideoSetQualityFactorRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetQualityFactor", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoGetQualityFactor(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetQualityFactorResponse, error) { + out := new(VideoGetQualityFactorResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetQualityFactor", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoSetEDID(ctx context.Context, in *VideoSetEDIDRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetEDID", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoGetEDID(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetEDIDResponse, error) { + out := new(VideoGetEDIDResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetEDID", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoLogStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoLogStatusResponse, error) { + out := new(VideoLogStatusResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoLogStatus", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoStop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoStop", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) VideoStart(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/VideoStart", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) GetLVGLVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetLVGLVersionResponse, error) { + out := new(GetLVGLVersionResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/GetLVGLVersion", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjHide(ctx context.Context, in *UIObjHideRequest, opts ...grpc.CallOption) (*UIObjHideResponse, error) { + out := new(UIObjHideResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjHide", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjShow(ctx context.Context, in *UIObjShowRequest, opts ...grpc.CallOption) (*UIObjShowResponse, error) { + out := new(UIObjShowResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjShow", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UISetVar(ctx context.Context, in *UISetVarRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/UISetVar", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIGetVar(ctx context.Context, in *UIGetVarRequest, opts ...grpc.CallOption) (*UIGetVarResponse, error) { + out := new(UIGetVarResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIGetVar", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjAddState(ctx context.Context, in *UIObjAddStateRequest, opts ...grpc.CallOption) (*UIObjAddStateResponse, error) { + out := new(UIObjAddStateResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjAddState", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjClearState(ctx context.Context, in *UIObjClearStateRequest, opts ...grpc.CallOption) (*UIObjClearStateResponse, error) { + out := new(UIObjClearStateResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjClearState", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjAddFlag(ctx context.Context, in *UIObjAddFlagRequest, opts ...grpc.CallOption) (*UIObjAddFlagResponse, error) { + out := new(UIObjAddFlagResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjAddFlag", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjClearFlag(ctx context.Context, in *UIObjClearFlagRequest, opts ...grpc.CallOption) (*UIObjClearFlagResponse, error) { + out := new(UIObjClearFlagResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjClearFlag", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjSetOpacity(ctx context.Context, in *UIObjSetOpacityRequest, opts ...grpc.CallOption) (*UIObjSetOpacityResponse, error) { + out := new(UIObjSetOpacityResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetOpacity", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjFadeIn(ctx context.Context, in *UIObjFadeInRequest, opts ...grpc.CallOption) (*UIObjFadeInResponse, error) { + out := new(UIObjFadeInResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjFadeIn", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjFadeOut(ctx context.Context, in *UIObjFadeOutRequest, opts ...grpc.CallOption) (*UIObjFadeOutResponse, error) { + out := new(UIObjFadeOutResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjFadeOut", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjSetLabelText(ctx context.Context, in *UIObjSetLabelTextRequest, opts ...grpc.CallOption) (*UIObjSetLabelTextResponse, error) { + out := new(UIObjSetLabelTextResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetLabelText", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UIObjSetImageSrc(ctx context.Context, in *UIObjSetImageSrcRequest, opts ...grpc.CallOption) (*UIObjSetImageSrcResponse, error) { + out := new(UIObjSetImageSrcResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetImageSrc", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) DisplaySetRotation(ctx context.Context, in *DisplaySetRotationRequest, opts ...grpc.CallOption) (*DisplaySetRotationResponse, error) { + out := new(DisplaySetRotationResponse) + err := c.cc.Invoke(ctx, "/native.NativeService/DisplaySetRotation", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UpdateLabelIfChanged(ctx context.Context, in *UpdateLabelIfChangedRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/UpdateLabelIfChanged", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) UpdateLabelAndChangeVisibility(ctx context.Context, in *UpdateLabelAndChangeVisibilityRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/UpdateLabelAndChangeVisibility", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) SwitchToScreenIf(ctx context.Context, in *SwitchToScreenIfRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/SwitchToScreenIf", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) SwitchToScreenIfDifferent(ctx context.Context, in *SwitchToScreenIfDifferentRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/SwitchToScreenIfDifferent", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/native.NativeService/DoNotUseThisIsForCrashTestingOnly", in, out, opts...) + return out, err +} + +func (c *nativeServiceClient) StreamEvents(ctx context.Context, in *Empty, opts ...grpc.CallOption) (NativeService_StreamEventsClient, error) { + stream, err := c.cc.NewStream(ctx, &NativeService_ServiceDesc.Streams[0], "/native.NativeService/StreamEvents", opts...) + if err != nil { + return nil, err + } + x := &nativeServiceStreamEventsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type NativeService_StreamEventsClient interface { + Recv() (*Event, error) + grpc.ClientStream +} + +type nativeServiceStreamEventsClient struct { + grpc.ClientStream +} + +func (x *nativeServiceStreamEventsClient) Recv() (*Event, error) { + m := new(Event) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// NativeServiceServer is the server API for NativeService service. +type NativeServiceServer interface { + // Video methods + VideoSetSleepMode(context.Context, *VideoSetSleepModeRequest) (*Empty, error) + VideoGetSleepMode(context.Context, *Empty) (*VideoGetSleepModeResponse, error) + VideoSleepModeSupported(context.Context, *Empty) (*VideoSleepModeSupportedResponse, error) + VideoSetQualityFactor(context.Context, *VideoSetQualityFactorRequest) (*Empty, error) + VideoGetQualityFactor(context.Context, *Empty) (*VideoGetQualityFactorResponse, error) + VideoSetEDID(context.Context, *VideoSetEDIDRequest) (*Empty, error) + VideoGetEDID(context.Context, *Empty) (*VideoGetEDIDResponse, error) + VideoLogStatus(context.Context, *Empty) (*VideoLogStatusResponse, error) + VideoStop(context.Context, *Empty) (*Empty, error) + VideoStart(context.Context, *Empty) (*Empty, error) + // UI methods + GetLVGLVersion(context.Context, *Empty) (*GetLVGLVersionResponse, error) + UIObjHide(context.Context, *UIObjHideRequest) (*UIObjHideResponse, error) + UIObjShow(context.Context, *UIObjShowRequest) (*UIObjShowResponse, error) + UISetVar(context.Context, *UISetVarRequest) (*Empty, error) + UIGetVar(context.Context, *UIGetVarRequest) (*UIGetVarResponse, error) + UIObjAddState(context.Context, *UIObjAddStateRequest) (*UIObjAddStateResponse, error) + UIObjClearState(context.Context, *UIObjClearStateRequest) (*UIObjClearStateResponse, error) + UIObjAddFlag(context.Context, *UIObjAddFlagRequest) (*UIObjAddFlagResponse, error) + UIObjClearFlag(context.Context, *UIObjClearFlagRequest) (*UIObjClearFlagResponse, error) + UIObjSetOpacity(context.Context, *UIObjSetOpacityRequest) (*UIObjSetOpacityResponse, error) + UIObjFadeIn(context.Context, *UIObjFadeInRequest) (*UIObjFadeInResponse, error) + UIObjFadeOut(context.Context, *UIObjFadeOutRequest) (*UIObjFadeOutResponse, error) + UIObjSetLabelText(context.Context, *UIObjSetLabelTextRequest) (*UIObjSetLabelTextResponse, error) + UIObjSetImageSrc(context.Context, *UIObjSetImageSrcRequest) (*UIObjSetImageSrcResponse, error) + DisplaySetRotation(context.Context, *DisplaySetRotationRequest) (*DisplaySetRotationResponse, error) + UpdateLabelIfChanged(context.Context, *UpdateLabelIfChangedRequest) (*Empty, error) + UpdateLabelAndChangeVisibility(context.Context, *UpdateLabelAndChangeVisibilityRequest) (*Empty, error) + SwitchToScreenIf(context.Context, *SwitchToScreenIfRequest) (*Empty, error) + SwitchToScreenIfDifferent(context.Context, *SwitchToScreenIfDifferentRequest) (*Empty, error) + // Testing + DoNotUseThisIsForCrashTestingOnly(context.Context, *Empty) (*Empty, error) + // Events stream + StreamEvents(*Empty, NativeService_StreamEventsServer) error + mustEmbedUnimplementedNativeServiceServer() +} + +// UnimplementedNativeServiceServer must be embedded to have forward compatible implementations. +type UnimplementedNativeServiceServer struct { +} + +func (UnimplementedNativeServiceServer) VideoSetSleepMode(context.Context, *VideoSetSleepModeRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoSetSleepMode not implemented") +} + +func (UnimplementedNativeServiceServer) VideoGetSleepMode(context.Context, *Empty) (*VideoGetSleepModeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoGetSleepMode not implemented") +} + +func (UnimplementedNativeServiceServer) VideoSleepModeSupported(context.Context, *Empty) (*VideoSleepModeSupportedResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoSleepModeSupported not implemented") +} + +func (UnimplementedNativeServiceServer) VideoSetQualityFactor(context.Context, *VideoSetQualityFactorRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoSetQualityFactor not implemented") +} + +func (UnimplementedNativeServiceServer) VideoGetQualityFactor(context.Context, *Empty) (*VideoGetQualityFactorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoGetQualityFactor not implemented") +} + +func (UnimplementedNativeServiceServer) VideoSetEDID(context.Context, *VideoSetEDIDRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoSetEDID not implemented") +} + +func (UnimplementedNativeServiceServer) VideoGetEDID(context.Context, *Empty) (*VideoGetEDIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoGetEDID not implemented") +} + +func (UnimplementedNativeServiceServer) VideoLogStatus(context.Context, *Empty) (*VideoLogStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoLogStatus not implemented") +} + +func (UnimplementedNativeServiceServer) VideoStop(context.Context, *Empty) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoStop not implemented") +} + +func (UnimplementedNativeServiceServer) VideoStart(context.Context, *Empty) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method VideoStart not implemented") +} + +func (UnimplementedNativeServiceServer) GetLVGLVersion(context.Context, *Empty) (*GetLVGLVersionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetLVGLVersion not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjHide(context.Context, *UIObjHideRequest) (*UIObjHideResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjHide not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjShow(context.Context, *UIObjShowRequest) (*UIObjShowResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjShow not implemented") +} + +func (UnimplementedNativeServiceServer) UISetVar(context.Context, *UISetVarRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UISetVar not implemented") +} + +func (UnimplementedNativeServiceServer) UIGetVar(context.Context, *UIGetVarRequest) (*UIGetVarResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIGetVar not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjAddState(context.Context, *UIObjAddStateRequest) (*UIObjAddStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjAddState not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjClearState(context.Context, *UIObjClearStateRequest) (*UIObjClearStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjClearState not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjAddFlag(context.Context, *UIObjAddFlagRequest) (*UIObjAddFlagResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjAddFlag not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjClearFlag(context.Context, *UIObjClearFlagRequest) (*UIObjClearFlagResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjClearFlag not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjSetOpacity(context.Context, *UIObjSetOpacityRequest) (*UIObjSetOpacityResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjSetOpacity not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjFadeIn(context.Context, *UIObjFadeInRequest) (*UIObjFadeInResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjFadeIn not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjFadeOut(context.Context, *UIObjFadeOutRequest) (*UIObjFadeOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjFadeOut not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjSetLabelText(context.Context, *UIObjSetLabelTextRequest) (*UIObjSetLabelTextResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjSetLabelText not implemented") +} + +func (UnimplementedNativeServiceServer) UIObjSetImageSrc(context.Context, *UIObjSetImageSrcRequest) (*UIObjSetImageSrcResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UIObjSetImageSrc not implemented") +} + +func (UnimplementedNativeServiceServer) DisplaySetRotation(context.Context, *DisplaySetRotationRequest) (*DisplaySetRotationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisplaySetRotation not implemented") +} + +func (UnimplementedNativeServiceServer) UpdateLabelIfChanged(context.Context, *UpdateLabelIfChangedRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateLabelIfChanged not implemented") +} + +func (UnimplementedNativeServiceServer) UpdateLabelAndChangeVisibility(context.Context, *UpdateLabelAndChangeVisibilityRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateLabelAndChangeVisibility not implemented") +} + +func (UnimplementedNativeServiceServer) SwitchToScreenIf(context.Context, *SwitchToScreenIfRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SwitchToScreenIf not implemented") +} + +func (UnimplementedNativeServiceServer) SwitchToScreenIfDifferent(context.Context, *SwitchToScreenIfDifferentRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SwitchToScreenIfDifferent not implemented") +} + +func (UnimplementedNativeServiceServer) DoNotUseThisIsForCrashTestingOnly(context.Context, *Empty) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DoNotUseThisIsForCrashTestingOnly not implemented") +} + +func (UnimplementedNativeServiceServer) StreamEvents(*Empty, NativeService_StreamEventsServer) error { + return status.Errorf(codes.Unimplemented, "method StreamEvents not implemented") +} + +func (UnimplementedNativeServiceServer) mustEmbedUnimplementedNativeServiceServer() {} + +// UnsafeNativeServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NativeServiceServer will +// result in compilation errors. +type UnsafeNativeServiceServer interface { + mustEmbedUnimplementedNativeServiceServer() +} + +func RegisterNativeServiceServer(s grpc.ServiceRegistrar, srv NativeServiceServer) { + s.RegisterService(&NativeService_ServiceDesc, srv) +} + +type NativeService_StreamEventsServer interface { + Send(*Event) error + grpc.ServerStream +} + +type nativeServiceStreamEventsServer struct { + grpc.ServerStream +} + +func (x *nativeServiceStreamEventsServer) Send(m *Event) error { + return x.ServerStream.SendMsg(m) +} + +// NativeService_ServiceDesc is the grpc.ServiceDesc for NativeService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NativeService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "native.NativeService", + HandlerType: (*NativeServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "VideoSetSleepMode", + Handler: nil, // Will be set by RegisterNativeServiceServer + }, + // Additional methods will be registered here + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamEvents", + Handler: nil, // Will be set by RegisterNativeServiceServer + ServerStreams: true, + }, + }, + Metadata: "native.proto", +} diff --git a/internal/native/proxy.go b/internal/native/proxy.go new file mode 100644 index 000000000..0523e905e --- /dev/null +++ b/internal/native/proxy.go @@ -0,0 +1,682 @@ +package native + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "sync" + "syscall" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/jetkvm/kvm/internal/supervisor" + "github.com/rs/zerolog" +) + +// cmdWrapper wraps exec.Cmd to implement processCmd interface +type cmdWrapper struct { + *exec.Cmd +} + +func (c *cmdWrapper) GetProcess() interface { + Kill() error + Signal(sig interface{}) error +} { + return &processWrapper{Process: c.Cmd.Process} +} + +type processWrapper struct { + *os.Process +} + +func (p *processWrapper) Signal(sig interface{}) error { + if sig == nil { + // Check if process is alive by sending signal 0 + return p.Process.Signal(os.Signal(syscall.Signal(0))) + } + if s, ok := sig.(os.Signal); ok { + return p.Process.Signal(s) + } + return fmt.Errorf("invalid signal type") +} + +// NativeProxy is a proxy that communicates with a separate native process +type NativeProxy struct { + client *IPCClient + cmd *exec.Cmd + wrapped *cmdWrapper + logger *zerolog.Logger + ready chan struct{} + restartM sync.Mutex + stopped bool + opts NativeProxyOptions + binaryPath string + configJSON []byte + processWait chan error +} + +// NativeProxyOptions are options for creating a NativeProxy +type NativeProxyOptions struct { + Disable bool + SystemVersion *semver.Version + AppVersion *semver.Version + DisplayRotation uint16 + DefaultQualityFactor float64 + OnVideoStateChange func(state VideoState) + OnVideoFrameReceived func(frame []byte, duration time.Duration) + OnIndevEvent func(event string) + OnRpcEvent func(event string) + Logger *zerolog.Logger +} + +// NewNativeProxy creates a new NativeProxy that spawns a separate process +func NewNativeProxy(opts NativeProxyOptions) (*NativeProxy, error) { + if opts.Logger == nil { + opts.Logger = nativeLogger + } + + // Get the current executable path to spawn itself + exePath, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("failed to get executable path: %w", err) + } + binaryPath := exePath + + config := ProcessConfig{ + Disable: opts.Disable, + SystemVersion: "", + AppVersion: "", + DisplayRotation: opts.DisplayRotation, + DefaultQualityFactor: opts.DefaultQualityFactor, + } + if opts.SystemVersion != nil { + config.SystemVersion = opts.SystemVersion.String() + } + if opts.AppVersion != nil { + config.AppVersion = opts.AppVersion.String() + } + + configJSON, err := json.Marshal(config) + if err != nil { + return nil, fmt.Errorf("failed to marshal config: %w", err) + } + + cmd := exec.Command(binaryPath, string(configJSON)) + cmd.Stderr = os.Stderr // Forward stderr to parent + // Set environment variable to indicate native process mode + cmd.Env = append(os.Environ(), fmt.Sprintf("%s=native", supervisor.EnvSubcomponent)) + + // Wrap cmd to implement processCmd interface + wrappedCmd := &cmdWrapper{Cmd: cmd} + + client, err := NewIPCClient(wrappedCmd, opts.Logger) + if err != nil { + return nil, fmt.Errorf("failed to create IPC client: %w", err) + } + + proxy := &NativeProxy{ + client: client, + cmd: cmd, + wrapped: wrappedCmd, + logger: opts.Logger, + ready: make(chan struct{}), + opts: opts, + binaryPath: binaryPath, + configJSON: configJSON, + processWait: make(chan error, 1), + } + + // Set up event handlers + proxy.setupEventHandlers(client) + + return proxy, nil +} + +// Start starts the native process +func (p *NativeProxy) Start() error { + p.restartM.Lock() + defer p.restartM.Unlock() + + if p.stopped { + return fmt.Errorf("proxy is stopped") + } + + if err := p.cmd.Start(); err != nil { + return fmt.Errorf("failed to start native process: %w", err) + } + + // Wait for ready signal from the native process + if err := p.client.WaitReady(); err != nil { + // Clean up if ready failed + if p.cmd.Process != nil { + _ = p.cmd.Process.Kill() + _ = p.cmd.Wait() + } + return err + } + + // Start monitoring process for crashes + go p.monitorProcess() + + close(p.ready) + return nil +} + +// monitorProcess monitors the native process and restarts it if it crashes +func (p *NativeProxy) monitorProcess() { + for { + p.restartM.Lock() + cmd := p.cmd + stopped := p.stopped + p.restartM.Unlock() + + if stopped { + return + } + + if cmd == nil { + return + } + + err := cmd.Wait() + select { + case p.processWait <- err: + default: + } + + p.restartM.Lock() + if p.stopped { + p.restartM.Unlock() + return + } + p.restartM.Unlock() + + p.logger.Warn().Err(err).Msg("native process exited, restarting...") + + // Wait a bit before restarting + time.Sleep(1 * time.Second) + + // Restart the process + if err := p.restartProcess(); err != nil { + p.logger.Error().Err(err).Msg("failed to restart native process") + // Wait longer before retrying + time.Sleep(5 * time.Second) + continue + } + } +} + +// restartProcess restarts the native process +func (p *NativeProxy) restartProcess() error { + p.restartM.Lock() + defer p.restartM.Unlock() + + if p.stopped { + return fmt.Errorf("proxy is stopped") + } + + // Create new command + cmd := exec.Command(p.binaryPath, string(p.configJSON)) + cmd.Stderr = os.Stderr + // Set environment variable to indicate native process mode + cmd.Env = append(os.Environ(), "JETKVM_NATIVE_PROCESS=1") + + // Wrap cmd to implement processCmd interface + wrappedCmd := &cmdWrapper{Cmd: cmd} + + // Close old client + if p.client != nil { + _ = p.client.Close() + } + + // Create new client + client, err := NewIPCClient(wrappedCmd, p.logger) + if err != nil { + return fmt.Errorf("failed to create IPC client: %w", err) + } + + // Set up event handlers again + p.setupEventHandlers(client) + + // Start the process + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start native process: %w", err) + } + + // Wait for ready + if err := client.WaitReady(); err != nil { + if cmd.Process != nil { + _ = cmd.Process.Kill() + _ = cmd.Wait() + } + return fmt.Errorf("timeout waiting for ready: %w", err) + } + + p.cmd = cmd + p.wrapped = wrappedCmd + p.client = client + + p.logger.Info().Msg("native process restarted successfully") + return nil +} + +func (p *NativeProxy) setupEventHandlers(client *IPCClient) { + if p.opts.OnVideoStateChange != nil { + client.OnEvent("video_state_change", func(data interface{}) { + dataBytes, err := json.Marshal(data) + if err != nil { + p.logger.Warn().Err(err).Msg("failed to marshal video state event") + return + } + var state VideoState + if err := json.Unmarshal(dataBytes, &state); err != nil { + p.logger.Warn().Err(err).Msg("failed to unmarshal video state event") + return + } + p.opts.OnVideoStateChange(state) + }) + } + + if p.opts.OnIndevEvent != nil { + client.OnEvent("indev_event", func(data interface{}) { + if event, ok := data.(string); ok { + p.opts.OnIndevEvent(event) + } + }) + } + + if p.opts.OnRpcEvent != nil { + client.OnEvent("rpc_event", func(data interface{}) { + if event, ok := data.(string); ok { + p.opts.OnRpcEvent(event) + } + }) + } + + if p.opts.OnVideoFrameReceived != nil { + client.OnEvent("video_frame", func(data interface{}) { + dataMap, ok := data.(map[string]interface{}) + if !ok { + p.logger.Warn().Msg("invalid video frame event data") + return + } + + frameData, ok := dataMap["frame"].([]interface{}) + if !ok { + p.logger.Warn().Msg("invalid frame data in event") + return + } + + frame := make([]byte, len(frameData)) + for i, v := range frameData { + if b, ok := v.(float64); ok { + frame[i] = byte(b) + } + } + + durationNs, ok := dataMap["duration"].(float64) + if !ok { + p.logger.Warn().Msg("invalid duration in event") + return + } + + p.opts.OnVideoFrameReceived(frame, time.Duration(durationNs)) + }) + } +} + +// Stop stops the native process +func (p *NativeProxy) Stop() error { + p.restartM.Lock() + defer p.restartM.Unlock() + + p.stopped = true + + if err := p.client.Close(); err != nil { + p.logger.Warn().Err(err).Msg("failed to close IPC client") + } + + if p.cmd.Process != nil { + if err := p.cmd.Process.Kill(); err != nil { + return fmt.Errorf("failed to kill native process: %w", err) + } + _ = p.cmd.Wait() + } + + return nil +} + +// Implement all Native methods by forwarding to IPC + +func (p *NativeProxy) VideoSetSleepMode(enabled bool) error { + _, err := p.client.Call("VideoSetSleepMode", map[string]interface{}{ + "enabled": enabled, + }) + return err +} + +func (p *NativeProxy) VideoGetSleepMode() (bool, error) { + resp, err := p.client.Call("VideoGetSleepMode", nil) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) VideoSleepModeSupported() bool { + resp, err := p.client.Call("VideoSleepModeSupported", nil) + if err != nil { + return false + } + result, ok := resp.Result.(bool) + if !ok { + return false + } + return result +} + +func (p *NativeProxy) VideoSetQualityFactor(factor float64) error { + _, err := p.client.Call("VideoSetQualityFactor", map[string]interface{}{ + "factor": factor, + }) + return err +} + +func (p *NativeProxy) VideoGetQualityFactor() (float64, error) { + resp, err := p.client.Call("VideoGetQualityFactor", nil) + if err != nil { + return 0, err + } + result, ok := resp.Result.(float64) + if !ok { + return 0, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) VideoSetEDID(edid string) error { + _, err := p.client.Call("VideoSetEDID", map[string]interface{}{ + "edid": edid, + }) + return err +} + +func (p *NativeProxy) VideoGetEDID() (string, error) { + resp, err := p.client.Call("VideoGetEDID", nil) + if err != nil { + return "", err + } + result, ok := resp.Result.(string) + if !ok { + return "", fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) VideoLogStatus() (string, error) { + resp, err := p.client.Call("VideoLogStatus", nil) + if err != nil { + return "", err + } + result, ok := resp.Result.(string) + if !ok { + return "", fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) VideoStop() error { + _, err := p.client.Call("VideoStop", nil) + return err +} + +func (p *NativeProxy) VideoStart() error { + _, err := p.client.Call("VideoStart", nil) + return err +} + +func (p *NativeProxy) GetLVGLVersion() (string, error) { + resp, err := p.client.Call("GetLVGLVersion", nil) + if err != nil { + return "", err + } + result, ok := resp.Result.(string) + if !ok { + return "", fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjHide(objName string) (bool, error) { + resp, err := p.client.Call("UIObjHide", map[string]interface{}{ + "obj_name": objName, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjShow(objName string) (bool, error) { + resp, err := p.client.Call("UIObjShow", map[string]interface{}{ + "obj_name": objName, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UISetVar(name string, value string) { + _, _ = p.client.Call("UISetVar", map[string]interface{}{ + "name": name, + "value": value, + }) +} + +func (p *NativeProxy) UIGetVar(name string) string { + resp, err := p.client.Call("UIGetVar", map[string]interface{}{ + "name": name, + }) + if err != nil { + return "" + } + result, ok := resp.Result.(string) + if !ok { + return "" + } + return result +} + +func (p *NativeProxy) UIObjAddState(objName string, state string) (bool, error) { + resp, err := p.client.Call("UIObjAddState", map[string]interface{}{ + "obj_name": objName, + "state": state, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjClearState(objName string, state string) (bool, error) { + resp, err := p.client.Call("UIObjClearState", map[string]interface{}{ + "obj_name": objName, + "state": state, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjAddFlag(objName string, flag string) (bool, error) { + resp, err := p.client.Call("UIObjAddFlag", map[string]interface{}{ + "obj_name": objName, + "flag": flag, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjClearFlag(objName string, flag string) (bool, error) { + resp, err := p.client.Call("UIObjClearFlag", map[string]interface{}{ + "obj_name": objName, + "flag": flag, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjSetOpacity(objName string, opacity int) (bool, error) { + resp, err := p.client.Call("UIObjSetOpacity", map[string]interface{}{ + "obj_name": objName, + "opacity": opacity, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjFadeIn(objName string, duration uint32) (bool, error) { + resp, err := p.client.Call("UIObjFadeIn", map[string]interface{}{ + "obj_name": objName, + "duration": duration, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjFadeOut(objName string, duration uint32) (bool, error) { + resp, err := p.client.Call("UIObjFadeOut", map[string]interface{}{ + "obj_name": objName, + "duration": duration, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjSetLabelText(objName string, text string) (bool, error) { + resp, err := p.client.Call("UIObjSetLabelText", map[string]interface{}{ + "obj_name": objName, + "text": text, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UIObjSetImageSrc(objName string, image string) (bool, error) { + resp, err := p.client.Call("UIObjSetImageSrc", map[string]interface{}{ + "obj_name": objName, + "image": image, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) DisplaySetRotation(rotation uint16) (bool, error) { + resp, err := p.client.Call("DisplaySetRotation", map[string]interface{}{ + "rotation": rotation, + }) + if err != nil { + return false, err + } + result, ok := resp.Result.(bool) + if !ok { + return false, fmt.Errorf("invalid response type") + } + return result, nil +} + +func (p *NativeProxy) UpdateLabelIfChanged(objName string, newText string) { + _, _ = p.client.Call("UpdateLabelIfChanged", map[string]interface{}{ + "obj_name": objName, + "new_text": newText, + }) +} + +func (p *NativeProxy) UpdateLabelAndChangeVisibility(objName string, newText string) { + _, _ = p.client.Call("UpdateLabelAndChangeVisibility", map[string]interface{}{ + "obj_name": objName, + "new_text": newText, + }) +} + +func (p *NativeProxy) SwitchToScreenIf(screenName string, shouldSwitch []string) { + _, _ = p.client.Call("SwitchToScreenIf", map[string]interface{}{ + "screen_name": screenName, + "should_switch": shouldSwitch, + }) +} + +func (p *NativeProxy) SwitchToScreenIfDifferent(screenName string) { + _, _ = p.client.Call("SwitchToScreenIfDifferent", map[string]interface{}{ + "screen_name": screenName, + }) +} + +func (p *NativeProxy) DoNotUseThisIsForCrashTestingOnly() { + _, _ = p.client.Call("DoNotUseThisIsForCrashTestingOnly", nil) +} diff --git a/internal/supervisor/consts.go b/internal/supervisor/consts.go new file mode 100644 index 000000000..5f2884895 --- /dev/null +++ b/internal/supervisor/consts.go @@ -0,0 +1,9 @@ +package supervisor + +const ( + EnvChildID = "JETKVM_CHILD_ID" // The child ID is the version of the app that is running + EnvSubcomponent = "JETKVM_SUBCOMPONENT" // The subcomponent is the component that is running + ErrorDumpDir = "/userdata/jetkvm/crashdump" // The error dump directory is the directory where the error dumps are stored + ErrorDumpLastFile = "last-crash.log" // The error dump last file is the last error dump file + ErrorDumpTemplate = "jetkvm-%s.log" // The error dump template is the template for the error dump file +) diff --git a/native.go b/native.go index 81a0e50de..518c3fa99 100644 --- a/native.go +++ b/native.go @@ -11,17 +11,19 @@ import ( ) var ( - nativeInstance *native.Native + nativeInstance native.NativeInterface nativeCmdLock = sync.Mutex{} ) func initNative(systemVersion *semver.Version, appVersion *semver.Version) { - nativeInstance = native.NewNative(native.NativeOptions{ + var err error + nativeInstance, err = native.NewNativeProxy(native.NativeProxyOptions{ Disable: failsafeModeActive, SystemVersion: systemVersion, AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(), DefaultQualityFactor: config.VideoQualityFactor, + Logger: nativeLogger, OnVideoStateChange: func(state native.VideoState) { lastVideoState = state triggerVideoStateUpdate() @@ -63,8 +65,13 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) { } }, }) + if err != nil { + nativeLogger.Fatal().Err(err).Msg("failed to create native proxy") + } - nativeInstance.Start() + if err := nativeInstance.Start(); err != nil { + nativeLogger.Fatal().Err(err).Msg("failed to start native proxy") + } go func() { if err := nativeInstance.VideoSetEDID(config.EdidString); err != nil { nativeLogger.Warn().Err(err).Msg("error setting EDID") diff --git a/native_process.go b/native_process.go new file mode 100644 index 000000000..c239afdfb --- /dev/null +++ b/native_process.go @@ -0,0 +1,528 @@ +package kvm + +import ( + "encoding/json" + "fmt" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/jetkvm/kvm/internal/native" + "github.com/rs/zerolog" +) + +// RunNativeProcess runs the native process mode +func RunNativeProcess() { + // Initialize logger + logger := zerolog.New(os.Stderr).With().Timestamp().Logger() + + // Parse command line arguments (config is passed as first arg) + if len(os.Args) < 2 { + logger.Fatal().Msg("usage: native process requires config_json argument") + } + + var config native.ProcessConfig + if err := json.Unmarshal([]byte(os.Args[1]), &config); err != nil { + logger.Fatal().Err(err).Msg("failed to parse config") + } + + // Parse version strings + var systemVersion *semver.Version + if config.SystemVersion != "" { + v, err := semver.NewVersion(config.SystemVersion) + if err != nil { + logger.Warn().Err(err).Str("version", config.SystemVersion).Msg("failed to parse system version") + } else { + systemVersion = v + } + } + + var appVersion *semver.Version + if config.AppVersion != "" { + v, err := semver.NewVersion(config.AppVersion) + if err != nil { + logger.Warn().Err(err).Str("version", config.AppVersion).Msg("failed to parse app version") + } else { + appVersion = v + } + } + + // Create native instance + nativeInstance := native.NewNative(native.NativeOptions{ + Disable: config.Disable, + SystemVersion: systemVersion, + AppVersion: appVersion, + DisplayRotation: config.DisplayRotation, + DefaultQualityFactor: config.DefaultQualityFactor, + OnVideoStateChange: func(state native.VideoState) { + sendEvent("video_state_change", state) + }, + OnIndevEvent: func(event string) { + sendEvent("indev_event", event) + }, + OnRpcEvent: func(event string) { + sendEvent("rpc_event", event) + }, + OnVideoFrameReceived: func(frame []byte, duration time.Duration) { + sendEvent("video_frame", map[string]interface{}{ + "frame": frame, + "duration": duration.Nanoseconds(), + }) + }, + }) + + // Start native instance + if err := nativeInstance.Start(); err != nil { + logger.Fatal().Err(err).Msg("failed to start native instance") + } + + // Create gRPC server + grpcServer := native.NewGRPCServer(nativeInstance, &logger) + + // Determine socket path + socketPath := os.Getenv("JETKVM_NATIVE_SOCKET") + if socketPath == "" { + // Default to a socket in /tmp + socketPath = filepath.Join("/tmp", fmt.Sprintf("jetkvm-native-%d.sock", os.Getpid())) + } + + // Start gRPC server + server, lis, err := native.StartGRPCServer(grpcServer, socketPath, &logger) + if err != nil { + logger.Fatal().Err(err).Msg("failed to start gRPC server") + } + + // Signal that we're ready by writing socket path to stdout (for parent to read) + fmt.Fprintf(os.Stdout, "%s\n", socketPath) + os.Stdout.Close() + + // Set up signal handling + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) + + // Wait for signal + <-sigChan + logger.Info().Msg("received termination signal") + + // Graceful shutdown + server.GracefulStop() + lis.Close() + + logger.Info().Msg("native process exiting") +} + +// All JSON-RPC handlers have been removed - now using gRPC + case "VideoSetSleepMode": + var params struct { + Enabled bool `json:"enabled"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + err = n.VideoSetSleepMode(params.Enabled) + + case "VideoGetSleepMode": + var result bool + result, err = n.VideoGetSleepMode() + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "VideoSleepModeSupported": + result = n.VideoSleepModeSupported() + sendResponse(encoder, req.ID, "result", result) + return + + case "VideoSetQualityFactor": + var params struct { + Factor float64 `json:"factor"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + err = n.VideoSetQualityFactor(params.Factor) + + case "VideoGetQualityFactor": + var result float64 + result, err = n.VideoGetQualityFactor() + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "VideoSetEDID": + var params struct { + EDID string `json:"edid"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + err = n.VideoSetEDID(params.EDID) + + case "VideoGetEDID": + var result string + result, err = n.VideoGetEDID() + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "VideoLogStatus": + var result string + result, err = n.VideoLogStatus() + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "VideoStop": + err = n.VideoStop() + + case "VideoStart": + err = n.VideoStart() + + case "GetLVGLVersion": + var result string + result, err = n.GetLVGLVersion() + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjHide": + var params struct { + ObjName string `json:"obj_name"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjHide(params.ObjName) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjShow": + var params struct { + ObjName string `json:"obj_name"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjShow(params.ObjName) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UISetVar": + var params struct { + Name string `json:"name"` + Value string `json:"value"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + n.UISetVar(params.Name, params.Value) + sendResponse(encoder, req.ID, "result", nil) + return + + case "UIGetVar": + var params struct { + Name string `json:"name"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + result = n.UIGetVar(params.Name) + sendResponse(encoder, req.ID, "result", result) + return + + case "UIObjAddState": + var params struct { + ObjName string `json:"obj_name"` + State string `json:"state"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjAddState(params.ObjName, params.State) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjClearState": + var params struct { + ObjName string `json:"obj_name"` + State string `json:"state"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjClearState(params.ObjName, params.State) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjAddFlag": + var params struct { + ObjName string `json:"obj_name"` + Flag string `json:"flag"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjAddFlag(params.ObjName, params.Flag) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjClearFlag": + var params struct { + ObjName string `json:"obj_name"` + Flag string `json:"flag"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjClearFlag(params.ObjName, params.Flag) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjSetOpacity": + var params struct { + ObjName string `json:"obj_name"` + Opacity int `json:"opacity"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjSetOpacity(params.ObjName, params.Opacity) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjFadeIn": + var params struct { + ObjName string `json:"obj_name"` + Duration uint32 `json:"duration"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjFadeIn(params.ObjName, params.Duration) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjFadeOut": + var params struct { + ObjName string `json:"obj_name"` + Duration uint32 `json:"duration"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjFadeOut(params.ObjName, params.Duration) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjSetLabelText": + var params struct { + ObjName string `json:"obj_name"` + Text string `json:"text"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjSetLabelText(params.ObjName, params.Text) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UIObjSetImageSrc": + var params struct { + ObjName string `json:"obj_name"` + Image string `json:"image"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.UIObjSetImageSrc(params.ObjName, params.Image) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "DisplaySetRotation": + var params struct { + Rotation uint16 `json:"rotation"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + var result bool + result, err = n.DisplaySetRotation(params.Rotation) + if err == nil { + sendResponse(encoder, req.ID, "result", result) + return + } + + case "UpdateLabelIfChanged": + var params struct { + ObjName string `json:"obj_name"` + NewText string `json:"new_text"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + n.UpdateLabelIfChanged(params.ObjName, params.NewText) + sendResponse(encoder, req.ID, "result", nil) + return + + case "UpdateLabelAndChangeVisibility": + var params struct { + ObjName string `json:"obj_name"` + NewText string `json:"new_text"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + n.UpdateLabelAndChangeVisibility(params.ObjName, params.NewText) + sendResponse(encoder, req.ID, "result", nil) + return + + case "SwitchToScreenIf": + var params struct { + ScreenName string `json:"screen_name"` + ShouldSwitch []string `json:"should_switch"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + n.SwitchToScreenIf(params.ScreenName, params.ShouldSwitch) + sendResponse(encoder, req.ID, "result", nil) + return + + case "SwitchToScreenIfDifferent": + var params struct { + ScreenName string `json:"screen_name"` + } + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + sendError(encoder, req.ID, -32602, "Invalid params", nil) + return + } + n.SwitchToScreenIfDifferent(params.ScreenName) + sendResponse(encoder, req.ID, "result", nil) + return + + case "DoNotUseThisIsForCrashTestingOnly": + n.DoNotUseThisIsForCrashTestingOnly() + sendResponse(encoder, req.ID, "result", nil) + return + + default: + sendError(encoder, req.ID, -32601, fmt.Sprintf("Method not found: %s", req.Method), nil) + return + } + + if err != nil { + sendError(encoder, req.ID, -32000, err.Error(), nil) + return + } + + sendResponse(encoder, req.ID, "result", result) +} + +func sendResponse(encoder *json.Encoder, id interface{}, key string, result interface{}) { + response := map[string]interface{}{ + "jsonrpc": "2.0", + key: result, + } + if id != nil { + response["id"] = id + } + if err := encoder.Encode(response); err != nil { + fmt.Fprintf(os.Stderr, "failed to send response: %v\n", err) + } +} + +func sendError(encoder *json.Encoder, id interface{}, code int, message string, data interface{}) { + response := map[string]interface{}{ + "jsonrpc": "2.0", + "error": map[string]interface{}{ + "code": code, + "message": message, + }, + } + if id != nil { + response["id"] = id + } + if data != nil { + response["error"].(map[string]interface{})["data"] = data + } + if err := encoder.Encode(response); err != nil { + fmt.Fprintf(os.Stderr, "failed to send error: %v\n", err) + } +} + +func sendEvent(eventType string, data interface{}) { + event := map[string]interface{}{ + "jsonrpc": "2.0", + "method": "event", + "params": map[string]interface{}{ + "type": eventType, + "data": data, + }, + } + encoder := json.NewEncoder(os.Stdout) + if err := encoder.Encode(event); err != nil { + fmt.Fprintf(os.Stderr, "failed to send event: %v\n", err) + } +} + diff --git a/scripts/generate_proto.sh b/scripts/generate_proto.sh new file mode 100755 index 000000000..150bef94e --- /dev/null +++ b/scripts/generate_proto.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Generate gRPC code from proto files + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_ROOT" + +# Check if protoc is installed +if ! command -v protoc &> /dev/null; then + echo "Error: protoc is not installed" + echo "Install it with:" + echo " apt-get install protobuf-compiler # Debian/Ubuntu" + echo " brew install protobuf # macOS" + exit 1 +fi + +# Check if protoc-gen-go is installed +if ! command -v protoc-gen-go &> /dev/null; then + echo "Error: protoc-gen-go is not installed" + echo "Install it with: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest" + exit 1 +fi + +# Check if protoc-gen-go-grpc is installed +if ! command -v protoc-gen-go-grpc &> /dev/null; then + echo "Error: protoc-gen-go-grpc is not installed" + echo "Install it with: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest" + exit 1 +fi + +# Generate code +echo "Generating gRPC code from proto files..." +protoc \ + --go_out=. \ + --go_opt=paths=source_relative \ + --go-grpc_out=. \ + --go-grpc_opt=paths=source_relative \ + internal/native/proto/native.proto + +echo "Done!" + diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100644 index 000000000..b6afef910 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -eE +set -o pipefail + +SCRIPT_PATH=$(realpath "$(dirname $(realpath "${BASH_SOURCE[0]}"))") +source ${SCRIPT_PATH}/build_utils.sh + +# Function to display help message +show_help() { + echo "Usage: $0 [options] -v " + echo + echo "Required:" + echo " --app-version App version to release" + echo " --system-version System version to release" + echo + echo "Optional:" + echo " -u, --user Remote username (default: root)" + echo " --run-go-tests Run go tests" + echo " --run-go-tests-only Run go tests and exit" + echo " --skip-ui-build Skip frontend/UI build" + echo " --skip-native-build Skip native build" + echo " --disable-docker Disable docker build" + echo " -i, --install Build for release and install the app" + echo " --help Display this help message" + echo + echo "Example:" + echo " $0 --system-version 0.2.6" +} + + +BUILD_VERSION=$1 +R2_PATH="r2://jetkvm-update/system" +PACK_BIN_PATH="./tools/linux/Linux_Pack_Firmware" +UNPACK_BIN="${PACK_BIN_PATH}/mk-update_unpack.sh" + +# Create temporary directory for downloads +TEMP_DIR=$(mktemp -d) +msg_ok "Created temporary directory: $TEMP_DIR" + +# Cleanup function +cleanup() { + if [ -d "$TEMP_DIR" ]; then + msg_info "Cleaning up temporary directory: $TEMP_DIR" + rm -rf "$TEMP_DIR" + fi +} + +# Set trap to cleanup on exit +# trap cleanup EXIT + +mkdir -p ${TEMP_DIR}/extracted-update +${UNPACK_BIN} -i update.img -o ${TEMP_DIR}/extracted-update + +exit 0 +# Check if the version already exists +if rclone lsf $R2_PATH/$BUILD_VERSION/ | grep -q .; then + msg_err "Error: Version $BUILD_VERSION already exists in the remote storage." + exit 1 +fi + +# Check if the version exists in the github +RELEASE_URL="https://api.github.com/repos/jetkvm/rv1106-system/releases/tags/v$BUILD_VERSION" + +# Download the release JSON +RELEASE_JSON=$(curl -s $RELEASE_URL) + +# Check if the release has assets we need +if echo $RELEASE_JSON | jq -e '.assets | length == 0' > /dev/null; then + msg_err "Error: Version $BUILD_VERSION does not have assets we need." + exit 1 +fi + +function get_file_by_name() { + local file_name=$1 + local file_url=$(echo $RELEASE_JSON | jq -r ".assets[] | select(.name == \"$file_name\") | .browser_download_url") + if [ -z "$file_url" ]; then + msg_err "Error: File $file_name not found in the release." + exit 1 + fi + local digest=$(echo $RELEASE_JSON | jq -r ".assets[] | select(.name == \"$file_name\") | .digest") + local temp_file_path="$TEMP_DIR/$file_name" + + msg_info "Downloading $file_name: $file_url" + + # Download the file to temporary directory + curl -L -o "$temp_file_path" "$file_url" + + # Verify digest if available + if [ "$digest" != "null" ] && [ -n "$digest" ]; then + msg_info "Verifying digest for $file_name ..." + local calculated_digest=$(sha256sum "$temp_file_path" | cut -d' ' -f1) + # Strip "sha256:" prefix if present + local expected_digest=$(echo "$digest" | sed 's/^sha256://') + if [ "$calculated_digest" != "$expected_digest" ]; then + msg_err "🙅 Digest verification failed for $file_name" + msg_info "Expected: $expected_digest" + msg_info "Calculated: $calculated_digest" + exit 1 + fi + else + msg_warn "Warning: No digest available for $file_name, skipping verification" + fi + + msg_ok "✅ $file_name downloaded and verified." +} + +get_file_by_name "update_ota.tar" +get_file_by_name "update.img" + +strings -d bin/jetkvm_app | grep -x '0.4.8' + +# Ask for confirmation +msg_info "Do you want to continue with the release? (y/n)" +read -n 1 -s -r -p "Press y to continue, any other key to exit" +echo -ne "\n" +if [ "$REPLY" != "y" ]; then + msg_err "🙅 Release cancelled." + exit 1 +fi + +msg_info "Releasing $BUILD_VERSION..." + +sha256sum $TEMP_DIR/update_ota.tar | awk '{print $1}' > $TEMP_DIR/update_ota.tar.sha256 +sha256sum $TEMP_DIR/update.img | awk '{print $1}' > $TEMP_DIR/update.img.sha256 + +# Check if the version already exists +msg_info "Copying to $R2_PATH/$BUILD_VERSION/" + +rclone copyto --progress $TEMP_DIR/update_ota.tar $R2_PATH/$BUILD_VERSION/system.tar +rclone copyto --progress $TEMP_DIR/update_ota.tar.sha256 $R2_PATH/$BUILD_VERSION/system.tar.sha256 +rclone copyto --progress $TEMP_DIR/update.img $R2_PATH/$BUILD_VERSION/update.img +rclone copyto --progress $TEMP_DIR/update.img.sha256 $R2_PATH/$BUILD_VERSION/update.img.sha256 + +msg_ok "✅ $BUILD_VERSION released." \ No newline at end of file From bca9afd1d76e40e93a21ad4d1060fd509d8c3708 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 11:53:06 +0000 Subject: [PATCH 02/25] first working POC --- cmd/main.go | 32 +- go.mod | 2 + go.sum | 4 + internal/native/grpc_client.go | 418 ++++ internal/native/grpc_server.go | 51 +- internal/native/ipc.go | 293 --- internal/native/log.go | 5 +- internal/native/native.go | 12 +- internal/native/proto/native.pb.go | 2987 ++++++++++++++++++++--- internal/native/proto/native.proto | 16 +- internal/native/proto/native_grpc.pb.go | 1034 +++++++- internal/native/proxy.go | 587 ++--- internal/native/server.go | 90 + main.go | 2 +- native.go | 4 +- native_process.go | 528 ---- 16 files changed, 4291 insertions(+), 1774 deletions(-) create mode 100644 internal/native/grpc_client.go delete mode 100644 internal/native/ipc.go create mode 100644 internal/native/server.go delete mode 100644 native_process.go diff --git a/cmd/main.go b/cmd/main.go index c9d48362d..479627e0a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,14 +13,8 @@ import ( "github.com/erikdubbelboer/gspt" "github.com/jetkvm/kvm" -) - -const ( - envChildID = "JETKVM_CHILD_ID" - envSubcomponent = "JETKVM_SUBCOMPONENT" - errorDumpDir = "/userdata/jetkvm/crashdump" - errorDumpLastFile = "last-crash.log" - errorDumpTemplate = "jetkvm-%s.log" + "github.com/jetkvm/kvm/internal/native" + "github.com/jetkvm/kvm/internal/supervisor" ) var ( @@ -28,14 +22,14 @@ var ( ) func program() { - subcomponentOverride := os.Getenv(envSubcomponent) + subcomponentOverride := os.Getenv(supervisor.EnvSubcomponent) if subcomponentOverride != "" { subcomponent = subcomponentOverride } switch subcomponent { case "native": gspt.SetProcTitle(os.Args[0] + " [native]") - kvm.RunNativeProcess() + native.RunNativeProcess(os.Args[0]) default: gspt.SetProcTitle(os.Args[0] + " [app]") kvm.Main() @@ -58,7 +52,7 @@ func main() { return } - childID := os.Getenv(envChildID) + childID := os.Getenv(supervisor.EnvChildID) switch childID { case "": doSupervise() @@ -90,11 +84,11 @@ func supervise() error { // run the child binary cmd := exec.Command(binPath) - lastFilePath := filepath.Join(errorDumpDir, errorDumpLastFile) + lastFilePath := filepath.Join(supervisor.ErrorDumpDir, supervisor.ErrorDumpLastFile) cmd.Env = append(os.Environ(), []string{ - fmt.Sprintf("%s=%s", envChildID, kvm.GetBuiltAppVersion()), - fmt.Sprintf("JETKVM_LAST_ERROR_PATH=%s", lastFilePath), + fmt.Sprintf("%s=%s", supervisor.EnvChildID, kvm.GetBuiltAppVersion()), + fmt.Sprintf("%s=%s", supervisor.ErrorDumpLastFile, lastFilePath), }...) cmd.Args = os.Args @@ -202,11 +196,11 @@ func renameFile(f *os.File, newName string) error { func ensureErrorDumpDir() error { // TODO: check if the directory is writable - f, err := os.Stat(errorDumpDir) + f, err := os.Stat(supervisor.ErrorDumpDir) if err == nil && f.IsDir() { return nil } - if err := os.MkdirAll(errorDumpDir, 0755); err != nil { + if err := os.MkdirAll(supervisor.ErrorDumpDir, 0755); err != nil { return fmt.Errorf("failed to create error dump directory: %w", err) } return nil @@ -216,7 +210,7 @@ func createErrorDump(logFile *os.File) { fmt.Println() fileName := fmt.Sprintf( - errorDumpTemplate, + supervisor.ErrorDumpTemplate, time.Now().Format("20060102-150405"), ) @@ -226,7 +220,7 @@ func createErrorDump(logFile *os.File) { return } - filePath := filepath.Join(errorDumpDir, fileName) + filePath := filepath.Join(supervisor.ErrorDumpDir, fileName) if err := renameFile(logFile, filePath); err != nil { fmt.Printf("failed to rename file: %v\n", err) return @@ -234,7 +228,7 @@ func createErrorDump(logFile *os.File) { fmt.Printf("error dump copied: %s\n", filePath) - lastFilePath := filepath.Join(errorDumpDir, errorDumpLastFile) + lastFilePath := filepath.Join(supervisor.ErrorDumpDir, supervisor.ErrorDumpLastFile) if err := ensureSymlink(filePath, lastFilePath); err != nil { fmt.Printf("failed to create symlink: %v\n", err) diff --git a/go.mod b/go.mod index bcfbf2cc1..aac587993 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/caarlos0/env/v11 v11.3.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/creack/goselect v0.1.2 // indirect @@ -87,6 +88,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/ugorji/go/codec v1.3.0 // indirect diff --git a/go.sum b/go.sum index 45ce4685e..8d33b159a 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQ github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b h1:dSbDgy72Y1sjLPWLv7vs0fMFuhMBMViiT9PJZiZWZNs= @@ -173,6 +175,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f h1:VgoRCP1efSCEZIcF2THLQ46+pIBzzgNiaUBe9wEDwYU= github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzro7BGorij2WgrjEammtrkbo3+xldxo+KaGLGUiD+Q= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go new file mode 100644 index 000000000..ffb6e5b73 --- /dev/null +++ b/internal/native/grpc_client.go @@ -0,0 +1,418 @@ +package native + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + pb "github.com/jetkvm/kvm/internal/native/proto" +) + +// GRPCClient wraps the gRPC client for the native service +type GRPCClient struct { + conn *grpc.ClientConn + client pb.NativeServiceClient + logger *zerolog.Logger + + eventStream pb.NativeService_StreamEventsClient + eventM sync.RWMutex + eventCh chan *pb.Event + eventDone chan struct{} + + closed bool + closeM sync.Mutex +} + +// NewGRPCClient creates a new gRPC client connected to the native service +func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, error) { + // Connect to the Unix domain socket + conn, err := grpc.NewClient( + fmt.Sprintf("unix-abstract:%v", socketPath), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, fmt.Errorf("failed to connect to gRPC server: %w", err) + } + + client := pb.NewNativeServiceClient(conn) + + grpcClient := &GRPCClient{ + conn: conn, + client: client, + logger: logger, + eventCh: make(chan *pb.Event, 100), + eventDone: make(chan struct{}), + } + + // Start event stream + go grpcClient.startEventStream() + + return grpcClient, nil +} + +func (c *GRPCClient) startEventStream() { + // for { + // return + // c.closeM.Lock() + // if c.closed { + // c.closeM.Unlock() + // return + // } + // c.closeM.Unlock() + + // ctx := context.Background() + // stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) + // if err != nil { + // c.logger.Warn().Err(err).Msg("failed to start event stream, retrying...") + // time.Sleep(1 * time.Second) + // continue + // } + + // c.eventM.Lock() + // c.eventStream = stream + // c.eventM.Unlock() + + // for { + // event, err := stream.Recv() + // if err == io.EOF { + // c.logger.Debug().Msg("event stream closed") + // break + // } + // if err != nil { + // c.logger.Warn().Err(err).Msg("event stream error") + // break + // } + + // select { + // case c.eventCh <- event: + // default: + // c.logger.Warn().Msg("event channel full, dropping event") + // } + // } + + // c.eventM.Lock() + // c.eventStream = nil + // c.eventM.Unlock() + + // // Wait before retrying + // time.Sleep(1 * time.Second) + // } +} + +func (c *GRPCClient) checkIsReady(ctx context.Context) error { + c.logger.Info().Msg("connection is idle, connecting...") + resp, err := c.client.IsReady(ctx, &pb.IsReadyRequest{}) + if err != nil { + if errors.Is(err, status.Error(codes.Unavailable, "")) { + return fmt.Errorf("timeout waiting for ready: %w", err) + } + return fmt.Errorf("failed to check if ready: %w", err) + } + if resp.Ready { + return nil + } + return nil +} + +// WaitReady waits for the gRPC connection to be ready +func (c *GRPCClient) WaitReady() error { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + for { + state := c.conn.GetState() + if state == connectivity.Idle || state == connectivity.Ready { + if err := c.checkIsReady(ctx); err != nil { + time.Sleep(1 * time.Second) + continue + } + } + + c.logger.Info().Str("state", state.String()).Msg("waiting for connection to be ready") + if state == connectivity.Ready { + return nil + } + if state == connectivity.Shutdown { + return fmt.Errorf("connection failed: %v", state) + } + + if !c.conn.WaitForStateChange(ctx, state) { + return ctx.Err() + } + } +} + +// OnEvent registers an event handler +func (c *GRPCClient) OnEvent(eventType string, handler func(data interface{})) { + return + // go func() { + // for { + // select { + // case event := <-c.eventCh: + // if event.Type == eventType { + // var data interface{} + // switch eventType { + // case "video_state_change": + // if event.VideoState != nil { + // data = VideoState{ + // Ready: event.VideoState.Ready, + // Error: event.VideoState.Error, + // Width: int(event.VideoState.Width), + // Height: int(event.VideoState.Height), + // FramePerSecond: event.VideoState.FramePerSecond, + // } + // } + // case "indev_event": + // data = event.IndevEvent + // case "rpc_event": + // data = event.RpcEvent + // case "video_frame": + // if event.VideoFrame != nil { + // data = map[string]interface{}{ + // "frame": event.VideoFrame.Frame, + // "duration": time.Duration(event.VideoFrame.DurationNs), + // } + // } + // } + // if data != nil { + // handler(data) + // } + // } + // case <-c.eventDone: + // return + // } + // } + // }() +} + +// Close closes the gRPC client +func (c *GRPCClient) Close() error { + c.closeM.Lock() + defer c.closeM.Unlock() + if c.closed { + return nil + } + c.closed = true + + close(c.eventDone) + + c.eventM.Lock() + if c.eventStream != nil { + c.eventStream.CloseSend() + } + c.eventM.Unlock() + + return c.conn.Close() +} + +// Video methods +func (c *GRPCClient) VideoSetSleepMode(enabled bool) error { + _, err := c.client.VideoSetSleepMode(context.Background(), &pb.VideoSetSleepModeRequest{Enabled: enabled}) + return err +} + +func (c *GRPCClient) VideoGetSleepMode() (bool, error) { + resp, err := c.client.VideoGetSleepMode(context.Background(), &pb.Empty{}) + if err != nil { + return false, err + } + return resp.Enabled, nil +} + +func (c *GRPCClient) VideoSleepModeSupported() bool { + resp, err := c.client.VideoSleepModeSupported(context.Background(), &pb.Empty{}) + if err != nil { + return false + } + return resp.Supported +} + +func (c *GRPCClient) VideoSetQualityFactor(factor float64) error { + _, err := c.client.VideoSetQualityFactor(context.Background(), &pb.VideoSetQualityFactorRequest{Factor: factor}) + return err +} + +func (c *GRPCClient) VideoGetQualityFactor() (float64, error) { + resp, err := c.client.VideoGetQualityFactor(context.Background(), &pb.Empty{}) + if err != nil { + return 0, err + } + return resp.Factor, nil +} + +func (c *GRPCClient) VideoSetEDID(edid string) error { + _, err := c.client.VideoSetEDID(context.Background(), &pb.VideoSetEDIDRequest{Edid: edid}) + return err +} + +func (c *GRPCClient) VideoGetEDID() (string, error) { + resp, err := c.client.VideoGetEDID(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Edid, nil +} + +func (c *GRPCClient) VideoLogStatus() (string, error) { + resp, err := c.client.VideoLogStatus(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Status, nil +} + +func (c *GRPCClient) VideoStop() error { + _, err := c.client.VideoStop(context.Background(), &pb.Empty{}) + return err +} + +func (c *GRPCClient) VideoStart() error { + _, err := c.client.VideoStart(context.Background(), &pb.Empty{}) + return err +} + +// UI methods +func (c *GRPCClient) GetLVGLVersion() (string, error) { + resp, err := c.client.GetLVGLVersion(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Version, nil +} + +func (c *GRPCClient) UIObjHide(objName string) (bool, error) { + resp, err := c.client.UIObjHide(context.Background(), &pb.UIObjHideRequest{ObjName: objName}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjShow(objName string) (bool, error) { + resp, err := c.client.UIObjShow(context.Background(), &pb.UIObjShowRequest{ObjName: objName}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UISetVar(name string, value string) { + _, _ = c.client.UISetVar(context.Background(), &pb.UISetVarRequest{Name: name, Value: value}) +} + +func (c *GRPCClient) UIGetVar(name string) string { + resp, err := c.client.UIGetVar(context.Background(), &pb.UIGetVarRequest{Name: name}) + if err != nil { + return "" + } + return resp.Value +} + +func (c *GRPCClient) UIObjAddState(objName string, state string) (bool, error) { + resp, err := c.client.UIObjAddState(context.Background(), &pb.UIObjAddStateRequest{ObjName: objName, State: state}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjClearState(objName string, state string) (bool, error) { + resp, err := c.client.UIObjClearState(context.Background(), &pb.UIObjClearStateRequest{ObjName: objName, State: state}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjAddFlag(objName string, flag string) (bool, error) { + resp, err := c.client.UIObjAddFlag(context.Background(), &pb.UIObjAddFlagRequest{ObjName: objName, Flag: flag}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjClearFlag(objName string, flag string) (bool, error) { + resp, err := c.client.UIObjClearFlag(context.Background(), &pb.UIObjClearFlagRequest{ObjName: objName, Flag: flag}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetOpacity(objName string, opacity int) (bool, error) { + resp, err := c.client.UIObjSetOpacity(context.Background(), &pb.UIObjSetOpacityRequest{ObjName: objName, Opacity: int32(opacity)}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjFadeIn(objName string, duration uint32) (bool, error) { + resp, err := c.client.UIObjFadeIn(context.Background(), &pb.UIObjFadeInRequest{ObjName: objName, Duration: duration}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjFadeOut(objName string, duration uint32) (bool, error) { + resp, err := c.client.UIObjFadeOut(context.Background(), &pb.UIObjFadeOutRequest{ObjName: objName, Duration: duration}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetLabelText(objName string, text string) (bool, error) { + resp, err := c.client.UIObjSetLabelText(context.Background(), &pb.UIObjSetLabelTextRequest{ObjName: objName, Text: text}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetImageSrc(objName string, image string) (bool, error) { + resp, err := c.client.UIObjSetImageSrc(context.Background(), &pb.UIObjSetImageSrcRequest{ObjName: objName, Image: image}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) DisplaySetRotation(rotation uint16) (bool, error) { + resp, err := c.client.DisplaySetRotation(context.Background(), &pb.DisplaySetRotationRequest{Rotation: uint32(rotation)}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UpdateLabelIfChanged(objName string, newText string) { + _, _ = c.client.UpdateLabelIfChanged(context.Background(), &pb.UpdateLabelIfChangedRequest{ObjName: objName, NewText: newText}) +} + +func (c *GRPCClient) UpdateLabelAndChangeVisibility(objName string, newText string) { + _, _ = c.client.UpdateLabelAndChangeVisibility(context.Background(), &pb.UpdateLabelAndChangeVisibilityRequest{ObjName: objName, NewText: newText}) +} + +func (c *GRPCClient) SwitchToScreenIf(screenName string, shouldSwitch []string) { + _, _ = c.client.SwitchToScreenIf(context.Background(), &pb.SwitchToScreenIfRequest{ScreenName: screenName, ShouldSwitch: shouldSwitch}) +} + +func (c *GRPCClient) SwitchToScreenIfDifferent(screenName string) { + _, _ = c.client.SwitchToScreenIfDifferent(context.Background(), &pb.SwitchToScreenIfDifferentRequest{ScreenName: screenName}) +} + +func (c *GRPCClient) DoNotUseThisIsForCrashTestingOnly() { + _, _ = c.client.DoNotUseThisIsForCrashTestingOnly(context.Background(), &pb.Empty{}) +} diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go index b5fcce4ad..e00a39088 100644 --- a/internal/native/grpc_server.go +++ b/internal/native/grpc_server.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "net" - "os" - "path/filepath" "sync" "time" @@ -47,12 +45,14 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { } event := &pb.Event{ Type: "video_state_change", - VideoState: &pb.VideoState{ - Ready: state.Ready, - Error: state.Error, - Width: int32(state.Width), - Height: int32(state.Height), - FramePerSecond: state.FramePerSecond, + Data: &pb.Event_VideoState{ + VideoState: &pb.VideoState{ + Ready: state.Ready, + Error: state.Error, + Width: int32(state.Width), + Height: int32(state.Height), + FramePerSecond: state.FramePerSecond, + }, }, } s.broadcastEvent(event) @@ -63,8 +63,10 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { originalIndevEvent(event) } s.broadcastEvent(&pb.Event{ - Type: "indev_event", - IndevEvent: event, + Type: "indev_event", + Data: &pb.Event_IndevEvent{ + IndevEvent: event, + }, }) } @@ -73,8 +75,10 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { originalRpcEvent(event) } s.broadcastEvent(&pb.Event{ - Type: "rpc_event", - RpcEvent: event, + Type: "rpc_event", + Data: &pb.Event_RpcEvent{ + RpcEvent: event, + }, }) } @@ -84,9 +88,11 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { } s.broadcastEvent(&pb.Event{ Type: "video_frame", - VideoFrame: &pb.VideoFrame{ - Frame: frame, - DurationNs: duration.Nanoseconds(), + Data: &pb.Event_VideoFrame{ + VideoFrame: &pb.VideoFrame{ + Frame: frame, + DurationNs: duration.Nanoseconds(), + }, }, }) } @@ -107,6 +113,10 @@ func (s *grpcServer) broadcastEvent(event *pb.Event) { } } +func (s *grpcServer) IsReady(ctx context.Context, req *pb.IsReadyRequest) (*pb.IsReadyResponse, error) { + return &pb.IsReadyResponse{Ready: true, VideoReady: true}, nil +} + // Video methods func (s *grpcServer) VideoSetSleepMode(ctx context.Context, req *pb.VideoSetSleepModeRequest) (*pb.Empty, error) { if err := s.native.VideoSetSleepMode(req.Enabled); err != nil { @@ -356,17 +366,6 @@ func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamE // StartGRPCServer starts the gRPC server on a Unix domain socket func StartGRPCServer(server *grpcServer, socketPath string, logger *zerolog.Logger) (*grpc.Server, net.Listener, error) { - // Remove socket if it exists - if _, err := os.Stat(socketPath); err == nil { - if err := os.Remove(socketPath); err != nil { - return nil, nil, fmt.Errorf("failed to remove existing socket: %w", err) - } - } - - // Ensure directory exists - if err := os.MkdirAll(filepath.Dir(socketPath), 0755); err != nil { - return nil, nil, fmt.Errorf("failed to create socket directory: %w", err) - } lis, err := net.Listen("unix", socketPath) if err != nil { diff --git a/internal/native/ipc.go b/internal/native/ipc.go deleted file mode 100644 index 4157c96b2..000000000 --- a/internal/native/ipc.go +++ /dev/null @@ -1,293 +0,0 @@ -package native - -import ( - "bufio" - "encoding/json" - "fmt" - "io" - "sync" - "time" - - "github.com/rs/zerolog" -) - -// Request represents a JSON-RPC request -type Request struct { - JSONRPC string `json:"jsonrpc"` - ID interface{} `json:"id,omitempty"` - Method string `json:"method"` - Params json.RawMessage `json:"params,omitempty"` -} - -// Response represents a JSON-RPC response -type Response struct { - JSONRPC string `json:"jsonrpc"` - ID interface{} `json:"id,omitempty"` - Result interface{} `json:"result,omitempty"` - Error *RPCError `json:"error,omitempty"` -} - -// RPCError represents a JSON-RPC error -type RPCError struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} - -// Event represents a JSON-RPC notification/event -type Event struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// ProcessConfig is the configuration for the native process -type ProcessConfig struct { - Disable bool `json:"disable"` - SystemVersion string `json:"system_version"` // Serialized as string - AppVersion string `json:"app_version"` // Serialized as string - DisplayRotation uint16 `json:"display_rotation"` - DefaultQualityFactor float64 `json:"default_quality_factor"` -} - -// IPCClient handles communication with the native process -type IPCClient struct { - stdin io.WriteCloser - stdout *bufio.Scanner - logger *zerolog.Logger - - requestID int64 - requestIDM sync.Mutex - - pendingRequests map[interface{}]chan *Response - pendingM sync.Mutex - - eventHandlers map[string][]func(data interface{}) - eventM sync.RWMutex - - readyCh chan struct{} - ready bool - - closed bool - closeM sync.Mutex -} - -type processCmd interface { - Start() error - Wait() error - GetProcess() interface { - Kill() error - Signal(sig interface{}) error - } - StdinPipe() (io.WriteCloser, error) - StdoutPipe() (io.ReadCloser, error) - StderrPipe() (io.ReadCloser, error) -} - -func NewIPCClient(cmd processCmd, logger *zerolog.Logger) (*IPCClient, error) { - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, fmt.Errorf("failed to get stdin pipe: %w", err) - } - - stdout, err := cmd.StdoutPipe() - if err != nil { - stdin.Close() - return nil, fmt.Errorf("failed to get stdout pipe: %w", err) - } - - client := &IPCClient{ - stdin: stdin, - stdout: bufio.NewScanner(stdout), - logger: logger, - pendingRequests: make(map[interface{}]chan *Response), - eventHandlers: make(map[string][]func(data interface{})), - readyCh: make(chan struct{}), - } - - // Start reading responses - go client.readLoop() - - return client, nil -} - -func (c *IPCClient) readLoop() { - for c.stdout.Scan() { - line := c.stdout.Bytes() - if len(line) == 0 { - continue - } - - // Try to parse as response - var resp Response - if err := json.Unmarshal(line, &resp); err == nil { - // Check if it's a ready signal (result is "ready" and no ID) - if resp.Result == "ready" && resp.ID == nil && !c.ready { - c.ready = true - close(c.readyCh) - continue - } - if resp.Result != nil || resp.Error != nil { - c.handleResponse(&resp) - continue - } - } - - // Try to parse as event - var event Event - if err := json.Unmarshal(line, &event); err == nil && event.Method == "event" { - c.handleEvent(&event) - continue - } - - c.logger.Warn().Bytes("line", line).Msg("unexpected message from native process") - } - - c.closeM.Lock() - if !c.closed { - c.closed = true - c.closeM.Unlock() - c.logger.Warn().Msg("native process stdout closed") - // Cancel all pending requests - c.pendingM.Lock() - for id, ch := range c.pendingRequests { - close(ch) - delete(c.pendingRequests, id) - } - c.pendingM.Unlock() - } else { - c.closeM.Unlock() - } -} - -func (c *IPCClient) handleResponse(resp *Response) { - c.pendingM.Lock() - ch, ok := c.pendingRequests[resp.ID] - if ok { - delete(c.pendingRequests, resp.ID) - } - c.pendingM.Unlock() - - if ok { - select { - case ch <- resp: - default: - } - } -} - -func (c *IPCClient) handleEvent(event *Event) { - if event.Method != "event" || event.Params == nil { - return - } - - eventType, ok := event.Params["type"].(string) - if !ok { - return - } - - data := event.Params["data"] - - c.eventM.RLock() - handlers := c.eventHandlers[eventType] - c.eventM.RUnlock() - - for _, handler := range handlers { - handler(data) - } -} - -func (c *IPCClient) Call(method string, params interface{}) (*Response, error) { - c.closeM.Lock() - if c.closed { - c.closeM.Unlock() - return nil, fmt.Errorf("client is closed") - } - c.closeM.Unlock() - - c.requestIDM.Lock() - c.requestID++ - id := c.requestID - c.requestIDM.Unlock() - - req := Request{ - JSONRPC: "2.0", - ID: id, - Method: method, - } - - if params != nil { - paramsBytes, err := json.Marshal(params) - if err != nil { - return nil, fmt.Errorf("failed to marshal params: %w", err) - } - req.Params = paramsBytes - } - - ch := make(chan *Response, 1) - c.pendingM.Lock() - c.pendingRequests[id] = ch - c.pendingM.Unlock() - - reqBytes, err := json.Marshal(req) - if err != nil { - c.pendingM.Lock() - delete(c.pendingRequests, id) - c.pendingM.Unlock() - return nil, fmt.Errorf("failed to marshal request: %w", err) - } - - if _, err := c.stdin.Write(append(reqBytes, '\n')); err != nil { - c.pendingM.Lock() - delete(c.pendingRequests, id) - c.pendingM.Unlock() - return nil, fmt.Errorf("failed to write request: %w", err) - } - - select { - case resp := <-ch: - if resp.Error != nil { - return nil, fmt.Errorf("RPC error: %s (code: %d)", resp.Error.Message, resp.Error.Code) - } - return resp, nil - case <-time.After(30 * time.Second): - c.pendingM.Lock() - delete(c.pendingRequests, id) - c.pendingM.Unlock() - return nil, fmt.Errorf("request timeout") - } -} - -func (c *IPCClient) OnEvent(eventType string, handler func(data interface{})) { - c.eventM.Lock() - defer c.eventM.Unlock() - c.eventHandlers[eventType] = append(c.eventHandlers[eventType], handler) -} - -func (c *IPCClient) WaitReady() error { - select { - case <-c.readyCh: - return nil - case <-time.After(10 * time.Second): - return fmt.Errorf("timeout waiting for ready signal") - } -} - -func (c *IPCClient) Close() error { - c.closeM.Lock() - defer c.closeM.Unlock() - if c.closed { - return nil - } - c.closed = true - - c.pendingM.Lock() - for id, ch := range c.pendingRequests { - close(ch) - delete(c.pendingRequests, id) - } - c.pendingM.Unlock() - - return c.stdin.Close() -} - diff --git a/internal/native/log.go b/internal/native/log.go index 41ae4df9f..d74b81a26 100644 --- a/internal/native/log.go +++ b/internal/native/log.go @@ -1,11 +1,14 @@ package native import ( + "os" + "github.com/jetkvm/kvm/internal/logging" "github.com/rs/zerolog" ) -var nativeLogger = logging.GetSubsystemLogger("native") +var nativeL = logging.GetSubsystemLogger("native").With().Int("pid", os.Getpid()).Logger() +var nativeLogger = &nativeL var displayLogger = logging.GetSubsystemLogger("display") type nativeLogMessage struct { diff --git a/internal/native/native.go b/internal/native/native.go index e772db526..edf5217a8 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -28,11 +28,11 @@ type Native struct { } type NativeOptions struct { - Disable bool - SystemVersion *semver.Version - AppVersion *semver.Version - DisplayRotation uint16 - DefaultQualityFactor float64 + Disable bool `env:"JETKVM_NATIVE_DISABLE"` + SystemVersion *semver.Version `env:"JETKVM_NATIVE_SYSTEM_VERSION"` + AppVersion *semver.Version `env:"JETKVM_NATIVE_APP_VERSION"` + DisplayRotation uint16 `env:"JETKVM_NATIVE_DISPLAY_ROTATION"` + DefaultQualityFactor float64 `env:"JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR"` OnVideoStateChange func(state VideoState) OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) @@ -50,7 +50,7 @@ func NewNative(opts NativeOptions) *Native { onVideoFrameReceived := opts.OnVideoFrameReceived if onVideoFrameReceived == nil { onVideoFrameReceived = func(frame []byte, duration time.Duration) { - nativeLogger.Info().Interface("frame", frame).Dur("duration", duration).Msg("video frame received") + nativeLogger.Trace().Interface("frame", frame).Dur("duration", duration).Msg("video frame received") } } diff --git a/internal/native/proto/native.pb.go b/internal/native/proto/native.pb.go index 0cb58fffc..7eb5fdcc8 100644 --- a/internal/native/proto/native.pb.go +++ b/internal/native/proto/native.pb.go @@ -1,596 +1,2775 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// This file should be regenerated from native.proto using: -// protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/native/proto/native.proto -// -// For now, this is a minimal implementation to allow compilation. -// TODO: Regenerate from proto file when protoc is available. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: internal/native/proto/native.proto package proto import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" ) const ( + // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Messages type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Empty) Reset() { *x = Empty{} + mi := &file_internal_native_proto_native_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Empty) String() string { - return "" + return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - return nil // Stub + mi := &file_internal_native_proto_native_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -type VideoState struct { - state protoimpl.MessageState +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{0} +} + +type IsReadyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache +} + +func (x *IsReadyRequest) Reset() { + *x = IsReadyRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IsReadyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsReadyRequest) ProtoMessage() {} + +func (x *IsReadyRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[1] + 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 IsReadyRequest.ProtoReflect.Descriptor instead. +func (*IsReadyRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{1} +} + +type IsReadyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ready bool `protobuf:"varint,1,opt,name=ready,proto3" json:"ready,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + VideoReady bool `protobuf:"varint,3,opt,name=video_ready,json=videoReady,proto3" json:"video_ready,omitempty"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IsReadyResponse) Reset() { + *x = IsReadyResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IsReadyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsReadyResponse) ProtoMessage() {} + +func (x *IsReadyResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[2] + 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 IsReadyResponse.ProtoReflect.Descriptor instead. +func (*IsReadyResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{2} +} + +func (x *IsReadyResponse) GetReady() bool { + if x != nil { + return x.Ready + } + return false +} + +func (x *IsReadyResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} - Ready bool `protobuf:"varint,1,opt,name=ready,proto3" json:"ready,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` - Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` - Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` - FramePerSecond float64 `protobuf:"fixed64,5,opt,name=frame_per_second,json=framePerSecond,proto3" json:"frame_per_second,omitempty"` +func (x *IsReadyResponse) GetVideoReady() bool { + if x != nil { + return x.VideoReady + } + return false +} + +type VideoState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ready bool `protobuf:"varint,1,opt,name=ready,proto3" json:"ready,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` + Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` + FramePerSecond float64 `protobuf:"fixed64,5,opt,name=frame_per_second,json=framePerSecond,proto3" json:"frame_per_second,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *VideoState) Reset() { *x = VideoState{} + mi := &file_internal_native_proto_native_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *VideoState) String() string { - return "" + return protoimpl.X.MessageStringOf(x) } func (*VideoState) ProtoMessage() {} func (x *VideoState) ProtoReflect() protoreflect.Message { - return nil // Stub + mi := &file_internal_native_proto_native_proto_msgTypes[3] + 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 VideoState.ProtoReflect.Descriptor instead. +func (*VideoState) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{3} +} + +func (x *VideoState) GetReady() bool { + if x != nil { + return x.Ready + } + return false +} + +func (x *VideoState) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *VideoState) GetWidth() int32 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *VideoState) GetHeight() int32 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *VideoState) GetFramePerSecond() float64 { + if x != nil { + return x.FramePerSecond + } + return 0 } -// Request/Response types - minimal implementations type VideoSetSleepModeRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` unknownFields protoimpl.UnknownFields - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + sizeCache protoimpl.SizeCache +} + +func (x *VideoSetSleepModeRequest) Reset() { + *x = VideoSetSleepModeRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoSetSleepModeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoSetSleepModeRequest) ProtoMessage() {} + +func (x *VideoSetSleepModeRequest) 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 VideoSetSleepModeRequest.ProtoReflect.Descriptor instead. +func (*VideoSetSleepModeRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{4} } -func (x *VideoSetSleepModeRequest) Reset() { *x = VideoSetSleepModeRequest{} } -func (x *VideoSetSleepModeRequest) String() string { return "" } -func (*VideoSetSleepModeRequest) ProtoMessage() {} -func (x *VideoSetSleepModeRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSetSleepModeRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} type VideoGetSleepModeResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` unknownFields protoimpl.UnknownFields - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + sizeCache protoimpl.SizeCache +} + +func (x *VideoGetSleepModeResponse) Reset() { + *x = VideoGetSleepModeResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *VideoGetSleepModeResponse) Reset() { *x = VideoGetSleepModeResponse{} } -func (x *VideoGetSleepModeResponse) String() string { return "" } -func (*VideoGetSleepModeResponse) ProtoMessage() {} -func (x *VideoGetSleepModeResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoGetSleepModeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoGetSleepModeResponse) ProtoMessage() {} + +func (x *VideoGetSleepModeResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[5] + 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 VideoGetSleepModeResponse.ProtoReflect.Descriptor instead. +func (*VideoGetSleepModeResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{5} +} + +func (x *VideoGetSleepModeResponse) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} type VideoSleepModeSupportedResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Supported bool `protobuf:"varint,1,opt,name=supported,proto3" json:"supported,omitempty"` unknownFields protoimpl.UnknownFields - Supported bool `protobuf:"varint,1,opt,name=supported,proto3" json:"supported,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *VideoSleepModeSupportedResponse) Reset() { *x = VideoSleepModeSupportedResponse{} } -func (x *VideoSleepModeSupportedResponse) String() string { return "" } -func (*VideoSleepModeSupportedResponse) ProtoMessage() {} -func (x *VideoSleepModeSupportedResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSleepModeSupportedResponse) Reset() { + *x = VideoSleepModeSupportedResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type VideoSetQualityFactorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` +func (x *VideoSleepModeSupportedResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *VideoSetQualityFactorRequest) Reset() { *x = VideoSetQualityFactorRequest{} } -func (x *VideoSetQualityFactorRequest) String() string { return "" } -func (*VideoSetQualityFactorRequest) ProtoMessage() {} -func (x *VideoSetQualityFactorRequest) ProtoReflect() protoreflect.Message { return nil } +func (*VideoSleepModeSupportedResponse) ProtoMessage() {} + +func (x *VideoSleepModeSupportedResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type VideoGetQualityFactorResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` +// Deprecated: Use VideoSleepModeSupportedResponse.ProtoReflect.Descriptor instead. +func (*VideoSleepModeSupportedResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{6} } -func (x *VideoGetQualityFactorResponse) Reset() { *x = VideoGetQualityFactorResponse{} } -func (x *VideoGetQualityFactorResponse) String() string { return "" } -func (*VideoGetQualityFactorResponse) ProtoMessage() {} -func (x *VideoGetQualityFactorResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSleepModeSupportedResponse) GetSupported() bool { + if x != nil { + return x.Supported + } + return false +} -type VideoSetEDIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type VideoSetQualityFactorRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` unknownFields protoimpl.UnknownFields - Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *VideoSetEDIDRequest) Reset() { *x = VideoSetEDIDRequest{} } -func (x *VideoSetEDIDRequest) String() string { return "" } -func (*VideoSetEDIDRequest) ProtoMessage() {} -func (x *VideoSetEDIDRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSetQualityFactorRequest) Reset() { + *x = VideoSetQualityFactorRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type VideoGetEDIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` +func (x *VideoSetQualityFactorRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *VideoGetEDIDResponse) Reset() { *x = VideoGetEDIDResponse{} } -func (x *VideoGetEDIDResponse) String() string { return "" } -func (*VideoGetEDIDResponse) ProtoMessage() {} -func (x *VideoGetEDIDResponse) ProtoReflect() protoreflect.Message { return nil } +func (*VideoSetQualityFactorRequest) ProtoMessage() {} + +func (x *VideoSetQualityFactorRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type VideoLogStatusResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +// Deprecated: Use VideoSetQualityFactorRequest.ProtoReflect.Descriptor instead. +func (*VideoSetQualityFactorRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{7} } -func (x *VideoLogStatusResponse) Reset() { *x = VideoLogStatusResponse{} } -func (x *VideoLogStatusResponse) String() string { return "" } -func (*VideoLogStatusResponse) ProtoMessage() {} -func (x *VideoLogStatusResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSetQualityFactorRequest) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} -type GetLVGLVersionResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type VideoGetQualityFactorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Factor float64 `protobuf:"fixed64,1,opt,name=factor,proto3" json:"factor,omitempty"` unknownFields protoimpl.UnknownFields - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *GetLVGLVersionResponse) Reset() { *x = GetLVGLVersionResponse{} } -func (x *GetLVGLVersionResponse) String() string { return "" } -func (*GetLVGLVersionResponse) ProtoMessage() {} -func (x *GetLVGLVersionResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoGetQualityFactorResponse) Reset() { + *x = VideoGetQualityFactorResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -// UI message types -type UIObjHideRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` +func (x *VideoGetQualityFactorResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjHideRequest) Reset() { *x = UIObjHideRequest{} } -func (x *UIObjHideRequest) String() string { return "" } -func (*UIObjHideRequest) ProtoMessage() {} -func (x *UIObjHideRequest) ProtoReflect() protoreflect.Message { return nil } +func (*VideoGetQualityFactorResponse) ProtoMessage() {} + +func (x *VideoGetQualityFactorResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UIObjHideResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +// Deprecated: Use VideoGetQualityFactorResponse.ProtoReflect.Descriptor instead. +func (*VideoGetQualityFactorResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{8} } -func (x *UIObjHideResponse) Reset() { *x = UIObjHideResponse{} } -func (x *UIObjHideResponse) String() string { return "" } -func (*UIObjHideResponse) ProtoMessage() {} -func (x *UIObjHideResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoGetQualityFactorResponse) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} -type UIObjShowRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type VideoSetEDIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjShowRequest) Reset() { *x = UIObjShowRequest{} } -func (x *UIObjShowRequest) String() string { return "" } -func (*UIObjShowRequest) ProtoMessage() {} -func (x *UIObjShowRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSetEDIDRequest) Reset() { + *x = VideoSetEDIDRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjShowResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +func (x *VideoSetEDIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjShowResponse) Reset() { *x = UIObjShowResponse{} } -func (x *UIObjShowResponse) String() string { return "" } -func (*UIObjShowResponse) ProtoMessage() {} -func (x *UIObjShowResponse) ProtoReflect() protoreflect.Message { return nil } +func (*VideoSetEDIDRequest) ProtoMessage() {} + +func (x *VideoSetEDIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UISetVarRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +// Deprecated: Use VideoSetEDIDRequest.ProtoReflect.Descriptor instead. +func (*VideoSetEDIDRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{9} } -func (x *UISetVarRequest) Reset() { *x = UISetVarRequest{} } -func (x *UISetVarRequest) String() string { return "" } -func (*UISetVarRequest) ProtoMessage() {} -func (x *UISetVarRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoSetEDIDRequest) GetEdid() string { + if x != nil { + return x.Edid + } + return "" +} -type UIGetVarRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type VideoGetEDIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Edid string `protobuf:"bytes,1,opt,name=edid,proto3" json:"edid,omitempty"` unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIGetVarRequest) Reset() { *x = UIGetVarRequest{} } -func (x *UIGetVarRequest) String() string { return "" } -func (*UIGetVarRequest) ProtoMessage() {} -func (x *UIGetVarRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoGetEDIDResponse) Reset() { + *x = VideoGetEDIDResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIGetVarResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +func (x *VideoGetEDIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIGetVarResponse) Reset() { *x = UIGetVarResponse{} } -func (x *UIGetVarResponse) String() string { return "" } -func (*UIGetVarResponse) ProtoMessage() {} -func (x *UIGetVarResponse) ProtoReflect() protoreflect.Message { return nil } +func (*VideoGetEDIDResponse) ProtoMessage() {} + +func (x *VideoGetEDIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -// Additional UI types - abbreviated for brevity, follow same pattern -type UIObjAddStateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` +// Deprecated: Use VideoGetEDIDResponse.ProtoReflect.Descriptor instead. +func (*VideoGetEDIDResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{10} } -func (x *UIObjAddStateRequest) Reset() { *x = UIObjAddStateRequest{} } -func (x *UIObjAddStateRequest) String() string { return "" } -func (*UIObjAddStateRequest) ProtoMessage() {} -func (x *UIObjAddStateRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoGetEDIDResponse) GetEdid() string { + if x != nil { + return x.Edid + } + return "" +} -type UIObjAddStateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type VideoLogStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjAddStateResponse) Reset() { *x = UIObjAddStateResponse{} } -func (x *UIObjAddStateResponse) String() string { return "" } -func (*UIObjAddStateResponse) ProtoMessage() {} -func (x *UIObjAddStateResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoLogStatusResponse) Reset() { + *x = VideoLogStatusResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -// Event types -type Event struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - // oneof data - using separate fields for now - VideoState *VideoState `protobuf:"bytes,2,opt,name=video_state,json=videoState,proto3" json:"video_state,omitempty"` - IndevEvent string `protobuf:"bytes,3,opt,name=indev_event,json=indevEvent,proto3" json:"indev_event,omitempty"` - RpcEvent string `protobuf:"bytes,4,opt,name=rpc_event,json=rpcEvent,proto3" json:"rpc_event,omitempty"` - VideoFrame *VideoFrame `protobuf:"bytes,5,opt,name=video_frame,json=videoFrame,proto3" json:"video_frame,omitempty"` +func (x *VideoLogStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *Event) Reset() { *x = Event{} } -func (x *Event) String() string { return "" } -func (*Event) ProtoMessage() {} -func (x *Event) ProtoReflect() protoreflect.Message { return nil } +func (*VideoLogStatusResponse) ProtoMessage() {} + +func (x *VideoLogStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type VideoFrame struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Frame []byte `protobuf:"bytes,1,opt,name=frame,proto3" json:"frame,omitempty"` - DurationNs int64 `protobuf:"varint,2,opt,name=duration_ns,json=durationNs,proto3" json:"duration_ns,omitempty"` +// Deprecated: Use VideoLogStatusResponse.ProtoReflect.Descriptor instead. +func (*VideoLogStatusResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{11} } -func (x *VideoFrame) Reset() { *x = VideoFrame{} } -func (x *VideoFrame) String() string { return "" } -func (*VideoFrame) ProtoMessage() {} -func (x *VideoFrame) ProtoReflect() protoreflect.Message { return nil } +func (x *VideoLogStatusResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} -// Additional request/response types - following same pattern -// (Abbreviated - all follow the same structure) -type UIObjClearStateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type GetLVGLVersionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjClearStateRequest) Reset() { *x = UIObjClearStateRequest{} } -func (x *UIObjClearStateRequest) String() string { return "" } -func (*UIObjClearStateRequest) ProtoMessage() {} -func (x *UIObjClearStateRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *GetLVGLVersionResponse) Reset() { + *x = GetLVGLVersionResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjClearStateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +func (x *GetLVGLVersionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLVGLVersionResponse) ProtoMessage() {} + +func (x *GetLVGLVersionResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (x *UIObjClearStateResponse) Reset() { *x = UIObjClearStateResponse{} } -func (x *UIObjClearStateResponse) String() string { return "" } -func (*UIObjClearStateResponse) ProtoMessage() {} -func (x *UIObjClearStateResponse) ProtoReflect() protoreflect.Message { return nil } +// Deprecated: Use GetLVGLVersionResponse.ProtoReflect.Descriptor instead. +func (*GetLVGLVersionResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{12} +} -// Remaining types follow same pattern - creating minimal stubs -// TODO: Regenerate from proto for complete implementation +func (x *GetLVGLVersionResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} -type UIObjAddFlagRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UIObjHideRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjAddFlagRequest) Reset() { *x = UIObjAddFlagRequest{} } -func (x *UIObjAddFlagRequest) String() string { return "" } -func (*UIObjAddFlagRequest) ProtoMessage() {} -func (x *UIObjAddFlagRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjHideRequest) Reset() { + *x = UIObjHideRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjAddFlagResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +func (x *UIObjHideRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjAddFlagResponse) Reset() { *x = UIObjAddFlagResponse{} } -func (x *UIObjAddFlagResponse) String() string { return "" } -func (*UIObjAddFlagResponse) ProtoMessage() {} -func (x *UIObjAddFlagResponse) ProtoReflect() protoreflect.Message { return nil } +func (*UIObjHideRequest) ProtoMessage() {} + +func (x *UIObjHideRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UIObjClearFlagRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` +// Deprecated: Use UIObjHideRequest.ProtoReflect.Descriptor instead. +func (*UIObjHideRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{13} } -func (x *UIObjClearFlagRequest) Reset() { *x = UIObjClearFlagRequest{} } -func (x *UIObjClearFlagRequest) String() string { return "" } -func (*UIObjClearFlagRequest) ProtoMessage() {} -func (x *UIObjClearFlagRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjHideRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} -type UIObjClearFlagResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UIObjHideResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjClearFlagResponse) Reset() { *x = UIObjClearFlagResponse{} } -func (x *UIObjClearFlagResponse) String() string { return "" } -func (*UIObjClearFlagResponse) ProtoMessage() {} -func (x *UIObjClearFlagResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjHideResponse) Reset() { + *x = UIObjHideResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjSetOpacityRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Opacity int32 `protobuf:"varint,2,opt,name=opacity,proto3" json:"opacity,omitempty"` +func (x *UIObjHideResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjSetOpacityRequest) Reset() { *x = UIObjSetOpacityRequest{} } -func (x *UIObjSetOpacityRequest) String() string { return "" } -func (*UIObjSetOpacityRequest) ProtoMessage() {} -func (x *UIObjSetOpacityRequest) ProtoReflect() protoreflect.Message { return nil } +func (*UIObjHideResponse) ProtoMessage() {} + +func (x *UIObjHideResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UIObjSetOpacityResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +// Deprecated: Use UIObjHideResponse.ProtoReflect.Descriptor instead. +func (*UIObjHideResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{14} } -func (x *UIObjSetOpacityResponse) Reset() { *x = UIObjSetOpacityResponse{} } -func (x *UIObjSetOpacityResponse) String() string { return "" } -func (*UIObjSetOpacityResponse) ProtoMessage() {} -func (x *UIObjSetOpacityResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjHideResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} -type UIObjFadeInRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UIObjShowRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjFadeInRequest) Reset() { *x = UIObjFadeInRequest{} } -func (x *UIObjFadeInRequest) String() string { return "" } -func (*UIObjFadeInRequest) ProtoMessage() {} -func (x *UIObjFadeInRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjShowRequest) Reset() { + *x = UIObjShowRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjFadeInResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +func (x *UIObjShowRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjFadeInResponse) Reset() { *x = UIObjFadeInResponse{} } -func (x *UIObjFadeInResponse) String() string { return "" } -func (*UIObjFadeInResponse) ProtoMessage() {} -func (x *UIObjFadeInResponse) ProtoReflect() protoreflect.Message { return nil } +func (*UIObjShowRequest) ProtoMessage() {} + +func (x *UIObjShowRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UIObjFadeOutRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` +// Deprecated: Use UIObjShowRequest.ProtoReflect.Descriptor instead. +func (*UIObjShowRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{15} } -func (x *UIObjFadeOutRequest) Reset() { *x = UIObjFadeOutRequest{} } -func (x *UIObjFadeOutRequest) String() string { return "" } -func (*UIObjFadeOutRequest) ProtoMessage() {} -func (x *UIObjFadeOutRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjShowRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} -type UIObjFadeOutResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UIObjShowResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjFadeOutResponse) Reset() { *x = UIObjFadeOutResponse{} } -func (x *UIObjFadeOutResponse) String() string { return "" } -func (*UIObjFadeOutResponse) ProtoMessage() {} -func (x *UIObjFadeOutResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjShowResponse) Reset() { + *x = UIObjShowResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjSetLabelTextRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` +func (x *UIObjShowResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjSetLabelTextRequest) Reset() { *x = UIObjSetLabelTextRequest{} } -func (x *UIObjSetLabelTextRequest) String() string { return "" } -func (*UIObjSetLabelTextRequest) ProtoMessage() {} -func (x *UIObjSetLabelTextRequest) ProtoReflect() protoreflect.Message { return nil } +func (*UIObjShowResponse) ProtoMessage() {} + +func (x *UIObjShowResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UIObjSetLabelTextResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +// Deprecated: Use UIObjShowResponse.ProtoReflect.Descriptor instead. +func (*UIObjShowResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{16} } -func (x *UIObjSetLabelTextResponse) Reset() { *x = UIObjSetLabelTextResponse{} } -func (x *UIObjSetLabelTextResponse) String() string { return "" } -func (*UIObjSetLabelTextResponse) ProtoMessage() {} -func (x *UIObjSetLabelTextResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *UIObjShowResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} -type UIObjSetImageSrcRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UISetVarRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *UIObjSetImageSrcRequest) Reset() { *x = UIObjSetImageSrcRequest{} } -func (x *UIObjSetImageSrcRequest) String() string { return "" } -func (*UIObjSetImageSrcRequest) ProtoMessage() {} -func (x *UIObjSetImageSrcRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UISetVarRequest) Reset() { + *x = UISetVarRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UIObjSetImageSrcResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +func (x *UISetVarRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UIObjSetImageSrcResponse) Reset() { *x = UIObjSetImageSrcResponse{} } -func (x *UIObjSetImageSrcResponse) String() string { return "" } -func (*UIObjSetImageSrcResponse) ProtoMessage() {} -func (x *UIObjSetImageSrcResponse) ProtoReflect() protoreflect.Message { return nil } +func (*UISetVarRequest) ProtoMessage() {} + +func (x *UISetVarRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type DisplaySetRotationRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Rotation uint32 `protobuf:"varint,1,opt,name=rotation,proto3" json:"rotation,omitempty"` +// Deprecated: Use UISetVarRequest.ProtoReflect.Descriptor instead. +func (*UISetVarRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{17} } -func (x *DisplaySetRotationRequest) Reset() { *x = DisplaySetRotationRequest{} } -func (x *DisplaySetRotationRequest) String() string { return "" } -func (*DisplaySetRotationRequest) ProtoMessage() {} -func (x *DisplaySetRotationRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UISetVarRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} -type DisplaySetRotationResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +func (x *UISetVarRequest) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type UIGetVarRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` unknownFields protoimpl.UnknownFields - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *DisplaySetRotationResponse) Reset() { *x = DisplaySetRotationResponse{} } -func (x *DisplaySetRotationResponse) String() string { return "" } -func (*DisplaySetRotationResponse) ProtoMessage() {} -func (x *DisplaySetRotationResponse) ProtoReflect() protoreflect.Message { return nil } +func (x *UIGetVarRequest) Reset() { + *x = UIGetVarRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type UpdateLabelIfChangedRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` +func (x *UIGetVarRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *UpdateLabelIfChangedRequest) Reset() { *x = UpdateLabelIfChangedRequest{} } -func (x *UpdateLabelIfChangedRequest) String() string { return "" } -func (*UpdateLabelIfChangedRequest) ProtoMessage() {} -func (x *UpdateLabelIfChangedRequest) ProtoReflect() protoreflect.Message { return nil } +func (*UIGetVarRequest) ProtoMessage() {} + +func (x *UIGetVarRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -type UpdateLabelAndChangeVisibilityRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` - NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` +// Deprecated: Use UIGetVarRequest.ProtoReflect.Descriptor instead. +func (*UIGetVarRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{18} } -func (x *UpdateLabelAndChangeVisibilityRequest) Reset() { *x = UpdateLabelAndChangeVisibilityRequest{} } -func (x *UpdateLabelAndChangeVisibilityRequest) String() string { return "" } -func (*UpdateLabelAndChangeVisibilityRequest) ProtoMessage() {} -func (x *UpdateLabelAndChangeVisibilityRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIGetVarRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} -type SwitchToScreenIfRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type UIGetVarResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` - ShouldSwitch []string `protobuf:"bytes,2,rep,name=should_switch,json=shouldSwitch,proto3" json:"should_switch,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *SwitchToScreenIfRequest) Reset() { *x = SwitchToScreenIfRequest{} } -func (x *SwitchToScreenIfRequest) String() string { return "" } -func (*SwitchToScreenIfRequest) ProtoMessage() {} -func (x *SwitchToScreenIfRequest) ProtoReflect() protoreflect.Message { return nil } +func (x *UIGetVarResponse) Reset() { + *x = UIGetVarResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -type SwitchToScreenIfDifferentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` +func (x *UIGetVarResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *SwitchToScreenIfDifferentRequest) Reset() { *x = SwitchToScreenIfDifferentRequest{} } -func (x *SwitchToScreenIfDifferentRequest) String() string { return "" } -func (*SwitchToScreenIfDifferentRequest) ProtoMessage() {} -func (x *SwitchToScreenIfDifferentRequest) ProtoReflect() protoreflect.Message { return nil } +func (*UIGetVarResponse) ProtoMessage() {} + +func (x *UIGetVarResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[19] + 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 UIGetVarResponse.ProtoReflect.Descriptor instead. +func (*UIGetVarResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{19} +} + +func (x *UIGetVarResponse) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type UIObjAddStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjAddStateRequest) Reset() { + *x = UIObjAddStateRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjAddStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjAddStateRequest) ProtoMessage() {} + +func (x *UIObjAddStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[20] + 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 UIObjAddStateRequest.ProtoReflect.Descriptor instead. +func (*UIObjAddStateRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{20} +} + +func (x *UIObjAddStateRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjAddStateRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type UIObjAddStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjAddStateResponse) Reset() { + *x = UIObjAddStateResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjAddStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjAddStateResponse) ProtoMessage() {} + +func (x *UIObjAddStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[21] + 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 UIObjAddStateResponse.ProtoReflect.Descriptor instead. +func (*UIObjAddStateResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{21} +} + +func (x *UIObjAddStateResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjClearStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjClearStateRequest) Reset() { + *x = UIObjClearStateRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjClearStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjClearStateRequest) ProtoMessage() {} + +func (x *UIObjClearStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[22] + 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 UIObjClearStateRequest.ProtoReflect.Descriptor instead. +func (*UIObjClearStateRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{22} +} + +func (x *UIObjClearStateRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjClearStateRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type UIObjClearStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjClearStateResponse) Reset() { + *x = UIObjClearStateResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjClearStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjClearStateResponse) ProtoMessage() {} + +func (x *UIObjClearStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[23] + 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 UIObjClearStateResponse.ProtoReflect.Descriptor instead. +func (*UIObjClearStateResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{23} +} + +func (x *UIObjClearStateResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjAddFlagRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjAddFlagRequest) Reset() { + *x = UIObjAddFlagRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjAddFlagRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjAddFlagRequest) ProtoMessage() {} + +func (x *UIObjAddFlagRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[24] + 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 UIObjAddFlagRequest.ProtoReflect.Descriptor instead. +func (*UIObjAddFlagRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{24} +} + +func (x *UIObjAddFlagRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjAddFlagRequest) GetFlag() string { + if x != nil { + return x.Flag + } + return "" +} + +type UIObjAddFlagResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjAddFlagResponse) Reset() { + *x = UIObjAddFlagResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjAddFlagResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjAddFlagResponse) ProtoMessage() {} + +func (x *UIObjAddFlagResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[25] + 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 UIObjAddFlagResponse.ProtoReflect.Descriptor instead. +func (*UIObjAddFlagResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{25} +} + +func (x *UIObjAddFlagResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjClearFlagRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Flag string `protobuf:"bytes,2,opt,name=flag,proto3" json:"flag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjClearFlagRequest) Reset() { + *x = UIObjClearFlagRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjClearFlagRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjClearFlagRequest) ProtoMessage() {} + +func (x *UIObjClearFlagRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[26] + 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 UIObjClearFlagRequest.ProtoReflect.Descriptor instead. +func (*UIObjClearFlagRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{26} +} + +func (x *UIObjClearFlagRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjClearFlagRequest) GetFlag() string { + if x != nil { + return x.Flag + } + return "" +} + +type UIObjClearFlagResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjClearFlagResponse) Reset() { + *x = UIObjClearFlagResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjClearFlagResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjClearFlagResponse) ProtoMessage() {} + +func (x *UIObjClearFlagResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[27] + 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 UIObjClearFlagResponse.ProtoReflect.Descriptor instead. +func (*UIObjClearFlagResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{27} +} + +func (x *UIObjClearFlagResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjSetOpacityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Opacity int32 `protobuf:"varint,2,opt,name=opacity,proto3" json:"opacity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetOpacityRequest) Reset() { + *x = UIObjSetOpacityRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetOpacityRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetOpacityRequest) ProtoMessage() {} + +func (x *UIObjSetOpacityRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[28] + 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 UIObjSetOpacityRequest.ProtoReflect.Descriptor instead. +func (*UIObjSetOpacityRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{28} +} + +func (x *UIObjSetOpacityRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjSetOpacityRequest) GetOpacity() int32 { + if x != nil { + return x.Opacity + } + return 0 +} + +type UIObjSetOpacityResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetOpacityResponse) Reset() { + *x = UIObjSetOpacityResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetOpacityResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetOpacityResponse) ProtoMessage() {} + +func (x *UIObjSetOpacityResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[29] + 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 UIObjSetOpacityResponse.ProtoReflect.Descriptor instead. +func (*UIObjSetOpacityResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{29} +} + +func (x *UIObjSetOpacityResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjFadeInRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjFadeInRequest) Reset() { + *x = UIObjFadeInRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjFadeInRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjFadeInRequest) ProtoMessage() {} + +func (x *UIObjFadeInRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[30] + 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 UIObjFadeInRequest.ProtoReflect.Descriptor instead. +func (*UIObjFadeInRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{30} +} + +func (x *UIObjFadeInRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjFadeInRequest) GetDuration() uint32 { + if x != nil { + return x.Duration + } + return 0 +} + +type UIObjFadeInResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjFadeInResponse) Reset() { + *x = UIObjFadeInResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjFadeInResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjFadeInResponse) ProtoMessage() {} + +func (x *UIObjFadeInResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[31] + 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 UIObjFadeInResponse.ProtoReflect.Descriptor instead. +func (*UIObjFadeInResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{31} +} + +func (x *UIObjFadeInResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjFadeOutRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Duration uint32 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjFadeOutRequest) Reset() { + *x = UIObjFadeOutRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjFadeOutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjFadeOutRequest) ProtoMessage() {} + +func (x *UIObjFadeOutRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[32] + 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 UIObjFadeOutRequest.ProtoReflect.Descriptor instead. +func (*UIObjFadeOutRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{32} +} + +func (x *UIObjFadeOutRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjFadeOutRequest) GetDuration() uint32 { + if x != nil { + return x.Duration + } + return 0 +} + +type UIObjFadeOutResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjFadeOutResponse) Reset() { + *x = UIObjFadeOutResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjFadeOutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjFadeOutResponse) ProtoMessage() {} + +func (x *UIObjFadeOutResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[33] + 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 UIObjFadeOutResponse.ProtoReflect.Descriptor instead. +func (*UIObjFadeOutResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{33} +} + +func (x *UIObjFadeOutResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjSetLabelTextRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetLabelTextRequest) Reset() { + *x = UIObjSetLabelTextRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetLabelTextRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetLabelTextRequest) ProtoMessage() {} + +func (x *UIObjSetLabelTextRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[34] + 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 UIObjSetLabelTextRequest.ProtoReflect.Descriptor instead. +func (*UIObjSetLabelTextRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{34} +} + +func (x *UIObjSetLabelTextRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjSetLabelTextRequest) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +type UIObjSetLabelTextResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetLabelTextResponse) Reset() { + *x = UIObjSetLabelTextResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetLabelTextResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetLabelTextResponse) ProtoMessage() {} + +func (x *UIObjSetLabelTextResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[35] + 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 UIObjSetLabelTextResponse.ProtoReflect.Descriptor instead. +func (*UIObjSetLabelTextResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{35} +} + +func (x *UIObjSetLabelTextResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UIObjSetImageSrcRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetImageSrcRequest) Reset() { + *x = UIObjSetImageSrcRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetImageSrcRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetImageSrcRequest) ProtoMessage() {} + +func (x *UIObjSetImageSrcRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[36] + 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 UIObjSetImageSrcRequest.ProtoReflect.Descriptor instead. +func (*UIObjSetImageSrcRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{36} +} + +func (x *UIObjSetImageSrcRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UIObjSetImageSrcRequest) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +type UIObjSetImageSrcResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIObjSetImageSrcResponse) Reset() { + *x = UIObjSetImageSrcResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIObjSetImageSrcResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIObjSetImageSrcResponse) ProtoMessage() {} + +func (x *UIObjSetImageSrcResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[37] + 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 UIObjSetImageSrcResponse.ProtoReflect.Descriptor instead. +func (*UIObjSetImageSrcResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{37} +} + +func (x *UIObjSetImageSrcResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type DisplaySetRotationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rotation uint32 `protobuf:"varint,1,opt,name=rotation,proto3" json:"rotation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DisplaySetRotationRequest) Reset() { + *x = DisplaySetRotationRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DisplaySetRotationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisplaySetRotationRequest) ProtoMessage() {} + +func (x *DisplaySetRotationRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[38] + 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 DisplaySetRotationRequest.ProtoReflect.Descriptor instead. +func (*DisplaySetRotationRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{38} +} + +func (x *DisplaySetRotationRequest) GetRotation() uint32 { + if x != nil { + return x.Rotation + } + return 0 +} + +type DisplaySetRotationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DisplaySetRotationResponse) Reset() { + *x = DisplaySetRotationResponse{} + mi := &file_internal_native_proto_native_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DisplaySetRotationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisplaySetRotationResponse) ProtoMessage() {} + +func (x *DisplaySetRotationResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[39] + 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 DisplaySetRotationResponse.ProtoReflect.Descriptor instead. +func (*DisplaySetRotationResponse) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{39} +} + +func (x *DisplaySetRotationResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type UpdateLabelIfChangedRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateLabelIfChangedRequest) Reset() { + *x = UpdateLabelIfChangedRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateLabelIfChangedRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateLabelIfChangedRequest) ProtoMessage() {} + +func (x *UpdateLabelIfChangedRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[40] + 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 UpdateLabelIfChangedRequest.ProtoReflect.Descriptor instead. +func (*UpdateLabelIfChangedRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{40} +} + +func (x *UpdateLabelIfChangedRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UpdateLabelIfChangedRequest) GetNewText() string { + if x != nil { + return x.NewText + } + return "" +} + +type UpdateLabelAndChangeVisibilityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ObjName string `protobuf:"bytes,1,opt,name=obj_name,json=objName,proto3" json:"obj_name,omitempty"` + NewText string `protobuf:"bytes,2,opt,name=new_text,json=newText,proto3" json:"new_text,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateLabelAndChangeVisibilityRequest) Reset() { + *x = UpdateLabelAndChangeVisibilityRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateLabelAndChangeVisibilityRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateLabelAndChangeVisibilityRequest) ProtoMessage() {} + +func (x *UpdateLabelAndChangeVisibilityRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[41] + 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 UpdateLabelAndChangeVisibilityRequest.ProtoReflect.Descriptor instead. +func (*UpdateLabelAndChangeVisibilityRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{41} +} + +func (x *UpdateLabelAndChangeVisibilityRequest) GetObjName() string { + if x != nil { + return x.ObjName + } + return "" +} + +func (x *UpdateLabelAndChangeVisibilityRequest) GetNewText() string { + if x != nil { + return x.NewText + } + return "" +} + +type SwitchToScreenIfRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` + ShouldSwitch []string `protobuf:"bytes,2,rep,name=should_switch,json=shouldSwitch,proto3" json:"should_switch,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SwitchToScreenIfRequest) Reset() { + *x = SwitchToScreenIfRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SwitchToScreenIfRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwitchToScreenIfRequest) ProtoMessage() {} + +func (x *SwitchToScreenIfRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[42] + 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 SwitchToScreenIfRequest.ProtoReflect.Descriptor instead. +func (*SwitchToScreenIfRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{42} +} + +func (x *SwitchToScreenIfRequest) GetScreenName() string { + if x != nil { + return x.ScreenName + } + return "" +} + +func (x *SwitchToScreenIfRequest) GetShouldSwitch() []string { + if x != nil { + return x.ShouldSwitch + } + return nil +} + +type SwitchToScreenIfDifferentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ScreenName string `protobuf:"bytes,1,opt,name=screen_name,json=screenName,proto3" json:"screen_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SwitchToScreenIfDifferentRequest) Reset() { + *x = SwitchToScreenIfDifferentRequest{} + mi := &file_internal_native_proto_native_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SwitchToScreenIfDifferentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwitchToScreenIfDifferentRequest) ProtoMessage() {} + +func (x *SwitchToScreenIfDifferentRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[43] + 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 SwitchToScreenIfDifferentRequest.ProtoReflect.Descriptor instead. +func (*SwitchToScreenIfDifferentRequest) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{43} +} + +func (x *SwitchToScreenIfDifferentRequest) GetScreenName() string { + if x != nil { + return x.ScreenName + } + return "" +} + +type Event struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // Types that are valid to be assigned to Data: + // + // *Event_VideoState + // *Event_IndevEvent + // *Event_RpcEvent + // *Event_VideoFrame + Data isEvent_Data `protobuf_oneof:"data"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Event) Reset() { + *x = Event{} + mi := &file_internal_native_proto_native_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[44] + 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 Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{44} +} + +func (x *Event) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Event) GetData() isEvent_Data { + if x != nil { + return x.Data + } + return nil +} + +func (x *Event) GetVideoState() *VideoState { + if x != nil { + if x, ok := x.Data.(*Event_VideoState); ok { + return x.VideoState + } + } + return nil +} + +func (x *Event) GetIndevEvent() string { + if x != nil { + if x, ok := x.Data.(*Event_IndevEvent); ok { + return x.IndevEvent + } + } + return "" +} + +func (x *Event) GetRpcEvent() string { + if x != nil { + if x, ok := x.Data.(*Event_RpcEvent); ok { + return x.RpcEvent + } + } + return "" +} + +func (x *Event) GetVideoFrame() *VideoFrame { + if x != nil { + if x, ok := x.Data.(*Event_VideoFrame); ok { + return x.VideoFrame + } + } + return nil +} + +type isEvent_Data interface { + isEvent_Data() +} + +type Event_VideoState struct { + VideoState *VideoState `protobuf:"bytes,2,opt,name=video_state,json=videoState,proto3,oneof"` +} + +type Event_IndevEvent struct { + IndevEvent string `protobuf:"bytes,3,opt,name=indev_event,json=indevEvent,proto3,oneof"` +} + +type Event_RpcEvent struct { + RpcEvent string `protobuf:"bytes,4,opt,name=rpc_event,json=rpcEvent,proto3,oneof"` +} + +type Event_VideoFrame struct { + VideoFrame *VideoFrame `protobuf:"bytes,5,opt,name=video_frame,json=videoFrame,proto3,oneof"` +} + +func (*Event_VideoState) isEvent_Data() {} + +func (*Event_IndevEvent) isEvent_Data() {} + +func (*Event_RpcEvent) isEvent_Data() {} + +func (*Event_VideoFrame) isEvent_Data() {} + +type VideoFrame struct { + state protoimpl.MessageState `protogen:"open.v1"` + Frame []byte `protobuf:"bytes,1,opt,name=frame,proto3" json:"frame,omitempty"` + DurationNs int64 `protobuf:"varint,2,opt,name=duration_ns,json=durationNs,proto3" json:"duration_ns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoFrame) Reset() { + *x = VideoFrame{} + mi := &file_internal_native_proto_native_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoFrame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoFrame) ProtoMessage() {} + +func (x *VideoFrame) ProtoReflect() protoreflect.Message { + mi := &file_internal_native_proto_native_proto_msgTypes[45] + 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 VideoFrame.ProtoReflect.Descriptor instead. +func (*VideoFrame) Descriptor() ([]byte, []int) { + return file_internal_native_proto_native_proto_rawDescGZIP(), []int{45} +} + +func (x *VideoFrame) GetFrame() []byte { + if x != nil { + return x.Frame + } + return nil +} + +func (x *VideoFrame) GetDurationNs() int64 { + if x != nil { + return x.DurationNs + } + return 0 +} + +var File_internal_native_proto_native_proto protoreflect.FileDescriptor + +var file_internal_native_proto_native_proto_rawDesc = string([]byte{ + 0x0a, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6e, 0x61, 0x74, 0x69, 0x76, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x10, 0x0a, 0x0e, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5e, 0x0a, 0x0f, 0x49, 0x73, 0x52, 0x65, 0x61, + 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, + 0x61, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x61, 0x64, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, + 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x76, 0x69, 0x64, + 0x65, 0x6f, 0x52, 0x65, 0x61, 0x64, 0x79, 0x22, 0x90, 0x01, 0x0a, 0x0a, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x61, 0x64, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x66, 0x72, 0x61, 0x6d, + 0x65, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x56, 0x69, + 0x64, 0x65, 0x6f, 0x53, 0x65, 0x74, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x22, 0x35, 0x0a, 0x19, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, 0x53, 0x6c, 0x65, 0x65, + 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x3f, 0x0a, 0x1f, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x36, 0x0a, 0x1c, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x53, 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, + 0x22, 0x37, 0x0a, 0x1d, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x06, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x13, 0x56, 0x69, 0x64, + 0x65, 0x6f, 0x53, 0x65, 0x74, 0x45, 0x44, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x65, 0x64, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x65, 0x64, 0x69, 0x64, 0x22, 0x2a, 0x0a, 0x14, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, + 0x45, 0x44, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x65, 0x64, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x64, 0x69, 0x64, + 0x22, 0x30, 0x0a, 0x16, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4c, 0x6f, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0x32, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4c, 0x56, 0x47, 0x4c, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2d, 0x0a, 0x10, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x48, + 0x69, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, + 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, + 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x48, 0x69, + 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x22, 0x2d, 0x0a, 0x10, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x68, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x68, 0x6f, 0x77, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x22, 0x3b, 0x0a, 0x0f, 0x55, 0x49, 0x53, 0x65, 0x74, 0x56, 0x61, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x25, 0x0a, 0x0f, 0x55, 0x49, 0x47, 0x65, 0x74, 0x56, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x28, 0x0a, 0x10, 0x55, 0x49, 0x47, 0x65, 0x74, 0x56, + 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x47, 0x0a, 0x14, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, 0x64, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x15, 0x55, 0x49, 0x4f, + 0x62, 0x6a, 0x41, 0x64, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x49, 0x0a, 0x16, + 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x33, 0x0a, 0x17, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x44, 0x0a, 0x13, + 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, 0x64, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, + 0x61, 0x67, 0x22, 0x30, 0x0a, 0x14, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, 0x64, 0x46, 0x6c, + 0x61, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x22, 0x46, 0x0a, 0x15, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, + 0x61, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x22, 0x32, 0x0a, 0x16, + 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x22, 0x4d, 0x0a, 0x16, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4f, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, + 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, + 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x22, + 0x33, 0x0a, 0x17, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4f, 0x70, 0x61, 0x63, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x22, 0x4b, 0x0a, 0x12, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, + 0x65, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, + 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, + 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x2f, 0x0a, 0x13, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x49, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x13, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x4f, + 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x30, 0x0a, 0x14, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x4f, 0x75, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x22, 0x49, 0x0a, 0x18, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x35, 0x0a, + 0x19, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x65, + 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x22, 0x4a, 0x0a, 0x17, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, + 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x72, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x22, 0x34, 0x0a, 0x18, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x53, 0x72, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x37, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x36, 0x0a, 0x1a, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x53, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x66, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x22, 0x5d, 0x0a, 0x25, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x41, 0x6e, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x62, 0x6a, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x65, 0x78, 0x74, 0x22, 0x5f, 0x0a, 0x17, 0x53, + 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x6f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x49, 0x66, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x63, 0x72, + 0x65, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x68, 0x6f, 0x75, 0x6c, + 0x64, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x22, 0x43, 0x0a, 0x20, + 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x6f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x49, 0x66, + 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4e, 0x61, 0x6d, + 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x35, 0x0a, 0x0b, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, + 0x64, 0x65, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x76, 0x5f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, + 0x6e, 0x64, 0x65, 0x76, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x09, 0x72, 0x70, 0x63, + 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, + 0x72, 0x70, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x48, 0x00, 0x52, 0x0a, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x42, + 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, 0x0a, 0x0a, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x73, 0x32, 0xfd, 0x11, 0x0a, + 0x0d, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, + 0x0a, 0x07, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x16, 0x2e, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x2e, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x49, 0x73, 0x52, 0x65, 0x61, + 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x11, 0x56, 0x69, + 0x64, 0x65, 0x6f, 0x53, 0x65, 0x74, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x12, + 0x20, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, + 0x74, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x45, 0x0a, 0x11, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, 0x53, 0x6c, 0x65, 0x65, + 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, + 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x17, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x27, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x53, 0x6c, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x56, 0x69, + 0x64, 0x65, 0x6f, 0x53, 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x24, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, + 0x65, 0x6f, 0x53, 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x47, 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x25, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, + 0x65, 0x74, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x53, 0x65, 0x74, 0x45, 0x44, 0x49, 0x44, 0x12, 0x1b, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x74, 0x45, 0x44, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0c, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x47, 0x65, 0x74, 0x45, + 0x44, 0x49, 0x44, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x47, 0x65, 0x74, 0x45, 0x44, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3f, 0x0a, 0x0e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4c, 0x6f, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x1e, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x4c, 0x6f, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x29, 0x0a, 0x09, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x0d, + 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x0a, + 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, + 0x56, 0x47, 0x4c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x6e, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1e, 0x2e, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x56, 0x47, 0x4c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x55, 0x49, 0x4f, + 0x62, 0x6a, 0x48, 0x69, 0x64, 0x65, 0x12, 0x18, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, + 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x48, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x48, + 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x55, + 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x18, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, + 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, + 0x6a, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, + 0x08, 0x55, 0x49, 0x53, 0x65, 0x74, 0x56, 0x61, 0x72, 0x12, 0x17, 0x2e, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x2e, 0x55, 0x49, 0x53, 0x65, 0x74, 0x56, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x3d, 0x0a, 0x08, 0x55, 0x49, 0x47, 0x65, 0x74, 0x56, 0x61, 0x72, 0x12, 0x17, 0x2e, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x47, 0x65, 0x74, 0x56, 0x61, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, + 0x55, 0x49, 0x47, 0x65, 0x74, 0x56, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4c, 0x0a, 0x0d, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, 0x64, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x1c, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x41, 0x64, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, + 0x0a, 0x0f, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x1e, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, 0x64, 0x46, 0x6c, + 0x61, 0x67, 0x12, 0x1b, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, + 0x6a, 0x41, 0x64, 0x64, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x41, 0x64, + 0x64, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, + 0x0e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x12, + 0x1d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, + 0x65, 0x61, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x43, 0x6c, 0x65, + 0x61, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, + 0x0a, 0x0f, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4f, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x1e, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x53, 0x65, 0x74, 0x4f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x53, 0x65, 0x74, 0x4f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x49, + 0x6e, 0x12, 0x1a, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, + 0x46, 0x61, 0x64, 0x65, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, + 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x55, 0x49, + 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x4f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x6e, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x4f, 0x75, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x46, 0x61, 0x64, 0x65, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x11, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, + 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x12, 0x20, 0x2e, 0x6e, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6e, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x55, 0x0a, 0x10, 0x55, 0x49, 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x53, 0x72, 0x63, 0x12, 0x1f, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, 0x4f, + 0x62, 0x6a, 0x53, 0x65, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x72, 0x63, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x49, + 0x4f, 0x62, 0x6a, 0x53, 0x65, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x72, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x6e, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x74, + 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x53, 0x65, 0x74, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x49, 0x66, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x23, 0x2e, 0x6e, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x49, 0x66, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x5e, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x41, 0x6e, + 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x12, 0x2d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x41, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x56, + 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x42, 0x0a, 0x10, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x6f, 0x53, 0x63, 0x72, 0x65, 0x65, + 0x6e, 0x49, 0x66, 0x12, 0x1f, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x53, 0x77, 0x69, + 0x74, 0x63, 0x68, 0x54, 0x6f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x49, 0x66, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x54, 0x0a, 0x19, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x6f, 0x53, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x49, 0x66, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, + 0x12, 0x28, 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, + 0x54, 0x6f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x49, 0x66, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x21, 0x44, 0x6f, 0x4e, + 0x6f, 0x74, 0x55, 0x73, 0x65, 0x54, 0x68, 0x69, 0x73, 0x49, 0x73, 0x46, 0x6f, 0x72, 0x43, 0x72, + 0x61, 0x73, 0x68, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x0d, + 0x2e, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, + 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x0c, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x0d, 0x2e, 0x6e, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x6e, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x65, 0x74, 0x6b, 0x76, + 0x6d, 0x2f, 0x6b, 0x76, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6e, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +}) + +var ( + file_internal_native_proto_native_proto_rawDescOnce sync.Once + file_internal_native_proto_native_proto_rawDescData []byte +) + +func file_internal_native_proto_native_proto_rawDescGZIP() []byte { + file_internal_native_proto_native_proto_rawDescOnce.Do(func() { + file_internal_native_proto_native_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_internal_native_proto_native_proto_rawDesc), len(file_internal_native_proto_native_proto_rawDesc))) + }) + return file_internal_native_proto_native_proto_rawDescData +} + +var file_internal_native_proto_native_proto_msgTypes = make([]protoimpl.MessageInfo, 46) +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 + (*VideoSetEDIDRequest)(nil), // 9: native.VideoSetEDIDRequest + (*VideoGetEDIDResponse)(nil), // 10: native.VideoGetEDIDResponse + (*VideoLogStatusResponse)(nil), // 11: native.VideoLogStatusResponse + (*GetLVGLVersionResponse)(nil), // 12: native.GetLVGLVersionResponse + (*UIObjHideRequest)(nil), // 13: native.UIObjHideRequest + (*UIObjHideResponse)(nil), // 14: native.UIObjHideResponse + (*UIObjShowRequest)(nil), // 15: native.UIObjShowRequest + (*UIObjShowResponse)(nil), // 16: native.UIObjShowResponse + (*UISetVarRequest)(nil), // 17: native.UISetVarRequest + (*UIGetVarRequest)(nil), // 18: native.UIGetVarRequest + (*UIGetVarResponse)(nil), // 19: native.UIGetVarResponse + (*UIObjAddStateRequest)(nil), // 20: native.UIObjAddStateRequest + (*UIObjAddStateResponse)(nil), // 21: native.UIObjAddStateResponse + (*UIObjClearStateRequest)(nil), // 22: native.UIObjClearStateRequest + (*UIObjClearStateResponse)(nil), // 23: native.UIObjClearStateResponse + (*UIObjAddFlagRequest)(nil), // 24: native.UIObjAddFlagRequest + (*UIObjAddFlagResponse)(nil), // 25: native.UIObjAddFlagResponse + (*UIObjClearFlagRequest)(nil), // 26: native.UIObjClearFlagRequest + (*UIObjClearFlagResponse)(nil), // 27: native.UIObjClearFlagResponse + (*UIObjSetOpacityRequest)(nil), // 28: native.UIObjSetOpacityRequest + (*UIObjSetOpacityResponse)(nil), // 29: native.UIObjSetOpacityResponse + (*UIObjFadeInRequest)(nil), // 30: native.UIObjFadeInRequest + (*UIObjFadeInResponse)(nil), // 31: native.UIObjFadeInResponse + (*UIObjFadeOutRequest)(nil), // 32: native.UIObjFadeOutRequest + (*UIObjFadeOutResponse)(nil), // 33: native.UIObjFadeOutResponse + (*UIObjSetLabelTextRequest)(nil), // 34: native.UIObjSetLabelTextRequest + (*UIObjSetLabelTextResponse)(nil), // 35: native.UIObjSetLabelTextResponse + (*UIObjSetImageSrcRequest)(nil), // 36: native.UIObjSetImageSrcRequest + (*UIObjSetImageSrcResponse)(nil), // 37: native.UIObjSetImageSrcResponse + (*DisplaySetRotationRequest)(nil), // 38: native.DisplaySetRotationRequest + (*DisplaySetRotationResponse)(nil), // 39: native.DisplaySetRotationResponse + (*UpdateLabelIfChangedRequest)(nil), // 40: native.UpdateLabelIfChangedRequest + (*UpdateLabelAndChangeVisibilityRequest)(nil), // 41: native.UpdateLabelAndChangeVisibilityRequest + (*SwitchToScreenIfRequest)(nil), // 42: native.SwitchToScreenIfRequest + (*SwitchToScreenIfDifferentRequest)(nil), // 43: native.SwitchToScreenIfDifferentRequest + (*Event)(nil), // 44: native.Event + (*VideoFrame)(nil), // 45: native.VideoFrame +} +var file_internal_native_proto_native_proto_depIdxs = []int32{ + 3, // 0: native.Event.video_state:type_name -> native.VideoState + 45, // 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 + 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 + 0, // 7: native.NativeService.VideoGetQualityFactor:input_type -> native.Empty + 9, // 8: native.NativeService.VideoSetEDID:input_type -> native.VideoSetEDIDRequest + 0, // 9: native.NativeService.VideoGetEDID:input_type -> native.Empty + 0, // 10: native.NativeService.VideoLogStatus:input_type -> native.Empty + 0, // 11: native.NativeService.VideoStop:input_type -> native.Empty + 0, // 12: native.NativeService.VideoStart:input_type -> native.Empty + 0, // 13: native.NativeService.GetLVGLVersion:input_type -> native.Empty + 13, // 14: native.NativeService.UIObjHide:input_type -> native.UIObjHideRequest + 15, // 15: native.NativeService.UIObjShow:input_type -> native.UIObjShowRequest + 17, // 16: native.NativeService.UISetVar:input_type -> native.UISetVarRequest + 18, // 17: native.NativeService.UIGetVar:input_type -> native.UIGetVarRequest + 20, // 18: native.NativeService.UIObjAddState:input_type -> native.UIObjAddStateRequest + 22, // 19: native.NativeService.UIObjClearState:input_type -> native.UIObjClearStateRequest + 24, // 20: native.NativeService.UIObjAddFlag:input_type -> native.UIObjAddFlagRequest + 26, // 21: native.NativeService.UIObjClearFlag:input_type -> native.UIObjClearFlagRequest + 28, // 22: native.NativeService.UIObjSetOpacity:input_type -> native.UIObjSetOpacityRequest + 30, // 23: native.NativeService.UIObjFadeIn:input_type -> native.UIObjFadeInRequest + 32, // 24: native.NativeService.UIObjFadeOut:input_type -> native.UIObjFadeOutRequest + 34, // 25: native.NativeService.UIObjSetLabelText:input_type -> native.UIObjSetLabelTextRequest + 36, // 26: native.NativeService.UIObjSetImageSrc:input_type -> native.UIObjSetImageSrcRequest + 38, // 27: native.NativeService.DisplaySetRotation:input_type -> native.DisplaySetRotationRequest + 40, // 28: native.NativeService.UpdateLabelIfChanged:input_type -> native.UpdateLabelIfChangedRequest + 41, // 29: native.NativeService.UpdateLabelAndChangeVisibility:input_type -> native.UpdateLabelAndChangeVisibilityRequest + 42, // 30: native.NativeService.SwitchToScreenIf:input_type -> native.SwitchToScreenIfRequest + 43, // 31: native.NativeService.SwitchToScreenIfDifferent:input_type -> native.SwitchToScreenIfDifferentRequest + 0, // 32: native.NativeService.DoNotUseThisIsForCrashTestingOnly:input_type -> native.Empty + 0, // 33: native.NativeService.StreamEvents:input_type -> native.Empty + 2, // 34: native.NativeService.IsReady:output_type -> native.IsReadyResponse + 0, // 35: native.NativeService.VideoSetSleepMode:output_type -> native.Empty + 5, // 36: native.NativeService.VideoGetSleepMode:output_type -> native.VideoGetSleepModeResponse + 6, // 37: native.NativeService.VideoSleepModeSupported:output_type -> native.VideoSleepModeSupportedResponse + 0, // 38: native.NativeService.VideoSetQualityFactor:output_type -> native.Empty + 8, // 39: native.NativeService.VideoGetQualityFactor:output_type -> native.VideoGetQualityFactorResponse + 0, // 40: native.NativeService.VideoSetEDID:output_type -> native.Empty + 10, // 41: native.NativeService.VideoGetEDID:output_type -> native.VideoGetEDIDResponse + 11, // 42: native.NativeService.VideoLogStatus:output_type -> native.VideoLogStatusResponse + 0, // 43: native.NativeService.VideoStop:output_type -> native.Empty + 0, // 44: native.NativeService.VideoStart:output_type -> native.Empty + 12, // 45: native.NativeService.GetLVGLVersion:output_type -> native.GetLVGLVersionResponse + 14, // 46: native.NativeService.UIObjHide:output_type -> native.UIObjHideResponse + 16, // 47: native.NativeService.UIObjShow:output_type -> native.UIObjShowResponse + 0, // 48: native.NativeService.UISetVar:output_type -> native.Empty + 19, // 49: native.NativeService.UIGetVar:output_type -> native.UIGetVarResponse + 21, // 50: native.NativeService.UIObjAddState:output_type -> native.UIObjAddStateResponse + 23, // 51: native.NativeService.UIObjClearState:output_type -> native.UIObjClearStateResponse + 25, // 52: native.NativeService.UIObjAddFlag:output_type -> native.UIObjAddFlagResponse + 27, // 53: native.NativeService.UIObjClearFlag:output_type -> native.UIObjClearFlagResponse + 29, // 54: native.NativeService.UIObjSetOpacity:output_type -> native.UIObjSetOpacityResponse + 31, // 55: native.NativeService.UIObjFadeIn:output_type -> native.UIObjFadeInResponse + 33, // 56: native.NativeService.UIObjFadeOut:output_type -> native.UIObjFadeOutResponse + 35, // 57: native.NativeService.UIObjSetLabelText:output_type -> native.UIObjSetLabelTextResponse + 37, // 58: native.NativeService.UIObjSetImageSrc:output_type -> native.UIObjSetImageSrcResponse + 39, // 59: native.NativeService.DisplaySetRotation:output_type -> native.DisplaySetRotationResponse + 0, // 60: native.NativeService.UpdateLabelIfChanged:output_type -> native.Empty + 0, // 61: native.NativeService.UpdateLabelAndChangeVisibility:output_type -> native.Empty + 0, // 62: native.NativeService.SwitchToScreenIf:output_type -> native.Empty + 0, // 63: native.NativeService.SwitchToScreenIfDifferent:output_type -> native.Empty + 0, // 64: native.NativeService.DoNotUseThisIsForCrashTestingOnly:output_type -> native.Empty + 44, // 65: native.NativeService.StreamEvents:output_type -> native.Event + 34, // [34:66] is the sub-list for method output_type + 2, // [2:34] 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 +} + +func init() { file_internal_native_proto_native_proto_init() } +func file_internal_native_proto_native_proto_init() { + if File_internal_native_proto_native_proto != nil { + return + } + file_internal_native_proto_native_proto_msgTypes[44].OneofWrappers = []any{ + (*Event_VideoState)(nil), + (*Event_IndevEvent)(nil), + (*Event_RpcEvent)(nil), + (*Event_VideoFrame)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + 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: 46, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_internal_native_proto_native_proto_goTypes, + DependencyIndexes: file_internal_native_proto_native_proto_depIdxs, + MessageInfos: file_internal_native_proto_native_proto_msgTypes, + }.Build() + File_internal_native_proto_native_proto = out.File + file_internal_native_proto_native_proto_goTypes = nil + file_internal_native_proto_native_proto_depIdxs = nil +} diff --git a/internal/native/proto/native.proto b/internal/native/proto/native.proto index c320d81de..538d33033 100644 --- a/internal/native/proto/native.proto +++ b/internal/native/proto/native.proto @@ -6,8 +6,8 @@ option go_package = "github.com/jetkvm/kvm/internal/native/proto"; // NativeService provides methods to interact with the native layer service NativeService { - // Init - rpc Init(InitRequest) returns (InitResponse); + // Ready check + rpc IsReady(IsReadyRequest) returns (IsReadyResponse); // Video methods rpc VideoSetSleepMode(VideoSetSleepModeRequest) returns (Empty); @@ -52,16 +52,12 @@ service NativeService { // Messages message Empty {} -message InitRequest { - string system_version = 1; - string app_version = 2; - uint32 display_rotation = 3; - double default_quality_factor = 4; -} +message IsReadyRequest {} -message InitResponse { - bool success = 1; +message IsReadyResponse { + bool ready = 1; string error = 2; + bool video_ready = 3; } message VideoState { diff --git a/internal/native/proto/native_grpc.pb.go b/internal/native/proto/native_grpc.pb.go index 59d2a2394..abdd38bb0 100644 --- a/internal/native/proto/native_grpc.pb.go +++ b/internal/native/proto/native_grpc.pb.go @@ -1,11 +1,13 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// This is a placeholder file. Run: protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/native/proto/native.proto +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.21.12 +// source: internal/native/proto/native.proto package proto import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -13,10 +15,50 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + NativeService_IsReady_FullMethodName = "/native.NativeService/IsReady" + NativeService_VideoSetSleepMode_FullMethodName = "/native.NativeService/VideoSetSleepMode" + NativeService_VideoGetSleepMode_FullMethodName = "/native.NativeService/VideoGetSleepMode" + NativeService_VideoSleepModeSupported_FullMethodName = "/native.NativeService/VideoSleepModeSupported" + NativeService_VideoSetQualityFactor_FullMethodName = "/native.NativeService/VideoSetQualityFactor" + NativeService_VideoGetQualityFactor_FullMethodName = "/native.NativeService/VideoGetQualityFactor" + NativeService_VideoSetEDID_FullMethodName = "/native.NativeService/VideoSetEDID" + NativeService_VideoGetEDID_FullMethodName = "/native.NativeService/VideoGetEDID" + NativeService_VideoLogStatus_FullMethodName = "/native.NativeService/VideoLogStatus" + NativeService_VideoStop_FullMethodName = "/native.NativeService/VideoStop" + NativeService_VideoStart_FullMethodName = "/native.NativeService/VideoStart" + NativeService_GetLVGLVersion_FullMethodName = "/native.NativeService/GetLVGLVersion" + NativeService_UIObjHide_FullMethodName = "/native.NativeService/UIObjHide" + NativeService_UIObjShow_FullMethodName = "/native.NativeService/UIObjShow" + NativeService_UISetVar_FullMethodName = "/native.NativeService/UISetVar" + NativeService_UIGetVar_FullMethodName = "/native.NativeService/UIGetVar" + NativeService_UIObjAddState_FullMethodName = "/native.NativeService/UIObjAddState" + NativeService_UIObjClearState_FullMethodName = "/native.NativeService/UIObjClearState" + NativeService_UIObjAddFlag_FullMethodName = "/native.NativeService/UIObjAddFlag" + NativeService_UIObjClearFlag_FullMethodName = "/native.NativeService/UIObjClearFlag" + NativeService_UIObjSetOpacity_FullMethodName = "/native.NativeService/UIObjSetOpacity" + NativeService_UIObjFadeIn_FullMethodName = "/native.NativeService/UIObjFadeIn" + NativeService_UIObjFadeOut_FullMethodName = "/native.NativeService/UIObjFadeOut" + NativeService_UIObjSetLabelText_FullMethodName = "/native.NativeService/UIObjSetLabelText" + NativeService_UIObjSetImageSrc_FullMethodName = "/native.NativeService/UIObjSetImageSrc" + NativeService_DisplaySetRotation_FullMethodName = "/native.NativeService/DisplaySetRotation" + NativeService_UpdateLabelIfChanged_FullMethodName = "/native.NativeService/UpdateLabelIfChanged" + NativeService_UpdateLabelAndChangeVisibility_FullMethodName = "/native.NativeService/UpdateLabelAndChangeVisibility" + NativeService_SwitchToScreenIf_FullMethodName = "/native.NativeService/SwitchToScreenIf" + NativeService_SwitchToScreenIfDifferent_FullMethodName = "/native.NativeService/SwitchToScreenIfDifferent" + NativeService_DoNotUseThisIsForCrashTestingOnly_FullMethodName = "/native.NativeService/DoNotUseThisIsForCrashTestingOnly" + NativeService_StreamEvents_FullMethodName = "/native.NativeService/StreamEvents" +) // NativeServiceClient is the client API for NativeService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type NativeServiceClient interface { + // Ready check + IsReady(ctx context.Context, in *IsReadyRequest, opts ...grpc.CallOption) (*IsReadyResponse, error) // Video methods VideoSetSleepMode(ctx context.Context, in *VideoSetSleepModeRequest, opts ...grpc.CallOption) (*Empty, error) VideoGetSleepMode(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetSleepModeResponse, error) @@ -62,192 +104,323 @@ func NewNativeServiceClient(cc grpc.ClientConnInterface) NativeServiceClient { return &nativeServiceClient{cc} } +func (c *nativeServiceClient) IsReady(ctx context.Context, in *IsReadyRequest, opts ...grpc.CallOption) (*IsReadyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(IsReadyResponse) + err := c.cc.Invoke(ctx, NativeService_IsReady_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nativeServiceClient) VideoSetSleepMode(ctx context.Context, in *VideoSetSleepModeRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetSleepMode", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoSetSleepMode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoGetSleepMode(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetSleepModeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VideoGetSleepModeResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetSleepMode", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoGetSleepMode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoSleepModeSupported(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoSleepModeSupportedResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VideoSleepModeSupportedResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoSleepModeSupported", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoSleepModeSupported_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoSetQualityFactor(ctx context.Context, in *VideoSetQualityFactorRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetQualityFactor", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoSetQualityFactor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoGetQualityFactor(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetQualityFactorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VideoGetQualityFactorResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetQualityFactor", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoGetQualityFactor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoSetEDID(ctx context.Context, in *VideoSetEDIDRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoSetEDID", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoSetEDID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoGetEDID(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoGetEDIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VideoGetEDIDResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoGetEDID", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoGetEDID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoLogStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VideoLogStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VideoLogStatusResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoLogStatus", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoLogStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoStop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoStop", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoStop_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) VideoStart(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/VideoStart", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_VideoStart_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) - err := c.cc.Invoke(ctx, "/native.NativeService/GetLVGLVersion", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_GetLVGLVersion_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjHide(ctx context.Context, in *UIObjHideRequest, opts ...grpc.CallOption) (*UIObjHideResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjHideResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjHide", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjHide_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjShow(ctx context.Context, in *UIObjShowRequest, opts ...grpc.CallOption) (*UIObjShowResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjShowResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjShow", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjShow_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UISetVar(ctx context.Context, in *UISetVarRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/UISetVar", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UISetVar_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIGetVar(ctx context.Context, in *UIGetVarRequest, opts ...grpc.CallOption) (*UIGetVarResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIGetVarResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIGetVar", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIGetVar_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjAddState(ctx context.Context, in *UIObjAddStateRequest, opts ...grpc.CallOption) (*UIObjAddStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjAddStateResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjAddState", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjAddState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjClearState(ctx context.Context, in *UIObjClearStateRequest, opts ...grpc.CallOption) (*UIObjClearStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjClearStateResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjClearState", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjClearState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjAddFlag(ctx context.Context, in *UIObjAddFlagRequest, opts ...grpc.CallOption) (*UIObjAddFlagResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjAddFlagResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjAddFlag", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjAddFlag_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjClearFlag(ctx context.Context, in *UIObjClearFlagRequest, opts ...grpc.CallOption) (*UIObjClearFlagResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjClearFlagResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjClearFlag", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjClearFlag_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjSetOpacity(ctx context.Context, in *UIObjSetOpacityRequest, opts ...grpc.CallOption) (*UIObjSetOpacityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjSetOpacityResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetOpacity", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjSetOpacity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjFadeIn(ctx context.Context, in *UIObjFadeInRequest, opts ...grpc.CallOption) (*UIObjFadeInResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjFadeInResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjFadeIn", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjFadeIn_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjFadeOut(ctx context.Context, in *UIObjFadeOutRequest, opts ...grpc.CallOption) (*UIObjFadeOutResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjFadeOutResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjFadeOut", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjFadeOut_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjSetLabelText(ctx context.Context, in *UIObjSetLabelTextRequest, opts ...grpc.CallOption) (*UIObjSetLabelTextResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjSetLabelTextResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetLabelText", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjSetLabelText_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UIObjSetImageSrc(ctx context.Context, in *UIObjSetImageSrcRequest, opts ...grpc.CallOption) (*UIObjSetImageSrcResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UIObjSetImageSrcResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/UIObjSetImageSrc", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UIObjSetImageSrc_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) DisplaySetRotation(ctx context.Context, in *DisplaySetRotationRequest, opts ...grpc.CallOption) (*DisplaySetRotationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DisplaySetRotationResponse) - err := c.cc.Invoke(ctx, "/native.NativeService/DisplaySetRotation", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_DisplaySetRotation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UpdateLabelIfChanged(ctx context.Context, in *UpdateLabelIfChangedRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/UpdateLabelIfChanged", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UpdateLabelIfChanged_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) UpdateLabelAndChangeVisibility(ctx context.Context, in *UpdateLabelAndChangeVisibilityRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/UpdateLabelAndChangeVisibility", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_UpdateLabelAndChangeVisibility_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) SwitchToScreenIf(ctx context.Context, in *SwitchToScreenIfRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/SwitchToScreenIf", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_SwitchToScreenIf_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) SwitchToScreenIfDifferent(ctx context.Context, in *SwitchToScreenIfDifferentRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/SwitchToScreenIfDifferent", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_SwitchToScreenIfDifferent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, "/native.NativeService/DoNotUseThisIsForCrashTestingOnly", in, out, opts...) - return out, err + err := c.cc.Invoke(ctx, NativeService_DoNotUseThisIsForCrashTestingOnly_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil } func (c *nativeServiceClient) StreamEvents(ctx context.Context, in *Empty, opts ...grpc.CallOption) (NativeService_StreamEventsClient, error) { - stream, err := c.cc.NewStream(ctx, &NativeService_ServiceDesc.Streams[0], "/native.NativeService/StreamEvents", opts...) + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &NativeService_ServiceDesc.Streams[0], NativeService_StreamEvents_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &nativeServiceStreamEventsClient{stream} + x := &nativeServiceStreamEventsClient{ClientStream: stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -275,7 +448,11 @@ func (x *nativeServiceStreamEventsClient) Recv() (*Event, error) { } // NativeServiceServer is the server API for NativeService service. +// All implementations must embed UnimplementedNativeServiceServer +// for forward compatibility type NativeServiceServer interface { + // Ready check + IsReady(context.Context, *IsReadyRequest) (*IsReadyResponse, error) // Video methods VideoSetSleepMode(context.Context, *VideoSetSleepModeRequest) (*Empty, error) VideoGetSleepMode(context.Context, *Empty) (*VideoGetSleepModeResponse, error) @@ -318,130 +495,102 @@ type NativeServiceServer interface { type UnimplementedNativeServiceServer struct { } +func (UnimplementedNativeServiceServer) IsReady(context.Context, *IsReadyRequest) (*IsReadyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsReady not implemented") +} func (UnimplementedNativeServiceServer) VideoSetSleepMode(context.Context, *VideoSetSleepModeRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoSetSleepMode not implemented") } - func (UnimplementedNativeServiceServer) VideoGetSleepMode(context.Context, *Empty) (*VideoGetSleepModeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoGetSleepMode not implemented") } - func (UnimplementedNativeServiceServer) VideoSleepModeSupported(context.Context, *Empty) (*VideoSleepModeSupportedResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoSleepModeSupported not implemented") } - func (UnimplementedNativeServiceServer) VideoSetQualityFactor(context.Context, *VideoSetQualityFactorRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoSetQualityFactor not implemented") } - func (UnimplementedNativeServiceServer) VideoGetQualityFactor(context.Context, *Empty) (*VideoGetQualityFactorResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoGetQualityFactor not implemented") } - func (UnimplementedNativeServiceServer) VideoSetEDID(context.Context, *VideoSetEDIDRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoSetEDID not implemented") } - func (UnimplementedNativeServiceServer) VideoGetEDID(context.Context, *Empty) (*VideoGetEDIDResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoGetEDID not implemented") } - func (UnimplementedNativeServiceServer) VideoLogStatus(context.Context, *Empty) (*VideoLogStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoLogStatus not implemented") } - func (UnimplementedNativeServiceServer) VideoStop(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoStop not implemented") } - func (UnimplementedNativeServiceServer) VideoStart(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method VideoStart not implemented") } - func (UnimplementedNativeServiceServer) GetLVGLVersion(context.Context, *Empty) (*GetLVGLVersionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetLVGLVersion not implemented") } - func (UnimplementedNativeServiceServer) UIObjHide(context.Context, *UIObjHideRequest) (*UIObjHideResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjHide not implemented") } - func (UnimplementedNativeServiceServer) UIObjShow(context.Context, *UIObjShowRequest) (*UIObjShowResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjShow not implemented") } - func (UnimplementedNativeServiceServer) UISetVar(context.Context, *UISetVarRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UISetVar not implemented") } - func (UnimplementedNativeServiceServer) UIGetVar(context.Context, *UIGetVarRequest) (*UIGetVarResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIGetVar not implemented") } - func (UnimplementedNativeServiceServer) UIObjAddState(context.Context, *UIObjAddStateRequest) (*UIObjAddStateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjAddState not implemented") } - func (UnimplementedNativeServiceServer) UIObjClearState(context.Context, *UIObjClearStateRequest) (*UIObjClearStateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjClearState not implemented") } - func (UnimplementedNativeServiceServer) UIObjAddFlag(context.Context, *UIObjAddFlagRequest) (*UIObjAddFlagResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjAddFlag not implemented") } - func (UnimplementedNativeServiceServer) UIObjClearFlag(context.Context, *UIObjClearFlagRequest) (*UIObjClearFlagResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjClearFlag not implemented") } - func (UnimplementedNativeServiceServer) UIObjSetOpacity(context.Context, *UIObjSetOpacityRequest) (*UIObjSetOpacityResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjSetOpacity not implemented") } - func (UnimplementedNativeServiceServer) UIObjFadeIn(context.Context, *UIObjFadeInRequest) (*UIObjFadeInResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjFadeIn not implemented") } - func (UnimplementedNativeServiceServer) UIObjFadeOut(context.Context, *UIObjFadeOutRequest) (*UIObjFadeOutResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjFadeOut not implemented") } - func (UnimplementedNativeServiceServer) UIObjSetLabelText(context.Context, *UIObjSetLabelTextRequest) (*UIObjSetLabelTextResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjSetLabelText not implemented") } - func (UnimplementedNativeServiceServer) UIObjSetImageSrc(context.Context, *UIObjSetImageSrcRequest) (*UIObjSetImageSrcResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UIObjSetImageSrc not implemented") } - func (UnimplementedNativeServiceServer) DisplaySetRotation(context.Context, *DisplaySetRotationRequest) (*DisplaySetRotationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DisplaySetRotation not implemented") } - func (UnimplementedNativeServiceServer) UpdateLabelIfChanged(context.Context, *UpdateLabelIfChangedRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateLabelIfChanged not implemented") } - func (UnimplementedNativeServiceServer) UpdateLabelAndChangeVisibility(context.Context, *UpdateLabelAndChangeVisibilityRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateLabelAndChangeVisibility not implemented") } - func (UnimplementedNativeServiceServer) SwitchToScreenIf(context.Context, *SwitchToScreenIfRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SwitchToScreenIf not implemented") } - func (UnimplementedNativeServiceServer) SwitchToScreenIfDifferent(context.Context, *SwitchToScreenIfDifferentRequest) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SwitchToScreenIfDifferent not implemented") } - func (UnimplementedNativeServiceServer) DoNotUseThisIsForCrashTestingOnly(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method DoNotUseThisIsForCrashTestingOnly not implemented") } - func (UnimplementedNativeServiceServer) StreamEvents(*Empty, NativeService_StreamEventsServer) error { return status.Errorf(codes.Unimplemented, "method StreamEvents not implemented") } - func (UnimplementedNativeServiceServer) mustEmbedUnimplementedNativeServiceServer() {} // UnsafeNativeServiceServer may be embedded to opt out of forward compatibility for this service. @@ -455,6 +604,572 @@ func RegisterNativeServiceServer(s grpc.ServiceRegistrar, srv NativeServiceServe s.RegisterService(&NativeService_ServiceDesc, srv) } +func _NativeService_IsReady_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IsReadyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).IsReady(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_IsReady_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).IsReady(ctx, req.(*IsReadyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoSetSleepMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VideoSetSleepModeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).VideoSetSleepMode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoSetSleepMode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoSetSleepMode(ctx, req.(*VideoSetSleepModeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoGetSleepMode_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).VideoGetSleepMode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoGetSleepMode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoGetSleepMode(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoSleepModeSupported_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).VideoSleepModeSupported(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoSleepModeSupported_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoSleepModeSupported(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoSetQualityFactor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VideoSetQualityFactorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).VideoSetQualityFactor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoSetQualityFactor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoSetQualityFactor(ctx, req.(*VideoSetQualityFactorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoGetQualityFactor_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).VideoGetQualityFactor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoGetQualityFactor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoGetQualityFactor(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoSetEDID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VideoSetEDIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).VideoSetEDID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoSetEDID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoSetEDID(ctx, req.(*VideoSetEDIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoGetEDID_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).VideoGetEDID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoGetEDID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoGetEDID(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoLogStatus_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).VideoLogStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoLogStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoLogStatus(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoStop_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).VideoStop(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoStop_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoStop(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_VideoStart_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).VideoStart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_VideoStart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).VideoStart(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 { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).GetLVGLVersion(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_GetLVGLVersion_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).GetLVGLVersion(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjHide_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjHideRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjHide(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjHide_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjHide(ctx, req.(*UIObjHideRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjShow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjShowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjShow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjShow_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjShow(ctx, req.(*UIObjShowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UISetVar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UISetVarRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UISetVar(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UISetVar_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UISetVar(ctx, req.(*UISetVarRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIGetVar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIGetVarRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIGetVar(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIGetVar_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIGetVar(ctx, req.(*UIGetVarRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjAddState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjAddStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjAddState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjAddState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjAddState(ctx, req.(*UIObjAddStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjClearState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjClearStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjClearState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjClearState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjClearState(ctx, req.(*UIObjClearStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjAddFlag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjAddFlagRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjAddFlag(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjAddFlag_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjAddFlag(ctx, req.(*UIObjAddFlagRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjClearFlag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjClearFlagRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjClearFlag(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjClearFlag_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjClearFlag(ctx, req.(*UIObjClearFlagRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjSetOpacity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjSetOpacityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjSetOpacity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjSetOpacity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjSetOpacity(ctx, req.(*UIObjSetOpacityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjFadeIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjFadeInRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjFadeIn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjFadeIn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjFadeIn(ctx, req.(*UIObjFadeInRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjFadeOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjFadeOutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjFadeOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjFadeOut_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjFadeOut(ctx, req.(*UIObjFadeOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjSetLabelText_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjSetLabelTextRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjSetLabelText(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjSetLabelText_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjSetLabelText(ctx, req.(*UIObjSetLabelTextRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UIObjSetImageSrc_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UIObjSetImageSrcRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UIObjSetImageSrc(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UIObjSetImageSrc_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UIObjSetImageSrc(ctx, req.(*UIObjSetImageSrcRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_DisplaySetRotation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DisplaySetRotationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).DisplaySetRotation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_DisplaySetRotation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).DisplaySetRotation(ctx, req.(*DisplaySetRotationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UpdateLabelIfChanged_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateLabelIfChangedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UpdateLabelIfChanged(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UpdateLabelIfChanged_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UpdateLabelIfChanged(ctx, req.(*UpdateLabelIfChangedRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_UpdateLabelAndChangeVisibility_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateLabelAndChangeVisibilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).UpdateLabelAndChangeVisibility(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_UpdateLabelAndChangeVisibility_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).UpdateLabelAndChangeVisibility(ctx, req.(*UpdateLabelAndChangeVisibilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_SwitchToScreenIf_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SwitchToScreenIfRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).SwitchToScreenIf(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_SwitchToScreenIf_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).SwitchToScreenIf(ctx, req.(*SwitchToScreenIfRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_SwitchToScreenIfDifferent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SwitchToScreenIfDifferentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NativeServiceServer).SwitchToScreenIfDifferent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_SwitchToScreenIfDifferent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).SwitchToScreenIfDifferent(ctx, req.(*SwitchToScreenIfDifferentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_DoNotUseThisIsForCrashTestingOnly_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).DoNotUseThisIsForCrashTestingOnly(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NativeService_DoNotUseThisIsForCrashTestingOnly_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NativeServiceServer).DoNotUseThisIsForCrashTestingOnly(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _NativeService_StreamEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(NativeServiceServer).StreamEvents(m, &nativeServiceStreamEventsServer{ServerStream: stream}) +} + type NativeService_StreamEventsServer interface { Send(*Event) error grpc.ServerStream @@ -475,18 +1190,137 @@ var NativeService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "native.NativeService", HandlerType: (*NativeServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "IsReady", + Handler: _NativeService_IsReady_Handler, + }, { MethodName: "VideoSetSleepMode", - Handler: nil, // Will be set by RegisterNativeServiceServer + Handler: _NativeService_VideoSetSleepMode_Handler, + }, + { + MethodName: "VideoGetSleepMode", + Handler: _NativeService_VideoGetSleepMode_Handler, + }, + { + MethodName: "VideoSleepModeSupported", + Handler: _NativeService_VideoSleepModeSupported_Handler, + }, + { + MethodName: "VideoSetQualityFactor", + Handler: _NativeService_VideoSetQualityFactor_Handler, + }, + { + MethodName: "VideoGetQualityFactor", + Handler: _NativeService_VideoGetQualityFactor_Handler, + }, + { + MethodName: "VideoSetEDID", + Handler: _NativeService_VideoSetEDID_Handler, + }, + { + MethodName: "VideoGetEDID", + Handler: _NativeService_VideoGetEDID_Handler, + }, + { + MethodName: "VideoLogStatus", + Handler: _NativeService_VideoLogStatus_Handler, + }, + { + MethodName: "VideoStop", + Handler: _NativeService_VideoStop_Handler, + }, + { + MethodName: "VideoStart", + Handler: _NativeService_VideoStart_Handler, + }, + { + MethodName: "GetLVGLVersion", + Handler: _NativeService_GetLVGLVersion_Handler, + }, + { + MethodName: "UIObjHide", + Handler: _NativeService_UIObjHide_Handler, + }, + { + MethodName: "UIObjShow", + Handler: _NativeService_UIObjShow_Handler, + }, + { + MethodName: "UISetVar", + Handler: _NativeService_UISetVar_Handler, + }, + { + MethodName: "UIGetVar", + Handler: _NativeService_UIGetVar_Handler, + }, + { + MethodName: "UIObjAddState", + Handler: _NativeService_UIObjAddState_Handler, + }, + { + MethodName: "UIObjClearState", + Handler: _NativeService_UIObjClearState_Handler, + }, + { + MethodName: "UIObjAddFlag", + Handler: _NativeService_UIObjAddFlag_Handler, + }, + { + MethodName: "UIObjClearFlag", + Handler: _NativeService_UIObjClearFlag_Handler, + }, + { + MethodName: "UIObjSetOpacity", + Handler: _NativeService_UIObjSetOpacity_Handler, + }, + { + MethodName: "UIObjFadeIn", + Handler: _NativeService_UIObjFadeIn_Handler, + }, + { + MethodName: "UIObjFadeOut", + Handler: _NativeService_UIObjFadeOut_Handler, + }, + { + MethodName: "UIObjSetLabelText", + Handler: _NativeService_UIObjSetLabelText_Handler, + }, + { + MethodName: "UIObjSetImageSrc", + Handler: _NativeService_UIObjSetImageSrc_Handler, + }, + { + MethodName: "DisplaySetRotation", + Handler: _NativeService_DisplaySetRotation_Handler, + }, + { + MethodName: "UpdateLabelIfChanged", + Handler: _NativeService_UpdateLabelIfChanged_Handler, + }, + { + MethodName: "UpdateLabelAndChangeVisibility", + Handler: _NativeService_UpdateLabelAndChangeVisibility_Handler, + }, + { + MethodName: "SwitchToScreenIf", + Handler: _NativeService_SwitchToScreenIf_Handler, + }, + { + MethodName: "SwitchToScreenIfDifferent", + Handler: _NativeService_SwitchToScreenIfDifferent_Handler, + }, + { + MethodName: "DoNotUseThisIsForCrashTestingOnly", + Handler: _NativeService_DoNotUseThisIsForCrashTestingOnly_Handler, }, - // Additional methods will be registered here }, Streams: []grpc.StreamDesc{ { StreamName: "StreamEvents", - Handler: nil, // Will be set by RegisterNativeServiceServer + Handler: _NativeService_StreamEvents_Handler, ServerStreams: true, }, }, - Metadata: "native.proto", + Metadata: "internal/native/proto/native.proto", } diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 0523e905e..b36c82e7b 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -1,19 +1,22 @@ package native import ( - "encoding/json" "fmt" + "net" "os" "os/exec" "sync" "syscall" "time" - "github.com/Masterminds/semver/v3" "github.com/jetkvm/kvm/internal/supervisor" "github.com/rs/zerolog" ) +const ( + maxFrameSize = 1920 * 1080 / 2 +) + // cmdWrapper wraps exec.Cmd to implement processCmd interface type cmdWrapper struct { *exec.Cmd @@ -43,94 +46,116 @@ func (p *processWrapper) Signal(sig interface{}) error { // NativeProxy is a proxy that communicates with a separate native process type NativeProxy struct { - client *IPCClient - cmd *exec.Cmd - wrapped *cmdWrapper + nativeUnixSocket string + videoStreamUnixSocket string + videoStreamListener net.Listener + binaryPath string + + client *GRPCClient + cmd *cmdWrapper logger *zerolog.Logger ready chan struct{} + options *NativeOptions restartM sync.Mutex stopped bool - opts NativeProxyOptions - binaryPath string - configJSON []byte processWait chan error } -// NativeProxyOptions are options for creating a NativeProxy -type NativeProxyOptions struct { - Disable bool - SystemVersion *semver.Version - AppVersion *semver.Version - DisplayRotation uint16 - DefaultQualityFactor float64 - OnVideoStateChange func(state VideoState) - OnVideoFrameReceived func(frame []byte, duration time.Duration) - OnIndevEvent func(event string) - OnRpcEvent func(event string) - Logger *zerolog.Logger +func ensureDirectoryExists(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return os.MkdirAll(path, 0600) + } + return nil } // NewNativeProxy creates a new NativeProxy that spawns a separate process -func NewNativeProxy(opts NativeProxyOptions) (*NativeProxy, error) { - if opts.Logger == nil { - opts.Logger = nativeLogger - } +func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { + nativeUnixSocket := "jetkvm-native-grpc" + videoStreamUnixSocket := "@jetkvm-native-video-stream" // Get the current executable path to spawn itself exePath, err := os.Executable() if err != nil { return nil, fmt.Errorf("failed to get executable path: %w", err) } - binaryPath := exePath - - config := ProcessConfig{ - Disable: opts.Disable, - SystemVersion: "", - AppVersion: "", - DisplayRotation: opts.DisplayRotation, - DefaultQualityFactor: opts.DefaultQualityFactor, - } - if opts.SystemVersion != nil { - config.SystemVersion = opts.SystemVersion.String() - } - if opts.AppVersion != nil { - config.AppVersion = opts.AppVersion.String() + + proxy := &NativeProxy{ + nativeUnixSocket: nativeUnixSocket, + videoStreamUnixSocket: videoStreamUnixSocket, + binaryPath: exePath, + logger: nativeLogger, + ready: make(chan struct{}), + options: &opts, + processWait: make(chan error, 1), + } + proxy.cmd, err = proxy.spawnProcess() + nativeLogger.Info().Msg("spawned process") + if err != nil { + return nil, fmt.Errorf("failed to spawn process: %w", err) } - configJSON, err := json.Marshal(config) + // create unix packet + listener, err := net.Listen("unixpacket", videoStreamUnixSocket) if err != nil { - return nil, fmt.Errorf("failed to marshal config: %w", err) + nativeLogger.Warn().Err(err).Msg("failed to start server") + return nil, fmt.Errorf("failed to start server: %w", err) } + go func() { + for { + conn, err := listener.Accept() + if err != nil { + nativeLogger.Warn().Err(err).Msg("failed to accept socket") + continue + } + nativeLogger.Info().Str("socket", conn.RemoteAddr().String()).Msg("accepted socket") + go proxy.handleVideoFrame(conn) + } + }() - cmd := exec.Command(binaryPath, string(configJSON)) + return proxy, nil +} + +func (p *NativeProxy) spawnProcess() (*cmdWrapper, error) { + cmd := exec.Command( + p.binaryPath, + "-subcomponent=native", + ) + cmd.Stdout = os.Stdout // Forward stdout to parent cmd.Stderr = os.Stderr // Forward stderr to parent // Set environment variable to indicate native process mode - cmd.Env = append(os.Environ(), fmt.Sprintf("%s=native", supervisor.EnvSubcomponent)) - + cmd.Env = append( + os.Environ(), + fmt.Sprintf("%s=native", supervisor.EnvSubcomponent), + fmt.Sprintf("%s=%s", "JETKVM_NATIVE_SOCKET", p.nativeUnixSocket), + fmt.Sprintf("%s=%s", "JETKVM_VIDEO_STREAM_SOCKET", p.videoStreamUnixSocket), + fmt.Sprintf("%s=%s", "JETKVM_NATIVE_SYSTEM_VERSION", p.options.SystemVersion), + fmt.Sprintf("%s=%s", "JETKVM_NATIVE_APP_VERSION", p.options.AppVersion), + fmt.Sprintf("%s=%d", "JETKVM_NATIVE_DISPLAY_ROTATION", p.options.DisplayRotation), + fmt.Sprintf("%s=%f", "JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR", p.options.DefaultQualityFactor), + ) // Wrap cmd to implement processCmd interface wrappedCmd := &cmdWrapper{Cmd: cmd} - client, err := NewIPCClient(wrappedCmd, opts.Logger) - if err != nil { - return nil, fmt.Errorf("failed to create IPC client: %w", err) - } + return wrappedCmd, nil +} - proxy := &NativeProxy{ - client: client, - cmd: cmd, - wrapped: wrappedCmd, - logger: opts.Logger, - ready: make(chan struct{}), - opts: opts, - binaryPath: binaryPath, - configJSON: configJSON, - processWait: make(chan error, 1), - } +func (p *NativeProxy) handleVideoFrame(conn net.Conn) { + defer conn.Close() - // Set up event handlers - proxy.setupEventHandlers(client) + inboundPacket := make([]byte, maxFrameSize) + lastFrame := time.Now() - return proxy, nil + for { + n, err := conn.Read(inboundPacket) + if err != nil { + nativeLogger.Warn().Err(err).Msg("failed to accept socket") + break + } + now := time.Now() + sinceLastFrame := now.Sub(lastFrame) + lastFrame = now + p.options.OnVideoFrameReceived(inboundPacket[:n], sinceLastFrame) + } } // Start starts the native process @@ -146,6 +171,15 @@ func (p *NativeProxy) Start() error { return fmt.Errorf("failed to start native process: %w", err) } + nativeLogger.Info().Msg("process ready") + + client, err := NewGRPCClient(p.nativeUnixSocket, nativeLogger) + nativeLogger.Info().Str("socket_path", p.nativeUnixSocket).Msg("created client") + if err != nil { + return fmt.Errorf("failed to create IPC client: %w", err) + } + p.client = client + // Wait for ready signal from the native process if err := p.client.WaitReady(); err != nil { // Clean up if ready failed @@ -153,9 +187,12 @@ func (p *NativeProxy) Start() error { _ = p.cmd.Process.Kill() _ = p.cmd.Wait() } - return err + return fmt.Errorf("failed to wait for ready: %w", err) } + // Set up event handlers + p.setupEventHandlers(client) + // Start monitoring process for crashes go p.monitorProcess() @@ -216,14 +253,10 @@ func (p *NativeProxy) restartProcess() error { return fmt.Errorf("proxy is stopped") } - // Create new command - cmd := exec.Command(p.binaryPath, string(p.configJSON)) - cmd.Stderr = os.Stderr - // Set environment variable to indicate native process mode - cmd.Env = append(os.Environ(), "JETKVM_NATIVE_PROCESS=1") - - // Wrap cmd to implement processCmd interface - wrappedCmd := &cmdWrapper{Cmd: cmd} + wrappedCmd, err := p.spawnProcess() + if err != nil { + return fmt.Errorf("failed to spawn process: %w", err) + } // Close old client if p.client != nil { @@ -231,7 +264,7 @@ func (p *NativeProxy) restartProcess() error { } // Create new client - client, err := NewIPCClient(wrappedCmd, p.logger) + client, err := NewGRPCClient(p.nativeUnixSocket, p.logger) if err != nil { return fmt.Errorf("failed to create IPC client: %w", err) } @@ -240,90 +273,89 @@ func (p *NativeProxy) restartProcess() error { p.setupEventHandlers(client) // Start the process - if err := cmd.Start(); err != nil { + if err := wrappedCmd.Start(); err != nil { return fmt.Errorf("failed to start native process: %w", err) } // Wait for ready if err := client.WaitReady(); err != nil { - if cmd.Process != nil { - _ = cmd.Process.Kill() - _ = cmd.Wait() + if wrappedCmd.Process != nil { + _ = wrappedCmd.Process.Kill() + _ = wrappedCmd.Wait() } return fmt.Errorf("timeout waiting for ready: %w", err) } - p.cmd = cmd - p.wrapped = wrappedCmd + p.cmd = wrappedCmd p.client = client p.logger.Info().Msg("native process restarted successfully") return nil } -func (p *NativeProxy) setupEventHandlers(client *IPCClient) { - if p.opts.OnVideoStateChange != nil { - client.OnEvent("video_state_change", func(data interface{}) { - dataBytes, err := json.Marshal(data) - if err != nil { - p.logger.Warn().Err(err).Msg("failed to marshal video state event") - return - } - var state VideoState - if err := json.Unmarshal(dataBytes, &state); err != nil { - p.logger.Warn().Err(err).Msg("failed to unmarshal video state event") - return - } - p.opts.OnVideoStateChange(state) - }) - } - - if p.opts.OnIndevEvent != nil { - client.OnEvent("indev_event", func(data interface{}) { - if event, ok := data.(string); ok { - p.opts.OnIndevEvent(event) - } - }) - } - - if p.opts.OnRpcEvent != nil { - client.OnEvent("rpc_event", func(data interface{}) { - if event, ok := data.(string); ok { - p.opts.OnRpcEvent(event) - } - }) - } - - if p.opts.OnVideoFrameReceived != nil { - client.OnEvent("video_frame", func(data interface{}) { - dataMap, ok := data.(map[string]interface{}) - if !ok { - p.logger.Warn().Msg("invalid video frame event data") - return - } - - frameData, ok := dataMap["frame"].([]interface{}) - if !ok { - p.logger.Warn().Msg("invalid frame data in event") - return - } - - frame := make([]byte, len(frameData)) - for i, v := range frameData { - if b, ok := v.(float64); ok { - frame[i] = byte(b) - } - } - - durationNs, ok := dataMap["duration"].(float64) - if !ok { - p.logger.Warn().Msg("invalid duration in event") - return - } - - p.opts.OnVideoFrameReceived(frame, time.Duration(durationNs)) - }) - } +func (p *NativeProxy) setupEventHandlers(client *GRPCClient) { + // if p.opts.OnVideoStateChange != nil { + // client.OnEvent("video_state_change", func(data interface{}) { + // dataBytes, err := json.Marshal(data) + // if err != nil { + // p.logger.Warn().Err(err).Msg("failed to marshal video state event") + // return + // } + // var state VideoState + // if err := json.Unmarshal(dataBytes, &state); err != nil { + // p.logger.Warn().Err(err).Msg("failed to unmarshal video state event") + // return + // } + // p.opts.OnVideoStateChange(state) + // }) + // } + + // if p.opts.OnIndevEvent != nil { + // client.OnEvent("indev_event", func(data interface{}) { + // if event, ok := data.(string); ok { + // p.opts.OnIndevEvent(event) + // } + // }) + // } + + // if p.opts.OnRpcEvent != nil { + // client.OnEvent("rpc_event", func(data interface{}) { + // if event, ok := data.(string); ok { + // p.opts.OnRpcEvent(event) + // } + // }) + // } + + // if p.opts.OnVideoFrameReceived != nil { + // client.OnEvent("video_frame", func(data interface{}) { + // dataMap, ok := data.(map[string]interface{}) + // if !ok { + // p.logger.Warn().Msg("invalid video frame event data") + // return + // } + + // frameData, ok := dataMap["frame"].([]interface{}) + // if !ok { + // p.logger.Warn().Msg("invalid frame data in event") + // return + // } + + // frame := make([]byte, len(frameData)) + // for i, v := range frameData { + // if b, ok := v.(float64); ok { + // frame[i] = byte(b) + // } + // } + + // durationNs, ok := dataMap["duration"].(float64) + // if !ok { + // p.logger.Warn().Msg("invalid duration in event") + // return + // } + + // p.opts.OnVideoFrameReceived(frame, time.Duration(durationNs)) + // }) + // } } // Stop stops the native process @@ -347,336 +379,123 @@ func (p *NativeProxy) Stop() error { return nil } -// Implement all Native methods by forwarding to IPC - +// Implement all Native methods by forwarding to gRPC client func (p *NativeProxy) VideoSetSleepMode(enabled bool) error { - _, err := p.client.Call("VideoSetSleepMode", map[string]interface{}{ - "enabled": enabled, - }) - return err + return p.client.VideoSetSleepMode(enabled) } func (p *NativeProxy) VideoGetSleepMode() (bool, error) { - resp, err := p.client.Call("VideoGetSleepMode", nil) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.VideoGetSleepMode() } func (p *NativeProxy) VideoSleepModeSupported() bool { - resp, err := p.client.Call("VideoSleepModeSupported", nil) - if err != nil { - return false - } - result, ok := resp.Result.(bool) - if !ok { - return false - } - return result + return p.client.VideoSleepModeSupported() } func (p *NativeProxy) VideoSetQualityFactor(factor float64) error { - _, err := p.client.Call("VideoSetQualityFactor", map[string]interface{}{ - "factor": factor, - }) - return err + return p.client.VideoSetQualityFactor(factor) } func (p *NativeProxy) VideoGetQualityFactor() (float64, error) { - resp, err := p.client.Call("VideoGetQualityFactor", nil) - if err != nil { - return 0, err - } - result, ok := resp.Result.(float64) - if !ok { - return 0, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.VideoGetQualityFactor() } func (p *NativeProxy) VideoSetEDID(edid string) error { - _, err := p.client.Call("VideoSetEDID", map[string]interface{}{ - "edid": edid, - }) - return err + return p.client.VideoSetEDID(edid) } func (p *NativeProxy) VideoGetEDID() (string, error) { - resp, err := p.client.Call("VideoGetEDID", nil) - if err != nil { - return "", err - } - result, ok := resp.Result.(string) - if !ok { - return "", fmt.Errorf("invalid response type") - } - return result, nil + return p.client.VideoGetEDID() } func (p *NativeProxy) VideoLogStatus() (string, error) { - resp, err := p.client.Call("VideoLogStatus", nil) - if err != nil { - return "", err - } - result, ok := resp.Result.(string) - if !ok { - return "", fmt.Errorf("invalid response type") - } - return result, nil + return p.client.VideoLogStatus() } func (p *NativeProxy) VideoStop() error { - _, err := p.client.Call("VideoStop", nil) - return err + return p.client.VideoStop() } func (p *NativeProxy) VideoStart() error { - _, err := p.client.Call("VideoStart", nil) - return err + return p.client.VideoStart() } func (p *NativeProxy) GetLVGLVersion() (string, error) { - resp, err := p.client.Call("GetLVGLVersion", nil) - if err != nil { - return "", err - } - result, ok := resp.Result.(string) - if !ok { - return "", fmt.Errorf("invalid response type") - } - return result, nil + return p.client.GetLVGLVersion() } func (p *NativeProxy) UIObjHide(objName string) (bool, error) { - resp, err := p.client.Call("UIObjHide", map[string]interface{}{ - "obj_name": objName, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjHide(objName) } func (p *NativeProxy) UIObjShow(objName string) (bool, error) { - resp, err := p.client.Call("UIObjShow", map[string]interface{}{ - "obj_name": objName, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjShow(objName) } func (p *NativeProxy) UISetVar(name string, value string) { - _, _ = p.client.Call("UISetVar", map[string]interface{}{ - "name": name, - "value": value, - }) + p.client.UISetVar(name, value) } func (p *NativeProxy) UIGetVar(name string) string { - resp, err := p.client.Call("UIGetVar", map[string]interface{}{ - "name": name, - }) - if err != nil { - return "" - } - result, ok := resp.Result.(string) - if !ok { - return "" - } - return result + return p.client.UIGetVar(name) } func (p *NativeProxy) UIObjAddState(objName string, state string) (bool, error) { - resp, err := p.client.Call("UIObjAddState", map[string]interface{}{ - "obj_name": objName, - "state": state, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjAddState(objName, state) } func (p *NativeProxy) UIObjClearState(objName string, state string) (bool, error) { - resp, err := p.client.Call("UIObjClearState", map[string]interface{}{ - "obj_name": objName, - "state": state, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjClearState(objName, state) } func (p *NativeProxy) UIObjAddFlag(objName string, flag string) (bool, error) { - resp, err := p.client.Call("UIObjAddFlag", map[string]interface{}{ - "obj_name": objName, - "flag": flag, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjAddFlag(objName, flag) } func (p *NativeProxy) UIObjClearFlag(objName string, flag string) (bool, error) { - resp, err := p.client.Call("UIObjClearFlag", map[string]interface{}{ - "obj_name": objName, - "flag": flag, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjClearFlag(objName, flag) } func (p *NativeProxy) UIObjSetOpacity(objName string, opacity int) (bool, error) { - resp, err := p.client.Call("UIObjSetOpacity", map[string]interface{}{ - "obj_name": objName, - "opacity": opacity, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjSetOpacity(objName, opacity) } func (p *NativeProxy) UIObjFadeIn(objName string, duration uint32) (bool, error) { - resp, err := p.client.Call("UIObjFadeIn", map[string]interface{}{ - "obj_name": objName, - "duration": duration, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjFadeIn(objName, duration) } func (p *NativeProxy) UIObjFadeOut(objName string, duration uint32) (bool, error) { - resp, err := p.client.Call("UIObjFadeOut", map[string]interface{}{ - "obj_name": objName, - "duration": duration, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjFadeOut(objName, duration) } func (p *NativeProxy) UIObjSetLabelText(objName string, text string) (bool, error) { - resp, err := p.client.Call("UIObjSetLabelText", map[string]interface{}{ - "obj_name": objName, - "text": text, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjSetLabelText(objName, text) } func (p *NativeProxy) UIObjSetImageSrc(objName string, image string) (bool, error) { - resp, err := p.client.Call("UIObjSetImageSrc", map[string]interface{}{ - "obj_name": objName, - "image": image, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.UIObjSetImageSrc(objName, image) } func (p *NativeProxy) DisplaySetRotation(rotation uint16) (bool, error) { - resp, err := p.client.Call("DisplaySetRotation", map[string]interface{}{ - "rotation": rotation, - }) - if err != nil { - return false, err - } - result, ok := resp.Result.(bool) - if !ok { - return false, fmt.Errorf("invalid response type") - } - return result, nil + return p.client.DisplaySetRotation(rotation) } func (p *NativeProxy) UpdateLabelIfChanged(objName string, newText string) { - _, _ = p.client.Call("UpdateLabelIfChanged", map[string]interface{}{ - "obj_name": objName, - "new_text": newText, - }) + p.client.UpdateLabelIfChanged(objName, newText) } func (p *NativeProxy) UpdateLabelAndChangeVisibility(objName string, newText string) { - _, _ = p.client.Call("UpdateLabelAndChangeVisibility", map[string]interface{}{ - "obj_name": objName, - "new_text": newText, - }) + p.client.UpdateLabelAndChangeVisibility(objName, newText) } func (p *NativeProxy) SwitchToScreenIf(screenName string, shouldSwitch []string) { - _, _ = p.client.Call("SwitchToScreenIf", map[string]interface{}{ - "screen_name": screenName, - "should_switch": shouldSwitch, - }) + p.client.SwitchToScreenIf(screenName, shouldSwitch) } func (p *NativeProxy) SwitchToScreenIfDifferent(screenName string) { - _, _ = p.client.Call("SwitchToScreenIfDifferent", map[string]interface{}{ - "screen_name": screenName, - }) + p.client.SwitchToScreenIfDifferent(screenName) } func (p *NativeProxy) DoNotUseThisIsForCrashTestingOnly() { - _, _ = p.client.Call("DoNotUseThisIsForCrashTestingOnly", nil) + p.client.DoNotUseThisIsForCrashTestingOnly() } diff --git a/internal/native/server.go b/internal/native/server.go new file mode 100644 index 000000000..cc6423812 --- /dev/null +++ b/internal/native/server.go @@ -0,0 +1,90 @@ +package native + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/caarlos0/env/v11" + "github.com/erikdubbelboer/gspt" + "github.com/rs/zerolog" +) + +// Native Process +// stdout - exchange messages with the parent process +// stderr - logging and error messages + +// RunNativeProcess runs the native process mode +func RunNativeProcess(binaryName string) { + // Initialize logger + logger := zerolog.New(os.Stderr).With().Timestamp().Logger() + + gspt.SetProcTitle(binaryName + " [native]") + + // Determine socket path + socketPath := os.Getenv("JETKVM_NATIVE_SOCKET") + videoStreamSocketPath := os.Getenv("JETKVM_VIDEO_STREAM_SOCKET") + + if socketPath == "" || videoStreamSocketPath == "" { + logger.Fatal().Str("socket_path", socketPath).Str("video_stream_socket_path", videoStreamSocketPath).Msg("socket path or video stream socket path is not set") + } + + // connect to video stream socket + conn, err := net.Dial("unixpacket", videoStreamSocketPath) + if err != nil { + logger.Fatal().Err(err).Msg("failed to connect to video stream socket") + } + logger.Info().Str("video_stream_socket_path", videoStreamSocketPath).Msg("connected to video stream socket") + + var nativeOptions NativeOptions + if err := env.Parse(&nativeOptions); err != nil { + logger.Fatal().Err(err).Msg("failed to parse native options") + } + + nativeOptions.OnVideoFrameReceived = func(frame []byte, duration time.Duration) { + _, err := conn.Write(frame) + if err != nil { + logger.Fatal().Err(err).Msg("failed to write frame to video stream socket") + } + } + + // Create native instance + nativeInstance := NewNative(nativeOptions) + + // Start native instance + if err := nativeInstance.Start(); err != nil { + logger.Fatal().Err(err).Msg("failed to start native instance") + } + + // Create gRPC server + grpcServer := NewGRPCServer(nativeInstance, &logger) + + logger.Info().Msg("starting gRPC server") + // Start gRPC server + server, lis, err := StartGRPCServer(grpcServer, fmt.Sprintf("@%v", socketPath), &logger) + if err != nil { + logger.Fatal().Err(err).Msg("failed to start gRPC server") + } + gspt.SetProcTitle(binaryName + " [native] ready") + + // Signal that we're ready by writing socket path to stdout (for parent to read) + fmt.Fprintf(os.Stdout, "%s\n", socketPath) + defer os.Stdout.Close() + + // Set up signal handling + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) + + // Wait for signal + <-sigChan + logger.Info().Msg("received termination signal") + + // Graceful shutdown + server.GracefulStop() + lis.Close() + + logger.Info().Msg("native process exiting") +} diff --git a/main.go b/main.go index d7ecc3e1c..08df3f4ad 100644 --- a/main.go +++ b/main.go @@ -40,8 +40,8 @@ func Main() { go runWatchdog() go confirmCurrentSystem() - initDisplay() initNative(systemVersionLocal, appVersionLocal) + initDisplay() http.DefaultClient.Timeout = 1 * time.Minute diff --git a/native.go b/native.go index 518c3fa99..ad7f4bde4 100644 --- a/native.go +++ b/native.go @@ -16,14 +16,14 @@ var ( ) func initNative(systemVersion *semver.Version, appVersion *semver.Version) { + nativeLogger.Info().Msg("initializing native") var err error - nativeInstance, err = native.NewNativeProxy(native.NativeProxyOptions{ + nativeInstance, err = native.NewNativeProxy(native.NativeOptions{ Disable: failsafeModeActive, SystemVersion: systemVersion, AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(), DefaultQualityFactor: config.VideoQualityFactor, - Logger: nativeLogger, OnVideoStateChange: func(state native.VideoState) { lastVideoState = state triggerVideoStateUpdate() diff --git a/native_process.go b/native_process.go deleted file mode 100644 index c239afdfb..000000000 --- a/native_process.go +++ /dev/null @@ -1,528 +0,0 @@ -package kvm - -import ( - "encoding/json" - "fmt" - "os" - "os/signal" - "path/filepath" - "syscall" - "time" - - "github.com/Masterminds/semver/v3" - "github.com/jetkvm/kvm/internal/native" - "github.com/rs/zerolog" -) - -// RunNativeProcess runs the native process mode -func RunNativeProcess() { - // Initialize logger - logger := zerolog.New(os.Stderr).With().Timestamp().Logger() - - // Parse command line arguments (config is passed as first arg) - if len(os.Args) < 2 { - logger.Fatal().Msg("usage: native process requires config_json argument") - } - - var config native.ProcessConfig - if err := json.Unmarshal([]byte(os.Args[1]), &config); err != nil { - logger.Fatal().Err(err).Msg("failed to parse config") - } - - // Parse version strings - var systemVersion *semver.Version - if config.SystemVersion != "" { - v, err := semver.NewVersion(config.SystemVersion) - if err != nil { - logger.Warn().Err(err).Str("version", config.SystemVersion).Msg("failed to parse system version") - } else { - systemVersion = v - } - } - - var appVersion *semver.Version - if config.AppVersion != "" { - v, err := semver.NewVersion(config.AppVersion) - if err != nil { - logger.Warn().Err(err).Str("version", config.AppVersion).Msg("failed to parse app version") - } else { - appVersion = v - } - } - - // Create native instance - nativeInstance := native.NewNative(native.NativeOptions{ - Disable: config.Disable, - SystemVersion: systemVersion, - AppVersion: appVersion, - DisplayRotation: config.DisplayRotation, - DefaultQualityFactor: config.DefaultQualityFactor, - OnVideoStateChange: func(state native.VideoState) { - sendEvent("video_state_change", state) - }, - OnIndevEvent: func(event string) { - sendEvent("indev_event", event) - }, - OnRpcEvent: func(event string) { - sendEvent("rpc_event", event) - }, - OnVideoFrameReceived: func(frame []byte, duration time.Duration) { - sendEvent("video_frame", map[string]interface{}{ - "frame": frame, - "duration": duration.Nanoseconds(), - }) - }, - }) - - // Start native instance - if err := nativeInstance.Start(); err != nil { - logger.Fatal().Err(err).Msg("failed to start native instance") - } - - // Create gRPC server - grpcServer := native.NewGRPCServer(nativeInstance, &logger) - - // Determine socket path - socketPath := os.Getenv("JETKVM_NATIVE_SOCKET") - if socketPath == "" { - // Default to a socket in /tmp - socketPath = filepath.Join("/tmp", fmt.Sprintf("jetkvm-native-%d.sock", os.Getpid())) - } - - // Start gRPC server - server, lis, err := native.StartGRPCServer(grpcServer, socketPath, &logger) - if err != nil { - logger.Fatal().Err(err).Msg("failed to start gRPC server") - } - - // Signal that we're ready by writing socket path to stdout (for parent to read) - fmt.Fprintf(os.Stdout, "%s\n", socketPath) - os.Stdout.Close() - - // Set up signal handling - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) - - // Wait for signal - <-sigChan - logger.Info().Msg("received termination signal") - - // Graceful shutdown - server.GracefulStop() - lis.Close() - - logger.Info().Msg("native process exiting") -} - -// All JSON-RPC handlers have been removed - now using gRPC - case "VideoSetSleepMode": - var params struct { - Enabled bool `json:"enabled"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - err = n.VideoSetSleepMode(params.Enabled) - - case "VideoGetSleepMode": - var result bool - result, err = n.VideoGetSleepMode() - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "VideoSleepModeSupported": - result = n.VideoSleepModeSupported() - sendResponse(encoder, req.ID, "result", result) - return - - case "VideoSetQualityFactor": - var params struct { - Factor float64 `json:"factor"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - err = n.VideoSetQualityFactor(params.Factor) - - case "VideoGetQualityFactor": - var result float64 - result, err = n.VideoGetQualityFactor() - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "VideoSetEDID": - var params struct { - EDID string `json:"edid"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - err = n.VideoSetEDID(params.EDID) - - case "VideoGetEDID": - var result string - result, err = n.VideoGetEDID() - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "VideoLogStatus": - var result string - result, err = n.VideoLogStatus() - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "VideoStop": - err = n.VideoStop() - - case "VideoStart": - err = n.VideoStart() - - case "GetLVGLVersion": - var result string - result, err = n.GetLVGLVersion() - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjHide": - var params struct { - ObjName string `json:"obj_name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjHide(params.ObjName) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjShow": - var params struct { - ObjName string `json:"obj_name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjShow(params.ObjName) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UISetVar": - var params struct { - Name string `json:"name"` - Value string `json:"value"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - n.UISetVar(params.Name, params.Value) - sendResponse(encoder, req.ID, "result", nil) - return - - case "UIGetVar": - var params struct { - Name string `json:"name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - result = n.UIGetVar(params.Name) - sendResponse(encoder, req.ID, "result", result) - return - - case "UIObjAddState": - var params struct { - ObjName string `json:"obj_name"` - State string `json:"state"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjAddState(params.ObjName, params.State) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjClearState": - var params struct { - ObjName string `json:"obj_name"` - State string `json:"state"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjClearState(params.ObjName, params.State) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjAddFlag": - var params struct { - ObjName string `json:"obj_name"` - Flag string `json:"flag"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjAddFlag(params.ObjName, params.Flag) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjClearFlag": - var params struct { - ObjName string `json:"obj_name"` - Flag string `json:"flag"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjClearFlag(params.ObjName, params.Flag) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjSetOpacity": - var params struct { - ObjName string `json:"obj_name"` - Opacity int `json:"opacity"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjSetOpacity(params.ObjName, params.Opacity) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjFadeIn": - var params struct { - ObjName string `json:"obj_name"` - Duration uint32 `json:"duration"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjFadeIn(params.ObjName, params.Duration) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjFadeOut": - var params struct { - ObjName string `json:"obj_name"` - Duration uint32 `json:"duration"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjFadeOut(params.ObjName, params.Duration) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjSetLabelText": - var params struct { - ObjName string `json:"obj_name"` - Text string `json:"text"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjSetLabelText(params.ObjName, params.Text) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UIObjSetImageSrc": - var params struct { - ObjName string `json:"obj_name"` - Image string `json:"image"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.UIObjSetImageSrc(params.ObjName, params.Image) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "DisplaySetRotation": - var params struct { - Rotation uint16 `json:"rotation"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - var result bool - result, err = n.DisplaySetRotation(params.Rotation) - if err == nil { - sendResponse(encoder, req.ID, "result", result) - return - } - - case "UpdateLabelIfChanged": - var params struct { - ObjName string `json:"obj_name"` - NewText string `json:"new_text"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - n.UpdateLabelIfChanged(params.ObjName, params.NewText) - sendResponse(encoder, req.ID, "result", nil) - return - - case "UpdateLabelAndChangeVisibility": - var params struct { - ObjName string `json:"obj_name"` - NewText string `json:"new_text"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - n.UpdateLabelAndChangeVisibility(params.ObjName, params.NewText) - sendResponse(encoder, req.ID, "result", nil) - return - - case "SwitchToScreenIf": - var params struct { - ScreenName string `json:"screen_name"` - ShouldSwitch []string `json:"should_switch"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - n.SwitchToScreenIf(params.ScreenName, params.ShouldSwitch) - sendResponse(encoder, req.ID, "result", nil) - return - - case "SwitchToScreenIfDifferent": - var params struct { - ScreenName string `json:"screen_name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - sendError(encoder, req.ID, -32602, "Invalid params", nil) - return - } - n.SwitchToScreenIfDifferent(params.ScreenName) - sendResponse(encoder, req.ID, "result", nil) - return - - case "DoNotUseThisIsForCrashTestingOnly": - n.DoNotUseThisIsForCrashTestingOnly() - sendResponse(encoder, req.ID, "result", nil) - return - - default: - sendError(encoder, req.ID, -32601, fmt.Sprintf("Method not found: %s", req.Method), nil) - return - } - - if err != nil { - sendError(encoder, req.ID, -32000, err.Error(), nil) - return - } - - sendResponse(encoder, req.ID, "result", result) -} - -func sendResponse(encoder *json.Encoder, id interface{}, key string, result interface{}) { - response := map[string]interface{}{ - "jsonrpc": "2.0", - key: result, - } - if id != nil { - response["id"] = id - } - if err := encoder.Encode(response); err != nil { - fmt.Fprintf(os.Stderr, "failed to send response: %v\n", err) - } -} - -func sendError(encoder *json.Encoder, id interface{}, code int, message string, data interface{}) { - response := map[string]interface{}{ - "jsonrpc": "2.0", - "error": map[string]interface{}{ - "code": code, - "message": message, - }, - } - if id != nil { - response["id"] = id - } - if data != nil { - response["error"].(map[string]interface{})["data"] = data - } - if err := encoder.Encode(response); err != nil { - fmt.Fprintf(os.Stderr, "failed to send error: %v\n", err) - } -} - -func sendEvent(eventType string, data interface{}) { - event := map[string]interface{}{ - "jsonrpc": "2.0", - "method": "event", - "params": map[string]interface{}{ - "type": eventType, - "data": data, - }, - } - encoder := json.NewEncoder(os.Stdout) - if err := encoder.Encode(event); err != nil { - fmt.Fprintf(os.Stderr, "failed to send event: %v\n", err) - } -} - From 2f868bc36db7f964410e31c7a1541caf2c63e5bf Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 14:16:53 +0000 Subject: [PATCH 03/25] dead simple dual build target POC --- internal/native/cgo/CMakeLists.txt | 14 +- internal/native/cgo/main.c | 227 +++++++++++++++++++++++++++++ internal/native/cgo/main.h | 26 ++++ internal/native/native.go | 10 +- internal/native/proxy.go | 80 +++++++--- internal/native/server.go | 28 ++-- scripts/dev_deploy.sh | 48 ++++-- 7 files changed, 378 insertions(+), 55 deletions(-) create mode 100644 internal/native/cgo/main.c create mode 100644 internal/native/cgo/main.h diff --git a/internal/native/cgo/CMakeLists.txt b/internal/native/cgo/CMakeLists.txt index 739dabe52..c4c01dedf 100644 --- a/internal/native/cgo/CMakeLists.txt +++ b/internal/native/cgo/CMakeLists.txt @@ -42,6 +42,8 @@ FetchContent_MakeAvailable(lvgl) # Get source files, excluding CMake generated files file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c" "ui/*.c") list(FILTER sources EXCLUDE REGEX "CMakeFiles.*CompilerId.*\\.c$") +# Exclude main.c from library sources (it's used for the binary target) +list(FILTER sources EXCLUDE REGEX "main\\.c$") add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/ctrl.h) @@ -68,4 +70,14 @@ target_link_libraries(jknative PRIVATE # libgpiod ) -install(TARGETS jknative DESTINATION lib) \ No newline at end of file +# Binary target using main.c as entry point +add_executable(jknative-bin ${CMAKE_CURRENT_SOURCE_DIR}/main.c) + +# Link the binary to the library (if needed in the future) +target_link_libraries(jknative-bin PRIVATE + jknative + pthread +) + +install(TARGETS jknative DESTINATION lib) +install(TARGETS jknative-bin DESTINATION bin) \ No newline at end of file diff --git a/internal/native/cgo/main.c b/internal/native/cgo/main.c new file mode 100644 index 000000000..a3e0e22fd --- /dev/null +++ b/internal/native/cgo/main.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctrl.h" +#include "main.h" + +#define SOCKET_PATH "/tmp/video.sock" +#define BUFFER_SIZE 4096 + +// Global state +static int client_fd = -1; +static pthread_mutex_t client_fd_mutex = PTHREAD_MUTEX_INITIALIZER; + +void jetkvm_c_log_handler(int level, const char *filename, const char *funcname, int line, const char *message) { + // printf("[%s] %s:%d %s: %s\n", filename ? filename : "unknown", funcname ? funcname : "unknown", line, message ? message : ""); + fprintf(stderr, "[%s] %s:%d %s: %s\n", filename ? filename : "unknown", funcname ? funcname : "unknown", line, message ? message : ""); +} + +// Video handler that pipes frames to the Unix socket +// This will be called by the video subsystem via video_send_frame -> jetkvm_set_video_handler's handler +void jetkvm_video_handler(const uint8_t *frame, ssize_t len) { + // pthread_mutex_lock(&client_fd_mutex); + // if (client_fd >= 0 && frame != NULL && len > 0) { + // ssize_t bytes_written = 0; + // while (bytes_written < len) { + // ssize_t n = write(client_fd, frame + bytes_written, len - bytes_written); + // if (n < 0) { + // if (errno == EPIPE || errno == ECONNRESET) { + // // Client disconnected + // close(client_fd); + // client_fd = -1; + // break; + // } + // perror("write"); + // break; + // } + // bytes_written += n; + // } + // } + // pthread_mutex_unlock(&client_fd_mutex); +} + +void jetkvm_video_state_handler(jetkvm_video_state_t *state) { + fprintf(stderr, "Video state: {\n" + "\"ready\": %d,\n" + "\"error\": \"%s\",\n" + "\"width\": %d,\n" + "\"height\": %d,\n" + "\"frame_per_second\": %f\n" + "}\n", state->ready, state->error, state->width, state->height, state->frame_per_second); +} + +void jetkvm_indev_handler(int code) { + fprintf(stderr, "Video indev: %d\n", code); +} + +void jetkvm_rpc_handler(const char *method, const char *params) { + fprintf(stderr, "Video rpc: %s %s\n", method, params); +} + +// Note: jetkvm_set_video_handler, jetkvm_set_indev_handler, jetkvm_set_rpc_handler, +// jetkvm_call_rpc_handler, and jetkvm_set_video_state_handler are implemented in +// the library (ctrl.c) and will be used from there when linking. + +int main(int argc, char *argv[]) { + const char *socket_path = SOCKET_PATH; + + // Allow custom socket path via command line argument + if (argc > 1) { + socket_path = argv[1]; + } + + // Remove existing socket file if it exists + unlink(socket_path); + + // Set handlers + jetkvm_set_log_handler(&jetkvm_c_log_handler); + jetkvm_set_video_handler(&jetkvm_video_handler); + jetkvm_set_video_state_handler(&jetkvm_video_state_handler); + jetkvm_set_indev_handler(&jetkvm_indev_handler); + jetkvm_set_rpc_handler(&jetkvm_rpc_handler); + + // Initialize video first (before accepting connections) + fprintf(stderr, "Initializing video...\n"); + if (jetkvm_video_init(1.0) != 0) { + fprintf(stderr, "Failed to initialize video\n"); + return 1; + } + + // Start video streaming - frames will be sent via video_send_frame + // which calls the video handler we set up + jetkvm_video_start(); + fprintf(stderr, "Video streaming started.\n"); + + // Create Unix domain socket + int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("socket"); + jetkvm_video_stop(); + jetkvm_video_shutdown(); + return 1; + } + + // Make socket non-blocking + int flags = fcntl(server_fd, F_GETFL, 0); + if (flags < 0 || fcntl(server_fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl"); + close(server_fd); + jetkvm_video_stop(); + jetkvm_video_shutdown(); + return 1; + } + + // Bind socket to path + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + close(server_fd); + jetkvm_video_stop(); + jetkvm_video_shutdown(); + return 1; + } + + // Listen for connections + if (listen(server_fd, 1) < 0) { + perror("listen"); + close(server_fd); + jetkvm_video_stop(); + jetkvm_video_shutdown(); + return 1; + } + + fprintf(stderr, "Listening on Unix socket: %s (non-blocking)\n", socket_path); + fprintf(stderr, "Video frames will be sent to connected clients...\n"); + + // Main loop: check for new connections and handle client disconnections + fd_set read_fds; + struct timeval timeout; + + while (1) { + FD_ZERO(&read_fds); + FD_SET(server_fd, &read_fds); + + pthread_mutex_lock(&client_fd_mutex); + int current_client_fd = client_fd; + if (current_client_fd >= 0) { + FD_SET(current_client_fd, &read_fds); + } + int max_fd = (current_client_fd > server_fd) ? current_client_fd : server_fd; + pthread_mutex_unlock(&client_fd_mutex); + + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + int result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); + if (result < 0) { + if (errno == EINTR) { + continue; + } + perror("select"); + break; + } + + // Check for new connection + if (FD_ISSET(server_fd, &read_fds)) { + int accepted_fd = accept(server_fd, NULL, NULL); + if (accepted_fd >= 0) { + fprintf(stderr, "Client connected\n"); + pthread_mutex_lock(&client_fd_mutex); + if (client_fd >= 0) { + // Close previous client if any + close(client_fd); + } + client_fd = accepted_fd; + pthread_mutex_unlock(&client_fd_mutex); + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("accept"); + } + } + + // Check if client disconnected + pthread_mutex_lock(&client_fd_mutex); + current_client_fd = client_fd; + pthread_mutex_unlock(&client_fd_mutex); + + if (current_client_fd >= 0 && FD_ISSET(current_client_fd, &read_fds)) { + // Client sent data or closed connection + char buffer[1]; + if (read(current_client_fd, buffer, 1) <= 0) { + fprintf(stderr, "Client disconnected\n"); + pthread_mutex_lock(&client_fd_mutex); + close(client_fd); + client_fd = -1; + pthread_mutex_unlock(&client_fd_mutex); + } + } + } + + // Stop video streaming + jetkvm_video_stop(); + jetkvm_video_shutdown(); + + // Cleanup + pthread_mutex_lock(&client_fd_mutex); + if (client_fd >= 0) { + close(client_fd); + client_fd = -1; + } + pthread_mutex_unlock(&client_fd_mutex); + + close(server_fd); + unlink(socket_path); + + return 0; +} + diff --git a/internal/native/cgo/main.h b/internal/native/cgo/main.h new file mode 100644 index 000000000..d9cc6c498 --- /dev/null +++ b/internal/native/cgo/main.h @@ -0,0 +1,26 @@ +#ifndef JETKVM_NATIVE_MAIN_H +#define JETKVM_NATIVE_MAIN_H + +#include +#include +#include +#include +#include +#include +#include +#include "ctrl.h" + +void jetkvm_c_log_handler(int level, const char *filename, const char *funcname, int line, const char *message); +void jetkvm_video_handler(const uint8_t *frame, ssize_t len); +void jetkvm_video_state_handler(jetkvm_video_state_t *state); +void jetkvm_indev_handler(int code); +void jetkvm_rpc_handler(const char *method, const char *params); + + +// typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state); +// typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, int line, const char *message); +// typedef void (jetkvm_rpc_handler_t)(const char *method, const char *params); +// typedef void (jetkvm_video_handler_t)(const uint8_t *frame, ssize_t len); +// typedef void (jetkvm_indev_handler_t)(int code); + +#endif \ No newline at end of file diff --git a/internal/native/native.go b/internal/native/native.go index edf5217a8..f60573d6b 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -28,11 +28,11 @@ type Native struct { } type NativeOptions struct { - Disable bool `env:"JETKVM_NATIVE_DISABLE"` - SystemVersion *semver.Version `env:"JETKVM_NATIVE_SYSTEM_VERSION"` - AppVersion *semver.Version `env:"JETKVM_NATIVE_APP_VERSION"` - DisplayRotation uint16 `env:"JETKVM_NATIVE_DISPLAY_ROTATION"` - DefaultQualityFactor float64 `env:"JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR"` + Disable bool + SystemVersion *semver.Version + AppVersion *semver.Version + DisplayRotation uint16 + DefaultQualityFactor float64 OnVideoStateChange func(state VideoState) OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) diff --git a/internal/native/proxy.go b/internal/native/proxy.go index b36c82e7b..60e3bea2a 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -9,7 +9,8 @@ import ( "syscall" "time" - "github.com/jetkvm/kvm/internal/supervisor" + "github.com/Masterminds/semver/v3" + "github.com/jetkvm/kvm/internal/utils" "github.com/rs/zerolog" ) @@ -17,6 +18,48 @@ const ( maxFrameSize = 1920 * 1080 / 2 ) +type nativeProxyOptions struct { + Disable bool `env:"JETKVM_NATIVE_DISABLE"` + SystemVersion *semver.Version `env:"JETKVM_NATIVE_SYSTEM_VERSION"` + AppVersion *semver.Version `env:"JETKVM_NATIVE_APP_VERSION"` + DisplayRotation uint16 `env:"JETKVM_NATIVE_DISPLAY_ROTATION"` + DefaultQualityFactor float64 `env:"JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR"` + CtrlUnixSocket string `env:"JETKVM_NATIVE_CTRL_UNIX_SOCKET"` + VideoStreamUnixSocket string `env:"JETKVM_NATIVE_VIDEO_STREAM_UNIX_SOCKET"` + BinaryPath string `env:"JETKVM_NATIVE_BINARY_PATH"` + LoggerLevel zerolog.Level `env:"JETKVM_NATIVE_LOGGER_LEVEL"` + HandshakeMessage string `env:"JETKVM_NATIVE_HANDSHAKE_MESSAGE"` + + OnVideoFrameReceived func(frame []byte, duration time.Duration) + OnIndevEvent func(event string) + OnRpcEvent func(event string) + OnVideoStateChange func(state VideoState) +} + +func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { + return &nativeProxyOptions{ + Disable: n.Disable, + SystemVersion: n.SystemVersion, + AppVersion: n.AppVersion, + DisplayRotation: n.DisplayRotation, + DefaultQualityFactor: n.DefaultQualityFactor, + OnVideoFrameReceived: n.OnVideoFrameReceived, + OnIndevEvent: n.OnIndevEvent, + OnRpcEvent: n.OnRpcEvent, + OnVideoStateChange: n.OnVideoStateChange, + } +} + +func (p *nativeProxyOptions) toNativeOptions() *NativeOptions { + return &NativeOptions{ + Disable: p.Disable, + SystemVersion: p.SystemVersion, + AppVersion: p.AppVersion, + DisplayRotation: p.DisplayRotation, + DefaultQualityFactor: p.DefaultQualityFactor, + } +} + // cmdWrapper wraps exec.Cmd to implement processCmd interface type cmdWrapper struct { *exec.Cmd @@ -55,23 +98,17 @@ type NativeProxy struct { cmd *cmdWrapper logger *zerolog.Logger ready chan struct{} - options *NativeOptions + options *nativeProxyOptions restartM sync.Mutex stopped bool processWait chan error } -func ensureDirectoryExists(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - return os.MkdirAll(path, 0600) - } - return nil -} - // NewNativeProxy creates a new NativeProxy that spawns a separate process func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { - nativeUnixSocket := "jetkvm-native-grpc" - videoStreamUnixSocket := "@jetkvm-native-video-stream" + proxyOptions := opts.toProxyOptions() + proxyOptions.CtrlUnixSocket = "jetkvm-native-grpc" + proxyOptions.VideoStreamUnixSocket = "@jetkvm-native-video-stream" // Get the current executable path to spawn itself exePath, err := os.Executable() @@ -80,12 +117,12 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { } proxy := &NativeProxy{ - nativeUnixSocket: nativeUnixSocket, - videoStreamUnixSocket: videoStreamUnixSocket, + nativeUnixSocket: proxyOptions.CtrlUnixSocket, + videoStreamUnixSocket: proxyOptions.VideoStreamUnixSocket, binaryPath: exePath, logger: nativeLogger, ready: make(chan struct{}), - options: &opts, + options: proxyOptions, processWait: make(chan error, 1), } proxy.cmd, err = proxy.spawnProcess() @@ -95,7 +132,7 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { } // create unix packet - listener, err := net.Listen("unixpacket", videoStreamUnixSocket) + listener, err := net.Listen("unixpacket", proxyOptions.VideoStreamUnixSocket) if err != nil { nativeLogger.Warn().Err(err).Msg("failed to start server") return nil, fmt.Errorf("failed to start server: %w", err) @@ -116,6 +153,11 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { } func (p *NativeProxy) spawnProcess() (*cmdWrapper, error) { + envArgs, err := utils.MarshalEnv(p.options) + if err != nil { + return nil, fmt.Errorf("failed to marshal environment variables: %w", err) + } + cmd := exec.Command( p.binaryPath, "-subcomponent=native", @@ -125,13 +167,7 @@ func (p *NativeProxy) spawnProcess() (*cmdWrapper, error) { // Set environment variable to indicate native process mode cmd.Env = append( os.Environ(), - fmt.Sprintf("%s=native", supervisor.EnvSubcomponent), - fmt.Sprintf("%s=%s", "JETKVM_NATIVE_SOCKET", p.nativeUnixSocket), - fmt.Sprintf("%s=%s", "JETKVM_VIDEO_STREAM_SOCKET", p.videoStreamUnixSocket), - fmt.Sprintf("%s=%s", "JETKVM_NATIVE_SYSTEM_VERSION", p.options.SystemVersion), - fmt.Sprintf("%s=%s", "JETKVM_NATIVE_APP_VERSION", p.options.AppVersion), - fmt.Sprintf("%s=%d", "JETKVM_NATIVE_DISPLAY_ROTATION", p.options.DisplayRotation), - fmt.Sprintf("%s=%f", "JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR", p.options.DefaultQualityFactor), + envArgs..., ) // Wrap cmd to implement processCmd interface wrappedCmd := &cmdWrapper{Cmd: cmd} diff --git a/internal/native/server.go b/internal/native/server.go index cc6423812..c278b9cab 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -10,7 +10,6 @@ import ( "github.com/caarlos0/env/v11" "github.com/erikdubbelboer/gspt" - "github.com/rs/zerolog" ) // Native Process @@ -19,31 +18,24 @@ import ( // RunNativeProcess runs the native process mode func RunNativeProcess(binaryName string) { + logger := *nativeLogger // Initialize logger - logger := zerolog.New(os.Stderr).With().Timestamp().Logger() gspt.SetProcTitle(binaryName + " [native]") - // Determine socket path - socketPath := os.Getenv("JETKVM_NATIVE_SOCKET") - videoStreamSocketPath := os.Getenv("JETKVM_VIDEO_STREAM_SOCKET") - - if socketPath == "" || videoStreamSocketPath == "" { - logger.Fatal().Str("socket_path", socketPath).Str("video_stream_socket_path", videoStreamSocketPath).Msg("socket path or video stream socket path is not set") + var proxyOptions nativeProxyOptions + if err := env.Parse(&proxyOptions); err != nil { + logger.Fatal().Err(err).Msg("failed to parse native options") } // connect to video stream socket - conn, err := net.Dial("unixpacket", videoStreamSocketPath) + conn, err := net.Dial("unixpacket", proxyOptions.VideoStreamUnixSocket) if err != nil { logger.Fatal().Err(err).Msg("failed to connect to video stream socket") } - logger.Info().Str("video_stream_socket_path", videoStreamSocketPath).Msg("connected to video stream socket") - - var nativeOptions NativeOptions - if err := env.Parse(&nativeOptions); err != nil { - logger.Fatal().Err(err).Msg("failed to parse native options") - } + logger.Info().Str("video_stream_socket_path", proxyOptions.VideoStreamUnixSocket).Msg("connected to video stream socket") + nativeOptions := proxyOptions.toNativeOptions() nativeOptions.OnVideoFrameReceived = func(frame []byte, duration time.Duration) { _, err := conn.Write(frame) if err != nil { @@ -52,7 +44,7 @@ func RunNativeProcess(binaryName string) { } // Create native instance - nativeInstance := NewNative(nativeOptions) + nativeInstance := NewNative(*nativeOptions) // Start native instance if err := nativeInstance.Start(); err != nil { @@ -64,14 +56,14 @@ func RunNativeProcess(binaryName string) { logger.Info().Msg("starting gRPC server") // Start gRPC server - server, lis, err := StartGRPCServer(grpcServer, fmt.Sprintf("@%v", socketPath), &logger) + server, lis, err := StartGRPCServer(grpcServer, fmt.Sprintf("@%v", proxyOptions.CtrlUnixSocket), &logger) if err != nil { logger.Fatal().Err(err).Msg("failed to start gRPC server") } gspt.SetProcTitle(binaryName + " [native] ready") // Signal that we're ready by writing socket path to stdout (for parent to read) - fmt.Fprintf(os.Stdout, "%s\n", socketPath) + fmt.Fprintf(os.Stdout, "%s\n", proxyOptions.CtrlUnixSocket) defer os.Stdout.Close() // Set up signal handling diff --git a/scripts/dev_deploy.sh b/scripts/dev_deploy.sh index 6c8b204c0..519206ea5 100755 --- a/scripts/dev_deploy.sh +++ b/scripts/dev_deploy.sh @@ -18,6 +18,7 @@ show_help() { echo " --skip-native-build Skip native build" echo " --disable-docker Disable docker build" echo " --enable-sync-trace Enable sync trace (do not use in release builds)" + echo " --native-binary Build and deploy the native binary (FOR DEBUGGING ONLY)" echo " -i, --install Build for release and install the app" echo " --help Display this help message" echo @@ -58,6 +59,7 @@ REMOTE_PATH="/userdata/jetkvm/bin" SKIP_UI_BUILD=false SKIP_UI_BUILD_RELEASE=0 SKIP_NATIVE_BUILD=0 +BUILD_NATIVE_BINARY=false ENABLE_SYNC_TRACE=0 RESET_USB_HID_DEVICE=false LOG_TRACE_SCOPES="${LOG_TRACE_SCOPES:-jetkvm,cloud,websocket,native,jsonrpc}" @@ -113,6 +115,10 @@ while [[ $# -gt 0 ]]; do RUN_GO_TESTS=true shift ;; + --native-binary) + BUILD_NATIVE_BINARY=true + shift + ;; -i|--install) INSTALL_APP=true shift @@ -141,6 +147,9 @@ fi # Check device connectivity before proceeding check_ping "${REMOTE_HOST}" check_ssh "${REMOTE_USER}" "${REMOTE_HOST}" +function sshdev() { + ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" $@ +} # check if the current CPU architecture is x86_64 if [ "$(uname -m)" != "x86_64" ]; then @@ -152,6 +161,27 @@ if [ "$BUILD_IN_DOCKER" = true ]; then build_docker_image fi +if [ "$BUILD_NATIVE_BINARY" = true ]; then + msg_info "â–¶ Building native binary" + make build_native + sshdev "killall -9 jetkvm_app jetkvm_app_debug jetkvm_native_debug || true" + sshdev "cat > ${REMOTE_PATH}/jetkvm_native_debug" < internal/native/cgo/build/jknative-bin + sshdev ash << EOF +set -e + +# Set the library path to include the directory where librockit.so is located +export LD_LIBRARY_PATH=/oem/usr/lib:\$LD_LIBRARY_PATH + +cd ${REMOTE_PATH} +killall -9 jetkvm_app jetkvm_app_debug jetkvm_native_debug || true +sleep 5 +echo 'V' > /dev/watchdog +chmod +x jetkvm_native_debug +./jetkvm_native_debug +EOF + exit 0 +fi + # Build the development version on the host # When using `make build_release`, the frontend will be built regardless of the `SKIP_UI_BUILD` flag # check if static/index.html exists @@ -176,10 +206,10 @@ if [ "$RUN_GO_TESTS" = true ]; then make build_dev_test msg_info "â–¶ Copying device-tests.tar.gz to remote host" - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz + sshdev "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz msg_info "â–¶ Running go tests" - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" ash << 'EOF' + sshdev ash << 'EOF' set -e TMP_DIR=$(mktemp -d) cd ${TMP_DIR} @@ -222,10 +252,10 @@ then ENABLE_SYNC_TRACE=${ENABLE_SYNC_TRACE} # Copy the binary to the remote host as if we were the OTA updater. - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > /userdata/jetkvm/jetkvm_app.update" < bin/jetkvm_app + sshdev "cat > /userdata/jetkvm/jetkvm_app.update" < bin/jetkvm_app # Reboot the device, the new app will be deployed by the startup process. - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "reboot" + sshdev "reboot" else msg_info "â–¶ Building development binary" do_make build_dev \ @@ -234,21 +264,21 @@ else ENABLE_SYNC_TRACE=${ENABLE_SYNC_TRACE} # Kill any existing instances of the application - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true" + sshdev "killall jetkvm_app_debug || true" # Copy the binary to the remote host - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > ${REMOTE_PATH}/jetkvm_app_debug" < bin/jetkvm_app + sshdev "cat > ${REMOTE_PATH}/jetkvm_app_debug" < bin/jetkvm_app if [ "$RESET_USB_HID_DEVICE" = true ]; then msg_info "â–¶ Resetting USB HID device" msg_warn "The option has been deprecated and will be removed in a future version, as JetKVM will now reset USB gadget configuration when needed" # Remove the old USB gadget configuration - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "rm -rf /sys/kernel/config/usb_gadget/jetkvm/configs/c.1/hid.usb*" - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "ls /sys/class/udc > /sys/kernel/config/usb_gadget/jetkvm/UDC" + sshdev "rm -rf /sys/kernel/config/usb_gadget/jetkvm/configs/c.1/hid.usb*" + sshdev "ls /sys/class/udc > /sys/kernel/config/usb_gadget/jetkvm/UDC" fi # Deploy and run the application on the remote host - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF + sshdev ash << EOF set -e # Set the library path to include the directory where librockit.so is located From db53e89df172880df30b8d4137ccbf8b723fd4de Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 14:42:22 +0000 Subject: [PATCH 04/25] chore: add vscode configuration for c development --- .vscode/c_cpp_properties.json | 17 ++++++++++++++ .vscode/settings.json | 7 ++++-- scripts/configure_vscode.py | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100755 scripts/configure_vscode.py diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 000000000..91241db04 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/opt/jetkvm-native-buildkit/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-arm", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 41aeee583..6ac6925bf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,9 @@ ] }, "git.ignoreLimitWarning": true, - "cmake.sourceDirectory": "/workspaces/kvm-static-ip/internal/native/cgo", - "cmake.ignoreCMakeListsMissing": true + "cmake.sourceDirectory": "${workspaceFolder}/internal/native/cgo", + "cmake.ignoreCMakeListsMissing": true, + "C_Cpp.inlayHints.autoDeclarationTypes.enabled": true, + "C_Cpp.inlayHints.parameterNames.enabled": true, + "C_Cpp.inlayHints.referenceOperator.enabled": true } \ No newline at end of file diff --git a/scripts/configure_vscode.py b/scripts/configure_vscode.py new file mode 100755 index 000000000..cac54685d --- /dev/null +++ b/scripts/configure_vscode.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import json +import os + +DEFAULT_C_INTELLISENSE_SETTINGS = { + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + # "compilerPath": "/opt/jetkvm-native-buildkit/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-arm", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} + +def configure_c_intellisense(): + settings_path = os.path.join('.vscode', 'c_cpp_properties.json') + settings = DEFAULT_C_INTELLISENSE_SETTINGS.copy() + + # open existing settings if they exist + if os.path.exists(settings_path): + with open(settings_path, 'r') as f: + settings = json.load(f) + + # update compiler path + settings['configurations'][0]['compilerPath'] = "/opt/jetkvm-native-buildkit/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc" + settings['configurations'][0]['configurationProvider'] = "ms-vscode.cmake-tools" + + with open(settings_path, 'w') as f: + json.dump(settings, f, indent=4) + + print("C/C++ IntelliSense configuration updated.") + + +if __name__ == "__main__": + configure_c_intellisense() \ No newline at end of file From ac9999171b6956ffe543e814aefae44263f17d5d Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 15:37:16 +0000 Subject: [PATCH 05/25] feat: add gdbserver support for native code debugging --- .devcontainer/install-deps.sh | 1 + .vscode/launch.json | 28 ++++++++++++++++++++++++++++ .vscode/settings.json | 9 ++++++++- .vscode/tasks.json | 30 ++++++++++++++++++++++++++++++ DEVELOPMENT.md | 6 ++++++ Makefile | 3 +++ scripts/build_cgo.sh | 4 +++- scripts/dev_deploy.sh | 24 +++++++++++++++++++----- 8 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.devcontainer/install-deps.sh b/.devcontainer/install-deps.sh index e2ff43e6f..043c5c44a 100755 --- a/.devcontainer/install-deps.sh +++ b/.devcontainer/install-deps.sh @@ -18,6 +18,7 @@ sudo apt-get install -y --no-install-recommends \ build-essential \ device-tree-compiler \ gperf g++-multilib gcc-multilib \ + gdb-multiarch \ libnl-3-dev libdbus-1-dev libelf-dev libmpc-dev dwarves \ bc openssl flex bison libssl-dev python3 python-is-python3 texinfo kmod cmake \ wget zstd \ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..710996ccb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "GDB Debug - Native (binary)", + "type": "cppdbg", + "request": "launch", + "program": "internal/native/cgo/build/jknative-bin", + "args": [], + "stopAtEntry": true, + "cwd": "${workspaceFolder}", + "environment": [], + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb-multiarch", + "miDebuggerServerAddress": "${config:TARGET_IP}:${config:DEBUG_PORT}", + "targetArchitecture": "arm", + "preLaunchTask": "deploy", + "setupCommands": [ + { + "description": "Pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "externalConsole": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ac6925bf..118dd5eda 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,12 @@ "cmake.ignoreCMakeListsMissing": true, "C_Cpp.inlayHints.autoDeclarationTypes.enabled": true, "C_Cpp.inlayHints.parameterNames.enabled": true, - "C_Cpp.inlayHints.referenceOperator.enabled": true + "C_Cpp.inlayHints.referenceOperator.enabled": true, + "files.associations": { + "*.yml": "yaml", + "*.libsonet": "jsonnet", + "rk_comm_mb.h": "c" + }, + "TARGET_IP": "192.168.0.199", + "DEBUG_PORT": "2345", } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..e1f9accca --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "deploy", + "isBackground": true, + "type": "shell", + "command": "bash", + "args": [ + "dev_deploy.sh", + "-r", + "${config:TARGET_IP}", + "--gdb-port", + "${config:DEBUG_PORT}", + "--native-binary", + "--disable-docker" + ], + "problemMatcher": { + "base": "$gcc", + "background": { + "activeOnStart": true, + "beginsPattern": "${config:BINARY}", + "endsPattern": "Listening on port [0-9]{4}" + } + } + }, + ] +} diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0ab65c4f2..9d3a4b624 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -208,6 +208,12 @@ rm /userdata/kvm_config.json systemctl restart jetkvm ``` +### Debug native code with gdbserver + +Change the `TARGET_IP` in `.vscode/settings.json` to your JetKVM device IP, then set breakpoints in your native code and start the `Debug Native` configuration in VSCode. + +The code and GDB server will be deployed automatically. + --- ## Testing Your Changes diff --git a/Makefile b/Makefile index e519a75a2..525114e38 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,8 @@ SKIP_NATIVE_IF_EXISTS ?= 0 SKIP_UI_BUILD ?= 0 ENABLE_SYNC_TRACE ?= 0 +CMAKE_BUILD_TYPE ?= Release + GO_BUILD_ARGS := -tags netgo,timetzdata,nomsgpack ifeq ($(ENABLE_SYNC_TRACE), 1) GO_BUILD_ARGS := $(GO_BUILD_ARGS),synctrace @@ -52,6 +54,7 @@ build_native: echo "Building native..."; \ CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \ LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \ + CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ ./scripts/build_cgo.sh; \ fi diff --git a/scripts/build_cgo.sh b/scripts/build_cgo.sh index 87577e39f..d67fa7cda 100755 --- a/scripts/build_cgo.sh +++ b/scripts/build_cgo.sh @@ -4,6 +4,8 @@ set -e SCRIPT_PATH=$(realpath "$(dirname $(realpath "${BASH_SOURCE[0]}"))") source ${SCRIPT_PATH}/build_utils.sh +CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} + CGO_PATH=$(realpath "${SCRIPT_PATH}/../internal/native/cgo") BUILD_DIR=${CGO_PATH}/build @@ -31,7 +33,7 @@ VERBOSE=1 cmake -B "${BUILD_DIR}" \ -DCONFIG_LV_BUILD_EXAMPLES=OFF \ -DCONFIG_LV_BUILD_DEMOS=OFF \ -DSKIP_GLIBC_NAMES=ON \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ -DCMAKE_INSTALL_PREFIX="${TMP_DIR}" msg_info "â–¶ Copying built library and header files" diff --git a/scripts/dev_deploy.sh b/scripts/dev_deploy.sh index 519206ea5..96e7cf60d 100755 --- a/scripts/dev_deploy.sh +++ b/scripts/dev_deploy.sh @@ -12,6 +12,7 @@ show_help() { echo echo "Optional:" echo " -u, --user Remote username (default: root)" + echo " --gdb-port GDB debug port (default: 2345)" echo " --run-go-tests Run go tests" echo " --run-go-tests-only Run go tests and exit" echo " --skip-ui-build Skip frontend/UI build" @@ -59,6 +60,7 @@ REMOTE_PATH="/userdata/jetkvm/bin" SKIP_UI_BUILD=false SKIP_UI_BUILD_RELEASE=0 SKIP_NATIVE_BUILD=0 +GDB_DEBUG_PORT=2345 BUILD_NATIVE_BINARY=false ENABLE_SYNC_TRACE=0 RESET_USB_HID_DEVICE=false @@ -81,6 +83,10 @@ while [[ $# -gt 0 ]]; do REMOTE_USER="$2" shift 2 ;; + --gdb-port) + GDB_DEBUG_PORT="$2" + shift 2 + ;; --skip-ui-build) SKIP_UI_BUILD=true shift @@ -148,7 +154,8 @@ fi check_ping "${REMOTE_HOST}" check_ssh "${REMOTE_USER}" "${REMOTE_HOST}" function sshdev() { - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" $@ + ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "$@" + return $? } # check if the current CPU architecture is x86_64 @@ -163,10 +170,17 @@ fi if [ "$BUILD_NATIVE_BINARY" = true ]; then msg_info "â–¶ Building native binary" - make build_native - sshdev "killall -9 jetkvm_app jetkvm_app_debug jetkvm_native_debug || true" + CMAKE_BUILD_TYPE=Debug make build_native + msg_info "â–¶ Checking if GDB is available on remote host" + if ! sshdev "command -v gdbserver > /dev/null 2>&1"; then + msg_warn "Error: gdbserver is not installed on the remote host" + tar -czf - -C /opt/jetkvm-native-buildkit/gdb/ . | sshdev "tar -xzf - -C /usr/bin" + msg_info "✓ gdbserver installed on remote host" + fi + msg_info "â–¶ Stopping any existing instances of jetkvm_native_debug on remote host" + sshdev "killall -9 jetkvm_app jetkvm_app_debug jetkvm_native_debug gdbserver || true >> /dev/null 2>&1" sshdev "cat > ${REMOTE_PATH}/jetkvm_native_debug" < internal/native/cgo/build/jknative-bin - sshdev ash << EOF + sshdev -t ash << EOF set -e # Set the library path to include the directory where librockit.so is located @@ -177,7 +191,7 @@ killall -9 jetkvm_app jetkvm_app_debug jetkvm_native_debug || true sleep 5 echo 'V' > /dev/watchdog chmod +x jetkvm_native_debug -./jetkvm_native_debug +gdbserver localhost:${GDB_DEBUG_PORT} ./jetkvm_native_debug EOF exit 0 fi From a494f2f15f70d78ad3aa4bc49d65561068549115 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 15:48:05 +0000 Subject: [PATCH 06/25] feat: add env utils --- internal/utils/env.go | 92 ++++++++++++++++++++++++++++++++++++++ internal/utils/env_test.go | 57 +++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 internal/utils/env.go create mode 100644 internal/utils/env_test.go diff --git a/internal/utils/env.go b/internal/utils/env.go new file mode 100644 index 000000000..4040b961c --- /dev/null +++ b/internal/utils/env.go @@ -0,0 +1,92 @@ +package utils + +import ( + "fmt" + "reflect" + "strconv" +) + +func MarshalEnv(instance interface{}) ([]string, error) { + v := reflect.ValueOf(instance) + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil, fmt.Errorf("instance is nil") + } + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("instance must be a struct or pointer to struct") + } + + t := v.Type() + var result []string + + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + fieldValue := v.Field(i) + + // Get the env tag + envTag := field.Tag.Get("env") + if envTag == "" || envTag == "-" { + continue + } + + // Skip unexported fields + if !fieldValue.CanInterface() { + continue + } + + var valueStr string + + // Handle different types + switch fieldValue.Kind() { + case reflect.Bool: + valueStr = strconv.FormatBool(fieldValue.Bool()) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + valueStr = strconv.FormatUint(fieldValue.Uint(), 10) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + valueStr = strconv.FormatInt(fieldValue.Int(), 10) + + case reflect.Float32, reflect.Float64: + valueStr = strconv.FormatFloat(fieldValue.Float(), 'f', -1, 64) + + case reflect.String: + valueStr = fieldValue.String() + + case reflect.Ptr: + if fieldValue.IsNil() { + continue // Skip nil pointers + } + elem := fieldValue.Elem() + // Handle *semver.Version and other pointer types + if elem.CanInterface() { + if stringer, ok := elem.Interface().(fmt.Stringer); ok { + valueStr = stringer.String() + } else { + valueStr = fmt.Sprintf("%v", elem.Interface()) + } + } else { + valueStr = fmt.Sprintf("%v", elem.Interface()) + } + + default: + // For other types, try to convert to string + if fieldValue.CanInterface() { + if stringer, ok := fieldValue.Interface().(fmt.Stringer); ok { + valueStr = stringer.String() + } else { + valueStr = fmt.Sprintf("%v", fieldValue.Interface()) + } + } else { + valueStr = fmt.Sprintf("%v", fieldValue.Interface()) + } + } + + result = append(result, fmt.Sprintf("%s=%s", envTag, valueStr)) + } + + return result, nil +} diff --git a/internal/utils/env_test.go b/internal/utils/env_test.go new file mode 100644 index 000000000..25313677b --- /dev/null +++ b/internal/utils/env_test.go @@ -0,0 +1,57 @@ +package utils + +import ( + "reflect" + "testing" + + "github.com/Masterminds/semver/v3" +) + +type nativeOptions struct { + Disable bool `env:"JETKVM_NATIVE_DISABLE"` + SystemVersion *semver.Version `env:"JETKVM_NATIVE_SYSTEM_VERSION"` + AppVersion *semver.Version `env:"JETKVM_NATIVE_APP_VERSION"` + DisplayRotation uint16 `env:"JETKVM_NATIVE_DISPLAY_ROTATION"` + DefaultQualityFactor float64 `env:"JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR"` +} + +func TestMarshalEnv(t *testing.T) { + tests := []struct { + name string + instance interface{} + want []string + wantErr bool + }{ + { + name: "basic struct", + instance: nativeOptions{ + Disable: false, + SystemVersion: semver.MustParse("1.1.0"), + AppVersion: semver.MustParse("1111.0.0"), + DisplayRotation: 1, + DefaultQualityFactor: 1.0, + }, + want: []string{ + "JETKVM_NATIVE_DISABLE=false", + "JETKVM_NATIVE_SYSTEM_VERSION=1.1.0", + "JETKVM_NATIVE_APP_VERSION=1111.0.0", + "JETKVM_NATIVE_DISPLAY_ROTATION=1", + "JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR=1", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MarshalEnv(tt.instance) + if (err != nil) != tt.wantErr { + t.Errorf("MarshalEnv() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalEnv() = %v, want %v", got, tt.want) + } + }) + } +} From d6c97d17ef8fa627756bee0b54f6ae31afb5f109 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Wed, 12 Nov 2025 15:56:48 +0000 Subject: [PATCH 07/25] fix: event handler --- internal/native/grpc_client.go | 84 +++++++++++++++++----------------- internal/native/grpc_server.go | 17 ------- internal/native/log.go | 5 +- internal/native/native.go | 7 ++- internal/native/proxy.go | 2 +- 5 files changed, 50 insertions(+), 65 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index ffb6e5b73..863459353 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "sync" "time" @@ -60,52 +61,53 @@ func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, erro } func (c *GRPCClient) startEventStream() { - // for { - // return - // c.closeM.Lock() - // if c.closed { - // c.closeM.Unlock() - // return - // } - // c.closeM.Unlock() - - // ctx := context.Background() - // stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) - // if err != nil { - // c.logger.Warn().Err(err).Msg("failed to start event stream, retrying...") - // time.Sleep(1 * time.Second) - // continue - // } + for { + c.closeM.Lock() + if c.closed { + c.closeM.Unlock() + return + } + c.closeM.Unlock() + + ctx := context.Background() + stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) + if err != nil { + c.logger.Warn().Err(err).Msg("failed to start event stream, retrying...") + time.Sleep(1 * time.Second) + continue + } - // c.eventM.Lock() - // c.eventStream = stream - // c.eventM.Unlock() + c.eventM.Lock() + c.eventStream = stream + c.eventM.Unlock() - // for { - // event, err := stream.Recv() - // if err == io.EOF { - // c.logger.Debug().Msg("event stream closed") - // break - // } - // if err != nil { - // c.logger.Warn().Err(err).Msg("event stream error") - // break - // } + for { + event, err := stream.Recv() + if err == io.EOF { + c.logger.Debug().Msg("event stream closed") + break + } + if err != nil { + c.logger.Warn().Err(err).Msg("event stream error") + break + } - // select { - // case c.eventCh <- event: - // default: - // c.logger.Warn().Msg("event channel full, dropping event") - // } - // } + c.logger.Info().Str("type", event.Type).Msg("received event") - // c.eventM.Lock() - // c.eventStream = nil - // c.eventM.Unlock() + select { + case c.eventCh <- event: + default: + c.logger.Warn().Msg("event channel full, dropping event") + } + } - // // Wait before retrying - // time.Sleep(1 * time.Second) - // } + c.eventM.Lock() + c.eventStream = nil + c.eventM.Unlock() + + // Wait before retrying + time.Sleep(1 * time.Second) + } } func (c *GRPCClient) checkIsReady(ctx context.Context) error { diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go index e00a39088..bfa2fc254 100644 --- a/internal/native/grpc_server.go +++ b/internal/native/grpc_server.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "sync" - "time" "github.com/rs/zerolog" "google.golang.org/grpc" @@ -36,7 +35,6 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { originalVideoStateChange := n.onVideoStateChange originalIndevEvent := n.onIndevEvent originalRpcEvent := n.onRpcEvent - originalVideoFrameReceived := n.onVideoFrameReceived // Wrap callbacks to both call original and broadcast events n.onVideoStateChange = func(state VideoState) { @@ -82,21 +80,6 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer { }) } - n.onVideoFrameReceived = func(frame []byte, duration time.Duration) { - if originalVideoFrameReceived != nil { - originalVideoFrameReceived(frame, duration) - } - s.broadcastEvent(&pb.Event{ - Type: "video_frame", - Data: &pb.Event_VideoFrame{ - VideoFrame: &pb.VideoFrame{ - Frame: frame, - DurationNs: duration.Nanoseconds(), - }, - }, - }) - } - return s } diff --git a/internal/native/log.go b/internal/native/log.go index d74b81a26..41ae4df9f 100644 --- a/internal/native/log.go +++ b/internal/native/log.go @@ -1,14 +1,11 @@ package native import ( - "os" - "github.com/jetkvm/kvm/internal/logging" "github.com/rs/zerolog" ) -var nativeL = logging.GetSubsystemLogger("native").With().Int("pid", os.Getpid()).Logger() -var nativeLogger = &nativeL +var nativeLogger = logging.GetSubsystemLogger("native") var displayLogger = logging.GetSubsystemLogger("display") type nativeLogMessage struct { diff --git a/internal/native/native.go b/internal/native/native.go index f60573d6b..5d126f32e 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -40,6 +40,9 @@ type NativeOptions struct { } func NewNative(opts NativeOptions) *Native { + nativeSubLogger := nativeLogger.With().Str("scope", "native").Logger() + displaySubLogger := displayLogger.With().Str("scope", "native").Logger() + onVideoStateChange := opts.OnVideoStateChange if onVideoStateChange == nil { onVideoStateChange = func(state VideoState) { @@ -78,8 +81,8 @@ func NewNative(opts NativeOptions) *Native { return &Native{ disable: opts.Disable, ready: make(chan struct{}), - l: nativeLogger, - lD: displayLogger, + l: &nativeSubLogger, + lD: &displaySubLogger, systemVersion: opts.SystemVersion, appVersion: opts.AppVersion, displayRotation: opts.DisplayRotation, diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 60e3bea2a..e31a53c0d 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -162,7 +162,7 @@ func (p *NativeProxy) spawnProcess() (*cmdWrapper, error) { p.binaryPath, "-subcomponent=native", ) - cmd.Stdout = os.Stdout // Forward stdout to parent + // cmd.Stdout = os.Stdout // Forward stdout to parent cmd.Stderr = os.Stderr // Forward stderr to parent // Set environment variable to indicate native process mode cmd.Env = append( From 91d3b47ec3f47ca2cde3ca930f51a59e1a3557e2 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 12:18:49 +0000 Subject: [PATCH 08/25] clean up code --- cmd/main.go | 16 +- internal/native/grpc_client.go | 195 ++++++++++++++--------- internal/native/grpc_server.go | 4 +- internal/native/interface.go | 1 - internal/native/proxy.go | 276 +++++++++++++++++---------------- internal/native/server.go | 15 +- main.go | 23 +++ native.go | 2 +- 8 files changed, 307 insertions(+), 225 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 479627e0a..fcf2cdfee 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -28,14 +28,20 @@ func program() { } switch subcomponent { case "native": - gspt.SetProcTitle(os.Args[0] + " [native]") native.RunNativeProcess(os.Args[0]) default: - gspt.SetProcTitle(os.Args[0] + " [app]") kvm.Main() } } +func setProcTitle(status string) { + if status != "" { + status = " " + status + } + title := fmt.Sprintf("jetkvm: [supervisor]%s", status) + gspt.SetProcTitle(title) +} + func main() { versionPtr := flag.Bool("version", false, "print version and exit") versionJSONPtr := flag.Bool("version-json", false, "print version as json and exit") @@ -65,6 +71,8 @@ func main() { } func supervise() error { + setProcTitle("") + // check binary path binPath, err := os.Executable() if err != nil { @@ -109,6 +117,8 @@ func supervise() error { return fmt.Errorf("failed to start command: %w", startErr) } + setProcTitle(fmt.Sprintf("started (pid=%d)", cmd.Process.Pid)) + go func() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) @@ -117,8 +127,6 @@ func supervise() error { _ = cmd.Process.Signal(sig) }() - gspt.SetProcTitle(os.Args[0] + " [sup]") - cmdErr := cmd.Wait() if cmdErr == nil { return nil diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 863459353..fec49ecb1 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -29,15 +29,27 @@ type GRPCClient struct { eventCh chan *pb.Event eventDone chan struct{} + onVideoStateChange func(state VideoState) + onIndevEvent func(event string) + onRpcEvent func(event string) + closed bool closeM sync.Mutex } +type grpcClientOptions struct { + SocketPath string + Logger *zerolog.Logger + OnVideoStateChange func(state VideoState) + OnIndevEvent func(event string) + OnRpcEvent func(event string) +} + // NewGRPCClient creates a new gRPC client connected to the native service -func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, error) { +func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) { // Connect to the Unix domain socket conn, err := grpc.NewClient( - fmt.Sprintf("unix-abstract:%v", socketPath), + fmt.Sprintf("unix-abstract:%v", opts.SocketPath), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { @@ -47,11 +59,14 @@ func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, erro client := pb.NewNativeServiceClient(conn) grpcClient := &GRPCClient{ - conn: conn, - client: client, - logger: logger, - eventCh: make(chan *pb.Event, 100), - eventDone: make(chan struct{}), + conn: conn, + client: client, + logger: opts.Logger, + eventCh: make(chan *pb.Event, 100), + eventDone: make(chan struct{}), + onVideoStateChange: opts.OnVideoStateChange, + onIndevEvent: opts.OnIndevEvent, + onRpcEvent: opts.OnRpcEvent, } // Start event stream @@ -60,8 +75,51 @@ func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, erro return grpcClient, nil } +func (c *GRPCClient) setStream(stream pb.NativeService_StreamEventsClient) { + c.eventM.Lock() + defer c.eventM.Unlock() + c.eventStream = stream +} + +func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClient) { + logger := *c.logger + for { + if stream == nil { + logger.Error().Msg("event stream is nil") + break + } + + event, err := stream.Recv() + // enrich the logger with the event type and data, if debug mode is enabled + if c.logger.GetLevel() <= zerolog.DebugLevel { + logger = logger.With(). + Str("type", event.Type). + Interface("data", event.Data). + Logger() + } + + if err != nil { + if errors.Is(err, io.EOF) { + logger.Debug().Msg("event stream closed") + } else { + logger.Warn().Err(err).Msg("event stream error") + } + break + } + + logger.Trace().Msg("received event") + + select { + case c.eventCh <- event: + default: + logger.Warn().Msg("event channel full, dropping event") + } + } +} + func (c *GRPCClient) startEventStream() { for { + // check if the client is closed c.closeM.Lock() if c.closed { c.closeM.Unlock() @@ -72,38 +130,14 @@ func (c *GRPCClient) startEventStream() { ctx := context.Background() stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) if err != nil { - c.logger.Warn().Err(err).Msg("failed to start event stream, retrying...") + c.logger.Warn().Err(err).Msg("failed to start event stream, retrying ...") time.Sleep(1 * time.Second) continue } - c.eventM.Lock() - c.eventStream = stream - c.eventM.Unlock() - - for { - event, err := stream.Recv() - if err == io.EOF { - c.logger.Debug().Msg("event stream closed") - break - } - if err != nil { - c.logger.Warn().Err(err).Msg("event stream error") - break - } - - c.logger.Info().Str("type", event.Type).Msg("received event") - - select { - case c.eventCh <- event: - default: - c.logger.Warn().Msg("event channel full, dropping event") - } - } - - c.eventM.Lock() - c.eventStream = nil - c.eventM.Unlock() + c.setStream(stream) + c.handleEventStream(stream) + c.setStream(nil) // Wait before retrying time.Sleep(1 * time.Second) @@ -111,7 +145,8 @@ func (c *GRPCClient) startEventStream() { } func (c *GRPCClient) checkIsReady(ctx context.Context) error { - c.logger.Info().Msg("connection is idle, connecting...") + c.logger.Trace().Msg("connection is idle, connecting ...") + resp, err := c.client.IsReady(ctx, &pb.IsReadyRequest{}) if err != nil { if errors.Is(err, status.Error(codes.Unavailable, "")) { @@ -130,8 +165,16 @@ func (c *GRPCClient) WaitReady() error { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() + prevState := connectivity.Idle for { state := c.conn.GetState() + c.logger. + With(). + Str("state", state.String()). + Int("prev_state", int(prevState)). + Logger() + + prevState = state if state == connectivity.Idle || state == connectivity.Ready { if err := c.checkIsReady(ctx); err != nil { time.Sleep(1 * time.Second) @@ -139,7 +182,8 @@ func (c *GRPCClient) WaitReady() error { } } - c.logger.Info().Str("state", state.String()).Msg("waiting for connection to be ready") + c.logger.Info().Msg("waiting for connection to be ready") + if state == connectivity.Ready { return nil } @@ -153,47 +197,42 @@ func (c *GRPCClient) WaitReady() error { } } +func (c *GRPCClient) handleEvent(event *pb.Event) { + switch event.Type { + case "video_state_change": + state := event.GetVideoState() + if state == nil { + c.logger.Warn().Msg("video state event is nil") + return + } + c.onVideoStateChange(VideoState{ + Ready: state.Ready, + Error: state.Error, + Width: int(state.Width), + Height: int(state.Height), + FramePerSecond: state.FramePerSecond, + }) + case "indev_event": + c.onIndevEvent(event.GetIndevEvent()) + case "rpc_event": + c.onRpcEvent(event.GetRpcEvent()) + default: + c.logger.Warn().Str("type", event.Type).Msg("unknown event type") + } +} + // OnEvent registers an event handler func (c *GRPCClient) OnEvent(eventType string, handler func(data interface{})) { - return - // go func() { - // for { - // select { - // case event := <-c.eventCh: - // if event.Type == eventType { - // var data interface{} - // switch eventType { - // case "video_state_change": - // if event.VideoState != nil { - // data = VideoState{ - // Ready: event.VideoState.Ready, - // Error: event.VideoState.Error, - // Width: int(event.VideoState.Width), - // Height: int(event.VideoState.Height), - // FramePerSecond: event.VideoState.FramePerSecond, - // } - // } - // case "indev_event": - // data = event.IndevEvent - // case "rpc_event": - // data = event.RpcEvent - // case "video_frame": - // if event.VideoFrame != nil { - // data = map[string]interface{}{ - // "frame": event.VideoFrame.Frame, - // "duration": time.Duration(event.VideoFrame.DurationNs), - // } - // } - // } - // if data != nil { - // handler(data) - // } - // } - // case <-c.eventDone: - // return - // } - // } - // }() + go func() { + for { + select { + case event := <-c.eventCh: + c.handleEvent(event) + case <-c.eventDone: + return + } + } + }() } // Close closes the gRPC client @@ -209,7 +248,9 @@ func (c *GRPCClient) Close() error { c.eventM.Lock() if c.eventStream != nil { - c.eventStream.CloseSend() + if err := c.eventStream.CloseSend(); err != nil { + c.logger.Warn().Err(err).Msg("failed to close event stream") + } } c.eventM.Unlock() diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go index bfa2fc254..4e162833f 100644 --- a/internal/native/grpc_server.go +++ b/internal/native/grpc_server.go @@ -6,6 +6,7 @@ import ( "net" "sync" + "github.com/erikdubbelboer/gspt" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -314,6 +315,8 @@ func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req // StreamEvents streams events from the native process func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error { + gspt.SetProcTitle("jetkvm: [native] connected") + eventCh := make(chan *pb.Event, 100) // Register this channel for events @@ -349,7 +352,6 @@ func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamE // StartGRPCServer starts the gRPC server on a Unix domain socket func StartGRPCServer(server *grpcServer, socketPath string, logger *zerolog.Logger) (*grpc.Server, net.Listener, error) { - lis, err := net.Listen("unix", socketPath) if err != nil { return nil, nil, fmt.Errorf("failed to listen on socket: %w", err) diff --git a/internal/native/interface.go b/internal/native/interface.go index be4cb960d..9974399df 100644 --- a/internal/native/interface.go +++ b/internal/native/interface.go @@ -34,4 +34,3 @@ type NativeInterface interface { SwitchToScreenIfDifferent(screenName string) DoNotUseThisIsForCrashTestingOnly() } - diff --git a/internal/native/proxy.go b/internal/native/proxy.go index e31a53c0d..51644eb97 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -1,10 +1,14 @@ package native import ( + "crypto/rand" + "encoding/hex" "fmt" "net" "os" "os/exec" + "runtime" + "strings" "sync" "syscall" "time" @@ -36,7 +40,19 @@ type nativeProxyOptions struct { OnVideoStateChange func(state VideoState) } +func randomId(binaryLength int) string { + s := make([]byte, binaryLength) + _, err := rand.Read(s) + if err != nil { + nativeLogger.Error().Err(err).Msg("failed to generate random ID") + return strings.Repeat("0", binaryLength*2) // return all zeros if error + } + return hex.EncodeToString(s) +} + func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { + // random 16 bytes hex string + handshakeMessage := randomId(16) return &nativeProxyOptions{ Disable: n.Disable, SystemVersion: n.SystemVersion, @@ -47,6 +63,7 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { OnIndevEvent: n.OnIndevEvent, OnRpcEvent: n.OnRpcEvent, OnVideoStateChange: n.OnVideoStateChange, + HandshakeMessage: handshakeMessage, } } @@ -63,13 +80,14 @@ func (p *nativeProxyOptions) toNativeOptions() *NativeOptions { // cmdWrapper wraps exec.Cmd to implement processCmd interface type cmdWrapper struct { *exec.Cmd + stdoutHandler *nativeProxyStdoutHandler } func (c *cmdWrapper) GetProcess() interface { Kill() error Signal(sig interface{}) error } { - return &processWrapper{Process: c.Cmd.Process} + return &processWrapper{Process: c.Process} } type processWrapper struct { @@ -107,8 +125,8 @@ type NativeProxy struct { // NewNativeProxy creates a new NativeProxy that spawns a separate process func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { proxyOptions := opts.toProxyOptions() - proxyOptions.CtrlUnixSocket = "jetkvm-native-grpc" - proxyOptions.VideoStreamUnixSocket = "@jetkvm-native-video-stream" + proxyOptions.CtrlUnixSocket = fmt.Sprintf("jetkvm/native/grpc/%s", randomId(4)) + proxyOptions.VideoStreamUnixSocket = fmt.Sprintf("@jetkvm/native/video-stream/%s", randomId(4)) // Get the current executable path to spawn itself exePath, err := os.Executable() @@ -125,54 +143,95 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { options: proxyOptions, processWait: make(chan error, 1), } - proxy.cmd, err = proxy.spawnProcess() - nativeLogger.Info().Msg("spawned process") + proxy.cmd, err = proxy.toProcessCommand() if err != nil { - return nil, fmt.Errorf("failed to spawn process: %w", err) + return nil, fmt.Errorf("failed to create process: %w", err) + } + + return proxy, nil +} + +func (p *NativeProxy) startVideoStreamListener() error { + if p.videoStreamListener != nil { + return nil } - // create unix packet - listener, err := net.Listen("unixpacket", proxyOptions.VideoStreamUnixSocket) + logger := p.logger.With().Str("socketPath", p.videoStreamUnixSocket).Logger() + listener, err := net.Listen("unixpacket", p.videoStreamUnixSocket) if err != nil { - nativeLogger.Warn().Err(err).Msg("failed to start server") - return nil, fmt.Errorf("failed to start server: %w", err) + logger.Warn().Err(err).Msg("failed to start video stream listener") + return fmt.Errorf("failed to start video stream listener: %w", err) } + logger.Info().Msg("video stream listener started") + p.videoStreamListener = listener + go func() { for { conn, err := listener.Accept() if err != nil { - nativeLogger.Warn().Err(err).Msg("failed to accept socket") + logger.Warn().Err(err).Msg("failed to accept socket") continue } - nativeLogger.Info().Str("socket", conn.RemoteAddr().String()).Msg("accepted socket") - go proxy.handleVideoFrame(conn) + + logger.Info().Msg("video stream socket accepted") + go p.handleVideoFrame(conn) } }() - return proxy, nil + return nil +} + +type nativeProxyStdoutHandler struct { + mu *sync.Mutex + handshakeCh chan bool + handshakeMessage string + handshakeDone bool } -func (p *NativeProxy) spawnProcess() (*cmdWrapper, error) { +func (w *nativeProxyStdoutHandler) Write(p []byte) (n int, err error) { + w.mu.Lock() + defer w.mu.Unlock() + + if !w.handshakeDone && strings.Contains(string(p), w.handshakeMessage) { + w.handshakeDone = true + w.handshakeCh <- true + } + + os.Stdout.Write(p) + + return len(p), nil +} + +func (p *NativeProxy) toProcessCommand() (*cmdWrapper, error) { envArgs, err := utils.MarshalEnv(p.options) if err != nil { return nil, fmt.Errorf("failed to marshal environment variables: %w", err) } - cmd := exec.Command( - p.binaryPath, - "-subcomponent=native", - ) - // cmd.Stdout = os.Stdout // Forward stdout to parent - cmd.Stderr = os.Stderr // Forward stderr to parent + cmd := &cmdWrapper{ + Cmd: exec.Command( + p.binaryPath, + "-subcomponent=native", + ), + stdoutHandler: &nativeProxyStdoutHandler{ + mu: &sync.Mutex{}, + handshakeCh: make(chan bool), + handshakeMessage: p.options.HandshakeMessage, + }, + } + cmd.Stdout = cmd.stdoutHandler + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Pdeathsig: syscall.SIGTERM, + } // Set environment variable to indicate native process mode cmd.Env = append( os.Environ(), envArgs..., ) - // Wrap cmd to implement processCmd interface - wrappedCmd := &cmdWrapper{Cmd: cmd} - return wrappedCmd, nil + return cmd, nil } func (p *NativeProxy) handleVideoFrame(conn net.Conn) { @@ -184,7 +243,7 @@ func (p *NativeProxy) handleVideoFrame(conn net.Conn) { for { n, err := conn.Read(inboundPacket) if err != nil { - nativeLogger.Warn().Err(err).Msg("failed to accept socket") + p.logger.Warn().Err(err).Msg("failed to read video frame from socket") break } now := time.Now() @@ -194,25 +253,27 @@ func (p *NativeProxy) handleVideoFrame(conn net.Conn) { } } -// Start starts the native process -func (p *NativeProxy) Start() error { - p.restartM.Lock() - defer p.restartM.Unlock() - - if p.stopped { - return fmt.Errorf("proxy is stopped") - } - - if err := p.cmd.Start(); err != nil { - return fmt.Errorf("failed to start native process: %w", err) +func (p *NativeProxy) setUpGRPCClient() error { + // wait until handshake completed + select { + case <-p.cmd.stdoutHandler.handshakeCh: + p.logger.Info().Msg("handshake completed") + case <-time.After(10 * time.Second): + return fmt.Errorf("handshake not completed within 10 seconds") } - nativeLogger.Info().Msg("process ready") + logger := p.logger.With().Str("socketPath", "@"+p.nativeUnixSocket).Logger() + client, err := NewGRPCClient(grpcClientOptions{ + SocketPath: p.nativeUnixSocket, + Logger: &logger, + OnIndevEvent: p.options.OnIndevEvent, + OnRpcEvent: p.options.OnRpcEvent, + OnVideoStateChange: p.options.OnVideoStateChange, + }) - client, err := NewGRPCClient(p.nativeUnixSocket, nativeLogger) - nativeLogger.Info().Str("socket_path", p.nativeUnixSocket).Msg("created client") + logger.Info().Msg("created gRPC client") if err != nil { - return fmt.Errorf("failed to create IPC client: %w", err) + return fmt.Errorf("failed to create gRPC client: %w", err) } p.client = client @@ -226,11 +287,46 @@ func (p *NativeProxy) Start() error { return fmt.Errorf("failed to wait for ready: %w", err) } - // Set up event handlers - p.setupEventHandlers(client) - // Start monitoring process for crashes go p.monitorProcess() + return nil +} + +func (p *NativeProxy) start() error { + // lock OS thread to prevent the process from being moved to a different thread + // see also https://go.dev/issue/27505 + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := p.cmd.Start(); err != nil { + return fmt.Errorf("failed to start native process: %w", err) + } + + p.logger.Info().Int("pid", p.cmd.Process.Pid).Msg("native process started") + + if err := p.setUpGRPCClient(); err != nil { + return fmt.Errorf("failed to set up gRPC client: %w", err) + } + + return nil +} + +// Start starts the native process +func (p *NativeProxy) Start() error { + p.restartM.Lock() + defer p.restartM.Unlock() + + if p.stopped { + return fmt.Errorf("proxy is stopped") + } + + if err := p.startVideoStreamListener(); err != nil { + return fmt.Errorf("failed to start video stream listener: %w", err) + } + + if err := p.start(); err != nil { + return fmt.Errorf("failed to start native process: %w", err) + } close(p.ready) return nil @@ -289,111 +385,19 @@ func (p *NativeProxy) restartProcess() error { return fmt.Errorf("proxy is stopped") } - wrappedCmd, err := p.spawnProcess() - if err != nil { - return fmt.Errorf("failed to spawn process: %w", err) - } - // Close old client if p.client != nil { _ = p.client.Close() } - // Create new client - client, err := NewGRPCClient(p.nativeUnixSocket, p.logger) - if err != nil { - return fmt.Errorf("failed to create IPC client: %w", err) - } - - // Set up event handlers again - p.setupEventHandlers(client) - - // Start the process - if err := wrappedCmd.Start(); err != nil { + if err := p.start(); err != nil { return fmt.Errorf("failed to start native process: %w", err) } - // Wait for ready - if err := client.WaitReady(); err != nil { - if wrappedCmd.Process != nil { - _ = wrappedCmd.Process.Kill() - _ = wrappedCmd.Wait() - } - return fmt.Errorf("timeout waiting for ready: %w", err) - } - - p.cmd = wrappedCmd - p.client = client - p.logger.Info().Msg("native process restarted successfully") return nil } -func (p *NativeProxy) setupEventHandlers(client *GRPCClient) { - // if p.opts.OnVideoStateChange != nil { - // client.OnEvent("video_state_change", func(data interface{}) { - // dataBytes, err := json.Marshal(data) - // if err != nil { - // p.logger.Warn().Err(err).Msg("failed to marshal video state event") - // return - // } - // var state VideoState - // if err := json.Unmarshal(dataBytes, &state); err != nil { - // p.logger.Warn().Err(err).Msg("failed to unmarshal video state event") - // return - // } - // p.opts.OnVideoStateChange(state) - // }) - // } - - // if p.opts.OnIndevEvent != nil { - // client.OnEvent("indev_event", func(data interface{}) { - // if event, ok := data.(string); ok { - // p.opts.OnIndevEvent(event) - // } - // }) - // } - - // if p.opts.OnRpcEvent != nil { - // client.OnEvent("rpc_event", func(data interface{}) { - // if event, ok := data.(string); ok { - // p.opts.OnRpcEvent(event) - // } - // }) - // } - - // if p.opts.OnVideoFrameReceived != nil { - // client.OnEvent("video_frame", func(data interface{}) { - // dataMap, ok := data.(map[string]interface{}) - // if !ok { - // p.logger.Warn().Msg("invalid video frame event data") - // return - // } - - // frameData, ok := dataMap["frame"].([]interface{}) - // if !ok { - // p.logger.Warn().Msg("invalid frame data in event") - // return - // } - - // frame := make([]byte, len(frameData)) - // for i, v := range frameData { - // if b, ok := v.(float64); ok { - // frame[i] = byte(b) - // } - // } - - // durationNs, ok := dataMap["duration"].(float64) - // if !ok { - // p.logger.Warn().Msg("invalid duration in event") - // return - // } - - // p.opts.OnVideoFrameReceived(frame, time.Duration(durationNs)) - // }) - // } -} - // Stop stops the native process func (p *NativeProxy) Stop() error { p.restartM.Lock() diff --git a/internal/native/server.go b/internal/native/server.go index c278b9cab..3f6266c23 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -21,7 +21,7 @@ func RunNativeProcess(binaryName string) { logger := *nativeLogger // Initialize logger - gspt.SetProcTitle(binaryName + " [native]") + gspt.SetProcTitle("jetkvm: [native] starting") var proxyOptions nativeProxyOptions if err := env.Parse(&proxyOptions); err != nil { @@ -45,12 +45,14 @@ func RunNativeProcess(binaryName string) { // Create native instance nativeInstance := NewNative(*nativeOptions) + gspt.SetProcTitle("jetkvm: [native] initializing") // Start native instance if err := nativeInstance.Start(); err != nil { logger.Fatal().Err(err).Msg("failed to start native instance") } + gspt.SetProcTitle("jetkvm: [native] starting gRPC server") // Create gRPC server grpcServer := NewGRPCServer(nativeInstance, &logger) @@ -60,11 +62,14 @@ func RunNativeProcess(binaryName string) { if err != nil { logger.Fatal().Err(err).Msg("failed to start gRPC server") } - gspt.SetProcTitle(binaryName + " [native] ready") + gspt.SetProcTitle("jetkvm: [native] ready") - // Signal that we're ready by writing socket path to stdout (for parent to read) - fmt.Fprintf(os.Stdout, "%s\n", proxyOptions.CtrlUnixSocket) - defer os.Stdout.Close() + // Signal that we're ready by writing handshake message to stdout (for parent to read) + // Stdout.Write is used to avoid buffering the message + _, err = os.Stdout.Write([]byte(proxyOptions.HandshakeMessage + "\n")) + if err != nil { + logger.Fatal().Err(err).Msg("failed to write handshake message to stdout") + } // Set up signal handling sigChan := make(chan os.Signal, 1) diff --git a/main.go b/main.go index 08df3f4ad..06b93ef99 100644 --- a/main.go +++ b/main.go @@ -2,22 +2,36 @@ package kvm import ( "context" + "fmt" "net/http" "os" "os/signal" "syscall" "time" + "github.com/erikdubbelboer/gspt" "github.com/gwatts/rootcerts" ) var appCtx context.Context +var procPrefix string = "jetkvm: [app]" + +func setProcTitle(status string) { + if status != "" { + status = " " + status + } + title := fmt.Sprintf("%s%s", procPrefix, status) + gspt.SetProcTitle(title) +} func Main() { + setProcTitle("starting") + logger.Log().Msg("JetKVM Starting Up") checkFailsafeReason() if failsafeModeActive { + procPrefix = "jetkvm: [app+failsafe]" logger.Warn().Str("reason", failsafeModeReason).Msg("failsafe mode activated") } @@ -40,6 +54,7 @@ func Main() { go runWatchdog() go confirmCurrentSystem() + setProcTitle("initNative") initNative(systemVersionLocal, appVersionLocal) initDisplay() @@ -54,6 +69,7 @@ func Main() { Msg("loaded Root CA certificates") // Initialize network + setProcTitle("initNetwork") if err := initNetwork(); err != nil { logger.Error().Err(err).Msg("failed to initialize network") // TODO: reset config to default @@ -61,17 +77,21 @@ func Main() { } // Initialize time sync + setProcTitle("initTimeSync") initTimeSync() timeSync.Start() // Initialize mDNS + setProcTitle("initMdns") if err := initMdns(); err != nil { logger.Error().Err(err).Msg("failed to initialize mDNS") } + setProcTitle("initPrometheus") initPrometheus() // initialize usb gadget + setProcTitle("initUsbGadget") initUsbGadget() if err := setInitialVirtualMediaState(); err != nil { logger.Warn().Err(err).Msg("failed to set initial virtual media state") @@ -135,6 +155,9 @@ func Main() { initPublicIPState() initSerialPort() + + setProcTitle("ready") + sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs diff --git a/native.go b/native.go index ad7f4bde4..77ba4a8fb 100644 --- a/native.go +++ b/native.go @@ -16,7 +16,7 @@ var ( ) func initNative(systemVersion *semver.Version, appVersion *semver.Version) { - nativeLogger.Info().Msg("initializing native") + nativeLogger.Info().Msg("initializing native proxy") var err error nativeInstance, err = native.NewNativeProxy(native.NativeOptions{ Disable: failsafeModeActive, From afd8ab75bc75329cadfd9f61ae394d922d727bfe Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 14:24:52 +0000 Subject: [PATCH 09/25] reconfigure display on native restart --- display.go | 8 +++++++ internal/native/grpc_server.go | 10 +++++++-- internal/native/native.go | 1 + internal/native/proxy.go | 24 ++++++++++++++++---- internal/native/server.go | 40 ++++++++++++++++++++++++---------- native.go | 3 +++ 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/display.go b/display.go index 68723b598..bf802e05d 100644 --- a/display.go +++ b/display.go @@ -232,6 +232,14 @@ func updateStaticContents() { // nativeInstance.UpdateLabelAndChangeVisibility("boot_screen_device_id", GetDeviceID()) } +// configureDisplayOnNativeRestart is called when the native process restarts +// it ensures the display is configured correctly after the restart +func configureDisplayOnNativeRestart() { + displayLogger.Info().Msg("native restarted, configuring display") + updateStaticContents() + requestDisplayUpdate(true, "native_restart") +} + // setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter // the backlight brightness of the JetKVM hardware's display. func setDisplayBrightness(brightness int, reason string) error { diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go index 4e162833f..f1a1e3019 100644 --- a/internal/native/grpc_server.go +++ b/internal/native/grpc_server.go @@ -6,7 +6,6 @@ import ( "net" "sync" - "github.com/erikdubbelboer/gspt" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -160,6 +159,9 @@ func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.Vid } func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + procPrefix = "jetkvm: [native]" + setProcTitle(lastProcTitle) + if err := s.native.VideoStop(); err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -167,6 +169,9 @@ func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, e } func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + procPrefix = "jetkvm: [native+video]" + setProcTitle(lastProcTitle) + if err := s.native.VideoStart(); err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -315,7 +320,8 @@ func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req // StreamEvents streams events from the native process func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error { - gspt.SetProcTitle("jetkvm: [native] connected") + setProcTitle("connected") + defer setProcTitle("waiting") eventCh := make(chan *pb.Event, 100) diff --git a/internal/native/native.go b/internal/native/native.go index 5d126f32e..131283a4b 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -37,6 +37,7 @@ type NativeOptions struct { OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) OnRpcEvent func(event string) + OnNativeRestart func() } func NewNative(opts NativeOptions) *Native { diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 51644eb97..d4433b266 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -33,11 +33,13 @@ type nativeProxyOptions struct { BinaryPath string `env:"JETKVM_NATIVE_BINARY_PATH"` LoggerLevel zerolog.Level `env:"JETKVM_NATIVE_LOGGER_LEVEL"` HandshakeMessage string `env:"JETKVM_NATIVE_HANDSHAKE_MESSAGE"` + MaxRestartAttempts uint OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) OnRpcEvent func(event string) OnVideoStateChange func(state VideoState) + OnNativeRestart func() } func randomId(binaryLength int) string { @@ -63,6 +65,7 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { OnIndevEvent: n.OnIndevEvent, OnRpcEvent: n.OnRpcEvent, OnVideoStateChange: n.OnVideoStateChange, + OnNativeRestart: n.OnNativeRestart, HandshakeMessage: handshakeMessage, } } @@ -118,6 +121,7 @@ type NativeProxy struct { ready chan struct{} options *nativeProxyOptions restartM sync.Mutex + restarts uint stopped bool processWait chan error } @@ -142,10 +146,7 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { ready: make(chan struct{}), options: proxyOptions, processWait: make(chan error, 1), - } - proxy.cmd, err = proxy.toProcessCommand() - if err != nil { - return nil, fmt.Errorf("failed to create process: %w", err) + restarts: 0, } return proxy, nil @@ -195,6 +196,7 @@ func (w *nativeProxyStdoutHandler) Write(p []byte) (n int, err error) { if !w.handshakeDone && strings.Contains(string(p), w.handshakeMessage) { w.handshakeDone = true w.handshakeCh <- true + return len(p), nil } os.Stdout.Write(p) @@ -287,6 +289,11 @@ func (p *NativeProxy) setUpGRPCClient() error { return fmt.Errorf("failed to wait for ready: %w", err) } + // Call on native restart callback if it exists and restarts are greater than 0 + if p.options.OnNativeRestart != nil && p.restarts > 0 { + p.options.OnNativeRestart() + } + // Start monitoring process for crashes go p.monitorProcess() return nil @@ -298,6 +305,13 @@ func (p *NativeProxy) start() error { runtime.LockOSThread() defer runtime.UnlockOSThread() + cmd, err := p.toProcessCommand() + if err != nil { + return fmt.Errorf("failed to create process: %w", err) + } + + p.cmd = cmd + if err := p.cmd.Start(); err != nil { return fmt.Errorf("failed to start native process: %w", err) } @@ -390,6 +404,8 @@ func (p *NativeProxy) restartProcess() error { _ = p.client.Close() } + p.restarts++ + if err := p.start(); err != nil { return fmt.Errorf("failed to start native process: %w", err) } diff --git a/internal/native/server.go b/internal/native/server.go index 3f6266c23..dd660e733 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -16,24 +16,37 @@ import ( // stdout - exchange messages with the parent process // stderr - logging and error messages +var ( + procPrefix string = "jetkvm: [native]" + lastProcTitle string +) + +func setProcTitle(status string) { + lastProcTitle = status + if status != "" { + status = " " + status + } + title := fmt.Sprintf("%s%s", procPrefix, status) + gspt.SetProcTitle(title) +} + // RunNativeProcess runs the native process mode func RunNativeProcess(binaryName string) { - logger := *nativeLogger - // Initialize logger - - gspt.SetProcTitle("jetkvm: [native] starting") + logger := nativeLogger.With().Int("pid", os.Getpid()).Logger() + setProcTitle("starting") + // Parse native options var proxyOptions nativeProxyOptions if err := env.Parse(&proxyOptions); err != nil { - logger.Fatal().Err(err).Msg("failed to parse native options") + logger.Fatal().Err(err).Msg("failed to parse native proxy options") } - // connect to video stream socket + // Connect to video stream socket conn, err := net.Dial("unixpacket", proxyOptions.VideoStreamUnixSocket) if err != nil { logger.Fatal().Err(err).Msg("failed to connect to video stream socket") } - logger.Info().Str("video_stream_socket_path", proxyOptions.VideoStreamUnixSocket).Msg("connected to video stream socket") + logger.Info().Str("videoStreamSocketPath", proxyOptions.VideoStreamUnixSocket).Msg("connected to video stream socket") nativeOptions := proxyOptions.toNativeOptions() nativeOptions.OnVideoFrameReceived = func(frame []byte, duration time.Duration) { @@ -52,9 +65,10 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to start native instance") } - gspt.SetProcTitle("jetkvm: [native] starting gRPC server") + grpcLogger := logger.With().Str("socketPath", fmt.Sprintf("@%v", proxyOptions.CtrlUnixSocket)).Logger() + setProcTitle("starting gRPC server") // Create gRPC server - grpcServer := NewGRPCServer(nativeInstance, &logger) + grpcServer := NewGRPCServer(nativeInstance, &grpcLogger) logger.Info().Msg("starting gRPC server") // Start gRPC server @@ -62,7 +76,7 @@ func RunNativeProcess(binaryName string) { if err != nil { logger.Fatal().Err(err).Msg("failed to start gRPC server") } - gspt.SetProcTitle("jetkvm: [native] ready") + setProcTitle("ready") // Signal that we're ready by writing handshake message to stdout (for parent to read) // Stdout.Write is used to avoid buffering the message @@ -76,8 +90,10 @@ func RunNativeProcess(binaryName string) { signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) // Wait for signal - <-sigChan - logger.Info().Msg("received termination signal") + sig := <-sigChan + logger.Info(). + Str("signal", sig.String()). + Msg("received termination signal") // Graceful shutdown server.GracefulStop() diff --git a/native.go b/native.go index 77ba4a8fb..0b1e35695 100644 --- a/native.go +++ b/native.go @@ -24,6 +24,9 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) { AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(), DefaultQualityFactor: config.VideoQualityFactor, + OnNativeRestart: func() { + configureDisplayOnNativeRestart() + }, OnVideoStateChange: func(state native.VideoState) { lastVideoState = state triggerVideoStateUpdate() From 6e9d84647158e1846307c282815be249606a00e4 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 14:29:55 +0000 Subject: [PATCH 10/25] feat: panic on max restart attempts --- internal/native/native.go | 1 + internal/native/proxy.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/native/native.go b/internal/native/native.go index 131283a4b..770da6752 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -33,6 +33,7 @@ type NativeOptions struct { AppVersion *semver.Version DisplayRotation uint16 DefaultQualityFactor float64 + MaxRestartAttempts uint OnVideoStateChange func(state VideoState) OnVideoFrameReceived func(frame []byte, duration time.Duration) OnIndevEvent func(event string) diff --git a/internal/native/proxy.go b/internal/native/proxy.go index d4433b266..3057a83f5 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -19,7 +19,8 @@ import ( ) const ( - maxFrameSize = 1920 * 1080 / 2 + maxFrameSize = 1920 * 1080 / 2 + defaultMaxRestartAttempts uint = 5 ) type nativeProxyOptions struct { @@ -55,6 +56,10 @@ func randomId(binaryLength int) string { func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { // random 16 bytes hex string handshakeMessage := randomId(16) + maxRestartAttempts := defaultMaxRestartAttempts + if n.MaxRestartAttempts > 0 { + maxRestartAttempts = n.MaxRestartAttempts + } return &nativeProxyOptions{ Disable: n.Disable, SystemVersion: n.SystemVersion, @@ -67,6 +72,7 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { OnVideoStateChange: n.OnVideoStateChange, OnNativeRestart: n.OnNativeRestart, HandshakeMessage: handshakeMessage, + MaxRestartAttempts: maxRestartAttempts, } } @@ -405,6 +411,10 @@ func (p *NativeProxy) restartProcess() error { } p.restarts++ + if p.restarts >= p.options.MaxRestartAttempts { + p.logger.Fatal().Msg("max restart attempts reached, exiting") + return fmt.Errorf("max restart attempts reached") + } if err := p.start(); err != nil { return fmt.Errorf("failed to start native process: %w", err) From 21641ffda55f12a41b921060bfc66a8b3ce487c8 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 15:02:30 +0000 Subject: [PATCH 11/25] chore: move generated methods to separate files --- internal/native/grpc_client.go | 203 ----------------------- internal/native/grpc_clientmethods.go | 212 ++++++++++++++++++++++++ internal/native/grpc_server.go | 220 ------------------------ internal/native/grpc_servermethods.go | 230 ++++++++++++++++++++++++++ 4 files changed, 442 insertions(+), 423 deletions(-) create mode 100644 internal/native/grpc_clientmethods.go create mode 100644 internal/native/grpc_servermethods.go diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index fec49ecb1..772fb8394 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -256,206 +256,3 @@ func (c *GRPCClient) Close() error { return c.conn.Close() } - -// Video methods -func (c *GRPCClient) VideoSetSleepMode(enabled bool) error { - _, err := c.client.VideoSetSleepMode(context.Background(), &pb.VideoSetSleepModeRequest{Enabled: enabled}) - return err -} - -func (c *GRPCClient) VideoGetSleepMode() (bool, error) { - resp, err := c.client.VideoGetSleepMode(context.Background(), &pb.Empty{}) - if err != nil { - return false, err - } - return resp.Enabled, nil -} - -func (c *GRPCClient) VideoSleepModeSupported() bool { - resp, err := c.client.VideoSleepModeSupported(context.Background(), &pb.Empty{}) - if err != nil { - return false - } - return resp.Supported -} - -func (c *GRPCClient) VideoSetQualityFactor(factor float64) error { - _, err := c.client.VideoSetQualityFactor(context.Background(), &pb.VideoSetQualityFactorRequest{Factor: factor}) - return err -} - -func (c *GRPCClient) VideoGetQualityFactor() (float64, error) { - resp, err := c.client.VideoGetQualityFactor(context.Background(), &pb.Empty{}) - if err != nil { - return 0, err - } - return resp.Factor, nil -} - -func (c *GRPCClient) VideoSetEDID(edid string) error { - _, err := c.client.VideoSetEDID(context.Background(), &pb.VideoSetEDIDRequest{Edid: edid}) - return err -} - -func (c *GRPCClient) VideoGetEDID() (string, error) { - resp, err := c.client.VideoGetEDID(context.Background(), &pb.Empty{}) - if err != nil { - return "", err - } - return resp.Edid, nil -} - -func (c *GRPCClient) VideoLogStatus() (string, error) { - resp, err := c.client.VideoLogStatus(context.Background(), &pb.Empty{}) - if err != nil { - return "", err - } - return resp.Status, nil -} - -func (c *GRPCClient) VideoStop() error { - _, err := c.client.VideoStop(context.Background(), &pb.Empty{}) - return err -} - -func (c *GRPCClient) VideoStart() error { - _, err := c.client.VideoStart(context.Background(), &pb.Empty{}) - return err -} - -// UI methods -func (c *GRPCClient) GetLVGLVersion() (string, error) { - resp, err := c.client.GetLVGLVersion(context.Background(), &pb.Empty{}) - if err != nil { - return "", err - } - return resp.Version, nil -} - -func (c *GRPCClient) UIObjHide(objName string) (bool, error) { - resp, err := c.client.UIObjHide(context.Background(), &pb.UIObjHideRequest{ObjName: objName}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjShow(objName string) (bool, error) { - resp, err := c.client.UIObjShow(context.Background(), &pb.UIObjShowRequest{ObjName: objName}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UISetVar(name string, value string) { - _, _ = c.client.UISetVar(context.Background(), &pb.UISetVarRequest{Name: name, Value: value}) -} - -func (c *GRPCClient) UIGetVar(name string) string { - resp, err := c.client.UIGetVar(context.Background(), &pb.UIGetVarRequest{Name: name}) - if err != nil { - return "" - } - return resp.Value -} - -func (c *GRPCClient) UIObjAddState(objName string, state string) (bool, error) { - resp, err := c.client.UIObjAddState(context.Background(), &pb.UIObjAddStateRequest{ObjName: objName, State: state}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjClearState(objName string, state string) (bool, error) { - resp, err := c.client.UIObjClearState(context.Background(), &pb.UIObjClearStateRequest{ObjName: objName, State: state}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjAddFlag(objName string, flag string) (bool, error) { - resp, err := c.client.UIObjAddFlag(context.Background(), &pb.UIObjAddFlagRequest{ObjName: objName, Flag: flag}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjClearFlag(objName string, flag string) (bool, error) { - resp, err := c.client.UIObjClearFlag(context.Background(), &pb.UIObjClearFlagRequest{ObjName: objName, Flag: flag}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjSetOpacity(objName string, opacity int) (bool, error) { - resp, err := c.client.UIObjSetOpacity(context.Background(), &pb.UIObjSetOpacityRequest{ObjName: objName, Opacity: int32(opacity)}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjFadeIn(objName string, duration uint32) (bool, error) { - resp, err := c.client.UIObjFadeIn(context.Background(), &pb.UIObjFadeInRequest{ObjName: objName, Duration: duration}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjFadeOut(objName string, duration uint32) (bool, error) { - resp, err := c.client.UIObjFadeOut(context.Background(), &pb.UIObjFadeOutRequest{ObjName: objName, Duration: duration}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjSetLabelText(objName string, text string) (bool, error) { - resp, err := c.client.UIObjSetLabelText(context.Background(), &pb.UIObjSetLabelTextRequest{ObjName: objName, Text: text}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UIObjSetImageSrc(objName string, image string) (bool, error) { - resp, err := c.client.UIObjSetImageSrc(context.Background(), &pb.UIObjSetImageSrcRequest{ObjName: objName, Image: image}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) DisplaySetRotation(rotation uint16) (bool, error) { - resp, err := c.client.DisplaySetRotation(context.Background(), &pb.DisplaySetRotationRequest{Rotation: uint32(rotation)}) - if err != nil { - return false, err - } - return resp.Success, nil -} - -func (c *GRPCClient) UpdateLabelIfChanged(objName string, newText string) { - _, _ = c.client.UpdateLabelIfChanged(context.Background(), &pb.UpdateLabelIfChangedRequest{ObjName: objName, NewText: newText}) -} - -func (c *GRPCClient) UpdateLabelAndChangeVisibility(objName string, newText string) { - _, _ = c.client.UpdateLabelAndChangeVisibility(context.Background(), &pb.UpdateLabelAndChangeVisibilityRequest{ObjName: objName, NewText: newText}) -} - -func (c *GRPCClient) SwitchToScreenIf(screenName string, shouldSwitch []string) { - _, _ = c.client.SwitchToScreenIf(context.Background(), &pb.SwitchToScreenIfRequest{ScreenName: screenName, ShouldSwitch: shouldSwitch}) -} - -func (c *GRPCClient) SwitchToScreenIfDifferent(screenName string) { - _, _ = c.client.SwitchToScreenIfDifferent(context.Background(), &pb.SwitchToScreenIfDifferentRequest{ScreenName: screenName}) -} - -func (c *GRPCClient) DoNotUseThisIsForCrashTestingOnly() { - _, _ = c.client.DoNotUseThisIsForCrashTestingOnly(context.Background(), &pb.Empty{}) -} diff --git a/internal/native/grpc_clientmethods.go b/internal/native/grpc_clientmethods.go new file mode 100644 index 000000000..c9a83adce --- /dev/null +++ b/internal/native/grpc_clientmethods.go @@ -0,0 +1,212 @@ +package native + +import ( + "context" + + pb "github.com/jetkvm/kvm/internal/native/proto" +) + +// Below are generated methods, do not edit manually + +// Video methods +func (c *GRPCClient) VideoSetSleepMode(enabled bool) error { + _, err := c.client.VideoSetSleepMode(context.Background(), &pb.VideoSetSleepModeRequest{Enabled: enabled}) + return err +} + +func (c *GRPCClient) VideoGetSleepMode() (bool, error) { + resp, err := c.client.VideoGetSleepMode(context.Background(), &pb.Empty{}) + if err != nil { + return false, err + } + return resp.Enabled, nil +} + +func (c *GRPCClient) VideoSleepModeSupported() bool { + resp, err := c.client.VideoSleepModeSupported(context.Background(), &pb.Empty{}) + if err != nil { + return false + } + return resp.Supported +} + +func (c *GRPCClient) VideoSetQualityFactor(factor float64) error { + _, err := c.client.VideoSetQualityFactor(context.Background(), &pb.VideoSetQualityFactorRequest{Factor: factor}) + return err +} + +func (c *GRPCClient) VideoGetQualityFactor() (float64, error) { + resp, err := c.client.VideoGetQualityFactor(context.Background(), &pb.Empty{}) + if err != nil { + return 0, err + } + return resp.Factor, nil +} + +func (c *GRPCClient) VideoSetEDID(edid string) error { + _, err := c.client.VideoSetEDID(context.Background(), &pb.VideoSetEDIDRequest{Edid: edid}) + return err +} + +func (c *GRPCClient) VideoGetEDID() (string, error) { + resp, err := c.client.VideoGetEDID(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Edid, nil +} + +func (c *GRPCClient) VideoLogStatus() (string, error) { + resp, err := c.client.VideoLogStatus(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Status, nil +} + +func (c *GRPCClient) VideoStop() error { + _, err := c.client.VideoStop(context.Background(), &pb.Empty{}) + return err +} + +func (c *GRPCClient) VideoStart() error { + _, err := c.client.VideoStart(context.Background(), &pb.Empty{}) + return err +} + +// UI methods +func (c *GRPCClient) GetLVGLVersion() (string, error) { + resp, err := c.client.GetLVGLVersion(context.Background(), &pb.Empty{}) + if err != nil { + return "", err + } + return resp.Version, nil +} + +func (c *GRPCClient) UIObjHide(objName string) (bool, error) { + resp, err := c.client.UIObjHide(context.Background(), &pb.UIObjHideRequest{ObjName: objName}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjShow(objName string) (bool, error) { + resp, err := c.client.UIObjShow(context.Background(), &pb.UIObjShowRequest{ObjName: objName}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UISetVar(name string, value string) { + _, _ = c.client.UISetVar(context.Background(), &pb.UISetVarRequest{Name: name, Value: value}) +} + +func (c *GRPCClient) UIGetVar(name string) string { + resp, err := c.client.UIGetVar(context.Background(), &pb.UIGetVarRequest{Name: name}) + if err != nil { + return "" + } + return resp.Value +} + +func (c *GRPCClient) UIObjAddState(objName string, state string) (bool, error) { + resp, err := c.client.UIObjAddState(context.Background(), &pb.UIObjAddStateRequest{ObjName: objName, State: state}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjClearState(objName string, state string) (bool, error) { + resp, err := c.client.UIObjClearState(context.Background(), &pb.UIObjClearStateRequest{ObjName: objName, State: state}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjAddFlag(objName string, flag string) (bool, error) { + resp, err := c.client.UIObjAddFlag(context.Background(), &pb.UIObjAddFlagRequest{ObjName: objName, Flag: flag}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjClearFlag(objName string, flag string) (bool, error) { + resp, err := c.client.UIObjClearFlag(context.Background(), &pb.UIObjClearFlagRequest{ObjName: objName, Flag: flag}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetOpacity(objName string, opacity int) (bool, error) { + resp, err := c.client.UIObjSetOpacity(context.Background(), &pb.UIObjSetOpacityRequest{ObjName: objName, Opacity: int32(opacity)}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjFadeIn(objName string, duration uint32) (bool, error) { + resp, err := c.client.UIObjFadeIn(context.Background(), &pb.UIObjFadeInRequest{ObjName: objName, Duration: duration}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjFadeOut(objName string, duration uint32) (bool, error) { + resp, err := c.client.UIObjFadeOut(context.Background(), &pb.UIObjFadeOutRequest{ObjName: objName, Duration: duration}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetLabelText(objName string, text string) (bool, error) { + resp, err := c.client.UIObjSetLabelText(context.Background(), &pb.UIObjSetLabelTextRequest{ObjName: objName, Text: text}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UIObjSetImageSrc(objName string, image string) (bool, error) { + resp, err := c.client.UIObjSetImageSrc(context.Background(), &pb.UIObjSetImageSrcRequest{ObjName: objName, Image: image}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) DisplaySetRotation(rotation uint16) (bool, error) { + resp, err := c.client.DisplaySetRotation(context.Background(), &pb.DisplaySetRotationRequest{Rotation: uint32(rotation)}) + if err != nil { + return false, err + } + return resp.Success, nil +} + +func (c *GRPCClient) UpdateLabelIfChanged(objName string, newText string) { + _, _ = c.client.UpdateLabelIfChanged(context.Background(), &pb.UpdateLabelIfChangedRequest{ObjName: objName, NewText: newText}) +} + +func (c *GRPCClient) UpdateLabelAndChangeVisibility(objName string, newText string) { + _, _ = c.client.UpdateLabelAndChangeVisibility(context.Background(), &pb.UpdateLabelAndChangeVisibilityRequest{ObjName: objName, NewText: newText}) +} + +func (c *GRPCClient) SwitchToScreenIf(screenName string, shouldSwitch []string) { + _, _ = c.client.SwitchToScreenIf(context.Background(), &pb.SwitchToScreenIfRequest{ScreenName: screenName, ShouldSwitch: shouldSwitch}) +} + +func (c *GRPCClient) SwitchToScreenIfDifferent(screenName string) { + _, _ = c.client.SwitchToScreenIfDifferent(context.Background(), &pb.SwitchToScreenIfDifferentRequest{ScreenName: screenName}) +} + +func (c *GRPCClient) DoNotUseThisIsForCrashTestingOnly() { + _, _ = c.client.DoNotUseThisIsForCrashTestingOnly(context.Background(), &pb.Empty{}) +} diff --git a/internal/native/grpc_server.go b/internal/native/grpc_server.go index f1a1e3019..304203ced 100644 --- a/internal/native/grpc_server.go +++ b/internal/native/grpc_server.go @@ -8,8 +8,6 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" pb "github.com/jetkvm/kvm/internal/native/proto" ) @@ -100,224 +98,6 @@ func (s *grpcServer) IsReady(ctx context.Context, req *pb.IsReadyRequest) (*pb.I return &pb.IsReadyResponse{Ready: true, VideoReady: true}, nil } -// Video methods -func (s *grpcServer) VideoSetSleepMode(ctx context.Context, req *pb.VideoSetSleepModeRequest) (*pb.Empty, error) { - if err := s.native.VideoSetSleepMode(req.Enabled); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.Empty{}, nil -} - -func (s *grpcServer) VideoGetSleepMode(ctx context.Context, req *pb.Empty) (*pb.VideoGetSleepModeResponse, error) { - enabled, err := s.native.VideoGetSleepMode() - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.VideoGetSleepModeResponse{Enabled: enabled}, nil -} - -func (s *grpcServer) VideoSleepModeSupported(ctx context.Context, req *pb.Empty) (*pb.VideoSleepModeSupportedResponse, error) { - return &pb.VideoSleepModeSupportedResponse{Supported: s.native.VideoSleepModeSupported()}, nil -} - -func (s *grpcServer) VideoSetQualityFactor(ctx context.Context, req *pb.VideoSetQualityFactorRequest) (*pb.Empty, error) { - if err := s.native.VideoSetQualityFactor(req.Factor); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.Empty{}, nil -} - -func (s *grpcServer) VideoGetQualityFactor(ctx context.Context, req *pb.Empty) (*pb.VideoGetQualityFactorResponse, error) { - factor, err := s.native.VideoGetQualityFactor() - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.VideoGetQualityFactorResponse{Factor: factor}, nil -} - -func (s *grpcServer) VideoSetEDID(ctx context.Context, req *pb.VideoSetEDIDRequest) (*pb.Empty, error) { - if err := s.native.VideoSetEDID(req.Edid); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.Empty{}, nil -} - -func (s *grpcServer) VideoGetEDID(ctx context.Context, req *pb.Empty) (*pb.VideoGetEDIDResponse, error) { - edid, err := s.native.VideoGetEDID() - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.VideoGetEDIDResponse{Edid: edid}, nil -} - -func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.VideoLogStatusResponse, error) { - logStatus, err := s.native.VideoLogStatus() - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.VideoLogStatusResponse{Status: logStatus}, nil -} - -func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { - procPrefix = "jetkvm: [native]" - setProcTitle(lastProcTitle) - - if err := s.native.VideoStop(); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.Empty{}, nil -} - -func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { - procPrefix = "jetkvm: [native+video]" - setProcTitle(lastProcTitle) - - if err := s.native.VideoStart(); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.Empty{}, nil -} - -// UI methods -func (s *grpcServer) GetLVGLVersion(ctx context.Context, req *pb.Empty) (*pb.GetLVGLVersionResponse, error) { - version, err := s.native.GetLVGLVersion() - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.GetLVGLVersionResponse{Version: version}, nil -} - -func (s *grpcServer) UIObjHide(ctx context.Context, req *pb.UIObjHideRequest) (*pb.UIObjHideResponse, error) { - success, err := s.native.UIObjHide(req.ObjName) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjHideResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjShow(ctx context.Context, req *pb.UIObjShowRequest) (*pb.UIObjShowResponse, error) { - success, err := s.native.UIObjShow(req.ObjName) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjShowResponse{Success: success}, nil -} - -func (s *grpcServer) UISetVar(ctx context.Context, req *pb.UISetVarRequest) (*pb.Empty, error) { - s.native.UISetVar(req.Name, req.Value) - return &pb.Empty{}, nil -} - -func (s *grpcServer) UIGetVar(ctx context.Context, req *pb.UIGetVarRequest) (*pb.UIGetVarResponse, error) { - value := s.native.UIGetVar(req.Name) - return &pb.UIGetVarResponse{Value: value}, nil -} - -func (s *grpcServer) UIObjAddState(ctx context.Context, req *pb.UIObjAddStateRequest) (*pb.UIObjAddStateResponse, error) { - success, err := s.native.UIObjAddState(req.ObjName, req.State) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjAddStateResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjClearState(ctx context.Context, req *pb.UIObjClearStateRequest) (*pb.UIObjClearStateResponse, error) { - success, err := s.native.UIObjClearState(req.ObjName, req.State) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjClearStateResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjAddFlag(ctx context.Context, req *pb.UIObjAddFlagRequest) (*pb.UIObjAddFlagResponse, error) { - success, err := s.native.UIObjAddFlag(req.ObjName, req.Flag) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjAddFlagResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjClearFlag(ctx context.Context, req *pb.UIObjClearFlagRequest) (*pb.UIObjClearFlagResponse, error) { - success, err := s.native.UIObjClearFlag(req.ObjName, req.Flag) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjClearFlagResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjSetOpacity(ctx context.Context, req *pb.UIObjSetOpacityRequest) (*pb.UIObjSetOpacityResponse, error) { - success, err := s.native.UIObjSetOpacity(req.ObjName, int(req.Opacity)) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjSetOpacityResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjFadeIn(ctx context.Context, req *pb.UIObjFadeInRequest) (*pb.UIObjFadeInResponse, error) { - success, err := s.native.UIObjFadeIn(req.ObjName, req.Duration) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjFadeInResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjFadeOut(ctx context.Context, req *pb.UIObjFadeOutRequest) (*pb.UIObjFadeOutResponse, error) { - success, err := s.native.UIObjFadeOut(req.ObjName, req.Duration) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjFadeOutResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjSetLabelText(ctx context.Context, req *pb.UIObjSetLabelTextRequest) (*pb.UIObjSetLabelTextResponse, error) { - success, err := s.native.UIObjSetLabelText(req.ObjName, req.Text) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjSetLabelTextResponse{Success: success}, nil -} - -func (s *grpcServer) UIObjSetImageSrc(ctx context.Context, req *pb.UIObjSetImageSrcRequest) (*pb.UIObjSetImageSrcResponse, error) { - success, err := s.native.UIObjSetImageSrc(req.ObjName, req.Image) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.UIObjSetImageSrcResponse{Success: success}, nil -} - -func (s *grpcServer) DisplaySetRotation(ctx context.Context, req *pb.DisplaySetRotationRequest) (*pb.DisplaySetRotationResponse, error) { - success, err := s.native.DisplaySetRotation(uint16(req.Rotation)) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - return &pb.DisplaySetRotationResponse{Success: success}, nil -} - -func (s *grpcServer) UpdateLabelIfChanged(ctx context.Context, req *pb.UpdateLabelIfChangedRequest) (*pb.Empty, error) { - s.native.UpdateLabelIfChanged(req.ObjName, req.NewText) - return &pb.Empty{}, nil -} - -func (s *grpcServer) UpdateLabelAndChangeVisibility(ctx context.Context, req *pb.UpdateLabelAndChangeVisibilityRequest) (*pb.Empty, error) { - s.native.UpdateLabelAndChangeVisibility(req.ObjName, req.NewText) - return &pb.Empty{}, nil -} - -func (s *grpcServer) SwitchToScreenIf(ctx context.Context, req *pb.SwitchToScreenIfRequest) (*pb.Empty, error) { - s.native.SwitchToScreenIf(req.ScreenName, req.ShouldSwitch) - return &pb.Empty{}, nil -} - -func (s *grpcServer) SwitchToScreenIfDifferent(ctx context.Context, req *pb.SwitchToScreenIfDifferentRequest) (*pb.Empty, error) { - s.native.SwitchToScreenIfDifferent(req.ScreenName) - return &pb.Empty{}, nil -} - -func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { - s.native.DoNotUseThisIsForCrashTestingOnly() - return &pb.Empty{}, nil -} - // StreamEvents streams events from the native process func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error { setProcTitle("connected") diff --git a/internal/native/grpc_servermethods.go b/internal/native/grpc_servermethods.go new file mode 100644 index 000000000..cc16dfd10 --- /dev/null +++ b/internal/native/grpc_servermethods.go @@ -0,0 +1,230 @@ +package native + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/jetkvm/kvm/internal/native/proto" +) + +// Below are generated methods, do not edit manually + +// Video methods +func (s *grpcServer) VideoSetSleepMode(ctx context.Context, req *pb.VideoSetSleepModeRequest) (*pb.Empty, error) { + if err := s.native.VideoSetSleepMode(req.Enabled); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetSleepMode(ctx context.Context, req *pb.Empty) (*pb.VideoGetSleepModeResponse, error) { + enabled, err := s.native.VideoGetSleepMode() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetSleepModeResponse{Enabled: enabled}, nil +} + +func (s *grpcServer) VideoSleepModeSupported(ctx context.Context, req *pb.Empty) (*pb.VideoSleepModeSupportedResponse, error) { + return &pb.VideoSleepModeSupportedResponse{Supported: s.native.VideoSleepModeSupported()}, nil +} + +func (s *grpcServer) VideoSetQualityFactor(ctx context.Context, req *pb.VideoSetQualityFactorRequest) (*pb.Empty, error) { + if err := s.native.VideoSetQualityFactor(req.Factor); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetQualityFactor(ctx context.Context, req *pb.Empty) (*pb.VideoGetQualityFactorResponse, error) { + factor, err := s.native.VideoGetQualityFactor() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetQualityFactorResponse{Factor: factor}, nil +} + +func (s *grpcServer) VideoSetEDID(ctx context.Context, req *pb.VideoSetEDIDRequest) (*pb.Empty, error) { + if err := s.native.VideoSetEDID(req.Edid); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoGetEDID(ctx context.Context, req *pb.Empty) (*pb.VideoGetEDIDResponse, error) { + edid, err := s.native.VideoGetEDID() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoGetEDIDResponse{Edid: edid}, nil +} + +func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.VideoLogStatusResponse, error) { + logStatus, err := s.native.VideoLogStatus() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.VideoLogStatusResponse{Status: logStatus}, nil +} + +func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + procPrefix = "jetkvm: [native]" + setProcTitle(lastProcTitle) + + if err := s.native.VideoStop(); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + procPrefix = "jetkvm: [native+video]" + setProcTitle(lastProcTitle) + + if err := s.native.VideoStart(); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.Empty{}, nil +} + +// UI methods +func (s *grpcServer) GetLVGLVersion(ctx context.Context, req *pb.Empty) (*pb.GetLVGLVersionResponse, error) { + version, err := s.native.GetLVGLVersion() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.GetLVGLVersionResponse{Version: version}, nil +} + +func (s *grpcServer) UIObjHide(ctx context.Context, req *pb.UIObjHideRequest) (*pb.UIObjHideResponse, error) { + success, err := s.native.UIObjHide(req.ObjName) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjHideResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjShow(ctx context.Context, req *pb.UIObjShowRequest) (*pb.UIObjShowResponse, error) { + success, err := s.native.UIObjShow(req.ObjName) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjShowResponse{Success: success}, nil +} + +func (s *grpcServer) UISetVar(ctx context.Context, req *pb.UISetVarRequest) (*pb.Empty, error) { + s.native.UISetVar(req.Name, req.Value) + return &pb.Empty{}, nil +} + +func (s *grpcServer) UIGetVar(ctx context.Context, req *pb.UIGetVarRequest) (*pb.UIGetVarResponse, error) { + value := s.native.UIGetVar(req.Name) + return &pb.UIGetVarResponse{Value: value}, nil +} + +func (s *grpcServer) UIObjAddState(ctx context.Context, req *pb.UIObjAddStateRequest) (*pb.UIObjAddStateResponse, error) { + success, err := s.native.UIObjAddState(req.ObjName, req.State) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjAddStateResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjClearState(ctx context.Context, req *pb.UIObjClearStateRequest) (*pb.UIObjClearStateResponse, error) { + success, err := s.native.UIObjClearState(req.ObjName, req.State) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjClearStateResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjAddFlag(ctx context.Context, req *pb.UIObjAddFlagRequest) (*pb.UIObjAddFlagResponse, error) { + success, err := s.native.UIObjAddFlag(req.ObjName, req.Flag) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjAddFlagResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjClearFlag(ctx context.Context, req *pb.UIObjClearFlagRequest) (*pb.UIObjClearFlagResponse, error) { + success, err := s.native.UIObjClearFlag(req.ObjName, req.Flag) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjClearFlagResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetOpacity(ctx context.Context, req *pb.UIObjSetOpacityRequest) (*pb.UIObjSetOpacityResponse, error) { + success, err := s.native.UIObjSetOpacity(req.ObjName, int(req.Opacity)) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetOpacityResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjFadeIn(ctx context.Context, req *pb.UIObjFadeInRequest) (*pb.UIObjFadeInResponse, error) { + success, err := s.native.UIObjFadeIn(req.ObjName, req.Duration) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjFadeInResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjFadeOut(ctx context.Context, req *pb.UIObjFadeOutRequest) (*pb.UIObjFadeOutResponse, error) { + success, err := s.native.UIObjFadeOut(req.ObjName, req.Duration) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjFadeOutResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetLabelText(ctx context.Context, req *pb.UIObjSetLabelTextRequest) (*pb.UIObjSetLabelTextResponse, error) { + success, err := s.native.UIObjSetLabelText(req.ObjName, req.Text) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetLabelTextResponse{Success: success}, nil +} + +func (s *grpcServer) UIObjSetImageSrc(ctx context.Context, req *pb.UIObjSetImageSrcRequest) (*pb.UIObjSetImageSrcResponse, error) { + success, err := s.native.UIObjSetImageSrc(req.ObjName, req.Image) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UIObjSetImageSrcResponse{Success: success}, nil +} + +func (s *grpcServer) DisplaySetRotation(ctx context.Context, req *pb.DisplaySetRotationRequest) (*pb.DisplaySetRotationResponse, error) { + success, err := s.native.DisplaySetRotation(uint16(req.Rotation)) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.DisplaySetRotationResponse{Success: success}, nil +} + +func (s *grpcServer) UpdateLabelIfChanged(ctx context.Context, req *pb.UpdateLabelIfChangedRequest) (*pb.Empty, error) { + s.native.UpdateLabelIfChanged(req.ObjName, req.NewText) + return &pb.Empty{}, nil +} + +func (s *grpcServer) UpdateLabelAndChangeVisibility(ctx context.Context, req *pb.UpdateLabelAndChangeVisibilityRequest) (*pb.Empty, error) { + s.native.UpdateLabelAndChangeVisibility(req.ObjName, req.NewText) + return &pb.Empty{}, nil +} + +func (s *grpcServer) SwitchToScreenIf(ctx context.Context, req *pb.SwitchToScreenIfRequest) (*pb.Empty, error) { + s.native.SwitchToScreenIf(req.ScreenName, req.ShouldSwitch) + return &pb.Empty{}, nil +} + +func (s *grpcServer) SwitchToScreenIfDifferent(ctx context.Context, req *pb.SwitchToScreenIfDifferentRequest) (*pb.Empty, error) { + s.native.SwitchToScreenIfDifferent(req.ScreenName) + return &pb.Empty{}, nil +} + +func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { + s.native.DoNotUseThisIsForCrashTestingOnly() + return &pb.Empty{}, nil +} From 953b9ded30d3133422991bf18a5c04b1940390b4 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 15:43:43 +0000 Subject: [PATCH 12/25] feat: simplify failsafe mode for native proxy --- failsafe.go | 4 +- internal/native/cgo_linux.go | 122 +-------------------------------- internal/native/empty.go | 111 ++++++++++++++++++++++++++++++ internal/native/grpc_client.go | 2 +- internal/native/native.go | 9 --- internal/native/proxy.go | 14 ++-- internal/native/server.go | 13 ++++ native.go | 7 +- 8 files changed, 145 insertions(+), 137 deletions(-) create mode 100644 internal/native/empty.go diff --git a/failsafe.go b/failsafe.go index 3c6b3d3aa..14e7bbd5b 100644 --- a/failsafe.go +++ b/failsafe.go @@ -77,10 +77,12 @@ func checkFailsafeReason() { _ = os.Remove(lastCrashPath) // TODO: read the goroutine stack trace and check which goroutine is panicking + failsafeModeActive = true if strings.Contains(failsafeCrashLog, "runtime.cgocall") { - failsafeModeActive = true failsafeModeReason = "video" return + } else { + failsafeModeReason = "unknown" } }) } diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index be1a5a361..b33eb5347 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -50,17 +50,9 @@ static inline void jetkvm_cgo_setup_rpc_handler() { import "C" var ( - cgoLock sync.Mutex - cgoDisabled bool + cgoLock sync.Mutex ) -func setCgoDisabled(disabled bool) { - cgoLock.Lock() - defer cgoLock.Unlock() - - cgoDisabled = disabled -} - //export jetkvm_go_video_state_handler func jetkvm_go_video_state_handler(state *C.jetkvm_video_state_t) { videoState := VideoState{ @@ -104,10 +96,6 @@ func jetkvm_go_rpc_handler(method *C.cchar_t, params *C.cchar_t) { var eventCodeToNameMap = map[int]string{} func uiEventCodeToName(code int) string { - if cgoDisabled { - return "" - } - name, ok := eventCodeToNameMap[code] if !ok { cCode := C.int(code) @@ -120,10 +108,6 @@ func uiEventCodeToName(code int) string { } func setUpNativeHandlers() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -135,10 +119,6 @@ func setUpNativeHandlers() { } func uiInit(rotation uint16) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -148,10 +128,6 @@ func uiInit(rotation uint16) { } func uiTick() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -159,10 +135,6 @@ func uiTick() { } func videoInit(factor float64) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -176,10 +148,6 @@ func videoInit(factor float64) error { } func videoShutdown() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -187,10 +155,6 @@ func videoShutdown() { } func videoStart() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -198,10 +162,6 @@ func videoStart() { } func videoStop() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -209,10 +169,6 @@ func videoStop() { } func videoLogStatus() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -223,10 +179,6 @@ func videoLogStatus() string { } func uiSetVar(name string, value string) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -240,10 +192,6 @@ func uiSetVar(name string, value string) { } func uiGetVar(name string) string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -254,10 +202,6 @@ func uiGetVar(name string) string { } func uiSwitchToScreen(screen string) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -267,10 +211,6 @@ func uiSwitchToScreen(screen string) { } func uiGetCurrentScreen() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -279,10 +219,6 @@ func uiGetCurrentScreen() string { } func uiObjAddState(objName string, state string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -295,10 +231,6 @@ func uiObjAddState(objName string, state string) (bool, error) { } func uiObjClearState(objName string, state string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -311,10 +243,6 @@ func uiObjClearState(objName string, state string) (bool, error) { } func uiGetLVGLVersion() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -323,10 +251,6 @@ func uiGetLVGLVersion() string { // TODO: use Enum instead of string but it's not a hot path and performance is not a concern now func uiObjAddFlag(objName string, flag string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -339,10 +263,6 @@ func uiObjAddFlag(objName string, flag string) (bool, error) { } func uiObjClearFlag(objName string, flag string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -363,10 +283,6 @@ func uiObjShow(objName string) (bool, error) { } func uiObjSetOpacity(objName string, opacity int) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -378,10 +294,6 @@ func uiObjSetOpacity(objName string, opacity int) (bool, error) { } func uiObjFadeIn(objName string, duration uint32) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -394,10 +306,6 @@ func uiObjFadeIn(objName string, duration uint32) (bool, error) { } func uiObjFadeOut(objName string, duration uint32) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -410,10 +318,6 @@ func uiObjFadeOut(objName string, duration uint32) (bool, error) { } func uiLabelSetText(objName string, text string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -431,10 +335,6 @@ func uiLabelSetText(objName string, text string) (bool, error) { } func uiImgSetSrc(objName string, src string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -450,10 +350,6 @@ func uiImgSetSrc(objName string, src string) (bool, error) { } func uiDispSetRotation(rotation uint16) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -466,10 +362,6 @@ func uiDispSetRotation(rotation uint16) (bool, error) { } func videoGetStreamQualityFactor() (float64, error) { - if cgoDisabled { - return 0, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -478,10 +370,6 @@ func videoGetStreamQualityFactor() (float64, error) { } func videoSetStreamQualityFactor(factor float64) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -490,10 +378,6 @@ func videoSetStreamQualityFactor(factor float64) error { } func videoGetEDID() (string, error) { - if cgoDisabled { - return "", nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -502,10 +386,6 @@ func videoGetEDID() (string, error) { } func videoSetEDID(edid string) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() diff --git a/internal/native/empty.go b/internal/native/empty.go new file mode 100644 index 000000000..846cf9aed --- /dev/null +++ b/internal/native/empty.go @@ -0,0 +1,111 @@ +package native + +type EmptyNativeInterface struct { +} + +func (e *EmptyNativeInterface) Start() error { return nil } + +func (e *EmptyNativeInterface) VideoSetSleepMode(enabled bool) error { return nil } + +func (e *EmptyNativeInterface) VideoGetSleepMode() (bool, error) { return false, nil } + +func (e *EmptyNativeInterface) VideoSleepModeSupported() bool { + return false +} + +func (e *EmptyNativeInterface) VideoSetQualityFactor(factor float64) error { + return nil +} + +func (e *EmptyNativeInterface) VideoGetQualityFactor() (float64, error) { + return 0, nil +} + +func (e *EmptyNativeInterface) VideoSetEDID(edid string) error { + return nil +} + +func (e *EmptyNativeInterface) VideoGetEDID() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) VideoLogStatus() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) VideoStop() error { + return nil +} + +func (e *EmptyNativeInterface) VideoStart() error { + return nil +} + +func (e *EmptyNativeInterface) GetLVGLVersion() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) UIObjHide(objName string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjShow(objName string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UISetVar(name string, value string) { +} + +func (e *EmptyNativeInterface) UIGetVar(name string) string { + return "" +} + +func (e *EmptyNativeInterface) UIObjAddState(objName string, state string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjClearState(objName string, state string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjAddFlag(objName string, flag string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjClearFlag(objName string, flag string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetOpacity(objName string, opacity int) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjFadeIn(objName string, duration uint32) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjFadeOut(objName string, duration uint32) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetLabelText(objName string, text string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetImageSrc(objName string, image string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) DisplaySetRotation(rotation uint16) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UpdateLabelIfChanged(objName string, newText string) {} + +func (e *EmptyNativeInterface) UpdateLabelAndChangeVisibility(objName string, newText string) {} + +func (e *EmptyNativeInterface) SwitchToScreenIf(screenName string, shouldSwitch []string) {} + +func (e *EmptyNativeInterface) SwitchToScreenIfDifferent(screenName string) {} + +func (e *EmptyNativeInterface) DoNotUseThisIsForCrashTestingOnly() {} diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 772fb8394..426fde8d3 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -131,7 +131,7 @@ func (c *GRPCClient) startEventStream() { stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) if err != nil { c.logger.Warn().Err(err).Msg("failed to start event stream, retrying ...") - time.Sleep(1 * time.Second) + time.Sleep(5 * time.Second) continue } diff --git a/internal/native/native.go b/internal/native/native.go index 770da6752..5d22361f4 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -9,7 +9,6 @@ import ( ) type Native struct { - disable bool ready chan struct{} l *zerolog.Logger lD *zerolog.Logger @@ -28,7 +27,6 @@ type Native struct { } type NativeOptions struct { - Disable bool SystemVersion *semver.Version AppVersion *semver.Version DisplayRotation uint16 @@ -81,7 +79,6 @@ func NewNative(opts NativeOptions) *Native { } return &Native{ - disable: opts.Disable, ready: make(chan struct{}), l: &nativeSubLogger, lD: &displaySubLogger, @@ -100,12 +97,6 @@ func NewNative(opts NativeOptions) *Native { } func (n *Native) Start() error { - if n.disable { - nativeLogger.Warn().Msg("native is disabled, skipping initialization") - setCgoDisabled(true) - return nil - } - // set up singleton setInstance(n) setUpNativeHandlers() diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 3057a83f5..5ba337262 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -61,7 +61,6 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { maxRestartAttempts = n.MaxRestartAttempts } return &nativeProxyOptions{ - Disable: n.Disable, SystemVersion: n.SystemVersion, AppVersion: n.AppVersion, DisplayRotation: n.DisplayRotation, @@ -78,7 +77,6 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { func (p *nativeProxyOptions) toNativeOptions() *NativeOptions { return &NativeOptions{ - Disable: p.Disable, SystemVersion: p.SystemVersion, AppVersion: p.AppVersion, DisplayRotation: p.DisplayRotation, @@ -135,7 +133,6 @@ type NativeProxy struct { // NewNativeProxy creates a new NativeProxy that spawns a separate process func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { proxyOptions := opts.toProxyOptions() - proxyOptions.CtrlUnixSocket = fmt.Sprintf("jetkvm/native/grpc/%s", randomId(4)) proxyOptions.VideoStreamUnixSocket = fmt.Sprintf("@jetkvm/native/video-stream/%s", randomId(4)) // Get the current executable path to spawn itself @@ -211,6 +208,12 @@ func (w *nativeProxyStdoutHandler) Write(p []byte) (n int, err error) { } func (p *NativeProxy) toProcessCommand() (*cmdWrapper, error) { + // generate a new random ID for the gRPC socket on each restart + // sometimes the socket is not closed properly when the process exits + // this is a workaround to avoid the issue + p.nativeUnixSocket = fmt.Sprintf("jetkvm/native/grpc/%s", randomId(4)) + p.options.CtrlUnixSocket = p.nativeUnixSocket + envArgs, err := utils.MarshalEnv(p.options) if err != nil { return nil, fmt.Errorf("failed to marshal environment variables: %w", err) @@ -407,7 +410,10 @@ func (p *NativeProxy) restartProcess() error { // Close old client if p.client != nil { - _ = p.client.Close() + if err := p.client.Close(); err != nil { + p.logger.Warn().Err(err).Msg("failed to close gRPC client") + } + p.client = nil // set to nil to avoid closing it again } p.restarts++ diff --git a/internal/native/server.go b/internal/native/server.go index dd660e733..a2835f19c 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -85,6 +85,19 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to write handshake message to stdout") } + go func() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGHUP) + + // non-blocking receive + select { + case <-sigChan: + logger.Info().Msg("received SIGHUP signal, emulating crash") + nativeInstance.DoNotUseThisIsForCrashTestingOnly() + default: + } + }() + // Set up signal handling sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) diff --git a/native.go b/native.go index 0b1e35695..b51aa9b69 100644 --- a/native.go +++ b/native.go @@ -16,10 +16,15 @@ var ( ) func initNative(systemVersion *semver.Version, appVersion *semver.Version) { + if failsafeModeActive { + nativeInstance = &native.EmptyNativeInterface{} + nativeLogger.Warn().Msg("failsafe mode active, using empty native interface") + return + } + nativeLogger.Info().Msg("initializing native proxy") var err error nativeInstance, err = native.NewNativeProxy(native.NativeOptions{ - Disable: failsafeModeActive, SystemVersion: systemVersion, AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(), From b3a8d847706139e90c004c2b82b2beea5d77e4a7 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 15:57:13 +0000 Subject: [PATCH 13/25] add a simple README for the native package --- internal/native/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 internal/native/README.md diff --git a/internal/native/README.md b/internal/native/README.md new file mode 100644 index 000000000..a02976e40 --- /dev/null +++ b/internal/native/README.md @@ -0,0 +1,6 @@ +# jetkvm-native + +This component (`internal/native/`) acts as a bridge between Golang and native (C/C++) code. +It manages spawning and communicating with a native process via sockets (gRPC and Unix stream). + +For performance-critical operations such as video frame, **a dedicated Unix socket should be used** to avoid the overhead of gRPC and ensure low-latency communication. \ No newline at end of file From 577424b2360c71ba6cef43414a6b7b7005d6e687 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 16:13:18 +0000 Subject: [PATCH 14/25] fix: event may be nil --- internal/native/grpc_client.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 426fde8d3..1029e06f2 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -90,13 +90,6 @@ func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClien } event, err := stream.Recv() - // enrich the logger with the event type and data, if debug mode is enabled - if c.logger.GetLevel() <= zerolog.DebugLevel { - logger = logger.With(). - Str("type", event.Type). - Interface("data", event.Data). - Logger() - } if err != nil { if errors.Is(err, io.EOF) { @@ -107,6 +100,13 @@ func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClien break } + // enrich the logger with the event type and data, if debug mode is enabled + if c.logger.GetLevel() <= zerolog.DebugLevel { + logger = logger.With(). + Str("type", event.Type). + Interface("data", event.Data). + Logger() + } logger.Trace().Msg("received event") select { From 470fcf4d64c4d30ad711aa3db5e2ff77ef7c6ec4 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 16:28:50 +0000 Subject: [PATCH 15/25] fix: protect proxy methods from nil client --- internal/native/proxy.go | 198 ++++++++++++++++++++++++++++++--------- 1 file changed, 152 insertions(+), 46 deletions(-) diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 5ba337262..344f306e3 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -120,11 +120,12 @@ type NativeProxy struct { binaryPath string client *GRPCClient + clientMu sync.RWMutex cmd *cmdWrapper logger *zerolog.Logger ready chan struct{} options *nativeProxyOptions - restartM sync.Mutex + restartMu sync.Mutex restarts uint stopped bool processWait chan error @@ -265,6 +266,9 @@ func (p *NativeProxy) handleVideoFrame(conn net.Conn) { } func (p *NativeProxy) setUpGRPCClient() error { + p.clientMu.Lock() + defer p.clientMu.Unlock() + // wait until handshake completed select { case <-p.cmd.stdoutHandler.handshakeCh: @@ -300,11 +304,12 @@ func (p *NativeProxy) setUpGRPCClient() error { // Call on native restart callback if it exists and restarts are greater than 0 if p.options.OnNativeRestart != nil && p.restarts > 0 { - p.options.OnNativeRestart() + go p.options.OnNativeRestart() } // Start monitoring process for crashes go p.monitorProcess() + return nil } @@ -336,8 +341,8 @@ func (p *NativeProxy) start() error { // Start starts the native process func (p *NativeProxy) Start() error { - p.restartM.Lock() - defer p.restartM.Unlock() + p.restartMu.Lock() + defer p.restartMu.Unlock() if p.stopped { return fmt.Errorf("proxy is stopped") @@ -358,10 +363,10 @@ func (p *NativeProxy) Start() error { // monitorProcess monitors the native process and restarts it if it crashes func (p *NativeProxy) monitorProcess() { for { - p.restartM.Lock() + p.restartMu.Lock() cmd := p.cmd stopped := p.stopped - p.restartM.Unlock() + p.restartMu.Unlock() if stopped { return @@ -377,12 +382,12 @@ func (p *NativeProxy) monitorProcess() { default: } - p.restartM.Lock() + p.restartMu.Lock() if p.stopped { - p.restartM.Unlock() + p.restartMu.Unlock() return } - p.restartM.Unlock() + p.restartMu.Unlock() p.logger.Warn().Err(err).Msg("native process exited, restarting...") @@ -401,20 +406,22 @@ func (p *NativeProxy) monitorProcess() { // restartProcess restarts the native process func (p *NativeProxy) restartProcess() error { - p.restartM.Lock() - defer p.restartM.Unlock() + p.restartMu.Lock() + defer p.restartMu.Unlock() if p.stopped { return fmt.Errorf("proxy is stopped") } // Close old client + p.clientMu.Lock() if p.client != nil { if err := p.client.Close(); err != nil { p.logger.Warn().Err(err).Msg("failed to close gRPC client") } p.client = nil // set to nil to avoid closing it again } + p.clientMu.Unlock() p.restarts++ if p.restarts >= p.options.MaxRestartAttempts { @@ -432,14 +439,16 @@ func (p *NativeProxy) restartProcess() error { // Stop stops the native process func (p *NativeProxy) Stop() error { - p.restartM.Lock() - defer p.restartM.Unlock() + p.restartMu.Lock() + defer p.restartMu.Unlock() p.stopped = true + p.clientMu.Lock() if err := p.client.Close(); err != nil { p.logger.Warn().Err(err).Msg("failed to close IPC client") } + p.clientMu.Unlock() if p.cmd.Process != nil { if err := p.cmd.Process.Kill(); err != nil { @@ -451,123 +460,220 @@ func (p *NativeProxy) Stop() error { return nil } +func zeroValue[V string | bool | float64]() V { + var v V + return v +} + +func nativeProxyClientExec[K comparable, V string | bool | float64](p *NativeProxy, fn func(*GRPCClient) (V, error)) (V, error) { + p.clientMu.RLock() + defer p.clientMu.RUnlock() + + if p.client == nil { + return zeroValue[V](), fmt.Errorf("gRPC client not initialized") + } + + return fn(p.client) +} + +func nativeProxyClientExecWithoutArgument(p *NativeProxy, fn func(*GRPCClient) error) error { + p.clientMu.RLock() + defer p.clientMu.RUnlock() + + if p.client == nil { + return fmt.Errorf("gRPC client not initialized") + } + + return fn(p.client) +} + // Implement all Native methods by forwarding to gRPC client func (p *NativeProxy) VideoSetSleepMode(enabled bool) error { - return p.client.VideoSetSleepMode(enabled) + return nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + return client.VideoSetSleepMode(enabled) + }) } func (p *NativeProxy) VideoGetSleepMode() (bool, error) { - return p.client.VideoGetSleepMode() + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.VideoGetSleepMode() + }) } func (p *NativeProxy) VideoSleepModeSupported() bool { - return p.client.VideoSleepModeSupported() + result, _ := nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.VideoSleepModeSupported(), nil + }) + return result } func (p *NativeProxy) VideoSetQualityFactor(factor float64) error { - return p.client.VideoSetQualityFactor(factor) + return nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + return client.VideoSetQualityFactor(factor) + }) } func (p *NativeProxy) VideoGetQualityFactor() (float64, error) { - return p.client.VideoGetQualityFactor() + return nativeProxyClientExec[float64](p, func(client *GRPCClient) (float64, error) { + return client.VideoGetQualityFactor() + }) } func (p *NativeProxy) VideoSetEDID(edid string) error { - return p.client.VideoSetEDID(edid) + return nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + return client.VideoSetEDID(edid) + }) } func (p *NativeProxy) VideoGetEDID() (string, error) { - return p.client.VideoGetEDID() + return nativeProxyClientExec[string](p, func(client *GRPCClient) (string, error) { + return client.VideoGetEDID() + }) } func (p *NativeProxy) VideoLogStatus() (string, error) { - return p.client.VideoLogStatus() + return nativeProxyClientExec[string](p, func(client *GRPCClient) (string, error) { + return client.VideoLogStatus() + }) } func (p *NativeProxy) VideoStop() error { - return p.client.VideoStop() + return nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + return client.VideoStop() + }) } func (p *NativeProxy) VideoStart() error { - return p.client.VideoStart() + return nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + return client.VideoStart() + }) } func (p *NativeProxy) GetLVGLVersion() (string, error) { - return p.client.GetLVGLVersion() + return nativeProxyClientExec[string](p, func(client *GRPCClient) (string, error) { + return client.GetLVGLVersion() + }) } func (p *NativeProxy) UIObjHide(objName string) (bool, error) { - return p.client.UIObjHide(objName) + result, err := nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjHide(objName) + }) + return result, err } func (p *NativeProxy) UIObjShow(objName string) (bool, error) { - return p.client.UIObjShow(objName) + result, err := nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjShow(objName) + }) + return result, err } func (p *NativeProxy) UISetVar(name string, value string) { - p.client.UISetVar(name, value) + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.UISetVar(name, value) + return nil + }) } func (p *NativeProxy) UIGetVar(name string) string { - return p.client.UIGetVar(name) + result, _ := nativeProxyClientExec[string](p, func(client *GRPCClient) (string, error) { + return client.UIGetVar(name), nil + }) + return result } func (p *NativeProxy) UIObjAddState(objName string, state string) (bool, error) { - return p.client.UIObjAddState(objName, state) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjAddState(objName, state) + }) } func (p *NativeProxy) UIObjClearState(objName string, state string) (bool, error) { - return p.client.UIObjClearState(objName, state) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjClearState(objName, state) + }) } func (p *NativeProxy) UIObjAddFlag(objName string, flag string) (bool, error) { - return p.client.UIObjAddFlag(objName, flag) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjAddFlag(objName, flag) + }) } func (p *NativeProxy) UIObjClearFlag(objName string, flag string) (bool, error) { - return p.client.UIObjClearFlag(objName, flag) -} - -func (p *NativeProxy) UIObjSetOpacity(objName string, opacity int) (bool, error) { - return p.client.UIObjSetOpacity(objName, opacity) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjClearFlag(objName, flag) + }) } func (p *NativeProxy) UIObjFadeIn(objName string, duration uint32) (bool, error) { - return p.client.UIObjFadeIn(objName, duration) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjFadeIn(objName, duration) + }) } func (p *NativeProxy) UIObjFadeOut(objName string, duration uint32) (bool, error) { - return p.client.UIObjFadeOut(objName, duration) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjFadeOut(objName, duration) + }) } func (p *NativeProxy) UIObjSetLabelText(objName string, text string) (bool, error) { - return p.client.UIObjSetLabelText(objName, text) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjSetLabelText(objName, text) + }) } func (p *NativeProxy) UIObjSetImageSrc(objName string, image string) (bool, error) { - return p.client.UIObjSetImageSrc(objName, image) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjSetImageSrc(objName, image) + }) +} + +func (p *NativeProxy) UIObjSetOpacity(objName string, opacity int) (bool, error) { + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.UIObjSetOpacity(objName, opacity) + }) } func (p *NativeProxy) DisplaySetRotation(rotation uint16) (bool, error) { - return p.client.DisplaySetRotation(rotation) + return nativeProxyClientExec[bool](p, func(client *GRPCClient) (bool, error) { + return client.DisplaySetRotation(rotation) + }) } func (p *NativeProxy) UpdateLabelIfChanged(objName string, newText string) { - p.client.UpdateLabelIfChanged(objName, newText) + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.UpdateLabelIfChanged(objName, newText) + return nil + }) } func (p *NativeProxy) UpdateLabelAndChangeVisibility(objName string, newText string) { - p.client.UpdateLabelAndChangeVisibility(objName, newText) + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.UpdateLabelAndChangeVisibility(objName, newText) + return nil + }) } func (p *NativeProxy) SwitchToScreenIf(screenName string, shouldSwitch []string) { - p.client.SwitchToScreenIf(screenName, shouldSwitch) + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.SwitchToScreenIf(screenName, shouldSwitch) + return nil + }) } func (p *NativeProxy) SwitchToScreenIfDifferent(screenName string) { - p.client.SwitchToScreenIfDifferent(screenName) + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.SwitchToScreenIfDifferent(screenName) + return nil + }) } func (p *NativeProxy) DoNotUseThisIsForCrashTestingOnly() { - p.client.DoNotUseThisIsForCrashTestingOnly() + _ = nativeProxyClientExecWithoutArgument(p, func(client *GRPCClient) error { + client.DoNotUseThisIsForCrashTestingOnly() + return nil + }) } From 9c9c085690d9c70364a8657b1b6205cc783ee9a1 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 09:39:02 +0000 Subject: [PATCH 16/25] chore: move setStream to handleEventStream --- internal/native/grpc_client.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 1029e06f2..c28ceb6cc 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -75,13 +75,14 @@ func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) { return grpcClient, nil } -func (c *GRPCClient) setStream(stream pb.NativeService_StreamEventsClient) { +func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClient) { c.eventM.Lock() - defer c.eventM.Unlock() c.eventStream = stream -} + defer func() { + c.eventStream = nil + c.eventM.Unlock() + }() -func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClient) { logger := *c.logger for { if stream == nil { @@ -135,9 +136,7 @@ func (c *GRPCClient) startEventStream() { continue } - c.setStream(stream) c.handleEventStream(stream) - c.setStream(nil) // Wait before retrying time.Sleep(1 * time.Second) From 64ec70d030413341cc69371051bfc486d332f43f Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 10:08:54 +0000 Subject: [PATCH 17/25] feat: cancel all ongoing requests when closing the gRPC client --- internal/native/grpc_client.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index c28ceb6cc..4ea7bcb20 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -20,6 +20,9 @@ import ( // GRPCClient wraps the gRPC client for the native service type GRPCClient struct { + ctx context.Context + cancel context.CancelFunc + conn *grpc.ClientConn client pb.NativeServiceClient logger *zerolog.Logger @@ -58,7 +61,11 @@ func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) { client := pb.NewNativeServiceClient(conn) + ctx, cancel := context.WithCancel(context.Background()) + grpcClient := &GRPCClient{ + ctx: ctx, + cancel: cancel, conn: conn, client: client, logger: opts.Logger, @@ -83,8 +90,8 @@ func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClien c.eventM.Unlock() }() - logger := *c.logger for { + logger := c.logger.With().Interface("stream", stream).Logger() if stream == nil { logger.Error().Msg("event stream is nil") break @@ -128,8 +135,7 @@ func (c *GRPCClient) startEventStream() { } c.closeM.Unlock() - ctx := context.Background() - stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) + stream, err := c.client.StreamEvents(c.ctx, &pb.Empty{}) if err != nil { c.logger.Warn().Err(err).Msg("failed to start event stream, retrying ...") time.Sleep(5 * time.Second) @@ -161,7 +167,7 @@ func (c *GRPCClient) checkIsReady(ctx context.Context) error { // WaitReady waits for the gRPC connection to be ready func (c *GRPCClient) WaitReady() error { - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancel := context.WithTimeout(c.ctx, 60*time.Second) defer cancel() prevState := connectivity.Idle @@ -243,6 +249,9 @@ func (c *GRPCClient) Close() error { } c.closed = true + // cancel all ongoing operations + c.cancel() + close(c.eventDone) c.eventM.Lock() From d84eb56c21594b7ec8cb669870ea2ede8420441e Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 10:47:58 +0000 Subject: [PATCH 18/25] feat: allow to override max restart attempts --- config.go | 1 + internal/native/proxy.go | 11 +++++++++-- native.go | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 5a3e7dc8f..9beaee02a 100644 --- a/config.go +++ b/config.go @@ -107,6 +107,7 @@ type Config struct { DefaultLogLevel string `json:"default_log_level"` VideoSleepAfterSec int `json:"video_sleep_after_sec"` VideoQualityFactor float64 `json:"video_quality_factor"` + NativeMaxRestart uint `json:"native_max_restart_attempts"` } func (c *Config) GetDisplayRotation() uint16 { diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 344f306e3..830d3ebeb 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -57,7 +57,9 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { // random 16 bytes hex string handshakeMessage := randomId(16) maxRestartAttempts := defaultMaxRestartAttempts - if n.MaxRestartAttempts > 0 { + // though it's unlikely to be less than 0 as it's uint, we'll add it just in case + // until we have a proper unit test for all things :-( + if n.MaxRestartAttempts <= 0 { maxRestartAttempts = n.MaxRestartAttempts } return &nativeProxyOptions{ @@ -330,7 +332,12 @@ func (p *NativeProxy) start() error { return fmt.Errorf("failed to start native process: %w", err) } - p.logger.Info().Int("pid", p.cmd.Process.Pid).Msg("native process started") + // here we'll replace the logger with a new one that includes the process ID + // there's no need to lock the mutex here as the side effect is acceptable + newLogger := p.logger.With().Int("pid", p.cmd.Process.Pid).Logger() + p.logger = &newLogger + + p.logger.Info().Msg("native process started") if err := p.setUpGRPCClient(); err != nil { return fmt.Errorf("failed to set up gRPC client: %w", err) diff --git a/native.go b/native.go index b51aa9b69..5f09ef83a 100644 --- a/native.go +++ b/native.go @@ -29,6 +29,7 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) { AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(), DefaultQualityFactor: config.VideoQualityFactor, + MaxRestartAttempts: config.NativeMaxRestart, OnNativeRestart: func() { configureDisplayOnNativeRestart() }, From 61dcd773137edad483af9c9851d0f52dafcb100b Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 10:53:17 +0000 Subject: [PATCH 19/25] feat: add logger to restartProcess --- internal/native/proxy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 830d3ebeb..397f06067 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -436,11 +436,13 @@ func (p *NativeProxy) restartProcess() error { return fmt.Errorf("max restart attempts reached") } + logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger() if err := p.start(); err != nil { + logger.Error().Err(err).Msg("failed to start native process") return fmt.Errorf("failed to start native process: %w", err) } - p.logger.Info().Msg("native process restarted successfully") + logger.Info().Msg("native process restarted successfully") return nil } From 6dee8a3e24371021b3e633057f4ed8df1a993f00 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 11:19:18 +0000 Subject: [PATCH 20/25] feat: add process ID to logger --- internal/native/native.go | 6 ++++-- internal/native/proxy.go | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/internal/native/native.go b/internal/native/native.go index 5d22361f4..61c4b0ac7 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -1,6 +1,7 @@ package native import ( + "os" "sync" "time" @@ -40,8 +41,9 @@ type NativeOptions struct { } func NewNative(opts NativeOptions) *Native { - nativeSubLogger := nativeLogger.With().Str("scope", "native").Logger() - displaySubLogger := displayLogger.With().Str("scope", "native").Logger() + pid := os.Getpid() + nativeSubLogger := nativeLogger.With().Int("pid", pid).Str("scope", "native").Logger() + displaySubLogger := displayLogger.With().Int("pid", pid).Str("scope", "native").Logger() onVideoStateChange := opts.OnVideoStateChange if onVideoStateChange == nil { diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 397f06067..74896e581 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -57,9 +57,7 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { // random 16 bytes hex string handshakeMessage := randomId(16) maxRestartAttempts := defaultMaxRestartAttempts - // though it's unlikely to be less than 0 as it's uint, we'll add it just in case - // until we have a proper unit test for all things :-( - if n.MaxRestartAttempts <= 0 { + if n.MaxRestartAttempts > 0 { maxRestartAttempts = n.MaxRestartAttempts } return &nativeProxyOptions{ @@ -396,7 +394,7 @@ func (p *NativeProxy) monitorProcess() { } p.restartMu.Unlock() - p.logger.Warn().Err(err).Msg("native process exited, restarting...") + p.logger.Warn().Err(err).Msg("native process exited, restarting ...") // Wait a bit before restarting time.Sleep(1 * time.Second) @@ -416,6 +414,14 @@ func (p *NativeProxy) restartProcess() error { p.restartMu.Lock() defer p.restartMu.Unlock() + p.restarts++ + logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger() + + if p.restarts >= p.options.MaxRestartAttempts { + logger.Fatal().Msg("max restart attempts reached, exiting") + return fmt.Errorf("max restart attempts reached") + } + if p.stopped { return fmt.Errorf("proxy is stopped") } @@ -424,19 +430,14 @@ func (p *NativeProxy) restartProcess() error { p.clientMu.Lock() if p.client != nil { if err := p.client.Close(); err != nil { - p.logger.Warn().Err(err).Msg("failed to close gRPC client") + logger.Warn().Err(err).Msg("failed to close gRPC client") } p.client = nil // set to nil to avoid closing it again } p.clientMu.Unlock() + logger.Info().Msg("gRPC client closed") - p.restarts++ - if p.restarts >= p.options.MaxRestartAttempts { - p.logger.Fatal().Msg("max restart attempts reached, exiting") - return fmt.Errorf("max restart attempts reached") - } - - logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger() + logger.Info().Msg("attempting to restart native process") if err := p.start(); err != nil { logger.Error().Err(err).Msg("failed to start native process") return fmt.Errorf("failed to start native process: %w", err) From 9c0ac4e3d0a6a78efa380d00dc627ea1407c634d Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 11:30:44 +0000 Subject: [PATCH 21/25] fix: multiple goroutines issues --- internal/native/grpc_client.go | 12 ++++++++++++ internal/native/proxy.go | 36 +++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 4ea7bcb20..297bc175b 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -82,6 +82,10 @@ func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) { return grpcClient, nil } +func (c *GRPCClient) getContext() context.Context { + return c.ctx +} + func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClient) { c.eventM.Lock() c.eventStream = stream @@ -135,6 +139,14 @@ func (c *GRPCClient) startEventStream() { } c.closeM.Unlock() + // check if the context is done + select { + case <-c.ctx.Done(): + c.logger.Info().Msg("event stream context done, closing") + return + default: + } + stream, err := c.client.StreamEvents(c.ctx, &pb.Empty{}) if err != nil { c.logger.Warn().Err(err).Msg("failed to start event stream, retrying ...") diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 74896e581..b5091a19e 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -1,6 +1,7 @@ package native import ( + "context" "crypto/rand" "encoding/hex" "fmt" @@ -119,16 +120,15 @@ type NativeProxy struct { videoStreamListener net.Listener binaryPath string - client *GRPCClient - clientMu sync.RWMutex - cmd *cmdWrapper - logger *zerolog.Logger - ready chan struct{} - options *nativeProxyOptions - restartMu sync.Mutex - restarts uint - stopped bool - processWait chan error + client *GRPCClient + clientMu sync.RWMutex + cmd *cmdWrapper + logger *zerolog.Logger + ready chan struct{} + options *nativeProxyOptions + restartMu sync.Mutex + restarts uint + stopped bool } // NewNativeProxy creates a new NativeProxy that spawns a separate process @@ -149,7 +149,6 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { logger: nativeLogger, ready: make(chan struct{}), options: proxyOptions, - processWait: make(chan error, 1), restarts: 0, } @@ -308,7 +307,7 @@ func (p *NativeProxy) setUpGRPCClient() error { } // Start monitoring process for crashes - go p.monitorProcess() + go p.monitorProcess(client.getContext()) return nil } @@ -366,8 +365,14 @@ func (p *NativeProxy) Start() error { } // monitorProcess monitors the native process and restarts it if it crashes -func (p *NativeProxy) monitorProcess() { +func (p *NativeProxy) monitorProcess(ctx context.Context) { for { + select { + case <-ctx.Done(): + return + default: + } + p.restartMu.Lock() cmd := p.cmd stopped := p.stopped @@ -382,8 +387,11 @@ func (p *NativeProxy) monitorProcess() { } err := cmd.Wait() + // check if the context is done + // if yes, it means that there should be already one restart in progress select { - case p.processWait <- err: + case <-ctx.Done(): + return default: } From f3b854c407ecbbed08fe91f86a5a0d9af9b94911 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 11:56:51 +0000 Subject: [PATCH 22/25] refactor(cgo-rpc): rewrite the monitor process logic --- internal/native/proxy.go | 98 ++++++++++++++++++--------------------- internal/native/server.go | 36 +++++++++----- 2 files changed, 68 insertions(+), 66 deletions(-) diff --git a/internal/native/proxy.go b/internal/native/proxy.go index b5091a19e..dda6a86e6 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -120,15 +120,20 @@ type NativeProxy struct { videoStreamListener net.Listener binaryPath string - client *GRPCClient - clientMu sync.RWMutex - cmd *cmdWrapper - logger *zerolog.Logger - ready chan struct{} - options *nativeProxyOptions - restartMu sync.Mutex - restarts uint - stopped bool + startMu sync.Mutex // mutex for the start process (context and isStopped) + ctx context.Context + cancel context.CancelFunc + + client *GRPCClient + clientMu sync.RWMutex // mutex for the client + + cmd *cmdWrapper + cmdMu sync.Mutex // mutex for the cmd + + logger *zerolog.Logger + options *nativeProxyOptions + restarts uint + stopped bool } // NewNativeProxy creates a new NativeProxy that spawns a separate process @@ -147,7 +152,6 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { videoStreamUnixSocket: proxyOptions.VideoStreamUnixSocket, binaryPath: exePath, logger: nativeLogger, - ready: make(chan struct{}), options: proxyOptions, restarts: 0, } @@ -264,10 +268,8 @@ func (p *NativeProxy) handleVideoFrame(conn net.Conn) { } } +// it should be only called by start() method, as it isn't thread-safe func (p *NativeProxy) setUpGRPCClient() error { - p.clientMu.Lock() - defer p.clientMu.Unlock() - // wait until handshake completed select { case <-p.cmd.stdoutHandler.handshakeCh: @@ -306,13 +308,13 @@ func (p *NativeProxy) setUpGRPCClient() error { go p.options.OnNativeRestart() } - // Start monitoring process for crashes - go p.monitorProcess(client.getContext()) - return nil } -func (p *NativeProxy) start() error { +func (p *NativeProxy) doStart() error { + p.cmdMu.Lock() + defer p.cmdMu.Unlock() + // lock OS thread to prevent the process from being moved to a different thread // see also https://go.dev/issue/27505 runtime.LockOSThread() @@ -345,8 +347,10 @@ func (p *NativeProxy) start() error { // Start starts the native process func (p *NativeProxy) Start() error { - p.restartMu.Lock() - defer p.restartMu.Unlock() + p.startMu.Lock() + defer p.startMu.Unlock() + + p.ctx, p.cancel = context.WithCancel(context.Background()) if p.stopped { return fmt.Errorf("proxy is stopped") @@ -356,52 +360,47 @@ func (p *NativeProxy) Start() error { return fmt.Errorf("failed to start video stream listener: %w", err) } - if err := p.start(); err != nil { + if err := p.doStart(); err != nil { return fmt.Errorf("failed to start native process: %w", err) } - close(p.ready) + go p.monitorProcess() + return nil } // monitorProcess monitors the native process and restarts it if it crashes -func (p *NativeProxy) monitorProcess(ctx context.Context) { +func (p *NativeProxy) monitorProcess() { for { + if p.stopped { + return + } + select { - case <-ctx.Done(): + case <-p.ctx.Done(): + p.logger.Trace().Msg("context done, stopping monitor process [before wait]") return default: } - p.restartMu.Lock() - cmd := p.cmd - stopped := p.stopped - p.restartMu.Unlock() - - if stopped { - return + p.cmdMu.Lock() + err := fmt.Errorf("native process not started") + if p.cmd != nil { + err = p.cmd.Wait() } + p.cmdMu.Unlock() - if cmd == nil { + if p.stopped { return } - err := cmd.Wait() - // check if the context is done - // if yes, it means that there should be already one restart in progress select { - case <-ctx.Done(): + case <-p.ctx.Done(): + p.logger.Trace().Msg("context done, stopping monitor process [after wait]") return default: } - p.restartMu.Lock() - if p.stopped { - p.restartMu.Unlock() - return - } - p.restartMu.Unlock() - p.logger.Warn().Err(err).Msg("native process exited, restarting ...") // Wait a bit before restarting @@ -419,9 +418,6 @@ func (p *NativeProxy) monitorProcess(ctx context.Context) { // restartProcess restarts the native process func (p *NativeProxy) restartProcess() error { - p.restartMu.Lock() - defer p.restartMu.Unlock() - p.restarts++ logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger() @@ -446,7 +442,7 @@ func (p *NativeProxy) restartProcess() error { logger.Info().Msg("gRPC client closed") logger.Info().Msg("attempting to restart native process") - if err := p.start(); err != nil { + if err := p.doStart(); err != nil { logger.Error().Err(err).Msg("failed to start native process") return fmt.Errorf("failed to start native process: %w", err) } @@ -457,17 +453,11 @@ func (p *NativeProxy) restartProcess() error { // Stop stops the native process func (p *NativeProxy) Stop() error { - p.restartMu.Lock() - defer p.restartMu.Unlock() + p.startMu.Lock() + defer p.startMu.Unlock() p.stopped = true - p.clientMu.Lock() - if err := p.client.Close(); err != nil { - p.logger.Warn().Err(err).Msg("failed to close IPC client") - } - p.clientMu.Unlock() - if p.cmd.Process != nil { if err := p.cmd.Process.Kill(); err != nil { return fmt.Errorf("failed to kill native process: %w", err) diff --git a/internal/native/server.go b/internal/native/server.go index a2835f19c..252648d6f 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -10,6 +10,7 @@ import ( "github.com/caarlos0/env/v11" "github.com/erikdubbelboer/gspt" + "github.com/rs/zerolog" ) // Native Process @@ -21,6 +22,10 @@ var ( lastProcTitle string ) +const ( + DebugModeFile = "/userdata/jetkvm/.native-debug-mode" +) + func setProcTitle(status string) { lastProcTitle = status if status != "" { @@ -30,6 +35,21 @@ func setProcTitle(status string) { gspt.SetProcTitle(title) } +func monitorCrashSignal(logger *zerolog.Logger, nativeInstance NativeInterface) { + logger.Info().Msg("DEBUG mode: will crash the process on SIGHUP signal") + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGHUP) + + // non-blocking receive + select { + case <-sigChan: + logger.Info().Msg("received SIGHUP signal, emulating crash") + nativeInstance.DoNotUseThisIsForCrashTestingOnly() + default: + } +} + // RunNativeProcess runs the native process mode func RunNativeProcess(binaryName string) { logger := nativeLogger.With().Int("pid", os.Getpid()).Logger() @@ -85,18 +105,10 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to write handshake message to stdout") } - go func() { - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGHUP) - - // non-blocking receive - select { - case <-sigChan: - logger.Info().Msg("received SIGHUP signal, emulating crash") - nativeInstance.DoNotUseThisIsForCrashTestingOnly() - default: - } - }() + if _, err := os.Stat(DebugModeFile); err == nil { + logger.Info().Msg("DEBUG mode: enabled") + go monitorCrashSignal(&logger, nativeInstance) + } // Set up signal handling sigChan := make(chan os.Signal, 1) From ddce62438cd5a9a5d4100194f55236d0f1eee9a0 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 12:09:10 +0000 Subject: [PATCH 23/25] fix: signal handling in the native process --- internal/native/grpc_client.go | 4 ---- internal/native/server.go | 37 +++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 297bc175b..300a22848 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -82,10 +82,6 @@ func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) { return grpcClient, nil } -func (c *GRPCClient) getContext() context.Context { - return c.ctx -} - func (c *GRPCClient) handleEventStream(stream pb.NativeService_StreamEventsClient) { c.eventM.Lock() c.eventStream = stream diff --git a/internal/native/server.go b/internal/native/server.go index 252648d6f..ae983159d 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -1,6 +1,7 @@ package native import ( + "context" "fmt" "net" "os" @@ -35,23 +36,29 @@ func setProcTitle(status string) { gspt.SetProcTitle(title) } -func monitorCrashSignal(logger *zerolog.Logger, nativeInstance NativeInterface) { +func monitorCrashSignal(ctx context.Context, logger *zerolog.Logger, nativeInstance NativeInterface) { logger.Info().Msg("DEBUG mode: will crash the process on SIGHUP signal") sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGHUP) - // non-blocking receive - select { - case <-sigChan: - logger.Info().Msg("received SIGHUP signal, emulating crash") - nativeInstance.DoNotUseThisIsForCrashTestingOnly() - default: + for { + select { + case sig := <-sigChan: + logger.Info().Str("signal", sig.String()).Msg("received termination signal") + nativeInstance.DoNotUseThisIsForCrashTestingOnly() + case <-ctx.Done(): + logger.Info().Msg("context done, stopping monitor process") + return + } } } // RunNativeProcess runs the native process mode func RunNativeProcess(binaryName string) { + appCtx, appCtxCancel := context.WithCancel(context.Background()) + defer appCtxCancel() + logger := nativeLogger.With().Int("pid", os.Getpid()).Logger() setProcTitle("starting") @@ -98,6 +105,11 @@ func RunNativeProcess(binaryName string) { } setProcTitle("ready") + if _, err := os.Stat(DebugModeFile); err == nil { + logger.Info().Msg("DEBUG mode: enabled") + go monitorCrashSignal(appCtx, &logger, nativeInstance) + } + // Signal that we're ready by writing handshake message to stdout (for parent to read) // Stdout.Write is used to avoid buffering the message _, err = os.Stdout.Write([]byte(proxyOptions.HandshakeMessage + "\n")) @@ -105,11 +117,6 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to write handshake message to stdout") } - if _, err := os.Stat(DebugModeFile); err == nil { - logger.Info().Msg("DEBUG mode: enabled") - go monitorCrashSignal(&logger, nativeInstance) - } - // Set up signal handling sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) @@ -120,8 +127,10 @@ func RunNativeProcess(binaryName string) { Str("signal", sig.String()). Msg("received termination signal") - // Graceful shutdown - server.GracefulStop() + // Graceful shutdown might stuck forever, + // we will use Stop() instead to force quit the gRPC server, + // we can implement a graceful shutdown with a timeout in the future if needed + server.Stop() lis.Close() logger.Info().Msg("native process exiting") From 9eccc225f93f1db5c07eb41d3f7378bba4b3b347 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Tue, 18 Nov 2025 12:11:43 +0000 Subject: [PATCH 24/25] chore: add README.md for native process debugging --- internal/native/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/native/README.md b/internal/native/README.md index a02976e40..97a2cf79a 100644 --- a/internal/native/README.md +++ b/internal/native/README.md @@ -3,4 +3,18 @@ This component (`internal/native/`) acts as a bridge between Golang and native (C/C++) code. It manages spawning and communicating with a native process via sockets (gRPC and Unix stream). -For performance-critical operations such as video frame, **a dedicated Unix socket should be used** to avoid the overhead of gRPC and ensure low-latency communication. \ No newline at end of file +For performance-critical operations such as video frame, **a dedicated Unix socket should be used** to avoid the overhead of gRPC and ensure low-latency communication. + +## Debugging + +To enable debug mode, create a file called `.native-debug-mode` in the `/userdata/jetkvm` directory. + +```bash +touch /userdata/jetkvm/.native-debug-mode +``` + +This will cause the native process to listen for SIGHUP signal and crash the process. + +```bash +pgrep native | xargs kill -SIGHUP +``` \ No newline at end of file From a1f90da665d74473c73d4defd214ce92a56033e0 Mon Sep 17 00:00:00 2001 From: Aveline <352441+ym@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:56:02 +0000 Subject: [PATCH 25/25] fix --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index 9a1e5e88e..a4d80fb74 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,6 @@ func Main() { Msg("starting JetKVM") go runWatchdog() - go confirmCurrentSystem() setProcTitle("initNative") initNative(systemVersionLocal, appVersionLocal)