package remote import ( "fmt" "os/exec" "time" "github.com/kataras/go-events" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "n.eko.moe/neko/internal/gst" "n.eko.moe/neko/internal/types" "n.eko.moe/neko/internal/types/config" "n.eko.moe/neko/internal/xorg" ) type RemoteManager struct { logger zerolog.Logger video *gst.Pipeline audio *gst.Pipeline config *config.Remote broadcast types.BroadcastManager cleanup *time.Ticker shutdown chan bool emmiter events.EventEmmiter streaming bool } func New(config *config.Remote, broadcast types.BroadcastManager) *RemoteManager { return &RemoteManager{ logger: log.With().Str("module", "remote").Logger(), cleanup: time.NewTicker(1 * time.Second), shutdown: make(chan bool), emmiter: events.New(), config: config, broadcast: broadcast, streaming: false, } } func (manager *RemoteManager) VideoCodec() string { return manager.config.VideoCodec } func (manager *RemoteManager) AudioCodec() string { return manager.config.AudioCodec } func (manager *RemoteManager) Start() { xorg.Display(manager.config.Display) if !xorg.ValidScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) { manager.logger.Warn().Msgf("invalid screen option %dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) } else if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil { manager.logger.Warn().Err(err).Msg("unable to change screen size") } manager.createPipelines() manager.broadcast.Start() go func() { defer func() { manager.logger.Info().Msg("shutdown") }() for { select { case <-manager.shutdown: return case sample := <-manager.video.Sample: manager.emmiter.Emit("video", sample) case sample := <-manager.audio.Sample: manager.emmiter.Emit("audio", sample) case <-manager.cleanup.C: xorg.CheckKeys(time.Second * 10) } } }() } func (manager *RemoteManager) Shutdown() error { manager.logger.Info().Msgf("remote shutting down") manager.video.Stop() manager.audio.Stop() manager.broadcast.Stop() manager.cleanup.Stop() manager.shutdown <- true return nil } func (manager *RemoteManager) OnVideoFrame(listener func(sample types.Sample)) { manager.emmiter.On("video", func(payload ...interface{}) { listener(payload[0].(types.Sample)) }) } func (manager *RemoteManager) OnAudioFrame(listener func(sample types.Sample)) { manager.emmiter.On("audio", func(payload ...interface{}) { listener(payload[0].(types.Sample)) }) } func (manager *RemoteManager) StartStream() { manager.createPipelines() manager.logger.Info(). Str("video_display", manager.config.Display). Str("video_codec", manager.config.VideoCodec). Str("audio_device", manager.config.Device). Str("audio_codec", manager.config.AudioCodec). Str("audio_pipeline_src", manager.audio.Src). Str("video_pipeline_src", manager.video.Src). Str("screen_resolution", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)). Msgf("Pipelines starting...") manager.video.Start() manager.audio.Start() manager.streaming = true } func (manager *RemoteManager) StopStream() { manager.logger.Info().Msgf("Pipelines shutting down...") manager.video.Stop() manager.audio.Stop() manager.streaming = false } func (manager *RemoteManager) Streaming() bool { return manager.streaming } func (manager *RemoteManager) createPipelines() { // handle maximum fps rate := manager.config.ScreenRate if manager.config.MaxFPS != 0 && manager.config.MaxFPS < manager.config.ScreenRate { rate = manager.config.MaxFPS } var err error manager.video, err = gst.CreateAppPipeline( manager.config.VideoCodec, manager.config.Display, manager.config.VideoParams, rate, manager.config.VideoBitrate, ) if err != nil { manager.logger.Panic().Err(err).Msg("unable to create video pipeline") } manager.audio, err = gst.CreateAppPipeline( manager.config.AudioCodec, manager.config.Device, manager.config.AudioParams, 0, // fps: n/a for audio manager.config.AudioBitrate, ) if err != nil { manager.logger.Panic().Err(err).Msg("unable to create audio pipeline") } } func (manager *RemoteManager) ChangeResolution(width int, height int, rate int) error { if !xorg.ValidScreenSize(width, height, rate) { return fmt.Errorf("unknown configuration") } manager.video.Stop() manager.broadcast.Stop() defer func() { manager.video.Start() manager.broadcast.Start() manager.logger.Info().Msg("starting video pipeline...") }() if err := xorg.ChangeScreenSize(width, height, rate); err != nil { return err } // handle maximum fps if manager.config.MaxFPS != 0 && manager.config.MaxFPS < rate { rate = manager.config.MaxFPS } var err error manager.video, err = gst.CreateAppPipeline( manager.config.VideoCodec, manager.config.Display, manager.config.VideoParams, rate, manager.config.VideoBitrate, ) if err != nil { manager.logger.Panic().Err(err).Msg("unable to create new video pipeline") } return nil } func (manager *RemoteManager) Move(x, y int) { xorg.Move(x, y) } func (manager *RemoteManager) Scroll(x, y int) { xorg.Scroll(x, y) } func (manager *RemoteManager) ButtonDown(code int) error { return xorg.ButtonDown(code) } func (manager *RemoteManager) KeyDown(code uint64) error { return xorg.KeyDown(code) } func (manager *RemoteManager) ButtonUp(code int) error { return xorg.ButtonUp(code) } func (manager *RemoteManager) KeyUp(code uint64) error { return xorg.KeyUp(code) } func (manager *RemoteManager) ReadClipboard() string { return xorg.ReadClipboard() } func (manager *RemoteManager) WriteClipboard(data string) { xorg.WriteClipboard(data) } func (manager *RemoteManager) ResetKeys() { xorg.ResetKeys() } func (manager *RemoteManager) ScreenConfigurations() map[int]types.ScreenConfiguration { return xorg.ScreenConfigurations } func (manager *RemoteManager) GetScreenSize() *types.ScreenSize { return xorg.GetScreenSize() } func (manager *RemoteManager) SetKeyboardLayout(layout string) { // Workaround for https://github.com/m1k1o/neko/issues/45 // When pressing `shift` + `,` instead of `<` comes `>`. variant := "" // if layout == "us" { // variant = "mac" // TODO: Test all keys. // } exec.Command("setxkbmap", layout, variant).Run() } func (manager *RemoteManager) SetKeyboardModifiers(NumLock int, CapsLock int, ScrollLock int) { xorg.SetKeyboardModifiers(NumLock, CapsLock, ScrollLock) }