mirror of https://github.com/jetkvm/kvm.git
Compare commits
12 Commits
efc7410e02
...
2e601d4c31
Author | SHA1 | Date |
---|---|---|
|
2e601d4c31 | |
|
bde0a086ab | |
|
9c9335da31 | |
|
090e0b4b47 | |
|
48a7a638a3 | |
|
e4f6a713a5 | |
|
9fcf74b398 | |
|
28a8fa05cc | |
|
c529c903d0 | |
|
9d12dd1e54 | |
|
cc83e4193f | |
|
466271d935 |
|
@ -23,7 +23,7 @@ jobs:
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: "**/package-lock.json"
|
cache-dependency-path: "**/package-lock.json"
|
||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.4"
|
go-version: "1.24.4"
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 # v4.2.1
|
uses: actions/setup-go@fa96338abe5531f6e34c5cc0bbe28c1a533d5505 # v4.2.1
|
||||||
with:
|
with:
|
||||||
go-version: 1.24.4
|
go-version: 1.24.4
|
||||||
- name: Create empty resource directory
|
- name: Create empty resource directory
|
||||||
|
|
|
@ -104,7 +104,7 @@ jobs:
|
||||||
EOF
|
EOF
|
||||||
ssh jkci "cat /tmp/device-tests.json" > device-tests.json
|
ssh jkci "cat /tmp/device-tests.json" > device-tests.json
|
||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.4"
|
go-version: "1.24.4"
|
||||||
- name: Golang Test Report
|
- name: Golang Test Report
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -2,8 +2,8 @@ BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
BUILDDATE ?= $(shell date -u +%FT%T%z)
|
BUILDDATE ?= $(shell date -u +%FT%T%z)
|
||||||
BUILDTS ?= $(shell date -u +%s)
|
BUILDTS ?= $(shell date -u +%s)
|
||||||
REVISION ?= $(shell git rev-parse HEAD)
|
REVISION ?= $(shell git rev-parse HEAD)
|
||||||
VERSION_DEV ?= 0.4.6-dev$(shell date +%Y%m%d%H%M)
|
VERSION_DEV ?= 0.4.7-dev$(shell date +%Y%m%d%H%M)
|
||||||
VERSION ?= 0.4.5
|
VERSION ?= 0.4.6
|
||||||
|
|
||||||
PROMETHEUS_TAG := github.com/prometheus/common/version
|
PROMETHEUS_TAG := github.com/prometheus/common/version
|
||||||
KVM_PKG_NAME := github.com/jetkvm/kvm
|
KVM_PKG_NAME := github.com/jetkvm/kvm
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package kvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runAudioClient() (cmd *exec.Cmd, err error) {
|
||||||
|
return startNativeBinary("/userdata/jetkvm/bin/jetkvm_audio")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartAudioServer() {
|
||||||
|
nativeAudioSocketListener = StartNativeSocketServer("/var/run/jetkvm_audio.sock", handleAudioClient, false)
|
||||||
|
nativeLogger.Debug().Msg("native app audio sock started")
|
||||||
|
}
|
|
@ -128,6 +128,7 @@ var defaultConfig = &Config{
|
||||||
RelativeMouse: true,
|
RelativeMouse: true,
|
||||||
Keyboard: true,
|
Keyboard: true,
|
||||||
MassStorage: true,
|
MassStorage: true,
|
||||||
|
Audio: true,
|
||||||
},
|
},
|
||||||
NetworkConfig: &network.NetworkConfig{},
|
NetworkConfig: &network.NetworkConfig{},
|
||||||
DefaultLogLevel: "INFO",
|
DefaultLogLevel: "INFO",
|
||||||
|
|
25
display.go
25
display.go
|
@ -9,9 +9,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var currentScreen = "ui_Boot_Screen"
|
|
||||||
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
|
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentScreen = "ui_Boot_Screen"
|
||||||
|
displayedTexts = make(map[string]string)
|
||||||
|
screenStateLock = sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dimTicker *time.Ticker
|
dimTicker *time.Ticker
|
||||||
offTicker *time.Ticker
|
offTicker *time.Ticker
|
||||||
|
@ -22,6 +27,8 @@ const (
|
||||||
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
|
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// do not call this function directly, use switchToScreenIfDifferent instead
|
||||||
|
// this function is not thread safe
|
||||||
func switchToScreen(screen string) {
|
func switchToScreen(screen string) {
|
||||||
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
|
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,8 +38,6 @@ func switchToScreen(screen string) {
|
||||||
currentScreen = screen
|
currentScreen = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayedTexts = make(map[string]string)
|
|
||||||
|
|
||||||
func lvObjSetState(objName string, state string) (*CtrlResponse, error) {
|
func lvObjSetState(objName string, state string) (*CtrlResponse, error) {
|
||||||
return CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": objName, "state": state})
|
return CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": objName, "state": state})
|
||||||
}
|
}
|
||||||
|
@ -78,6 +83,9 @@ func lvDispSetRotation(rotation string) (*CtrlResponse, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLabelIfChanged(objName string, newText string) {
|
func updateLabelIfChanged(objName string, newText string) {
|
||||||
|
screenStateLock.Lock()
|
||||||
|
defer screenStateLock.Unlock()
|
||||||
|
|
||||||
if newText != "" && newText != displayedTexts[objName] {
|
if newText != "" && newText != displayedTexts[objName] {
|
||||||
_, _ = lvLabelSetText(objName, newText)
|
_, _ = lvLabelSetText(objName, newText)
|
||||||
displayedTexts[objName] = newText
|
displayedTexts[objName] = newText
|
||||||
|
@ -85,12 +93,23 @@ func updateLabelIfChanged(objName string, newText string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchToScreenIfDifferent(screenName string) {
|
func switchToScreenIfDifferent(screenName string) {
|
||||||
|
screenStateLock.Lock()
|
||||||
|
defer screenStateLock.Unlock()
|
||||||
|
|
||||||
if currentScreen != screenName {
|
if currentScreen != screenName {
|
||||||
displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen")
|
displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen")
|
||||||
switchToScreen(screenName)
|
switchToScreen(screenName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearDisplayState() {
|
||||||
|
screenStateLock.Lock()
|
||||||
|
defer screenStateLock.Unlock()
|
||||||
|
|
||||||
|
displayedTexts = make(map[string]string)
|
||||||
|
currentScreen = "ui_Boot_Screen"
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cloudBlinkLock sync.Mutex = sync.Mutex{}
|
cloudBlinkLock sync.Mutex = sync.Mutex{}
|
||||||
cloudBlinkStopped bool
|
cloudBlinkStopped bool
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -3,7 +3,7 @@ module github.com/jetkvm/kvm
|
||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.3.1
|
github.com/Masterminds/semver/v3 v3.4.0
|
||||||
github.com/beevik/ntp v1.4.3
|
github.com/beevik/ntp v1.4.3
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
|
@ -17,7 +17,7 @@ require (
|
||||||
github.com/hanwen/go-fuse/v2 v2.8.0
|
github.com/hanwen/go-fuse/v2 v2.8.0
|
||||||
github.com/pion/logging v0.2.4
|
github.com/pion/logging v0.2.4
|
||||||
github.com/pion/mdns/v2 v2.0.7
|
github.com/pion/mdns/v2 v2.0.7
|
||||||
github.com/pion/webrtc/v4 v4.1.2
|
github.com/pion/webrtc/v4 v4.1.3
|
||||||
github.com/pojntfx/go-nbd v0.3.2
|
github.com/pojntfx/go-nbd v0.3.2
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/prometheus/common v0.65.0
|
github.com/prometheus/common v0.65.0
|
||||||
|
@ -66,7 +66,7 @@ require (
|
||||||
github.com/pion/interceptor v0.1.40 // indirect
|
github.com/pion/interceptor v0.1.40 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.15 // indirect
|
github.com/pion/rtcp v1.2.15 // indirect
|
||||||
github.com/pion/rtp v1.8.19 // indirect
|
github.com/pion/rtp v1.8.20 // indirect
|
||||||
github.com/pion/sctp v1.8.39 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.14 // indirect
|
github.com/pion/sdp/v3 v3.0.14 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.6 // indirect
|
github.com/pion/srtp/v3 v3.0.6 // indirect
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -1,5 +1,5 @@
|
||||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
@ -117,8 +117,8 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c=
|
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
|
||||||
github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
|
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
|
||||||
|
@ -131,8 +131,8 @@ github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||||
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||||
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||||
github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54=
|
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
|
||||||
github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U=
|
github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
|
@ -59,6 +59,23 @@ var defaultGadgetConfig = map[string]gadgetConfigItem{
|
||||||
// mass storage
|
// mass storage
|
||||||
"mass_storage_base": massStorageBaseConfig,
|
"mass_storage_base": massStorageBaseConfig,
|
||||||
"mass_storage_lun0": massStorageLun0Config,
|
"mass_storage_lun0": massStorageLun0Config,
|
||||||
|
// audio
|
||||||
|
"audio": {
|
||||||
|
order: 4000,
|
||||||
|
device: "uac1.usb0",
|
||||||
|
path: []string{"functions", "uac1.usb0"},
|
||||||
|
configPath: []string{"uac1.usb0"},
|
||||||
|
attrs: gadgetAttributes{
|
||||||
|
"p_chmask": "3",
|
||||||
|
"p_srate": "48000",
|
||||||
|
"p_ssize": "2",
|
||||||
|
"p_volume_present": "0",
|
||||||
|
"c_chmask": "3",
|
||||||
|
"c_srate": "48000",
|
||||||
|
"c_ssize": "2",
|
||||||
|
"c_volume_present": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
|
func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
|
||||||
|
@ -73,6 +90,8 @@ func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
|
||||||
return u.enabledDevices.MassStorage
|
return u.enabledDevices.MassStorage
|
||||||
case "mass_storage_lun0":
|
case "mass_storage_lun0":
|
||||||
return u.enabledDevices.MassStorage
|
return u.enabledDevices.MassStorage
|
||||||
|
case "audio":
|
||||||
|
return u.enabledDevices.Audio
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
|
||||||
default:
|
default:
|
||||||
l.Trace().Msg("reading from keyboard")
|
l.Trace().Msg("reading from keyboard")
|
||||||
if u.keyboardHidFile == nil {
|
if u.keyboardHidFile == nil {
|
||||||
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
u.logWithSuppression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
||||||
// show the error every 100 times to avoid spamming the logs
|
// show the error every 100 times to avoid spamming the logs
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
continue
|
continue
|
||||||
|
@ -153,7 +153,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
|
||||||
|
|
||||||
n, err := u.keyboardHidFile.Read(buf)
|
n, err := u.keyboardHidFile.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.logWithSupression("keyboardHidFileRead", 100, &l, err, "failed to read")
|
u.logWithSuppression("keyboardHidFileRead", 100, &l, err, "failed to read")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
u.resetLogSuppressionCounter("keyboardHidFileRead")
|
u.resetLogSuppressionCounter("keyboardHidFileRead")
|
||||||
|
@ -201,7 +201,7 @@ func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.keyboardHidFile.Write(data)
|
_, err := u.keyboardHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
||||||
u.keyboardHidFile.Close()
|
u.keyboardHidFile.Close()
|
||||||
u.keyboardHidFile = nil
|
u.keyboardHidFile = nil
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -75,7 +75,7 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.absMouseHidFile.Write(data)
|
_, err := u.absMouseHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.logWithSupression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1")
|
u.logWithSuppression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1")
|
||||||
u.absMouseHidFile.Close()
|
u.absMouseHidFile.Close()
|
||||||
u.absMouseHidFile = nil
|
u.absMouseHidFile = nil
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.relMouseHidFile.Write(data)
|
_, err := u.relMouseHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.logWithSupression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2")
|
u.logWithSuppression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2")
|
||||||
u.relMouseHidFile.Close()
|
u.relMouseHidFile.Close()
|
||||||
u.relMouseHidFile = nil
|
u.relMouseHidFile = nil
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Devices struct {
|
||||||
RelativeMouse bool `json:"relative_mouse"`
|
RelativeMouse bool `json:"relative_mouse"`
|
||||||
Keyboard bool `json:"keyboard"`
|
Keyboard bool `json:"keyboard"`
|
||||||
MassStorage bool `json:"mass_storage"`
|
MassStorage bool `json:"mass_storage"`
|
||||||
|
Audio bool `json:"audio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a struct that represents the customizations for a USB gadget.
|
// Config is a struct that represents the customizations for a USB gadget.
|
||||||
|
|
|
@ -81,7 +81,7 @@ func compareFileContent(oldContent []byte, newContent []byte, looserMatch bool)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) logWithSupression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...interface{}) {
|
func (u *UsbGadget) logWithSuppression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...interface{}) {
|
||||||
u.logSuppressionLock.Lock()
|
u.logSuppressionLock.Lock()
|
||||||
defer u.logSuppressionLock.Unlock()
|
defer u.logSuppressionLock.Unlock()
|
||||||
|
|
||||||
|
|
6
main.go
6
main.go
|
@ -77,6 +77,12 @@ func Main() {
|
||||||
|
|
||||||
// initialize usb gadget
|
// initialize usb gadget
|
||||||
initUsbGadget()
|
initUsbGadget()
|
||||||
|
|
||||||
|
StartAudioServer()
|
||||||
|
if _, err := runAudioClient(); err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to run audio client")
|
||||||
|
}
|
||||||
|
|
||||||
if err := setInitialVirtualMediaState(); err != nil {
|
if err := setInitialVirtualMediaState(); err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to set initial virtual media state")
|
logger.Warn().Err(err).Msg("failed to set initial virtual media state")
|
||||||
}
|
}
|
||||||
|
|
37
native.go
37
native.go
|
@ -13,7 +13,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/resource"
|
"github.com/jetkvm/kvm/resource"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v4/pkg/media"
|
"github.com/pion/webrtc/v4/pkg/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -106,6 +105,7 @@ func WriteCtrlMessage(message []byte) error {
|
||||||
|
|
||||||
var nativeCtrlSocketListener net.Listener //nolint:unused
|
var nativeCtrlSocketListener net.Listener //nolint:unused
|
||||||
var nativeVideoSocketListener net.Listener //nolint:unused
|
var nativeVideoSocketListener net.Listener //nolint:unused
|
||||||
|
var nativeAudioSocketListener net.Listener //nolint:unused
|
||||||
|
|
||||||
var ctrlClientConnected = make(chan struct{})
|
var ctrlClientConnected = make(chan struct{})
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ func handleVideoClient(conn net.Conn) {
|
||||||
|
|
||||||
scopedLogger.Info().Msg("native video socket client connected")
|
scopedLogger.Info().Msg("native video socket client connected")
|
||||||
|
|
||||||
inboundPacket := make([]byte, maxFrameSize)
|
inboundPacket := make([]byte, maxVideoFrameSize)
|
||||||
lastFrame := time.Now()
|
lastFrame := time.Now()
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(inboundPacket)
|
n, err := conn.Read(inboundPacket)
|
||||||
|
@ -251,6 +251,32 @@ func handleVideoClient(conn net.Conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAudioClient(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
scopedLogger := nativeLogger.With().
|
||||||
|
Str("type", "audio").
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
scopedLogger.Info().Msg("native audio socket client connected")
|
||||||
|
inboundPacket := make([]byte, maxAudioFrameSize)
|
||||||
|
for {
|
||||||
|
n, err := conn.Read(inboundPacket)
|
||||||
|
if err != nil {
|
||||||
|
scopedLogger.Warn().Err(err).Msg("error during read")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentSession != nil {
|
||||||
|
if err := currentSession.AudioTrack.WriteSample(media.Sample{
|
||||||
|
Data: inboundPacket[:n],
|
||||||
|
Duration: 20 * time.Millisecond,
|
||||||
|
}); err != nil {
|
||||||
|
scopedLogger.Warn().Err(err).Msg("error writing sample")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func startNativeBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
|
func startNativeBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
|
||||||
nativeCmdLock.Lock()
|
nativeCmdLock.Lock()
|
||||||
defer nativeCmdLock.Unlock()
|
defer nativeCmdLock.Unlock()
|
||||||
|
@ -272,6 +298,13 @@ func restartNativeBinary(binaryPath string) error {
|
||||||
nativeLogger.Warn().Err(err).Msg("failed to restart binary")
|
nativeLogger.Warn().Err(err).Msg("failed to restart binary")
|
||||||
}
|
}
|
||||||
nativeCmd = cmd
|
nativeCmd = cmd
|
||||||
|
|
||||||
|
// reset the display state
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
clearDisplayState()
|
||||||
|
updateStaticContents()
|
||||||
|
requestDisplayUpdate(true)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -695,7 +695,7 @@ export default function WebRTCVideo() {
|
||||||
controls={false}
|
controls={false}
|
||||||
onPlaying={onVideoPlaying}
|
onPlaying={onVideoPlaying}
|
||||||
onPlay={onVideoPlaying}
|
onPlay={onVideoPlaying}
|
||||||
muted={true}
|
muted={false}
|
||||||
playsInline
|
playsInline
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
controlsList="nofullscreen"
|
controlsList="nofullscreen"
|
||||||
|
|
|
@ -479,6 +479,8 @@ export default function KvmIdRoute() {
|
||||||
};
|
};
|
||||||
|
|
||||||
setTransceiver(pc.addTransceiver("video", { direction: "recvonly" }));
|
setTransceiver(pc.addTransceiver("video", { direction: "recvonly" }));
|
||||||
|
// Add audio transceiver to receive audio from the server
|
||||||
|
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
|
||||||
const rpcDataChannel = pc.createDataChannel("rpc");
|
const rpcDataChannel = pc.createDataChannel("rpc");
|
||||||
rpcDataChannel.onopen = () => {
|
rpcDataChannel.onopen = () => {
|
||||||
|
|
3
video.go
3
video.go
|
@ -5,7 +5,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// max frame size for 1080p video, specified in mpp venc setting
|
// max frame size for 1080p video, specified in mpp venc setting
|
||||||
const maxFrameSize = 1920 * 1080 / 2
|
const maxVideoFrameSize = 1920 * 1080 / 2
|
||||||
|
const maxAudioFrameSize = 1500
|
||||||
|
|
||||||
func writeCtrlAction(action string) error {
|
func writeCtrlAction(action string) error {
|
||||||
actionMessage := map[string]string{
|
actionMessage := map[string]string{
|
||||||
|
|
33
webrtc.go
33
webrtc.go
|
@ -18,6 +18,7 @@ import (
|
||||||
type Session struct {
|
type Session struct {
|
||||||
peerConnection *webrtc.PeerConnection
|
peerConnection *webrtc.PeerConnection
|
||||||
VideoTrack *webrtc.TrackLocalStaticSample
|
VideoTrack *webrtc.TrackLocalStaticSample
|
||||||
|
AudioTrack *webrtc.TrackLocalStaticSample
|
||||||
ControlChannel *webrtc.DataChannel
|
ControlChannel *webrtc.DataChannel
|
||||||
RPCChannel *webrtc.DataChannel
|
RPCChannel *webrtc.DataChannel
|
||||||
HidChannel *webrtc.DataChannel
|
HidChannel *webrtc.DataChannel
|
||||||
|
@ -136,7 +137,17 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpSender, err := peerConnection.AddTrack(session.VideoTrack)
|
session.AudioTrack, err = webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "kvm")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
videoRtpSender, err := peerConnection.AddTrack(session.VideoTrack)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
audioRtpSender, err := peerConnection.AddTrack(session.AudioTrack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -144,14 +155,9 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
// Read incoming RTCP packets
|
// Read incoming RTCP packets
|
||||||
// Before these packets are returned they are processed by interceptors. For things
|
// Before these packets are returned they are processed by interceptors. For things
|
||||||
// like NACK this needs to be called.
|
// like NACK this needs to be called.
|
||||||
go func() {
|
go drainRtpSender(videoRtpSender)
|
||||||
rtcpBuf := make([]byte, 1500)
|
go drainRtpSender(audioRtpSender)
|
||||||
for {
|
|
||||||
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var isConnected bool
|
var isConnected bool
|
||||||
|
|
||||||
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||||
|
@ -203,6 +209,15 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func drainRtpSender(rtpSender *webrtc.RTPSender) {
|
||||||
|
rtcpBuf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
if _, _, err := rtpSender.Read(rtcpBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var actionSessions = 0
|
var actionSessions = 0
|
||||||
|
|
||||||
func onActiveSessionsChanged() {
|
func onActiveSessionsChanged() {
|
||||||
|
|
Loading…
Reference in New Issue