reconfigure display on native restart

This commit is contained in:
Siyuan 2025-11-13 14:24:52 +00:00
parent 91d3b47ec3
commit afd8ab75bc
6 changed files with 68 additions and 18 deletions

View File

@ -232,6 +232,14 @@ func updateStaticContents() {
// nativeInstance.UpdateLabelAndChangeVisibility("boot_screen_device_id", GetDeviceID()) // nativeInstance.UpdateLabelAndChangeVisibility("boot_screen_device_id", GetDeviceID())
} }
// configureDisplayOnNativeRestart is called when the native process restarts
// it ensures the display is configured correctly after the restart
func configureDisplayOnNativeRestart() {
displayLogger.Info().Msg("native restarted, configuring display")
updateStaticContents()
requestDisplayUpdate(true, "native_restart")
}
// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter // setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter
// the backlight brightness of the JetKVM hardware's display. // the backlight brightness of the JetKVM hardware's display.
func setDisplayBrightness(brightness int, reason string) error { func setDisplayBrightness(brightness int, reason string) error {

View File

@ -6,7 +6,6 @@ import (
"net" "net"
"sync" "sync"
"github.com/erikdubbelboer/gspt"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -160,6 +159,9 @@ func (s *grpcServer) VideoLogStatus(ctx context.Context, req *pb.Empty) (*pb.Vid
} }
func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, error) {
procPrefix = "jetkvm: [native]"
setProcTitle(lastProcTitle)
if err := s.native.VideoStop(); err != nil { if err := s.native.VideoStop(); err != nil {
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
@ -167,6 +169,9 @@ func (s *grpcServer) VideoStop(ctx context.Context, req *pb.Empty) (*pb.Empty, e
} }
func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) { func (s *grpcServer) VideoStart(ctx context.Context, req *pb.Empty) (*pb.Empty, error) {
procPrefix = "jetkvm: [native+video]"
setProcTitle(lastProcTitle)
if err := s.native.VideoStart(); err != nil { if err := s.native.VideoStart(); err != nil {
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
@ -315,7 +320,8 @@ func (s *grpcServer) DoNotUseThisIsForCrashTestingOnly(ctx context.Context, req
// StreamEvents streams events from the native process // StreamEvents streams events from the native process
func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error { func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamEventsServer) error {
gspt.SetProcTitle("jetkvm: [native] connected") setProcTitle("connected")
defer setProcTitle("waiting")
eventCh := make(chan *pb.Event, 100) eventCh := make(chan *pb.Event, 100)

View File

@ -37,6 +37,7 @@ type NativeOptions struct {
OnVideoFrameReceived func(frame []byte, duration time.Duration) OnVideoFrameReceived func(frame []byte, duration time.Duration)
OnIndevEvent func(event string) OnIndevEvent func(event string)
OnRpcEvent func(event string) OnRpcEvent func(event string)
OnNativeRestart func()
} }
func NewNative(opts NativeOptions) *Native { func NewNative(opts NativeOptions) *Native {

View File

@ -33,11 +33,13 @@ type nativeProxyOptions struct {
BinaryPath string `env:"JETKVM_NATIVE_BINARY_PATH"` BinaryPath string `env:"JETKVM_NATIVE_BINARY_PATH"`
LoggerLevel zerolog.Level `env:"JETKVM_NATIVE_LOGGER_LEVEL"` LoggerLevel zerolog.Level `env:"JETKVM_NATIVE_LOGGER_LEVEL"`
HandshakeMessage string `env:"JETKVM_NATIVE_HANDSHAKE_MESSAGE"` HandshakeMessage string `env:"JETKVM_NATIVE_HANDSHAKE_MESSAGE"`
MaxRestartAttempts uint
OnVideoFrameReceived func(frame []byte, duration time.Duration) OnVideoFrameReceived func(frame []byte, duration time.Duration)
OnIndevEvent func(event string) OnIndevEvent func(event string)
OnRpcEvent func(event string) OnRpcEvent func(event string)
OnVideoStateChange func(state VideoState) OnVideoStateChange func(state VideoState)
OnNativeRestart func()
} }
func randomId(binaryLength int) string { func randomId(binaryLength int) string {
@ -63,6 +65,7 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions {
OnIndevEvent: n.OnIndevEvent, OnIndevEvent: n.OnIndevEvent,
OnRpcEvent: n.OnRpcEvent, OnRpcEvent: n.OnRpcEvent,
OnVideoStateChange: n.OnVideoStateChange, OnVideoStateChange: n.OnVideoStateChange,
OnNativeRestart: n.OnNativeRestart,
HandshakeMessage: handshakeMessage, HandshakeMessage: handshakeMessage,
} }
} }
@ -118,6 +121,7 @@ type NativeProxy struct {
ready chan struct{} ready chan struct{}
options *nativeProxyOptions options *nativeProxyOptions
restartM sync.Mutex restartM sync.Mutex
restarts uint
stopped bool stopped bool
processWait chan error processWait chan error
} }
@ -142,10 +146,7 @@ func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) {
ready: make(chan struct{}), ready: make(chan struct{}),
options: proxyOptions, options: proxyOptions,
processWait: make(chan error, 1), processWait: make(chan error, 1),
} restarts: 0,
proxy.cmd, err = proxy.toProcessCommand()
if err != nil {
return nil, fmt.Errorf("failed to create process: %w", err)
} }
return proxy, nil return proxy, nil
@ -195,6 +196,7 @@ func (w *nativeProxyStdoutHandler) Write(p []byte) (n int, err error) {
if !w.handshakeDone && strings.Contains(string(p), w.handshakeMessage) { if !w.handshakeDone && strings.Contains(string(p), w.handshakeMessage) {
w.handshakeDone = true w.handshakeDone = true
w.handshakeCh <- true w.handshakeCh <- true
return len(p), nil
} }
os.Stdout.Write(p) os.Stdout.Write(p)
@ -287,6 +289,11 @@ func (p *NativeProxy) setUpGRPCClient() error {
return fmt.Errorf("failed to wait for ready: %w", err) return fmt.Errorf("failed to wait for ready: %w", err)
} }
// Call on native restart callback if it exists and restarts are greater than 0
if p.options.OnNativeRestart != nil && p.restarts > 0 {
p.options.OnNativeRestart()
}
// Start monitoring process for crashes // Start monitoring process for crashes
go p.monitorProcess() go p.monitorProcess()
return nil return nil
@ -298,6 +305,13 @@ func (p *NativeProxy) start() error {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
cmd, err := p.toProcessCommand()
if err != nil {
return fmt.Errorf("failed to create process: %w", err)
}
p.cmd = cmd
if err := p.cmd.Start(); err != nil { if err := p.cmd.Start(); err != nil {
return fmt.Errorf("failed to start native process: %w", err) return fmt.Errorf("failed to start native process: %w", err)
} }
@ -390,6 +404,8 @@ func (p *NativeProxy) restartProcess() error {
_ = p.client.Close() _ = p.client.Close()
} }
p.restarts++
if err := p.start(); err != nil { if err := p.start(); err != nil {
return fmt.Errorf("failed to start native process: %w", err) return fmt.Errorf("failed to start native process: %w", err)
} }

View File

@ -16,24 +16,37 @@ import (
// stdout - exchange messages with the parent process // stdout - exchange messages with the parent process
// stderr - logging and error messages // stderr - logging and error messages
var (
procPrefix string = "jetkvm: [native]"
lastProcTitle string
)
func setProcTitle(status string) {
lastProcTitle = status
if status != "" {
status = " " + status
}
title := fmt.Sprintf("%s%s", procPrefix, status)
gspt.SetProcTitle(title)
}
// RunNativeProcess runs the native process mode // RunNativeProcess runs the native process mode
func RunNativeProcess(binaryName string) { func RunNativeProcess(binaryName string) {
logger := *nativeLogger logger := nativeLogger.With().Int("pid", os.Getpid()).Logger()
// Initialize logger setProcTitle("starting")
gspt.SetProcTitle("jetkvm: [native] starting")
// Parse native options
var proxyOptions nativeProxyOptions var proxyOptions nativeProxyOptions
if err := env.Parse(&proxyOptions); err != nil { if err := env.Parse(&proxyOptions); err != nil {
logger.Fatal().Err(err).Msg("failed to parse native options") logger.Fatal().Err(err).Msg("failed to parse native proxy options")
} }
// connect to video stream socket // Connect to video stream socket
conn, err := net.Dial("unixpacket", proxyOptions.VideoStreamUnixSocket) conn, err := net.Dial("unixpacket", proxyOptions.VideoStreamUnixSocket)
if err != nil { if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to video stream socket") logger.Fatal().Err(err).Msg("failed to connect to video stream socket")
} }
logger.Info().Str("video_stream_socket_path", proxyOptions.VideoStreamUnixSocket).Msg("connected to video stream socket") logger.Info().Str("videoStreamSocketPath", proxyOptions.VideoStreamUnixSocket).Msg("connected to video stream socket")
nativeOptions := proxyOptions.toNativeOptions() nativeOptions := proxyOptions.toNativeOptions()
nativeOptions.OnVideoFrameReceived = func(frame []byte, duration time.Duration) { nativeOptions.OnVideoFrameReceived = func(frame []byte, duration time.Duration) {
@ -52,9 +65,10 @@ func RunNativeProcess(binaryName string) {
logger.Fatal().Err(err).Msg("failed to start native instance") logger.Fatal().Err(err).Msg("failed to start native instance")
} }
gspt.SetProcTitle("jetkvm: [native] starting gRPC server") grpcLogger := logger.With().Str("socketPath", fmt.Sprintf("@%v", proxyOptions.CtrlUnixSocket)).Logger()
setProcTitle("starting gRPC server")
// Create gRPC server // Create gRPC server
grpcServer := NewGRPCServer(nativeInstance, &logger) grpcServer := NewGRPCServer(nativeInstance, &grpcLogger)
logger.Info().Msg("starting gRPC server") logger.Info().Msg("starting gRPC server")
// Start gRPC server // Start gRPC server
@ -62,7 +76,7 @@ func RunNativeProcess(binaryName string) {
if err != nil { if err != nil {
logger.Fatal().Err(err).Msg("failed to start gRPC server") logger.Fatal().Err(err).Msg("failed to start gRPC server")
} }
gspt.SetProcTitle("jetkvm: [native] ready") setProcTitle("ready")
// Signal that we're ready by writing handshake message to stdout (for parent to read) // Signal that we're ready by writing handshake message to stdout (for parent to read)
// Stdout.Write is used to avoid buffering the message // Stdout.Write is used to avoid buffering the message
@ -76,8 +90,10 @@ func RunNativeProcess(binaryName string) {
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
// Wait for signal // Wait for signal
<-sigChan sig := <-sigChan
logger.Info().Msg("received termination signal") logger.Info().
Str("signal", sig.String()).
Msg("received termination signal")
// Graceful shutdown // Graceful shutdown
server.GracefulStop() server.GracefulStop()

View File

@ -24,6 +24,9 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
AppVersion: appVersion, AppVersion: appVersion,
DisplayRotation: config.GetDisplayRotation(), DisplayRotation: config.GetDisplayRotation(),
DefaultQualityFactor: config.VideoQualityFactor, DefaultQualityFactor: config.VideoQualityFactor,
OnNativeRestart: func() {
configureDisplayOnNativeRestart()
},
OnVideoStateChange: func(state native.VideoState) { OnVideoStateChange: func(state native.VideoState) {
lastVideoState = state lastVideoState = state
triggerVideoStateUpdate() triggerVideoStateUpdate()