mirror of https://github.com/jetkvm/kvm.git
209 lines
5.2 KiB
Go
209 lines
5.2 KiB
Go
package native
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
|
|
|
// DefaultEDID is the default EDID for the video stream.
|
|
const DefaultEDID = "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"
|
|
|
|
var extraLockTimeout = 5 * time.Second
|
|
|
|
// VideoState is the state of the video stream.
|
|
type VideoState struct {
|
|
Ready bool `json:"ready"`
|
|
Streaming VideoStreamingStatus `json:"streaming"`
|
|
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
FramePerSecond float64 `json:"fps"`
|
|
}
|
|
|
|
func isSleepModeSupported() bool {
|
|
_, err := os.Stat(sleepModeFile)
|
|
return err == nil
|
|
}
|
|
|
|
const sleepModeWaitTimeout = 3 * time.Second
|
|
|
|
func (n *Native) waitForVideoStreamingStatus(status VideoStreamingStatus) error {
|
|
timeout := time.After(sleepModeWaitTimeout)
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
if videoGetStreamingStatus() == status {
|
|
return nil
|
|
}
|
|
select {
|
|
case <-timeout:
|
|
return fmt.Errorf("timed out waiting for video streaming status to be %s", status.String())
|
|
case <-ticker.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
// before calling this function, make sure to lock n.videoLock
|
|
func (n *Native) setSleepMode(enabled bool) error {
|
|
if !n.sleepModeSupported {
|
|
return nil
|
|
}
|
|
|
|
bEnabled := "0"
|
|
shouldWait := false
|
|
if enabled {
|
|
bEnabled = "1"
|
|
|
|
switch videoGetStreamingStatus() {
|
|
case VideoStreamingStatusActive:
|
|
n.l.Info().Msg("stopping video stream to enable sleep mode")
|
|
videoStop()
|
|
shouldWait = true
|
|
case VideoStreamingStatusStopping:
|
|
n.l.Info().Msg("video stream is stopping, will enable sleep mode in a few seconds")
|
|
shouldWait = true
|
|
}
|
|
}
|
|
|
|
if shouldWait {
|
|
if err := n.waitForVideoStreamingStatus(VideoStreamingStatusInactive); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return os.WriteFile(sleepModeFile, []byte(bEnabled), 0644)
|
|
}
|
|
|
|
func (n *Native) getSleepMode() (bool, error) {
|
|
if !n.sleepModeSupported {
|
|
return false, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(sleepModeFile)
|
|
if err == nil {
|
|
return string(data) == "1", nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// VideoSetSleepMode sets the sleep mode for the video stream.
|
|
func (n *Native) VideoSetSleepMode(enabled bool) error {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return n.setSleepMode(enabled)
|
|
}
|
|
|
|
// VideoGetSleepMode gets the sleep mode for the video stream.
|
|
func (n *Native) VideoGetSleepMode() (bool, error) {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return n.getSleepMode()
|
|
}
|
|
|
|
// VideoSleepModeSupported checks if the sleep mode is supported.
|
|
func (n *Native) VideoSleepModeSupported() bool {
|
|
return n.sleepModeSupported
|
|
}
|
|
|
|
// useExtraLock uses the extra lock to execute a function.
|
|
// if the lock is currently held by another goroutine, returns an error.
|
|
//
|
|
// it's used to ensure that only one change is made to the video stream at a time.
|
|
// as the change usually requires to restart video streaming
|
|
// TODO: check video streaming status instead of using a hardcoded timeout
|
|
func (n *Native) useExtraLock(fn func() error) error {
|
|
if !n.extraLock.TryLock() {
|
|
return fmt.Errorf("the previous change hasn't been completed yet")
|
|
}
|
|
err := fn()
|
|
if err == nil {
|
|
time.Sleep(extraLockTimeout)
|
|
}
|
|
n.extraLock.Unlock()
|
|
return err
|
|
}
|
|
|
|
// VideoSetQualityFactor sets the quality factor for the video stream.
|
|
func (n *Native) VideoSetQualityFactor(factor float64) error {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return n.useExtraLock(func() error {
|
|
return videoSetStreamQualityFactor(factor)
|
|
})
|
|
}
|
|
|
|
// VideoGetQualityFactor gets the quality factor for the video stream.
|
|
func (n *Native) VideoGetQualityFactor() (float64, error) {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return videoGetStreamQualityFactor()
|
|
}
|
|
|
|
// VideoSetEDID sets the EDID for the video stream.
|
|
func (n *Native) VideoSetEDID(edid string) error {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
if edid == "" {
|
|
edid = DefaultEDID
|
|
}
|
|
|
|
return n.useExtraLock(func() error {
|
|
return videoSetEDID(edid)
|
|
})
|
|
}
|
|
|
|
// VideoGetEDID gets the EDID for the video stream.
|
|
func (n *Native) VideoGetEDID() (string, error) {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return videoGetEDID()
|
|
}
|
|
|
|
// VideoLogStatus gets the log status for the video stream.
|
|
func (n *Native) VideoLogStatus() (string, error) {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return videoLogStatus(), nil
|
|
}
|
|
|
|
// VideoStop stops the video stream.
|
|
func (n *Native) VideoStop() error {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
videoStop()
|
|
return nil
|
|
}
|
|
|
|
// VideoStart starts the video stream.
|
|
func (n *Native) VideoStart() error {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
// disable sleep mode before starting video
|
|
_ = n.setSleepMode(false)
|
|
|
|
videoStart()
|
|
return nil
|
|
}
|
|
|
|
// VideoGetStreamingStatus gets the streaming status of the video.
|
|
func (n *Native) VideoGetStreamingStatus() VideoStreamingStatus {
|
|
n.videoLock.Lock()
|
|
defer n.videoLock.Unlock()
|
|
|
|
return videoGetStreamingStatus()
|
|
}
|