kvm/internal/native/grpc_client.go

419 lines
11 KiB
Go

package native
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/rs/zerolog"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
pb "github.com/jetkvm/kvm/internal/native/proto"
)
// GRPCClient wraps the gRPC client for the native service
type GRPCClient struct {
conn *grpc.ClientConn
client pb.NativeServiceClient
logger *zerolog.Logger
eventStream pb.NativeService_StreamEventsClient
eventM sync.RWMutex
eventCh chan *pb.Event
eventDone chan struct{}
closed bool
closeM sync.Mutex
}
// NewGRPCClient creates a new gRPC client connected to the native service
func NewGRPCClient(socketPath string, logger *zerolog.Logger) (*GRPCClient, error) {
// Connect to the Unix domain socket
conn, err := grpc.NewClient(
fmt.Sprintf("unix-abstract:%v", socketPath),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gRPC server: %w", err)
}
client := pb.NewNativeServiceClient(conn)
grpcClient := &GRPCClient{
conn: conn,
client: client,
logger: logger,
eventCh: make(chan *pb.Event, 100),
eventDone: make(chan struct{}),
}
// Start event stream
go grpcClient.startEventStream()
return grpcClient, nil
}
func (c *GRPCClient) startEventStream() {
// for {
// return
// c.closeM.Lock()
// if c.closed {
// c.closeM.Unlock()
// return
// }
// c.closeM.Unlock()
// ctx := context.Background()
// stream, err := c.client.StreamEvents(ctx, &pb.Empty{})
// if err != nil {
// c.logger.Warn().Err(err).Msg("failed to start event stream, retrying...")
// time.Sleep(1 * time.Second)
// continue
// }
// c.eventM.Lock()
// c.eventStream = stream
// c.eventM.Unlock()
// for {
// event, err := stream.Recv()
// if err == io.EOF {
// c.logger.Debug().Msg("event stream closed")
// break
// }
// if err != nil {
// c.logger.Warn().Err(err).Msg("event stream error")
// break
// }
// select {
// case c.eventCh <- event:
// default:
// c.logger.Warn().Msg("event channel full, dropping event")
// }
// }
// c.eventM.Lock()
// c.eventStream = nil
// c.eventM.Unlock()
// // Wait before retrying
// time.Sleep(1 * time.Second)
// }
}
func (c *GRPCClient) checkIsReady(ctx context.Context) error {
c.logger.Info().Msg("connection is idle, connecting...")
resp, err := c.client.IsReady(ctx, &pb.IsReadyRequest{})
if err != nil {
if errors.Is(err, status.Error(codes.Unavailable, "")) {
return fmt.Errorf("timeout waiting for ready: %w", err)
}
return fmt.Errorf("failed to check if ready: %w", err)
}
if resp.Ready {
return nil
}
return nil
}
// WaitReady waits for the gRPC connection to be ready
func (c *GRPCClient) WaitReady() error {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
for {
state := c.conn.GetState()
if state == connectivity.Idle || state == connectivity.Ready {
if err := c.checkIsReady(ctx); err != nil {
time.Sleep(1 * time.Second)
continue
}
}
c.logger.Info().Str("state", state.String()).Msg("waiting for connection to be ready")
if state == connectivity.Ready {
return nil
}
if state == connectivity.Shutdown {
return fmt.Errorf("connection failed: %v", state)
}
if !c.conn.WaitForStateChange(ctx, state) {
return ctx.Err()
}
}
}
// OnEvent registers an event handler
func (c *GRPCClient) OnEvent(eventType string, handler func(data interface{})) {
return
// go func() {
// for {
// select {
// case event := <-c.eventCh:
// if event.Type == eventType {
// var data interface{}
// switch eventType {
// case "video_state_change":
// if event.VideoState != nil {
// data = VideoState{
// Ready: event.VideoState.Ready,
// Error: event.VideoState.Error,
// Width: int(event.VideoState.Width),
// Height: int(event.VideoState.Height),
// FramePerSecond: event.VideoState.FramePerSecond,
// }
// }
// case "indev_event":
// data = event.IndevEvent
// case "rpc_event":
// data = event.RpcEvent
// case "video_frame":
// if event.VideoFrame != nil {
// data = map[string]interface{}{
// "frame": event.VideoFrame.Frame,
// "duration": time.Duration(event.VideoFrame.DurationNs),
// }
// }
// }
// if data != nil {
// handler(data)
// }
// }
// case <-c.eventDone:
// return
// }
// }
// }()
}
// Close closes the gRPC client
func (c *GRPCClient) Close() error {
c.closeM.Lock()
defer c.closeM.Unlock()
if c.closed {
return nil
}
c.closed = true
close(c.eventDone)
c.eventM.Lock()
if c.eventStream != nil {
c.eventStream.CloseSend()
}
c.eventM.Unlock()
return c.conn.Close()
}
// Video methods
func (c *GRPCClient) VideoSetSleepMode(enabled bool) error {
_, err := c.client.VideoSetSleepMode(context.Background(), &pb.VideoSetSleepModeRequest{Enabled: enabled})
return err
}
func (c *GRPCClient) VideoGetSleepMode() (bool, error) {
resp, err := c.client.VideoGetSleepMode(context.Background(), &pb.Empty{})
if err != nil {
return false, err
}
return resp.Enabled, nil
}
func (c *GRPCClient) VideoSleepModeSupported() bool {
resp, err := c.client.VideoSleepModeSupported(context.Background(), &pb.Empty{})
if err != nil {
return false
}
return resp.Supported
}
func (c *GRPCClient) VideoSetQualityFactor(factor float64) error {
_, err := c.client.VideoSetQualityFactor(context.Background(), &pb.VideoSetQualityFactorRequest{Factor: factor})
return err
}
func (c *GRPCClient) VideoGetQualityFactor() (float64, error) {
resp, err := c.client.VideoGetQualityFactor(context.Background(), &pb.Empty{})
if err != nil {
return 0, err
}
return resp.Factor, nil
}
func (c *GRPCClient) VideoSetEDID(edid string) error {
_, err := c.client.VideoSetEDID(context.Background(), &pb.VideoSetEDIDRequest{Edid: edid})
return err
}
func (c *GRPCClient) VideoGetEDID() (string, error) {
resp, err := c.client.VideoGetEDID(context.Background(), &pb.Empty{})
if err != nil {
return "", err
}
return resp.Edid, nil
}
func (c *GRPCClient) VideoLogStatus() (string, error) {
resp, err := c.client.VideoLogStatus(context.Background(), &pb.Empty{})
if err != nil {
return "", err
}
return resp.Status, nil
}
func (c *GRPCClient) VideoStop() error {
_, err := c.client.VideoStop(context.Background(), &pb.Empty{})
return err
}
func (c *GRPCClient) VideoStart() error {
_, err := c.client.VideoStart(context.Background(), &pb.Empty{})
return err
}
// UI methods
func (c *GRPCClient) GetLVGLVersion() (string, error) {
resp, err := c.client.GetLVGLVersion(context.Background(), &pb.Empty{})
if err != nil {
return "", err
}
return resp.Version, nil
}
func (c *GRPCClient) UIObjHide(objName string) (bool, error) {
resp, err := c.client.UIObjHide(context.Background(), &pb.UIObjHideRequest{ObjName: objName})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjShow(objName string) (bool, error) {
resp, err := c.client.UIObjShow(context.Background(), &pb.UIObjShowRequest{ObjName: objName})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UISetVar(name string, value string) {
_, _ = c.client.UISetVar(context.Background(), &pb.UISetVarRequest{Name: name, Value: value})
}
func (c *GRPCClient) UIGetVar(name string) string {
resp, err := c.client.UIGetVar(context.Background(), &pb.UIGetVarRequest{Name: name})
if err != nil {
return ""
}
return resp.Value
}
func (c *GRPCClient) UIObjAddState(objName string, state string) (bool, error) {
resp, err := c.client.UIObjAddState(context.Background(), &pb.UIObjAddStateRequest{ObjName: objName, State: state})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjClearState(objName string, state string) (bool, error) {
resp, err := c.client.UIObjClearState(context.Background(), &pb.UIObjClearStateRequest{ObjName: objName, State: state})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjAddFlag(objName string, flag string) (bool, error) {
resp, err := c.client.UIObjAddFlag(context.Background(), &pb.UIObjAddFlagRequest{ObjName: objName, Flag: flag})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjClearFlag(objName string, flag string) (bool, error) {
resp, err := c.client.UIObjClearFlag(context.Background(), &pb.UIObjClearFlagRequest{ObjName: objName, Flag: flag})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjSetOpacity(objName string, opacity int) (bool, error) {
resp, err := c.client.UIObjSetOpacity(context.Background(), &pb.UIObjSetOpacityRequest{ObjName: objName, Opacity: int32(opacity)})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjFadeIn(objName string, duration uint32) (bool, error) {
resp, err := c.client.UIObjFadeIn(context.Background(), &pb.UIObjFadeInRequest{ObjName: objName, Duration: duration})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjFadeOut(objName string, duration uint32) (bool, error) {
resp, err := c.client.UIObjFadeOut(context.Background(), &pb.UIObjFadeOutRequest{ObjName: objName, Duration: duration})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjSetLabelText(objName string, text string) (bool, error) {
resp, err := c.client.UIObjSetLabelText(context.Background(), &pb.UIObjSetLabelTextRequest{ObjName: objName, Text: text})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UIObjSetImageSrc(objName string, image string) (bool, error) {
resp, err := c.client.UIObjSetImageSrc(context.Background(), &pb.UIObjSetImageSrcRequest{ObjName: objName, Image: image})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) DisplaySetRotation(rotation uint16) (bool, error) {
resp, err := c.client.DisplaySetRotation(context.Background(), &pb.DisplaySetRotationRequest{Rotation: uint32(rotation)})
if err != nil {
return false, err
}
return resp.Success, nil
}
func (c *GRPCClient) UpdateLabelIfChanged(objName string, newText string) {
_, _ = c.client.UpdateLabelIfChanged(context.Background(), &pb.UpdateLabelIfChangedRequest{ObjName: objName, NewText: newText})
}
func (c *GRPCClient) UpdateLabelAndChangeVisibility(objName string, newText string) {
_, _ = c.client.UpdateLabelAndChangeVisibility(context.Background(), &pb.UpdateLabelAndChangeVisibilityRequest{ObjName: objName, NewText: newText})
}
func (c *GRPCClient) SwitchToScreenIf(screenName string, shouldSwitch []string) {
_, _ = c.client.SwitchToScreenIf(context.Background(), &pb.SwitchToScreenIfRequest{ScreenName: screenName, ShouldSwitch: shouldSwitch})
}
func (c *GRPCClient) SwitchToScreenIfDifferent(screenName string) {
_, _ = c.client.SwitchToScreenIfDifferent(context.Background(), &pb.SwitchToScreenIfDifferentRequest{ScreenName: screenName})
}
func (c *GRPCClient) DoNotUseThisIsForCrashTestingOnly() {
_, _ = c.client.DoNotUseThisIsForCrashTestingOnly(context.Background(), &pb.Empty{})
}