wip: move cgo to separate process

This commit is contained in:
Siyuan 2025-11-11 15:53:40 +00:00
parent 5fb4c629dd
commit ffc26ac4e7
18 changed files with 3748 additions and 7 deletions

View File

@ -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() {
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 {

3
go.mod
View File

@ -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
)

6
go.sum
View File

@ -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=

View File

@ -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
+}

View File

@ -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
}

View File

@ -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()
}

293
internal/native/ipc.go Normal file
View File

@ -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()
}

View File

@ -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

View File

@ -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.

View File

@ -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 }

View File

@ -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;
}

View File

@ -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",
}

682
internal/native/proxy.go Normal file
View File

@ -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)
}

View File

@ -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
)

View File

@ -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")

528
native_process.go Normal file
View File

@ -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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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, &params); 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)
}
}

44
scripts/generate_proto.sh Executable file
View File

@ -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!"

134
scripts/release.sh Normal file
View File

@ -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 <version>"
echo
echo "Required:"
echo " --app-version <version> App version to release"
echo " --system-version <version> System version to release"
echo
echo "Optional:"
echo " -u, --user <remote_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."