mirror of https://github.com/jetkvm/kvm.git
Compare commits
12 Commits
17357b17c8
...
38460dcfe6
| Author | SHA1 | Date |
|---|---|---|
|
|
38460dcfe6 | |
|
|
cc9ff74276 | |
|
|
57fbee1490 | |
|
|
0e65c0a9a9 | |
|
|
2dafb5c9c1 | |
|
|
566305549f | |
|
|
1505c37e4c | |
|
|
564eee9b00 | |
|
|
fab575dbe0 | |
|
|
97958e7b86 | |
|
|
2f7042df18 | |
|
|
2cadda4e00 |
|
|
@ -0,0 +1,126 @@
|
||||||
|
name: Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-22.04]
|
||||||
|
go: [1.21, 1.23.4]
|
||||||
|
node: [21]
|
||||||
|
goos: [linux]
|
||||||
|
goarch: [arm]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Install Go Dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: Build Binaries
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=dev-${GIT_COMMIT:0:7}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Upload Debug Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ (github.ref == 'refs/heads/main' || github.event_name == 'pull_request') && matrix.go == '1.21' }}
|
||||||
|
with:
|
||||||
|
name: jetkvm_app_debug
|
||||||
|
path: bin/jetkvm_app
|
||||||
|
|
||||||
|
comment:
|
||||||
|
name: Comment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Links
|
||||||
|
id: linksa
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[0].id')
|
||||||
|
echo "ARTIFACT_URL=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" >> $GITHUB_ENV
|
||||||
|
echo "LATEST_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Comment on PR
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||||
|
else
|
||||||
|
TITLE="main branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMENT=$(cat << EOF
|
||||||
|
✅ **Build successfully for $TITLE!**
|
||||||
|
|
||||||
|
| Name | Link |
|
||||||
|
|------------------|----------------------------------------------------------------------|
|
||||||
|
| 🔗 Debug Binary | [Download](${{ env.ARTIFACT_URL }}) |
|
||||||
|
| 🔗 Latest commit | [${{ env.LATEST_COMMIT }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) |
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Post Comment
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
# Look for an existing comment
|
||||||
|
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments \
|
||||||
|
--jq '.[] | select(.body | contains("✅ **Build successfully for")) | .id')
|
||||||
|
|
||||||
|
if [ -z "$COMMENT_ID" ]; then
|
||||||
|
# Create a new comment if none exists
|
||||||
|
gh pr comment $PR_NUMBER --body "$COMMENT"
|
||||||
|
else
|
||||||
|
# Update the existing comment
|
||||||
|
gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID \
|
||||||
|
--method PATCH \
|
||||||
|
-f body="$COMMENT"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Log the comment for main branch
|
||||||
|
echo "$COMMENT"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.21
|
||||||
|
|
||||||
|
- name: Build Release Binaries
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=${REF:11}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Create checksum
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
SUM=$(shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1)
|
||||||
|
echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM bin/jetkvm_app\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||||
|
echo -e "$SUM bin/jetkvm_app\n" > checksums.txt
|
||||||
|
|
||||||
|
- name: Create Release Branch
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
BRANCH=release/${REF:10}
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git checkout -b ${BRANCH}
|
||||||
|
git push -u origin ${BRANCH}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
|
body_path: ./RELEASE_CHANGELOG
|
||||||
|
|
||||||
|
- name: Upload JetKVM binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: bin/jetkvm_app
|
||||||
|
asset_name: jetkvm_app
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload checksum
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./checksums.txt
|
||||||
|
asset_name: checksums.txt
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
@ -104,6 +104,7 @@ type Config struct {
|
||||||
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
||||||
NetworkConfig *network.NetworkConfig `json:"network_config"`
|
NetworkConfig *network.NetworkConfig `json:"network_config"`
|
||||||
DefaultLogLevel string `json:"default_log_level"`
|
DefaultLogLevel string `json:"default_log_level"`
|
||||||
|
VideoSleepAfterSec int `json:"video_sleep_after_sec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) GetDisplayRotation() uint16 {
|
func (c *Config) GetDisplayRotation() uint16 {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ type Native 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)
|
||||||
|
sleepModeSupported bool
|
||||||
videoLock sync.Mutex
|
videoLock sync.Mutex
|
||||||
screenLock sync.Mutex
|
screenLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +63,8 @@ func NewNative(opts NativeOptions) *Native {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sleepModeSupported := isSleepModeSupported()
|
||||||
|
|
||||||
return &Native{
|
return &Native{
|
||||||
ready: make(chan struct{}),
|
ready: make(chan struct{}),
|
||||||
l: nativeLogger,
|
l: nativeLogger,
|
||||||
|
|
@ -73,6 +76,7 @@ func NewNative(opts NativeOptions) *Native {
|
||||||
onVideoFrameReceived: onVideoFrameReceived,
|
onVideoFrameReceived: onVideoFrameReceived,
|
||||||
onIndevEvent: onIndevEvent,
|
onIndevEvent: onIndevEvent,
|
||||||
onRpcEvent: onRpcEvent,
|
onRpcEvent: onRpcEvent,
|
||||||
|
sleepModeSupported: sleepModeSupported,
|
||||||
videoLock: sync.Mutex{},
|
videoLock: sync.Mutex{},
|
||||||
screenLock: sync.Mutex{},
|
screenLock: sync.Mutex{},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
||||||
|
|
||||||
|
// VideoState is the state of the video stream.
|
||||||
type VideoState struct {
|
type VideoState struct {
|
||||||
Ready bool `json:"ready"`
|
Ready bool `json:"ready"`
|
||||||
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
||||||
|
|
@ -8,6 +15,58 @@ type VideoState struct {
|
||||||
FramePerSecond float64 `json:"fps"`
|
FramePerSecond float64 `json:"fps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSleepModeSupported() bool {
|
||||||
|
_, err := os.Stat(sleepModeFile)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Native) setSleepMode(enabled bool) error {
|
||||||
|
if !n.sleepModeSupported {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bEnabled := "0"
|
||||||
|
if enabled {
|
||||||
|
bEnabled = "1"
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoSetQualityFactor sets the quality factor for the video stream.
|
||||||
func (n *Native) VideoSetQualityFactor(factor float64) error {
|
func (n *Native) VideoSetQualityFactor(factor float64) error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -15,6 +74,7 @@ func (n *Native) VideoSetQualityFactor(factor float64) error {
|
||||||
return videoSetStreamQualityFactor(factor)
|
return videoSetStreamQualityFactor(factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoGetQualityFactor gets the quality factor for the video stream.
|
||||||
func (n *Native) VideoGetQualityFactor() (float64, error) {
|
func (n *Native) VideoGetQualityFactor() (float64, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -22,6 +82,7 @@ func (n *Native) VideoGetQualityFactor() (float64, error) {
|
||||||
return videoGetStreamQualityFactor()
|
return videoGetStreamQualityFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoSetEDID sets the EDID for the video stream.
|
||||||
func (n *Native) VideoSetEDID(edid string) error {
|
func (n *Native) VideoSetEDID(edid string) error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -29,6 +90,7 @@ func (n *Native) VideoSetEDID(edid string) error {
|
||||||
return videoSetEDID(edid)
|
return videoSetEDID(edid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoGetEDID gets the EDID for the video stream.
|
||||||
func (n *Native) VideoGetEDID() (string, error) {
|
func (n *Native) VideoGetEDID() (string, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -36,6 +98,7 @@ func (n *Native) VideoGetEDID() (string, error) {
|
||||||
return videoGetEDID()
|
return videoGetEDID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoLogStatus gets the log status for the video stream.
|
||||||
func (n *Native) VideoLogStatus() (string, error) {
|
func (n *Native) VideoLogStatus() (string, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -43,6 +106,7 @@ func (n *Native) VideoLogStatus() (string, error) {
|
||||||
return videoLogStatus(), nil
|
return videoLogStatus(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoStop stops the video stream.
|
||||||
func (n *Native) VideoStop() error {
|
func (n *Native) VideoStop() error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -51,10 +115,14 @@ func (n *Native) VideoStop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoStart starts the video stream.
|
||||||
func (n *Native) VideoStart() error {
|
func (n *Native) VideoStart() error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
||||||
|
// disable sleep mode before starting video
|
||||||
|
_ = n.setSleepMode(false)
|
||||||
|
|
||||||
videoStart()
|
videoStart()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1215,6 +1215,8 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"getEDID": {Func: rpcGetEDID},
|
"getEDID": {Func: rpcGetEDID},
|
||||||
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
||||||
"getVideoLogStatus": {Func: rpcGetVideoLogStatus},
|
"getVideoLogStatus": {Func: rpcGetVideoLogStatus},
|
||||||
|
"getVideoSleepMode": {Func: rpcGetVideoSleepMode},
|
||||||
|
"setVideoSleepMode": {Func: rpcSetVideoSleepMode, Params: []string{"duration"}},
|
||||||
"getDevChannelState": {Func: rpcGetDevChannelState},
|
"getDevChannelState": {Func: rpcGetDevChannelState},
|
||||||
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
||||||
"getLocalVersion": {Func: rpcGetLocalVersion},
|
"getLocalVersion": {Func: rpcGetLocalVersion},
|
||||||
|
|
|
||||||
3
main.go
3
main.go
|
|
@ -77,6 +77,9 @@ func Main() {
|
||||||
// initialize display
|
// initialize display
|
||||||
initDisplay()
|
initDisplay()
|
||||||
|
|
||||||
|
// start video sleep mode timer
|
||||||
|
startVideoSleepModeTicker()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(15 * time.Minute)
|
time.Sleep(15 * time.Minute)
|
||||||
for {
|
for {
|
||||||
|
|
|
||||||
103
video.go
103
video.go
|
|
@ -1,10 +1,22 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/native"
|
"github.com/jetkvm/kvm/internal/native"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastVideoState native.VideoState
|
var (
|
||||||
|
lastVideoState native.VideoState
|
||||||
|
videoSleepModeCtx context.Context
|
||||||
|
videoSleepModeCancel context.CancelFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultVideoSleepModeDuration = 1 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
func triggerVideoStateUpdate() {
|
func triggerVideoStateUpdate() {
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -17,3 +29,92 @@ func triggerVideoStateUpdate() {
|
||||||
func rpcGetVideoState() (native.VideoState, error) {
|
func rpcGetVideoState() (native.VideoState, error) {
|
||||||
return lastVideoState, nil
|
return lastVideoState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rpcVideoSleepModeResponse struct {
|
||||||
|
Supported bool `json:"supported"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcGetVideoSleepMode() rpcVideoSleepModeResponse {
|
||||||
|
sleepMode, _ := nativeInstance.VideoGetSleepMode()
|
||||||
|
return rpcVideoSleepModeResponse{
|
||||||
|
Supported: nativeInstance.VideoSleepModeSupported(),
|
||||||
|
Enabled: sleepMode,
|
||||||
|
Duration: config.VideoSleepAfterSec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcSetVideoSleepMode(duration int) error {
|
||||||
|
if duration < 0 {
|
||||||
|
duration = -1 // disable
|
||||||
|
}
|
||||||
|
|
||||||
|
config.VideoSleepAfterSec = duration
|
||||||
|
if err := SaveConfig(); err != nil {
|
||||||
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we won't restart the ticker here,
|
||||||
|
// as the session can't be inactive when this function is called
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopVideoSleepModeTicker() {
|
||||||
|
nativeLogger.Trace().Msg("stopping HDMI sleep mode ticker")
|
||||||
|
|
||||||
|
if videoSleepModeCancel != nil {
|
||||||
|
nativeLogger.Trace().Msg("canceling HDMI sleep mode ticker context")
|
||||||
|
videoSleepModeCancel()
|
||||||
|
videoSleepModeCancel = nil
|
||||||
|
videoSleepModeCtx = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startVideoSleepModeTicker() {
|
||||||
|
if !nativeInstance.VideoSleepModeSupported() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration time.Duration
|
||||||
|
|
||||||
|
if config.VideoSleepAfterSec == 0 {
|
||||||
|
duration = defaultVideoSleepModeDuration
|
||||||
|
} else if config.VideoSleepAfterSec > 0 {
|
||||||
|
duration = time.Duration(config.VideoSleepAfterSec) * time.Second
|
||||||
|
} else {
|
||||||
|
stopVideoSleepModeTicker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop any existing timer and goroutine
|
||||||
|
stopVideoSleepModeTicker()
|
||||||
|
|
||||||
|
// Create new context for this ticker
|
||||||
|
videoSleepModeCtx, videoSleepModeCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
go doVideoSleepModeTicker(videoSleepModeCtx, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doVideoSleepModeTicker(ctx context.Context, duration time.Duration) {
|
||||||
|
timer := time.NewTimer(duration)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
nativeLogger.Trace().Msg("HDMI sleep mode ticker started")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
if getActiveSessions() > 0 {
|
||||||
|
nativeLogger.Warn().Msg("not going to enter HDMI sleep mode because there are active sessions")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeLogger.Trace().Msg("entering HDMI sleep mode")
|
||||||
|
_ = nativeInstance.VideoSetSleepMode(true)
|
||||||
|
case <-ctx.Done():
|
||||||
|
nativeLogger.Trace().Msg("HDMI sleep mode ticker stopped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
38
webrtc.go
38
webrtc.go
|
|
@ -39,6 +39,34 @@ type Session struct {
|
||||||
keysDownStateQueue chan usbgadget.KeysDownState
|
keysDownStateQueue chan usbgadget.KeysDownState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
actionSessions int = 0
|
||||||
|
activeSessionsMutex = &sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func incrActiveSessions() int {
|
||||||
|
activeSessionsMutex.Lock()
|
||||||
|
defer activeSessionsMutex.Unlock()
|
||||||
|
|
||||||
|
actionSessions++
|
||||||
|
return actionSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrActiveSessions() int {
|
||||||
|
activeSessionsMutex.Lock()
|
||||||
|
defer activeSessionsMutex.Unlock()
|
||||||
|
|
||||||
|
actionSessions--
|
||||||
|
return actionSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveSessions() int {
|
||||||
|
activeSessionsMutex.Lock()
|
||||||
|
defer activeSessionsMutex.Unlock()
|
||||||
|
|
||||||
|
return actionSessions
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Session) resetKeepAliveTime() {
|
func (s *Session) resetKeepAliveTime() {
|
||||||
s.keepAliveJitterLock.Lock()
|
s.keepAliveJitterLock.Lock()
|
||||||
defer s.keepAliveJitterLock.Unlock()
|
defer s.keepAliveJitterLock.Unlock()
|
||||||
|
|
@ -312,9 +340,8 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
if connectionState == webrtc.ICEConnectionStateConnected {
|
if connectionState == webrtc.ICEConnectionStateConnected {
|
||||||
if !isConnected {
|
if !isConnected {
|
||||||
isConnected = true
|
isConnected = true
|
||||||
actionSessions++
|
|
||||||
onActiveSessionsChanged()
|
onActiveSessionsChanged()
|
||||||
if actionSessions == 1 {
|
if incrActiveSessions() == 1 {
|
||||||
onFirstSessionConnected()
|
onFirstSessionConnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -353,9 +380,8 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
}
|
}
|
||||||
if isConnected {
|
if isConnected {
|
||||||
isConnected = false
|
isConnected = false
|
||||||
actionSessions--
|
|
||||||
onActiveSessionsChanged()
|
onActiveSessionsChanged()
|
||||||
if actionSessions == 0 {
|
if decrActiveSessions() == 0 {
|
||||||
onLastSessionDisconnected()
|
onLastSessionDisconnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -364,16 +390,16 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionSessions = 0
|
|
||||||
|
|
||||||
func onActiveSessionsChanged() {
|
func onActiveSessionsChanged() {
|
||||||
requestDisplayUpdate(true, "active_sessions_changed")
|
requestDisplayUpdate(true, "active_sessions_changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func onFirstSessionConnected() {
|
func onFirstSessionConnected() {
|
||||||
_ = nativeInstance.VideoStart()
|
_ = nativeInstance.VideoStart()
|
||||||
|
stopVideoSleepModeTicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
func onLastSessionDisconnected() {
|
func onLastSessionDisconnected() {
|
||||||
_ = nativeInstance.VideoStop()
|
_ = nativeInstance.VideoStop()
|
||||||
|
startVideoSleepModeTicker()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue