mirror of https://github.com/jetkvm/kvm.git
Merge branch 'jetkvm:dev' into dev
This commit is contained in:
commit
a57267d293
|
@ -0,0 +1,17 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /ui
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
|
@ -23,9 +23,9 @@ 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@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.3"
|
go-version: "1.24.4"
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: |
|
run: |
|
||||||
make frontend
|
make frontend
|
||||||
|
|
|
@ -24,9 +24,9 @@ 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@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
|
uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 # v4.2.1
|
||||||
with:
|
with:
|
||||||
go-version: 1.24.3
|
go-version: 1.24.4
|
||||||
- name: Create empty resource directory
|
- name: Create empty resource directory
|
||||||
run: |
|
run: |
|
||||||
mkdir -p static && touch static/.gitkeep
|
mkdir -p static && touch static/.gitkeep
|
||||||
|
|
|
@ -104,9 +104,9 @@ 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@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.3"
|
go-version: "1.24.4"
|
||||||
- name: Golang Test Report
|
- name: Golang Test Report
|
||||||
uses: becheran/go-testreport@v0.3.2
|
uses: becheran/go-testreport@v0.3.2
|
||||||
with:
|
with:
|
||||||
|
|
11
Makefile
11
Makefile
|
@ -2,12 +2,14 @@ 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.2-dev$(shell date +%Y%m%d%H%M)
|
VERSION_DEV ?= 0.4.5-dev$(shell date +%Y%m%d%H%M)
|
||||||
VERSION := 0.4.1
|
VERSION ?= 0.4.4
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
GO_BUILD_ARGS := -tags netgo
|
||||||
|
GO_RELEASE_BUILD_ARGS := -trimpath $(GO_BUILD_ARGS)
|
||||||
GO_LDFLAGS := \
|
GO_LDFLAGS := \
|
||||||
-s -w \
|
-s -w \
|
||||||
-X $(PROMETHEUS_TAG).Branch=$(BRANCH) \
|
-X $(PROMETHEUS_TAG).Branch=$(BRANCH) \
|
||||||
|
@ -27,7 +29,7 @@ build_dev: hash_resource
|
||||||
@echo "Building..."
|
@echo "Building..."
|
||||||
$(GO_CMD) build \
|
$(GO_CMD) build \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
-trimpath \
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
-o $(BIN_DIR)/jetkvm_app cmd/main.go
|
-o $(BIN_DIR)/jetkvm_app cmd/main.go
|
||||||
|
|
||||||
build_test2json:
|
build_test2json:
|
||||||
|
@ -50,6 +52,7 @@ build_dev_test: build_test2json build_gotestsum
|
||||||
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
||||||
$(GO_CMD) test -v \
|
$(GO_CMD) test -v \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
|
$(GO_BUILD_ARGS) \
|
||||||
-c -o $(BIN_DIR)/tests/$$test_filename $$test; \
|
-c -o $(BIN_DIR)/tests/$$test_filename $$test; \
|
||||||
echo "runTest ./$$test_filename $$test_pkg_full_name" >> $(BIN_DIR)/tests/run_all_tests; \
|
echo "runTest ./$$test_filename $$test_pkg_full_name" >> $(BIN_DIR)/tests/run_all_tests; \
|
||||||
done; \
|
done; \
|
||||||
|
@ -71,7 +74,7 @@ build_release: frontend hash_resource
|
||||||
@echo "Building release..."
|
@echo "Building release..."
|
||||||
$(GO_CMD) build \
|
$(GO_CMD) build \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" \
|
||||||
-trimpath \
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
-o bin/jetkvm_app cmd/main.go
|
-o bin/jetkvm_app cmd/main.go
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
|
22
cloud.go
22
cloud.go
|
@ -51,34 +51,34 @@ var (
|
||||||
)
|
)
|
||||||
metricCloudConnectionEstablishedTimestamp = promauto.NewGauge(
|
metricCloudConnectionEstablishedTimestamp = promauto.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "jetkvm_cloud_connection_established_timestamp",
|
Name: "jetkvm_cloud_connection_established_timestamp_seconds",
|
||||||
Help: "The timestamp when the cloud connection was established",
|
Help: "The timestamp when the cloud connection was established",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricConnectionLastPingTimestamp = promauto.NewGaugeVec(
|
metricConnectionLastPingTimestamp = promauto.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "jetkvm_connection_last_ping_timestamp",
|
Name: "jetkvm_connection_last_ping_timestamp_seconds",
|
||||||
Help: "The timestamp when the last ping response was received",
|
Help: "The timestamp when the last ping response was received",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionLastPingReceivedTimestamp = promauto.NewGaugeVec(
|
metricConnectionLastPingReceivedTimestamp = promauto.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "jetkvm_connection_last_ping_received_timestamp",
|
Name: "jetkvm_connection_last_ping_received_timestamp_seconds",
|
||||||
Help: "The timestamp when the last ping request was received",
|
Help: "The timestamp when the last ping request was received",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionLastPingDuration = promauto.NewGaugeVec(
|
metricConnectionLastPingDuration = promauto.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "jetkvm_connection_last_ping_duration",
|
Name: "jetkvm_connection_last_ping_duration_seconds",
|
||||||
Help: "The duration of the last ping response",
|
Help: "The duration of the last ping response",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionPingDuration = promauto.NewHistogramVec(
|
metricConnectionPingDuration = promauto.NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "jetkvm_connection_ping_duration",
|
Name: "jetkvm_connection_ping_duration_seconds",
|
||||||
Help: "The duration of the ping response",
|
Help: "The duration of the ping response",
|
||||||
Buckets: []float64{
|
Buckets: []float64{
|
||||||
0.1, 0.5, 1, 10,
|
0.1, 0.5, 1, 10,
|
||||||
|
@ -88,28 +88,28 @@ var (
|
||||||
)
|
)
|
||||||
metricConnectionTotalPingSentCount = promauto.NewCounterVec(
|
metricConnectionTotalPingSentCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_connection_total_ping_sent",
|
Name: "jetkvm_connection_ping_sent_total",
|
||||||
Help: "The total number of pings sent to the connection",
|
Help: "The total number of pings sent to the connection",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionTotalPingReceivedCount = promauto.NewCounterVec(
|
metricConnectionTotalPingReceivedCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_connection_total_ping_received",
|
Name: "jetkvm_connection_ping_received_total",
|
||||||
Help: "The total number of pings received from the connection",
|
Help: "The total number of pings received from the connection",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionSessionRequestCount = promauto.NewCounterVec(
|
metricConnectionSessionRequestCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_connection_session_total_requests",
|
Name: "jetkvm_connection_session_requests_total",
|
||||||
Help: "The total number of session requests received",
|
Help: "The total number of session requests received",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
)
|
)
|
||||||
metricConnectionSessionRequestDuration = promauto.NewHistogramVec(
|
metricConnectionSessionRequestDuration = promauto.NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "jetkvm_connection_session_request_duration",
|
Name: "jetkvm_connection_session_request_duration_seconds",
|
||||||
Help: "The duration of session requests",
|
Help: "The duration of session requests",
|
||||||
Buckets: []float64{
|
Buckets: []float64{
|
||||||
0.1, 0.5, 1, 10,
|
0.1, 0.5, 1, 10,
|
||||||
|
@ -119,7 +119,7 @@ var (
|
||||||
)
|
)
|
||||||
metricConnectionLastSessionRequestTimestamp = promauto.NewGaugeVec(
|
metricConnectionLastSessionRequestTimestamp = promauto.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "jetkvm_connection_last_session_request_timestamp",
|
Name: "jetkvm_connection_last_session_request_timestamp_seconds",
|
||||||
Help: "The timestamp of the last session request",
|
Help: "The timestamp of the last session request",
|
||||||
},
|
},
|
||||||
[]string{"type", "source"},
|
[]string{"type", "source"},
|
||||||
|
@ -133,7 +133,7 @@ var (
|
||||||
)
|
)
|
||||||
metricCloudConnectionFailureCount = promauto.NewCounter(
|
metricCloudConnectionFailureCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_cloud_connection_failure_count",
|
Name: "jetkvm_cloud_connection_failure_total",
|
||||||
Help: "The number of times the cloud connection has failed",
|
Help: "The number of times the cloud connection has failed",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -111,7 +111,7 @@ var defaultConfig = &Config{
|
||||||
ActiveExtension: "",
|
ActiveExtension: "",
|
||||||
KeyboardMacros: []KeyboardMacro{},
|
KeyboardMacros: []KeyboardMacro{},
|
||||||
DisplayRotation: "270",
|
DisplayRotation: "270",
|
||||||
KeyboardLayout: "en-US",
|
KeyboardLayout: "en_US",
|
||||||
DisplayMaxBrightness: 64,
|
DisplayMaxBrightness: 64,
|
||||||
DisplayDimAfterSec: 120, // 2 minutes
|
DisplayDimAfterSec: 120, // 2 minutes
|
||||||
DisplayOffAfterSec: 1800, // 30 minutes
|
DisplayOffAfterSec: 1800, // 30 minutes
|
||||||
|
|
|
@ -174,7 +174,7 @@ cd "${REMOTE_PATH}"
|
||||||
chmod +x jetkvm_app_debug
|
chmod +x jetkvm_app_debug
|
||||||
|
|
||||||
# Run the application in the background
|
# Run the application in the background
|
||||||
PION_LOG_TRACE=${LOG_TRACE_SCOPES} ./jetkvm_app_debug
|
PION_LOG_TRACE=${LOG_TRACE_SCOPES} GODEBUG=netdns=1 ./jetkvm_app_debug
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Deployment complete."
|
echo "Deployment complete."
|
15
display.go
15
display.go
|
@ -339,10 +339,18 @@ func startBacklightTickers() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if dimTicker == nil && config.DisplayDimAfterSec != 0 {
|
// Stop existing tickers to prevent multiple active instances on repeated calls
|
||||||
|
if dimTicker != nil {
|
||||||
|
dimTicker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if offTicker != nil {
|
||||||
|
offTicker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DisplayDimAfterSec != 0 {
|
||||||
displayLogger.Info().Msg("dim_ticker has started")
|
displayLogger.Info().Msg("dim_ticker has started")
|
||||||
dimTicker = time.NewTicker(time.Duration(config.DisplayDimAfterSec) * time.Second)
|
dimTicker = time.NewTicker(time.Duration(config.DisplayDimAfterSec) * time.Second)
|
||||||
defer dimTicker.Stop()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for { //nolint:staticcheck
|
for { //nolint:staticcheck
|
||||||
|
@ -354,10 +362,9 @@ func startBacklightTickers() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if offTicker == nil && config.DisplayOffAfterSec != 0 {
|
if config.DisplayOffAfterSec != 0 {
|
||||||
displayLogger.Info().Msg("off_ticker has started")
|
displayLogger.Info().Msg("off_ticker has started")
|
||||||
offTicker = time.NewTicker(time.Duration(config.DisplayOffAfterSec) * time.Second)
|
offTicker = time.NewTicker(time.Duration(config.DisplayOffAfterSec) * time.Second)
|
||||||
defer offTicker.Stop()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for { //nolint:staticcheck
|
for { //nolint:staticcheck
|
||||||
|
|
56
go.mod
56
go.mod
|
@ -5,33 +5,33 @@ go 1.23.4
|
||||||
toolchain go1.24.3
|
toolchain go1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.3.0
|
github.com/Masterminds/semver/v3 v3.3.1
|
||||||
github.com/beevik/ntp v1.3.1
|
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.11.0
|
github.com/coreos/go-oidc/v3 v3.11.0
|
||||||
github.com/creack/pty v1.1.23
|
github.com/creack/pty v1.1.23
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/gin-contrib/logger v1.2.5
|
github.com/gin-contrib/logger v1.2.6
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/guregu/null/v6 v6.0.0
|
github.com/guregu/null/v6 v6.0.0
|
||||||
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
|
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1
|
github.com/hanwen/go-fuse/v2 v2.8.0
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.3
|
||||||
github.com/pion/mdns/v2 v2.0.7
|
github.com/pion/mdns/v2 v2.0.7
|
||||||
github.com/pion/webrtc/v4 v4.0.0
|
github.com/pion/webrtc/v4 v4.0.16
|
||||||
github.com/pojntfx/go-nbd v0.3.2
|
github.com/pojntfx/go-nbd v0.3.2
|
||||||
github.com/prometheus/client_golang v1.21.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/prometheus/common v0.62.0
|
github.com/prometheus/common v0.62.0
|
||||||
github.com/prometheus/procfs v0.15.1
|
github.com/prometheus/procfs v0.16.1
|
||||||
github.com/psanford/httpreadat v0.1.0
|
github.com/psanford/httpreadat v0.1.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f
|
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
go.bug.st/serial v1.6.2
|
go.bug.st/serial v1.6.2
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.39.0
|
||||||
golang.org/x/net v0.40.0
|
golang.org/x/net v0.41.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.33.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,16 +45,14 @@ require (
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/creack/goselect v0.1.2 // indirect
|
github.com/creack/goselect v0.1.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
@ -62,21 +60,21 @@ require (
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pilebones/go-udev v0.9.0 // indirect
|
github.com/pilebones/go-udev v0.9.0 // indirect
|
||||||
github.com/pion/datachannel v1.5.9 // indirect
|
github.com/pion/datachannel v1.5.10 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.3 // indirect
|
github.com/pion/dtls/v3 v3.0.6 // indirect
|
||||||
github.com/pion/ice/v4 v4.0.2 // indirect
|
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||||
github.com/pion/interceptor v0.1.37 // 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.14 // indirect
|
github.com/pion/rtcp v1.2.15 // indirect
|
||||||
github.com/pion/rtp v1.8.9 // indirect
|
github.com/pion/rtp v1.8.18 // indirect
|
||||||
github.com/pion/sctp v1.8.33 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.9 // indirect
|
github.com/pion/sdp/v3 v3.0.13 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
github.com/pion/srtp/v3 v3.0.5 // indirect
|
||||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
|
@ -84,9 +82,9 @@ require (
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/arch v0.15.0 // indirect
|
golang.org/x/arch v0.17.0 // indirect
|
||||||
golang.org/x/oauth2 v0.24.0 // indirect
|
golang.org/x/oauth2 v0.24.0 // indirect
|
||||||
golang.org/x/text v0.25.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
122
go.sum
122
go.sum
|
@ -1,7 +1,7 @@
|
||||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/beevik/ntp v1.3.1 h1:Y/srlT8L1yQr58kyPWFPZIxRL8ttx2SRIpVYJqZIlAM=
|
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||||
github.com/beevik/ntp v1.3.1/go.mod h1:fT6PylBq86Tsq23ZMEe47b7QQrZfYBFPnpzt0a9kJxw=
|
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=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
|
@ -30,16 +30,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/gin-contrib/logger v1.2.5 h1:qVQI4omayQecuN4zX9ZZnsOq7w9J/ZLds3J/FMn8ypM=
|
github.com/gin-contrib/logger v1.2.6 h1:EPolruKUTzNXMVBD9LuAFQmRjTs7AH7yKGuXgYqrKWc=
|
||||||
github.com/gin-contrib/logger v1.2.5/go.mod h1:/bj+vNMuA2xOEQ1aRHoJ1m9+uyaaXIAxQTvM2llsc6I=
|
github.com/gin-contrib/logger v1.2.6/go.mod h1:7niPrd7F0Nscw/zvgz8RiGJxSdbKM2yfQNy8xCHcm64=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
@ -60,12 +60,12 @@ github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ=
|
||||||
github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
|
github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
|
||||||
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto5zjQ3/VEnDM5rPbqIFuOhS0U0ByeA=
|
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto5zjQ3/VEnDM5rPbqIFuOhS0U0ByeA=
|
||||||
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=
|
github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs=
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
|
github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
@ -77,7 +77,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
@ -89,8 +88,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -98,53 +97,53 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q=
|
github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q=
|
||||||
github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI=
|
github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI=
|
||||||
github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA=
|
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||||
github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE=
|
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||||
github.com/pion/dtls/v3 v3.0.3 h1:j5ajZbQwff7Z8k3pE3S+rQ4STvKvXUdKsi/07ka+OWM=
|
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
||||||
github.com/pion/dtls/v3 v3.0.3/go.mod h1:weOTUyIV4z0bQaVzKe8kpaP17+us3yAuiQsEAG1STMU=
|
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
||||||
github.com/pion/ice/v4 v4.0.2 h1:1JhBRX8iQLi0+TfcavTjPjI6GO41MFn4CeTBX+Y9h5s=
|
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||||
github.com/pion/ice/v4 v4.0.2/go.mod h1:DCdqyzgtsDNYN6/3U8044j3U7qsJ9KFJC92VnOWHvXg=
|
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
|
||||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
||||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
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.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
|
github.com/pion/rtp v1.8.18 h1:yEAb4+4a8nkPCecWzQB6V/uEU18X1lQCGAQCjP+pyvU=
|
||||||
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
github.com/pion/rtp v1.8.18/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
|
github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4=
|
||||||
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
github.com/pion/srtp/v3 v3.0.5 h1:8XLB6Dt3QXkMkRFpoqC3314BemkpMQK2mZeJc4pUKqo=
|
||||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
github.com/pion/srtp/v3 v3.0.5/go.mod h1:r1G7y5r1scZRLe2QJI/is+/O83W2d+JoEsuIexpw+uM=
|
||||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||||
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.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
|
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||||
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
|
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||||
github.com/pion/webrtc/v4 v4.0.0 h1:x8ec7uJQPP3D1iI8ojPAiTOylPI7Fa7QgqZrhpLyqZ8=
|
github.com/pion/webrtc/v4 v4.0.16 h1:5f8QMVIbNvJr2mPRGi2QamkPa/LVUB6NWolOCwphKHA=
|
||||||
github.com/pion/webrtc/v4 v4.0.0/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto=
|
github.com/pion/webrtc/v4 v4.0.16/go.mod h1:C3uTCPzVafUA0eUzru9f47OgNt3nEO7ZJ6zNY6VSJno=
|
||||||
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=
|
||||||
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
|
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
||||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
@ -157,14 +156,11 @@ github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzr
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
@ -179,16 +175,14 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||||
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
|
||||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -196,8 +190,8 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -95,16 +95,27 @@ func (t *TimeSync) queryMultipleHttp(urls []string, timeout time.Duration) (now
|
||||||
} else if errors.Is(err, context.Canceled) {
|
} else if errors.Is(err, context.Canceled) {
|
||||||
metricHttpCancelCount.WithLabelValues(url).Inc()
|
metricHttpCancelCount.WithLabelValues(url).Inc()
|
||||||
metricHttpTotalCancelCount.Inc()
|
metricHttpTotalCancelCount.Inc()
|
||||||
|
results <- nil
|
||||||
} else {
|
} else {
|
||||||
scopedLogger.Warn().
|
scopedLogger.Warn().
|
||||||
Str("error", err.Error()).
|
Str("error", err.Error()).
|
||||||
Int("status", status).
|
Int("status", status).
|
||||||
Msg("failed to query HTTP server")
|
Msg("failed to query HTTP server")
|
||||||
|
results <- nil
|
||||||
}
|
}
|
||||||
}(url)
|
}(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <-results
|
for range urls {
|
||||||
|
result := <-results
|
||||||
|
if result == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
now = result
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryHttpTime(
|
func queryHttpTime(
|
||||||
|
|
|
@ -14,44 +14,44 @@ var (
|
||||||
)
|
)
|
||||||
metricTimeSyncCount = promauto.NewCounter(
|
metricTimeSyncCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_count",
|
Name: "jetkvm_timesync_total",
|
||||||
Help: "The number of times the timesync has been run",
|
Help: "The number of times the timesync has been run",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricTimeSyncSuccessCount = promauto.NewCounter(
|
metricTimeSyncSuccessCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_success_count",
|
Name: "jetkvm_timesync_success_total",
|
||||||
Help: "The number of times the timesync has been successful",
|
Help: "The number of times the timesync has been successful",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricRTCUpdateCount = promauto.NewCounter( //nolint:unused
|
metricRTCUpdateCount = promauto.NewCounter( //nolint:unused
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_rtc_update_count",
|
Name: "jetkvm_timesync_rtc_update_total",
|
||||||
Help: "The number of times the RTC has been updated",
|
Help: "The number of times the RTC has been updated",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricNtpTotalSuccessCount = promauto.NewCounter(
|
metricNtpTotalSuccessCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_ntp_total_success_count",
|
Name: "jetkvm_timesync_ntp_total_success_total",
|
||||||
Help: "The total number of successful NTP requests",
|
Help: "The total number of successful NTP requests",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricNtpTotalRequestCount = promauto.NewCounter(
|
metricNtpTotalRequestCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_ntp_total_request_count",
|
Name: "jetkvm_timesync_ntp_total_request_total",
|
||||||
Help: "The total number of NTP requests sent",
|
Help: "The total number of NTP requests sent",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricNtpSuccessCount = promauto.NewCounterVec(
|
metricNtpSuccessCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_ntp_success_count",
|
Name: "jetkvm_timesync_ntp_success_total",
|
||||||
Help: "The number of successful NTP requests",
|
Help: "The number of successful NTP requests",
|
||||||
},
|
},
|
||||||
[]string{"url"},
|
[]string{"url"},
|
||||||
)
|
)
|
||||||
metricNtpRequestCount = promauto.NewCounterVec(
|
metricNtpRequestCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_ntp_request_count",
|
Name: "jetkvm_timesync_ntp_request_total",
|
||||||
Help: "The number of NTP requests sent to the server",
|
Help: "The number of NTP requests sent to the server",
|
||||||
},
|
},
|
||||||
[]string{"url"},
|
[]string{"url"},
|
||||||
|
@ -83,39 +83,39 @@ var (
|
||||||
|
|
||||||
metricHttpTotalSuccessCount = promauto.NewCounter(
|
metricHttpTotalSuccessCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_total_success_count",
|
Name: "jetkvm_timesync_http_total_success_total",
|
||||||
Help: "The total number of successful HTTP requests",
|
Help: "The total number of successful HTTP requests",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricHttpTotalRequestCount = promauto.NewCounter(
|
metricHttpTotalRequestCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_total_request_count",
|
Name: "jetkvm_timesync_http_total_request_total",
|
||||||
Help: "The total number of HTTP requests sent",
|
Help: "The total number of HTTP requests sent",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricHttpTotalCancelCount = promauto.NewCounter(
|
metricHttpTotalCancelCount = promauto.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_total_cancel_count",
|
Name: "jetkvm_timesync_http_total_cancel_total",
|
||||||
Help: "The total number of HTTP requests cancelled",
|
Help: "The total number of HTTP requests cancelled",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
metricHttpSuccessCount = promauto.NewCounterVec(
|
metricHttpSuccessCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_success_count",
|
Name: "jetkvm_timesync_http_success_total",
|
||||||
Help: "The number of successful HTTP requests",
|
Help: "The number of successful HTTP requests",
|
||||||
},
|
},
|
||||||
[]string{"url"},
|
[]string{"url"},
|
||||||
)
|
)
|
||||||
metricHttpRequestCount = promauto.NewCounterVec(
|
metricHttpRequestCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_request_count",
|
Name: "jetkvm_timesync_http_request_total",
|
||||||
Help: "The number of HTTP requests sent to the server",
|
Help: "The number of HTTP requests sent to the server",
|
||||||
},
|
},
|
||||||
[]string{"url"},
|
[]string{"url"},
|
||||||
)
|
)
|
||||||
metricHttpCancelCount = promauto.NewCounterVec(
|
metricHttpCancelCount = promauto.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "jetkvm_timesync_http_cancel_count",
|
Name: "jetkvm_timesync_http_cancel_total",
|
||||||
Help: "The number of HTTP requests cancelled",
|
Help: "The number of HTTP requests cancelled",
|
||||||
},
|
},
|
||||||
[]string{"url"},
|
[]string{"url"},
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
@ -149,6 +150,12 @@ func (c *DHCPClient) loadLeaseFile() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
isFirstLoad := c.lease == nil
|
isFirstLoad := c.lease == nil
|
||||||
|
|
||||||
|
// Skip processing if lease hasn't changed to avoid unnecessary wake-ups.
|
||||||
|
if reflect.DeepEqual(c.lease, lease) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
c.lease = lease
|
c.lease = lease
|
||||||
|
|
||||||
if lease.IPAddress == nil {
|
if lease.IPAddress == nil {
|
||||||
|
|
|
@ -143,15 +143,21 @@ 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 {
|
||||||
l.Error().Msg("keyboardHidFile is nil")
|
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
|
||||||
|
// show the error every 100 times to avoid spamming the logs
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// reset the counter
|
||||||
|
u.resetLogSuppressionCounter("keyboardHidFileNil")
|
||||||
|
|
||||||
n, err := u.keyboardHidFile.Read(buf)
|
n, err := u.keyboardHidFile.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error().Err(err).Msg("failed to read")
|
u.logWithSupression("keyboardHidFileRead", 100, &l, err, "failed to read")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
u.resetLogSuppressionCounter("keyboardHidFileRead")
|
||||||
|
|
||||||
l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard")
|
l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard")
|
||||||
if n != 1 {
|
if n != 1 {
|
||||||
l.Trace().Int("n", n).Msg("expected 1 byte, got")
|
l.Trace().Int("n", n).Msg("expected 1 byte, got")
|
||||||
|
@ -195,12 +201,12 @@ func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.keyboardHidFile.Write(data)
|
_, err := u.keyboardHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.log.Error().Err(err).Msg("failed to write to hidg0")
|
u.logWithSupression("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
|
||||||
}
|
}
|
||||||
|
u.resetLogSuppressionCounter("keyboardWriteHidFile")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ var absoluteMouseConfig = gadgetConfigItem{
|
||||||
configPath: []string{"hid.usb1"},
|
configPath: []string{"hid.usb1"},
|
||||||
attrs: gadgetAttributes{
|
attrs: gadgetAttributes{
|
||||||
"protocol": "2",
|
"protocol": "2",
|
||||||
"subclass": "1",
|
"subclass": "0",
|
||||||
"report_length": "6",
|
"report_length": "6",
|
||||||
},
|
},
|
||||||
reportDesc: absoluteMouseCombinedReportDesc,
|
reportDesc: absoluteMouseCombinedReportDesc,
|
||||||
|
@ -75,11 +75,12 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.absMouseHidFile.Write(data)
|
_, err := u.absMouseHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.log.Error().Err(err).Msg("failed to write to hidg1")
|
u.logWithSupression("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
|
||||||
}
|
}
|
||||||
|
u.resetLogSuppressionCounter("absMouseWriteHidFile")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,11 +65,12 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
|
||||||
|
|
||||||
_, err := u.relMouseHidFile.Write(data)
|
_, err := u.relMouseHidFile.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.log.Error().Err(err).Msg("failed to write to hidg2")
|
u.logWithSupression("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
|
||||||
}
|
}
|
||||||
|
u.resetLogSuppressionCounter("relMouseWriteHidFile")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,8 @@ type UsbGadget struct {
|
||||||
onKeyboardStateChange *func(state KeyboardState)
|
onKeyboardStateChange *func(state KeyboardState)
|
||||||
|
|
||||||
log *zerolog.Logger
|
log *zerolog.Logger
|
||||||
|
|
||||||
|
logSuppressionCounter map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
const configFSPath = "/sys/kernel/config"
|
const configFSPath = "/sys/kernel/config"
|
||||||
|
@ -126,6 +128,8 @@ func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDev
|
||||||
|
|
||||||
strictMode: config.strictMode,
|
strictMode: config.strictMode,
|
||||||
|
|
||||||
|
logSuppressionCounter: make(map[string]int),
|
||||||
|
|
||||||
absMouseAccumulatedWheelY: 0,
|
absMouseAccumulatedWheelY: 0,
|
||||||
}
|
}
|
||||||
if err := g.Init(); err != nil {
|
if err := g.Init(); err != nil {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func joinPath(basePath string, paths []string) string {
|
func joinPath(basePath string, paths []string) string {
|
||||||
|
@ -78,3 +80,27 @@ 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{}) {
|
||||||
|
if _, ok := u.logSuppressionCounter[counterName]; !ok {
|
||||||
|
u.logSuppressionCounter[counterName] = 0
|
||||||
|
} else {
|
||||||
|
u.logSuppressionCounter[counterName]++
|
||||||
|
}
|
||||||
|
|
||||||
|
l := logger.With().Int("counter", u.logSuppressionCounter[counterName]).Logger()
|
||||||
|
|
||||||
|
if u.logSuppressionCounter[counterName]%every == 0 {
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Err(err).Msgf(msg, args...)
|
||||||
|
} else {
|
||||||
|
l.Error().Msgf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UsbGadget) resetLogSuppressionCounter(counterName string) {
|
||||||
|
if _, ok := u.logSuppressionCounter[counterName]; !ok {
|
||||||
|
u.logSuppressionCounter[counterName] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
99
native.go
99
native.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -41,6 +42,11 @@ var ongoingRequests = make(map[int32]chan *CtrlResponse)
|
||||||
|
|
||||||
var lock = &sync.Mutex{}
|
var lock = &sync.Mutex{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nativeCmd *exec.Cmd
|
||||||
|
nativeCmdLock = &sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
func CallCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
|
func CallCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
@ -129,16 +135,26 @@ func StartNativeSocketServer(socketPath string, handleClient func(net.Conn), isC
|
||||||
scopedLogger.Info().Msg("server listening")
|
scopedLogger.Info().Msg("server listening")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := listener.Accept()
|
for {
|
||||||
listener.Close()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
|
||||||
scopedLogger.Warn().Err(err).Msg("failed to accept socket")
|
if err != nil {
|
||||||
|
scopedLogger.Warn().Err(err).Msg("failed to accept socket")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isCtrl {
|
||||||
|
// check if the channel is closed
|
||||||
|
select {
|
||||||
|
case <-ctrlClientConnected:
|
||||||
|
scopedLogger.Debug().Msg("ctrl client reconnected")
|
||||||
|
default:
|
||||||
|
close(ctrlClientConnected)
|
||||||
|
scopedLogger.Debug().Msg("first native ctrl socket client connected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go handleClient(conn)
|
||||||
}
|
}
|
||||||
if isCtrl {
|
|
||||||
close(ctrlClientConnected)
|
|
||||||
scopedLogger.Debug().Msg("first native ctrl socket client connected")
|
|
||||||
}
|
|
||||||
handleClient(conn)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return listener
|
return listener
|
||||||
|
@ -235,6 +251,51 @@ func handleVideoClient(conn net.Conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startNativeBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
|
||||||
|
nativeCmdLock.Lock()
|
||||||
|
defer nativeCmdLock.Unlock()
|
||||||
|
|
||||||
|
cmd, err := startNativeBinary(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nativeCmd = cmd
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartNativeBinary(binaryPath string) error {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
// restart the binary
|
||||||
|
nativeLogger.Info().Msg("restarting jetkvm_native binary")
|
||||||
|
cmd, err := startNativeBinary(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
nativeLogger.Warn().Err(err).Msg("failed to restart binary")
|
||||||
|
}
|
||||||
|
nativeCmd = cmd
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func superviseNativeBinary(binaryPath string) error {
|
||||||
|
nativeCmdLock.Lock()
|
||||||
|
defer nativeCmdLock.Unlock()
|
||||||
|
|
||||||
|
if nativeCmd == nil || nativeCmd.Process == nil {
|
||||||
|
return restartNativeBinary(binaryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nativeCmd.Wait()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
nativeLogger.Info().Err(err).Msg("jetkvm_native binary exited with no error")
|
||||||
|
} else if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
nativeLogger.Warn().Int("exit_code", exiterr.ExitCode()).Msg("jetkvm_native binary exited with error")
|
||||||
|
} else {
|
||||||
|
nativeLogger.Warn().Err(err).Msg("jetkvm_native binary exited with unknown error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return restartNativeBinary(binaryPath)
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractAndRunNativeBin() error {
|
func ExtractAndRunNativeBin() error {
|
||||||
binaryPath := "/userdata/jetkvm/bin/jetkvm_native"
|
binaryPath := "/userdata/jetkvm/bin/jetkvm_native"
|
||||||
if err := ensureBinaryUpdated(binaryPath); err != nil {
|
if err := ensureBinaryUpdated(binaryPath); err != nil {
|
||||||
|
@ -246,12 +307,28 @@ func ExtractAndRunNativeBin() error {
|
||||||
return fmt.Errorf("failed to make binary executable: %w", err)
|
return fmt.Errorf("failed to make binary executable: %w", err)
|
||||||
}
|
}
|
||||||
// Run the binary in the background
|
// Run the binary in the background
|
||||||
cmd, err := startNativeBinary(binaryPath)
|
cmd, err := startNativeBinaryWithLock(binaryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to start binary: %w", err)
|
return fmt.Errorf("failed to start binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: add auto restart
|
// check if the binary is still running every 10 seconds
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-appCtx.Done():
|
||||||
|
nativeLogger.Info().Msg("stopping native binary supervisor")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err := superviseNativeBinary(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
nativeLogger.Warn().Err(err).Msg("failed to supervise native binary")
|
||||||
|
time.Sleep(1 * time.Second) // Add a short delay to prevent rapid successive calls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-appCtx.Done()
|
<-appCtx.Done()
|
||||||
nativeLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
|
nativeLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
|
||||||
|
|
|
@ -21,6 +21,7 @@ func networkStateChanged() {
|
||||||
|
|
||||||
// always restart mDNS when the network state changes
|
// always restart mDNS when the network state changes
|
||||||
if mDNS != nil {
|
if mDNS != nil {
|
||||||
|
_ = mDNS.SetListenOptions(config.NetworkConfig.GetMDNSMode())
|
||||||
_ = mDNS.SetLocalNames([]string{
|
_ = mDNS.SetLocalNames([]string{
|
||||||
networkState.GetHostname(),
|
networkState.GetHostname(),
|
||||||
networkState.GetFQDN(),
|
networkState.GetFQDN(),
|
||||||
|
@ -54,14 +55,6 @@ func initNetwork() error {
|
||||||
OnConfigChange: func(networkConfig *network.NetworkConfig) {
|
OnConfigChange: func(networkConfig *network.NetworkConfig) {
|
||||||
config.NetworkConfig = networkConfig
|
config.NetworkConfig = networkConfig
|
||||||
networkStateChanged()
|
networkStateChanged()
|
||||||
|
|
||||||
if mDNS != nil {
|
|
||||||
_ = mDNS.SetListenOptions(networkConfig.GetMDNSMode())
|
|
||||||
_ = mDNS.SetLocalNames([]string{
|
|
||||||
networkState.GetHostname(),
|
|
||||||
networkState.GetFQDN(),
|
|
||||||
}, true)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
24
terminal.go
24
terminal.go
|
@ -1,6 +1,7 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -55,18 +56,23 @@ func handleTerminalChannel(d *webrtc.DataChannel) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if msg.IsString {
|
if msg.IsString {
|
||||||
var size TerminalSize
|
maybeJson := bytes.TrimSpace(msg.Data)
|
||||||
err := json.Unmarshal([]byte(msg.Data), &size)
|
// Cheap check to see if this resembles JSON
|
||||||
if err == nil {
|
if len(maybeJson) > 1 && maybeJson[0] == '{' && maybeJson[len(maybeJson)-1] == '}' {
|
||||||
err = pty.Setsize(ptmx, &pty.Winsize{
|
var size TerminalSize
|
||||||
Rows: uint16(size.Rows),
|
err := json.Unmarshal(maybeJson, &size)
|
||||||
Cols: uint16(size.Cols),
|
|
||||||
})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
err = pty.Setsize(ptmx, &pty.Winsize{
|
||||||
|
Rows: uint16(size.Rows),
|
||||||
|
Cols: uint16(size.Cols),
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
scopedLogger.Info().Int("rows", size.Rows).Int("cols", size.Cols).Msg("Set terminal size")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
scopedLogger.Warn().Err(err).Msg("Failed to parse terminal size")
|
||||||
}
|
}
|
||||||
scopedLogger.Warn().Err(err).Msg("Failed to parse terminal size")
|
|
||||||
}
|
}
|
||||||
_, err := ptmx.Write(msg.Data)
|
_, err := ptmx.Write(msg.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { MdOutlineContentPasteGo } from "react-icons/md";
|
import { MdOutlineContentPasteGo } from "react-icons/md";
|
||||||
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
|
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
|
||||||
import { FaKeyboard, FaLock} from "react-icons/fa6";
|
import { FaKeyboard } from "react-icons/fa6";
|
||||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||||
import { Fragment, useCallback, useRef } from "react";
|
import { Fragment, useCallback, useRef } from "react";
|
||||||
import { CommandLineIcon } from "@heroicons/react/20/solid";
|
import { CommandLineIcon } from "@heroicons/react/20/solid";
|
||||||
|
@ -19,8 +19,6 @@ import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index";
|
||||||
import MountPopopover from "@/components/popovers/MountPopover";
|
import MountPopopover from "@/components/popovers/MountPopover";
|
||||||
import ExtensionPopover from "@/components/popovers/ExtensionPopover";
|
import ExtensionPopover from "@/components/popovers/ExtensionPopover";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
import useKeyboard from "@/hooks/useKeyboard";
|
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
|
||||||
|
|
||||||
export default function Actionbar({
|
export default function Actionbar({
|
||||||
requestFullscreen,
|
requestFullscreen,
|
||||||
|
@ -58,8 +56,6 @@ export default function Actionbar({
|
||||||
[setDisableFocusTrap],
|
[setDisableFocusTrap],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { sendKeyboardEvent, resetKeyboardState } = useKeyboard();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
|
<Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
|
||||||
<div
|
<div
|
||||||
|
@ -266,7 +262,7 @@ export default function Actionbar({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{useSettingsStore().actionBarCtrlAltDel && (
|
{/* {useSettingsStore().actionBarCtrlAltDel && (
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
|
@ -282,7 +278,7 @@ export default function Actionbar({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
|
|
|
@ -10,7 +10,7 @@ import LoadingSpinner from "@components/LoadingSpinner";
|
||||||
import Card, { GridCard } from "@components/Card";
|
import Card, { GridCard } from "@components/Card";
|
||||||
|
|
||||||
interface OverlayContentProps {
|
interface OverlayContentProps {
|
||||||
children: React.ReactNode;
|
readonly children: React.ReactNode;
|
||||||
}
|
}
|
||||||
function OverlayContent({ children }: OverlayContentProps) {
|
function OverlayContent({ children }: OverlayContentProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -23,7 +23,7 @@ function OverlayContent({ children }: OverlayContentProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingOverlayProps {
|
interface LoadingOverlayProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LoadingVideoOverlay({ show }: LoadingOverlayProps) {
|
export function LoadingVideoOverlay({ show }: LoadingOverlayProps) {
|
||||||
|
@ -57,8 +57,8 @@ export function LoadingVideoOverlay({ show }: LoadingOverlayProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingConnectionOverlayProps {
|
interface LoadingConnectionOverlayProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
text: string;
|
readonly text: string;
|
||||||
}
|
}
|
||||||
export function LoadingConnectionOverlay({ show, text }: LoadingConnectionOverlayProps) {
|
export function LoadingConnectionOverlay({ show, text }: LoadingConnectionOverlayProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -91,8 +91,8 @@ export function LoadingConnectionOverlay({ show, text }: LoadingConnectionOverla
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectionErrorOverlayProps {
|
interface ConnectionErrorOverlayProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
setupPeerConnection: () => Promise<void>;
|
readonly setupPeerConnection: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectionFailedOverlay({
|
export function ConnectionFailedOverlay({
|
||||||
|
@ -153,7 +153,7 @@ export function ConnectionFailedOverlay({
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PeerConnectionDisconnectedOverlay {
|
interface PeerConnectionDisconnectedOverlay {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PeerConnectionDisconnectedOverlay({
|
export function PeerConnectionDisconnectedOverlay({
|
||||||
|
@ -207,8 +207,8 @@ export function PeerConnectionDisconnectedOverlay({
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HDMIErrorOverlayProps {
|
interface HDMIErrorOverlayProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
hdmiState: string;
|
readonly hdmiState: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HDMIErrorOverlay({ show, hdmiState }: HDMIErrorOverlayProps) {
|
export function HDMIErrorOverlay({ show, hdmiState }: HDMIErrorOverlayProps) {
|
||||||
|
@ -310,8 +310,8 @@ export function HDMIErrorOverlay({ show, hdmiState }: HDMIErrorOverlayProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NoAutoplayPermissionsOverlayProps {
|
interface NoAutoplayPermissionsOverlayProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
onPlayClick: () => void;
|
readonly onPlayClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NoAutoplayPermissionsOverlay({
|
export function NoAutoplayPermissionsOverlay({
|
||||||
|
@ -361,7 +361,7 @@ export function NoAutoplayPermissionsOverlay({
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PointerLockBarProps {
|
interface PointerLockBarProps {
|
||||||
show: boolean;
|
readonly show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PointerLockBar({ show }: PointerLockBarProps) {
|
export function PointerLockBar({ show }: PointerLockBarProps) {
|
||||||
|
@ -369,10 +369,10 @@ export function PointerLockBar({ show }: PointerLockBarProps) {
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{show ? (
|
{show ? (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute -top-[36px] left-0 right-0 z-20 bg-white"
|
className="flex w-full items-center justify-between bg-transparent"
|
||||||
initial={{ y: 20, opacity: 0, zIndex: 0 }}
|
initial={{ opacity: 0, zIndex: 0 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, zIndex: 20 }}
|
||||||
exit={{ y: 43, zIndex: 0 }}
|
exit={{ opacity: 0, zIndex: 0 }}
|
||||||
transition={{ duration: 0.5, ease: "easeInOut", delay: 0.5 }}
|
transition={{ duration: 0.5, ease: "easeInOut", delay: 0.5 }}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useResizeObserver } from "usehooks-ts";
|
import { useResizeObserver } from "usehooks-ts";
|
||||||
|
|
||||||
|
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||||
|
import Actionbar from "@components/ActionBar";
|
||||||
|
import MacroBar from "@/components/MacroBar";
|
||||||
|
import InfoBar from "@components/InfoBar";
|
||||||
|
import notifications from "@/notifications";
|
||||||
|
import useKeyboard from "@/hooks/useKeyboard";
|
||||||
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
import { cx } from "@/cva.config";
|
||||||
|
import { keys, modifiers } from "@/keyboardMappings";
|
||||||
import {
|
import {
|
||||||
useHidStore,
|
useHidStore,
|
||||||
useMouseStore,
|
useMouseStore,
|
||||||
useRTCStore,
|
useRTCStore,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useUiStore,
|
|
||||||
useVideoStore,
|
useVideoStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
|
||||||
import { cx } from "@/cva.config";
|
|
||||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
|
||||||
import Actionbar from "@components/ActionBar";
|
|
||||||
import MacroBar from "@/components/MacroBar";
|
|
||||||
import InfoBar from "@components/InfoBar";
|
|
||||||
import useKeyboard from "@/hooks/useKeyboard";
|
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
|
||||||
import notifications from "@/notifications";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HDMIErrorOverlay,
|
HDMIErrorOverlay,
|
||||||
|
@ -47,6 +46,11 @@ export default function WebRTCVideo() {
|
||||||
clientHeight: videoClientHeight,
|
clientHeight: videoClientHeight,
|
||||||
} = useVideoStore();
|
} = useVideoStore();
|
||||||
|
|
||||||
|
// Video enhancement settings
|
||||||
|
const videoSaturation = useSettingsStore(state => state.videoSaturation);
|
||||||
|
const videoBrightness = useSettingsStore(state => state.videoBrightness);
|
||||||
|
const videoContrast = useSettingsStore(state => state.videoContrast);
|
||||||
|
|
||||||
// HID related states
|
// HID related states
|
||||||
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
||||||
const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync);
|
const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync);
|
||||||
|
@ -67,8 +71,9 @@ export default function WebRTCVideo() {
|
||||||
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
|
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
|
||||||
const isVideoLoading = !isPlaying;
|
const isVideoLoading = !isPlaying;
|
||||||
|
|
||||||
|
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
|
||||||
|
|
||||||
// Misc states and hooks
|
// Misc states and hooks
|
||||||
const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
|
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
// Video-related
|
// Video-related
|
||||||
|
@ -106,32 +111,77 @@ export default function WebRTCVideo() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pointer lock and keyboard lock related
|
// Pointer lock and keyboard lock related
|
||||||
const isPointerLockPossible = window.location.protocol === "https:";
|
const isPointerLockPossible = window.location.protocol === "https:" || window.location.hostname === "localhost";
|
||||||
|
const isFullscreenEnabled = document.fullscreenEnabled;
|
||||||
|
|
||||||
const checkNavigatorPermissions = useCallback(async (permissionName: string) => {
|
const checkNavigatorPermissions = useCallback(async (permissionName: string) => {
|
||||||
const name = permissionName as PermissionName;
|
if (!navigator.permissions || !navigator.permissions.query) {
|
||||||
const { state } = await navigator.permissions.query({ name });
|
return false; // if can't query permissions, assume NOT granted
|
||||||
return state === "granted";
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const name = permissionName as PermissionName;
|
||||||
|
const { state } = await navigator.permissions.query({ name });
|
||||||
|
return state === "granted";
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
return false; // if query fails, assume NOT granted
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const requestPointerLock = useCallback(async () => {
|
const requestPointerLock = useCallback(async () => {
|
||||||
if (document.pointerLockElement) return;
|
if (!isPointerLockPossible
|
||||||
|
|| videoElm.current === null
|
||||||
|
|| document.pointerLockElement) return;
|
||||||
|
|
||||||
const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock");
|
const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock");
|
||||||
|
|
||||||
if (isPointerLockGranted && settings.mouseMode === "relative") {
|
if (isPointerLockGranted && settings.mouseMode === "relative") {
|
||||||
videoElm.current?.requestPointerLock();
|
try {
|
||||||
|
await videoElm.current.requestPointerLock();
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [checkNavigatorPermissions, settings.mouseMode]);
|
}, [checkNavigatorPermissions, isPointerLockPossible, settings.mouseMode]);
|
||||||
|
|
||||||
|
const requestKeyboardLock = useCallback(async () => {
|
||||||
|
if (videoElm.current === null) return;
|
||||||
|
|
||||||
|
const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock");
|
||||||
|
|
||||||
|
if (isKeyboardLockGranted && "keyboard" in navigator) {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error - keyboard lock is not supported in all browsers
|
||||||
|
await navigator.keyboard.lock();
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [checkNavigatorPermissions]);
|
||||||
|
|
||||||
|
const releaseKeyboardLock = useCallback(async () => {
|
||||||
|
if (videoElm.current === null || document.fullscreenElement !== videoElm.current) return;
|
||||||
|
|
||||||
|
if ("keyboard" in navigator) {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error - keyboard unlock is not supported in all browsers
|
||||||
|
await navigator.keyboard.unlock();
|
||||||
|
} catch {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPointerLockPossible || !videoElm.current) return;
|
if (!isPointerLockPossible || !videoElm.current) return;
|
||||||
|
|
||||||
const handlePointerLockChange = () => {
|
const handlePointerLockChange = () => {
|
||||||
if (document.pointerLockElement) {
|
if (document.pointerLockElement) {
|
||||||
notifications.success("Pointer lock Enabled, hold escape to exit");
|
notifications.success("Pointer lock Enabled, press escape to unlock");
|
||||||
setIsPointerLockActive(true);
|
setIsPointerLockActive(true);
|
||||||
} else {
|
} else {
|
||||||
notifications.success("Pointer lock disabled");
|
notifications.success("Pointer lock Disabled");
|
||||||
setIsPointerLockActive(false);
|
setIsPointerLockActive(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -144,27 +194,39 @@ export default function WebRTCVideo() {
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
}, [isPointerLockPossible, videoElm]);
|
}, [isPointerLockPossible]);
|
||||||
|
|
||||||
const requestFullscreen = useCallback(async () => {
|
const requestFullscreen = useCallback(async () => {
|
||||||
videoElm.current?.requestFullscreen({
|
if (!isFullscreenEnabled || !videoElm.current) return;
|
||||||
navigationUI: "show",
|
|
||||||
});
|
|
||||||
|
|
||||||
// we do not care about pointer lock if it's for fullscreen
|
// per https://wicg.github.io/keyboard-lock/#system-key-press-handler
|
||||||
|
// If keyboard lock is activated after fullscreen is already in effect, then the user my
|
||||||
|
// see multiple messages about how to exit fullscreen. For this reason, we recommend that
|
||||||
|
// developers call lock() before they enter fullscreen:
|
||||||
|
await requestKeyboardLock();
|
||||||
await requestPointerLock();
|
await requestPointerLock();
|
||||||
|
|
||||||
const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock");
|
await videoElm.current.requestFullscreen({
|
||||||
if (isKeyboardLockGranted) {
|
navigationUI: "show",
|
||||||
if ("keyboard" in navigator) {
|
});
|
||||||
// @ts-expect-error - keyboard lock is not supported in all browsers
|
}, [isFullscreenEnabled, requestKeyboardLock, requestPointerLock]);
|
||||||
await navigator.keyboard.lock();
|
|
||||||
|
// setup to release the keyboard lock anytime the fullscreen ends
|
||||||
|
useEffect(() => {
|
||||||
|
if (!videoElm.current) return;
|
||||||
|
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
releaseKeyboardLock();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}, [requestPointerLock, checkNavigatorPermissions]);
|
|
||||||
|
document.addEventListener("fullscreenchange ", handleFullscreenChange);
|
||||||
|
}, [releaseKeyboardLock]);
|
||||||
|
|
||||||
// Mouse-related
|
// Mouse-related
|
||||||
const calcDelta = (pos: number) => (Math.abs(pos) < 10 ? pos * 2 : pos);
|
const calcDelta = (pos: number) => (Math.abs(pos) < 10 ? pos * 2 : pos);
|
||||||
|
|
||||||
const sendRelMouseMovement = useCallback(
|
const sendRelMouseMovement = useCallback(
|
||||||
(x: number, y: number, buttons: number) => {
|
(x: number, y: number, buttons: number) => {
|
||||||
if (settings.mouseMode !== "relative") return;
|
if (settings.mouseMode !== "relative") return;
|
||||||
|
@ -179,18 +241,13 @@ export default function WebRTCVideo() {
|
||||||
const relMouseMoveHandler = useCallback(
|
const relMouseMoveHandler = useCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
if (settings.mouseMode !== "relative") return;
|
if (settings.mouseMode !== "relative") return;
|
||||||
if (isPointerLockActive === false && isPointerLockPossible === true) return;
|
if (isPointerLockActive === false && isPointerLockPossible) return;
|
||||||
|
|
||||||
// Send mouse movement
|
// Send mouse movement
|
||||||
const { buttons } = e;
|
const { buttons } = e;
|
||||||
sendRelMouseMovement(e.movementX, e.movementY, buttons);
|
sendRelMouseMovement(e.movementX, e.movementY, buttons);
|
||||||
},
|
},
|
||||||
[
|
[isPointerLockActive, isPointerLockPossible, sendRelMouseMovement, settings.mouseMode],
|
||||||
isPointerLockActive,
|
|
||||||
isPointerLockPossible,
|
|
||||||
sendRelMouseMovement,
|
|
||||||
settings.mouseMode,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendAbsMouseMovement = useCallback(
|
const sendAbsMouseMovement = useCallback(
|
||||||
|
@ -244,18 +301,16 @@ export default function WebRTCVideo() {
|
||||||
const { buttons } = e;
|
const { buttons } = e;
|
||||||
sendAbsMouseMovement(x, y, buttons);
|
sendAbsMouseMovement(x, y, buttons);
|
||||||
},
|
},
|
||||||
[
|
[settings.mouseMode, videoClientWidth, videoClientHeight, videoWidth, videoHeight, sendAbsMouseMovement],
|
||||||
sendAbsMouseMovement,
|
|
||||||
videoClientHeight,
|
|
||||||
videoClientWidth,
|
|
||||||
videoWidth,
|
|
||||||
videoHeight,
|
|
||||||
settings.mouseMode,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const mouseWheelHandler = useCallback(
|
const mouseWheelHandler = useCallback(
|
||||||
(e: WheelEvent) => {
|
(e: WheelEvent) => {
|
||||||
|
|
||||||
|
if (settings.scrollThrottling && blockWheelEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if the wheel event is an accel scroll value
|
// Determine if the wheel event is an accel scroll value
|
||||||
const isAccel = Math.abs(e.deltaY) >= 100;
|
const isAccel = Math.abs(e.deltaY) >= 100;
|
||||||
|
|
||||||
|
@ -263,7 +318,7 @@ export default function WebRTCVideo() {
|
||||||
const accelScrollValue = e.deltaY / 100;
|
const accelScrollValue = e.deltaY / 100;
|
||||||
|
|
||||||
// Calculate the no accel scroll value
|
// Calculate the no accel scroll value
|
||||||
const noAccelScrollValue = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
|
const noAccelScrollValue = Math.sign(e.deltaY);
|
||||||
|
|
||||||
// Get scroll value
|
// Get scroll value
|
||||||
const scrollValue = isAccel ? accelScrollValue : noAccelScrollValue;
|
const scrollValue = isAccel ? accelScrollValue : noAccelScrollValue;
|
||||||
|
@ -275,8 +330,14 @@ export default function WebRTCVideo() {
|
||||||
const invertedScrollValue = -clampedScrollValue;
|
const invertedScrollValue = -clampedScrollValue;
|
||||||
|
|
||||||
send("wheelReport", { wheelY: invertedScrollValue });
|
send("wheelReport", { wheelY: invertedScrollValue });
|
||||||
|
|
||||||
|
// Apply blocking delay based of throttling settings
|
||||||
|
if (settings.scrollThrottling && !blockWheelEvent) {
|
||||||
|
setBlockWheelEvent(true);
|
||||||
|
setTimeout(() => setBlockWheelEvent(false), settings.scrollThrottling);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[send],
|
[send, blockWheelEvent, settings],
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetMousePosition = useCallback(() => {
|
const resetMousePosition = useCallback(() => {
|
||||||
|
@ -356,13 +417,6 @@ export default function WebRTCVideo() {
|
||||||
let code = e.code;
|
let code = e.code;
|
||||||
const key = e.key;
|
const key = e.key;
|
||||||
|
|
||||||
// if (document.activeElement?.id !== "videoFocusTrap") {
|
|
||||||
// console.log("KEYUP: Not focusing on the video", document.activeElement);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log(document.activeElement);
|
|
||||||
|
|
||||||
if (!isKeyboardLedManagedByHost) {
|
if (!isKeyboardLedManagedByHost) {
|
||||||
setIsNumLockActive(e.getModifierState("NumLock"));
|
setIsNumLockActive(e.getModifierState("NumLock"));
|
||||||
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
||||||
|
@ -440,13 +494,15 @@ export default function WebRTCVideo() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const videoKeyUpHandler = useCallback((e: KeyboardEvent) => {
|
const videoKeyUpHandler = useCallback((e: KeyboardEvent) => {
|
||||||
|
if (!videoElm.current) return;
|
||||||
|
|
||||||
// In fullscreen mode in chrome & safari, the space key is used to pause/play the video
|
// In fullscreen mode in chrome & safari, the space key is used to pause/play the video
|
||||||
// there is no way to prevent this, so we need to simply force play the video when it's paused.
|
// there is no way to prevent this, so we need to simply force play the video when it's paused.
|
||||||
// Fix only works in chrome based browsers.
|
// Fix only works in chrome based browsers.
|
||||||
if (e.code === "Space") {
|
if (e.code === "Space") {
|
||||||
if (videoElm.current?.paused == true) {
|
if (videoElm.current.paused) {
|
||||||
console.log("Force playing video");
|
console.log("Force playing video");
|
||||||
videoElm.current?.play();
|
videoElm.current.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -455,7 +511,6 @@ export default function WebRTCVideo() {
|
||||||
(mediaStream: MediaStream) => {
|
(mediaStream: MediaStream) => {
|
||||||
if (!videoElm.current) return;
|
if (!videoElm.current) return;
|
||||||
const videoElmRefValue = videoElm.current;
|
const videoElmRefValue = videoElm.current;
|
||||||
// console.log("Adding stream to video element", videoElmRefValue);
|
|
||||||
videoElmRefValue.srcObject = mediaStream;
|
videoElmRefValue.srcObject = mediaStream;
|
||||||
updateVideoSizeStore(videoElmRefValue);
|
updateVideoSizeStore(videoElmRefValue);
|
||||||
},
|
},
|
||||||
|
@ -471,7 +526,6 @@ export default function WebRTCVideo() {
|
||||||
peerConnection.addEventListener(
|
peerConnection.addEventListener(
|
||||||
"track",
|
"track",
|
||||||
(e: RTCTrackEvent) => {
|
(e: RTCTrackEvent) => {
|
||||||
// console.log("Adding stream to video element");
|
|
||||||
addStreamToVideoElm(e.streams[0]);
|
addStreamToVideoElm(e.streams[0]);
|
||||||
},
|
},
|
||||||
{ signal },
|
{ signal },
|
||||||
|
@ -487,7 +541,6 @@ export default function WebRTCVideo() {
|
||||||
useEffect(
|
useEffect(
|
||||||
function updateVideoStream() {
|
function updateVideoStream() {
|
||||||
if (!mediaStream) return;
|
if (!mediaStream) return;
|
||||||
console.log("Updating video stream from mediaStream");
|
|
||||||
// We set the as early as possible
|
// We set the as early as possible
|
||||||
addStreamToVideoElm(mediaStream);
|
addStreamToVideoElm(mediaStream);
|
||||||
},
|
},
|
||||||
|
@ -509,9 +562,6 @@ export default function WebRTCVideo() {
|
||||||
document.addEventListener("keydown", keyDownHandler, { signal });
|
document.addEventListener("keydown", keyDownHandler, { signal });
|
||||||
document.addEventListener("keyup", keyUpHandler, { signal });
|
document.addEventListener("keyup", keyUpHandler, { signal });
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
window.clearKeys = () => sendKeyboardEvent([], []);
|
|
||||||
window.addEventListener("blur", resetKeyboardState, { signal });
|
window.addEventListener("blur", resetKeyboardState, { signal });
|
||||||
document.addEventListener("visibilitychange", resetKeyboardState, { signal });
|
document.addEventListener("visibilitychange", resetKeyboardState, { signal });
|
||||||
|
|
||||||
|
@ -519,7 +569,7 @@ export default function WebRTCVideo() {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
|
[keyDownHandler, keyUpHandler, resetKeyboardState],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup Video Event Listeners
|
// Setup Video Event Listeners
|
||||||
|
@ -541,38 +591,42 @@ export default function WebRTCVideo() {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[
|
[onVideoPlaying, videoKeyUpHandler],
|
||||||
absMouseMoveHandler,
|
|
||||||
resetMousePosition,
|
|
||||||
onVideoPlaying,
|
|
||||||
mouseWheelHandler,
|
|
||||||
videoKeyUpHandler,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup Absolute Mouse Events
|
// Setup Mouse Events
|
||||||
useEffect(
|
useEffect(
|
||||||
function setAbsoluteMouseModeEventListeners() {
|
function setMouseModeEventListeners() {
|
||||||
const videoElmRefValue = videoElm.current;
|
const videoElmRefValue = videoElm.current;
|
||||||
if (!videoElmRefValue) return;
|
if (!videoElmRefValue) return;
|
||||||
|
const isRelativeMouseMode = (settings.mouseMode === "relative");
|
||||||
if (settings.mouseMode !== "absolute") return;
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const signal = abortController.signal;
|
const signal = abortController.signal;
|
||||||
|
|
||||||
videoElmRefValue.addEventListener("mousemove", absMouseMoveHandler, { signal });
|
videoElmRefValue.addEventListener("mousemove", isRelativeMouseMode ? relMouseMoveHandler : absMouseMoveHandler, { signal });
|
||||||
videoElmRefValue.addEventListener("pointerdown", absMouseMoveHandler, { signal });
|
videoElmRefValue.addEventListener("pointerdown", isRelativeMouseMode ? relMouseMoveHandler : absMouseMoveHandler, { signal });
|
||||||
videoElmRefValue.addEventListener("pointerup", absMouseMoveHandler, { signal });
|
videoElmRefValue.addEventListener("pointerup", isRelativeMouseMode ? relMouseMoveHandler :absMouseMoveHandler, { signal });
|
||||||
videoElmRefValue.addEventListener("wheel", mouseWheelHandler, {
|
videoElmRefValue.addEventListener("wheel", mouseWheelHandler, {
|
||||||
signal,
|
signal,
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset the mouse position when the window is blurred or the document is hidden
|
if (isRelativeMouseMode) {
|
||||||
const local = resetMousePosition;
|
videoElmRefValue.addEventListener("click",
|
||||||
window.addEventListener("blur", local, { signal });
|
() => {
|
||||||
document.addEventListener("visibilitychange", local, { signal });
|
if (isPointerLockPossible && !isPointerLockActive && !document.pointerLockElement) {
|
||||||
|
requestPointerLock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Reset the mouse position when the window is blurred or the document is hidden
|
||||||
|
window.addEventListener("blur", resetMousePosition, { signal });
|
||||||
|
document.addEventListener("visibilitychange", resetMousePosition, { signal });
|
||||||
|
}
|
||||||
|
|
||||||
const preventContextMenu = (e: MouseEvent) => e.preventDefault();
|
const preventContextMenu = (e: MouseEvent) => e.preventDefault();
|
||||||
videoElmRefValue.addEventListener("contextmenu", preventContextMenu, { signal });
|
videoElmRefValue.addEventListener("contextmenu", preventContextMenu, { signal });
|
||||||
|
|
||||||
|
@ -580,65 +634,18 @@ export default function WebRTCVideo() {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[absMouseMoveHandler, mouseWheelHandler, resetMousePosition, settings.mouseMode],
|
[absMouseMoveHandler, isPointerLockActive, isPointerLockPossible, mouseWheelHandler, relMouseMoveHandler, requestPointerLock, resetMousePosition, settings.mouseMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup Relative Mouse Events
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function setupRelativeMouseEventListeners() {
|
|
||||||
if (settings.mouseMode !== "relative") return;
|
|
||||||
// Relative mouse mode should only be active if the pointer lock is active and Pointer Lock is possible
|
|
||||||
|
|
||||||
const videoElmRefValue = videoElm.current;
|
|
||||||
if (!videoElmRefValue) return;
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const signal = abortController.signal;
|
|
||||||
|
|
||||||
videoElmRefValue.addEventListener("mousemove", relMouseMoveHandler, { signal });
|
|
||||||
videoElmRefValue.addEventListener("pointerdown", relMouseMoveHandler, { signal });
|
|
||||||
videoElmRefValue.addEventListener("pointerup", relMouseMoveHandler, { signal });
|
|
||||||
videoElmRefValue.addEventListener(
|
|
||||||
"click",
|
|
||||||
() => {
|
|
||||||
if (isPointerLockPossible && !document.pointerLockElement) {
|
|
||||||
requestPointerLock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ signal },
|
|
||||||
);
|
|
||||||
videoElmRefValue.addEventListener("wheel", mouseWheelHandler, {
|
|
||||||
signal,
|
|
||||||
passive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const preventContextMenu = (e: MouseEvent) => e.preventDefault();
|
|
||||||
videoElmRefValue.addEventListener("contextmenu", preventContextMenu, { signal });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
abortController.abort();
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[
|
|
||||||
settings.mouseMode,
|
|
||||||
relMouseMoveHandler,
|
|
||||||
mouseWheelHandler,
|
|
||||||
disableVideoFocusTrap,
|
|
||||||
requestPointerLock,
|
|
||||||
isPointerLockPossible,
|
|
||||||
isPointerLockActive,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasNoAutoPlayPermissions = useMemo(() => {
|
const hasNoAutoPlayPermissions = useMemo(() => {
|
||||||
if (peerConnection?.connectionState !== "connected") return false;
|
if (peerConnection?.connectionState !== "connected") return false;
|
||||||
if (isPlaying) return false;
|
if (isPlaying) return false;
|
||||||
if (hdmiError) return false;
|
if (hdmiError) return false;
|
||||||
if (videoHeight === 0 || videoWidth === 0) return false;
|
if (videoHeight === 0 || videoWidth === 0) return false;
|
||||||
return true;
|
return true;
|
||||||
}, [peerConnection?.connectionState, isPlaying, hdmiError, videoHeight, videoWidth]);
|
}, [hdmiError, isPlaying, peerConnection?.connectionState, videoHeight, videoWidth]);
|
||||||
|
|
||||||
const showPointerLockBar = useMemo(() => {
|
const showPointerLockBar = useMemo(() => {
|
||||||
if (settings.mouseMode !== "relative") return false;
|
if (settings.mouseMode !== "relative") return false;
|
||||||
|
@ -648,15 +655,7 @@ export default function WebRTCVideo() {
|
||||||
if (!isPlaying) return false;
|
if (!isPlaying) return false;
|
||||||
if (videoHeight === 0 || videoWidth === 0) return false;
|
if (videoHeight === 0 || videoWidth === 0) return false;
|
||||||
return true;
|
return true;
|
||||||
}, [
|
}, [isPlaying, isPointerLockActive, isPointerLockPossible, isVideoLoading, settings.mouseMode, videoHeight, videoWidth]);
|
||||||
settings.mouseMode,
|
|
||||||
isPointerLockPossible,
|
|
||||||
isPointerLockActive,
|
|
||||||
isVideoLoading,
|
|
||||||
isPlaying,
|
|
||||||
videoHeight,
|
|
||||||
videoWidth,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full grid-rows-(--grid-layout)">
|
<div className="grid h-full w-full grid-rows-(--grid-layout)">
|
||||||
|
@ -686,10 +685,10 @@ export default function WebRTCVideo() {
|
||||||
<div className="relative grow overflow-hidden">
|
<div className="relative grow overflow-hidden">
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<div className="grid grow grid-rows-(--grid-bodyFooter) overflow-hidden">
|
<div className="grid grow grid-rows-(--grid-bodyFooter) overflow-hidden">
|
||||||
|
{/* In relative mouse mode and under https, we enable the pointer lock, and to do so we need a bar to show the user to click on the video to enable mouse control */}
|
||||||
|
<PointerLockBar show={showPointerLockBar} />
|
||||||
<div className="relative mx-4 my-2 flex items-center justify-center overflow-hidden">
|
<div className="relative mx-4 my-2 flex items-center justify-center overflow-hidden">
|
||||||
<div className="relative flex h-full w-full items-center justify-center">
|
<div className="relative flex h-full w-full items-center justify-center">
|
||||||
{/* In relative mouse mode and under https, we enable the pointer lock, and to do so we need a bar to show the user to click on the video to enable mouse control */}
|
|
||||||
<PointerLockBar show={showPointerLockBar} />
|
|
||||||
<video
|
<video
|
||||||
ref={videoElm}
|
ref={videoElm}
|
||||||
autoPlay={true}
|
autoPlay={true}
|
||||||
|
@ -700,6 +699,9 @@ export default function WebRTCVideo() {
|
||||||
playsInline
|
playsInline
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
controlsList="nofullscreen"
|
controlsList="nofullscreen"
|
||||||
|
style={{
|
||||||
|
filter: `saturate(${videoSaturation}) brightness(${videoBrightness}) contrast(${videoContrast})`,
|
||||||
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
"max-h-full min-h-[384px] max-w-full min-w-[512px] bg-black/50 object-contain transition-all duration-1000",
|
"max-h-full min-h-[384px] max-w-full min-w-[512px] bg-black/50 object-contain transition-all duration-1000",
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,11 +39,11 @@ export default function PasteModal() {
|
||||||
state => state.setKeyboardLayout,
|
state => state.setKeyboardLayout,
|
||||||
);
|
);
|
||||||
|
|
||||||
// this ensures we always get the original en-US if it hasn't been set yet
|
// this ensures we always get the original en_US if it hasn't been set yet
|
||||||
const safeKeyboardLayout = useMemo(() => {
|
const safeKeyboardLayout = useMemo(() => {
|
||||||
if (keyboardLayout && keyboardLayout.length > 0)
|
if (keyboardLayout && keyboardLayout.length > 0)
|
||||||
return keyboardLayout;
|
return keyboardLayout;
|
||||||
return "en-US";
|
return "en_US";
|
||||||
}, [keyboardLayout]);
|
}, [keyboardLayout]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -314,8 +314,19 @@ interface SettingsState {
|
||||||
keyboardLedSync: KeyboardLedSync;
|
keyboardLedSync: KeyboardLedSync;
|
||||||
setKeyboardLedSync: (sync: KeyboardLedSync) => void;
|
setKeyboardLedSync: (sync: KeyboardLedSync) => void;
|
||||||
|
|
||||||
|
scrollThrottling: number;
|
||||||
|
setScrollThrottling: (value: number) => void;
|
||||||
|
|
||||||
showPressedKeys: boolean;
|
showPressedKeys: boolean;
|
||||||
setShowPressedKeys: (show: boolean) => void;
|
setShowPressedKeys: (show: boolean) => void;
|
||||||
|
|
||||||
|
// Video enhancement settings
|
||||||
|
videoSaturation: number;
|
||||||
|
setVideoSaturation: (value: number) => void;
|
||||||
|
videoBrightness: number;
|
||||||
|
setVideoBrightness: (value: number) => void;
|
||||||
|
videoContrast: number;
|
||||||
|
setVideoContrast: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSettingsStore = create(
|
export const useSettingsStore = create(
|
||||||
|
@ -354,8 +365,19 @@ export const useSettingsStore = create(
|
||||||
keyboardLedSync: "auto",
|
keyboardLedSync: "auto",
|
||||||
setKeyboardLedSync: sync => set({ keyboardLedSync: sync }),
|
setKeyboardLedSync: sync => set({ keyboardLedSync: sync }),
|
||||||
|
|
||||||
|
scrollThrottling: 0,
|
||||||
|
setScrollThrottling: value => set({ scrollThrottling: value }),
|
||||||
|
|
||||||
showPressedKeys: true,
|
showPressedKeys: true,
|
||||||
setShowPressedKeys: show => set({ showPressedKeys: show }),
|
setShowPressedKeys: show => set({ showPressedKeys: show }),
|
||||||
|
|
||||||
|
// Video enhancement settings with default values (1.0 = normal)
|
||||||
|
videoSaturation: 1.0,
|
||||||
|
setVideoSaturation: value => set({ videoSaturation: value }),
|
||||||
|
videoBrightness: 1.0,
|
||||||
|
setVideoBrightness: value => set({ videoBrightness: value }),
|
||||||
|
videoContrast: 1.0,
|
||||||
|
setVideoContrast: value => set({ videoContrast: value }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "settings",
|
name: "settings",
|
||||||
|
|
|
@ -300,6 +300,10 @@ if (isOnDevice) {
|
||||||
path: "hardware",
|
path: "hardware",
|
||||||
element: <SettingsHardwareRoute />,
|
element: <SettingsHardwareRoute />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "network",
|
||||||
|
element: <SettingsNetworkRoute />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "access",
|
path: "access",
|
||||||
children: [
|
children: [
|
||||||
|
@ -358,7 +362,8 @@ if (isOnDevice) {
|
||||||
{
|
{
|
||||||
path: "devices",
|
path: "devices",
|
||||||
element: <DevicesRoute />,
|
element: <DevicesRoute />,
|
||||||
loader: DevicesRoute.loader },
|
loader: DevicesRoute.loader
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
||||||
|
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
|
|
||||||
|
import { SettingsItem } from "./devices.$id.settings";
|
||||||
|
|
||||||
export default function SettingsCtrlAltDelRoute() {
|
export default function SettingsCtrlAltDelRoute() {
|
||||||
const enableCtrlAltDel = useSettingsStore(state => state.actionBarCtrlAltDel);
|
const enableCtrlAltDel = useSettingsStore(state => state.actionBarCtrlAltDel);
|
||||||
const setEnableCtrlAltDel = useSettingsStore(state => state.setActionBarCtrlAltDel);
|
const setEnableCtrlAltDel = useSettingsStore(state => state.setActionBarCtrlAltDel);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SettingsItem } from "@routes/devices.$id.settings";
|
import { SettingsItem } from "@routes/devices.$id.settings";
|
||||||
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import Checkbox from "@components/Checkbox";
|
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
||||||
|
|
||||||
|
@ -12,14 +11,6 @@ import notifications from "../notifications";
|
||||||
import { UsbInfoSetting } from "../components/UsbInfoSetting";
|
import { UsbInfoSetting } from "../components/UsbInfoSetting";
|
||||||
import { FeatureFlag } from "../components/FeatureFlag";
|
import { FeatureFlag } from "../components/FeatureFlag";
|
||||||
|
|
||||||
export interface ActionBarConfig {
|
|
||||||
ctrlAltDel: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultActionBarConfig: ActionBarConfig = {
|
|
||||||
ctrlAltDel: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SettingsHardwareRoute() {
|
export default function SettingsHardwareRoute() {
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
|
@ -80,18 +71,6 @@ export default function SettingsHardwareRoute() {
|
||||||
});
|
});
|
||||||
}, [send, setBacklightSettings]);
|
}, [send, setBacklightSettings]);
|
||||||
|
|
||||||
const [actionBarConfig, setActionBarConfig] = useState<ActionBarConfig>(defaultActionBarConfig);
|
|
||||||
|
|
||||||
const onActionBarItemChange = useCallback(
|
|
||||||
(key: keyof ActionBarConfig) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setActionBarConfig(prev => ({
|
|
||||||
...prev,
|
|
||||||
[key]: e.target.checked,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
|
@ -137,7 +116,7 @@ export default function SettingsHardwareRoute() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
<SettingsItem
|
{/* <SettingsItem
|
||||||
title="Enable Ctrl+Alt+Del Action Bar"
|
title="Enable Ctrl+Alt+Del Action Bar"
|
||||||
description="Enable or disable the action bar action for sending a Ctrl+Alt+Del to the host"
|
description="Enable or disable the action bar action for sending a Ctrl+Alt+Del to the host"
|
||||||
>
|
>
|
||||||
|
@ -145,7 +124,7 @@ export default function SettingsHardwareRoute() {
|
||||||
checked={actionBarConfig.ctrlAltDel}
|
checked={actionBarConfig.ctrlAltDel}
|
||||||
onChange={onActionBarItemChange("ctrlAltDel")}
|
onChange={onActionBarItemChange("ctrlAltDel")}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem> */}
|
||||||
{settings.backlightSettings.max_brightness != 0 && (
|
{settings.backlightSettings.max_brightness != 0 && (
|
||||||
<>
|
<>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
|
|
|
@ -25,11 +25,11 @@ export default function SettingsKeyboardRoute() {
|
||||||
state => state.setShowPressedKeys,
|
state => state.setShowPressedKeys,
|
||||||
);
|
);
|
||||||
|
|
||||||
// this ensures we always get the original en-US if it hasn't been set yet
|
// this ensures we always get the original en_US if it hasn't been set yet
|
||||||
const safeKeyboardLayout = useMemo(() => {
|
const safeKeyboardLayout = useMemo(() => {
|
||||||
if (keyboardLayout && keyboardLayout.length > 0)
|
if (keyboardLayout && keyboardLayout.length > 0)
|
||||||
return keyboardLayout;
|
return keyboardLayout;
|
||||||
return "en-US";
|
return "en_US";
|
||||||
}, [keyboardLayout]);
|
}, [keyboardLayout]);
|
||||||
|
|
||||||
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
|
|
||||||
import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
||||||
import { cx } from "../cva.config";
|
import { cx } from "../cva.config";
|
||||||
|
@ -26,6 +27,19 @@ export default function SettingsMouseRoute() {
|
||||||
|
|
||||||
const [jiggler, setJiggler] = useState(false);
|
const [jiggler, setJiggler] = useState(false);
|
||||||
|
|
||||||
|
const scrollThrottling = useSettingsStore(state => state.scrollThrottling);
|
||||||
|
const setScrollThrottling = useSettingsStore(
|
||||||
|
state => state.setScrollThrottling,
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollThrottlingOptions = [
|
||||||
|
{ value: "0", label: "Off" },
|
||||||
|
{ value: "10", label: "Low" },
|
||||||
|
{ value: "25", label: "Medium" },
|
||||||
|
{ value: "50", label: "High" },
|
||||||
|
{ value: "100", label: "Very High" },
|
||||||
|
];
|
||||||
|
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -65,6 +79,21 @@ export default function SettingsMouseRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
|
<SettingsItem
|
||||||
|
title="Scroll Throttling"
|
||||||
|
description="Reduce the frequency of scroll events"
|
||||||
|
>
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM"
|
||||||
|
label=""
|
||||||
|
className="max-w-[292px]"
|
||||||
|
value={scrollThrottling}
|
||||||
|
fullWidth
|
||||||
|
onChange={e => setScrollThrottling(parseInt(e.target.value))}
|
||||||
|
options={scrollThrottlingOptions}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title="Jiggler"
|
title="Jiggler"
|
||||||
description="Simulate movement of a computer mouse. Prevents sleep mode, standby mode or the screensaver from activating"
|
description="Simulate movement of a computer mouse. Prevents sleep mode, standby mode or the screensaver from activating"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Button } from "@/components/Button";
|
||||||
import { TextAreaWithLabel } from "@/components/TextArea";
|
import { TextAreaWithLabel } from "@/components/TextArea";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
|
|
||||||
import notifications from "../notifications";
|
import notifications from "../notifications";
|
||||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||||
|
@ -45,6 +46,14 @@ export default function SettingsVideoRoute() {
|
||||||
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
|
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
|
||||||
const [edid, setEdid] = useState<string | null>(null);
|
const [edid, setEdid] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Video enhancement settings from store
|
||||||
|
const videoSaturation = useSettingsStore(state => state.videoSaturation);
|
||||||
|
const setVideoSaturation = useSettingsStore(state => state.setVideoSaturation);
|
||||||
|
const videoBrightness = useSettingsStore(state => state.videoBrightness);
|
||||||
|
const setVideoBrightness = useSettingsStore(state => state.setVideoBrightness);
|
||||||
|
const videoContrast = useSettingsStore(state => state.videoContrast);
|
||||||
|
const setVideoContrast = useSettingsStore(state => state.setVideoContrast);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
send("getStreamQualityFactor", {}, resp => {
|
send("getStreamQualityFactor", {}, resp => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
|
@ -126,6 +135,73 @@ export default function SettingsVideoRoute() {
|
||||||
onChange={e => handleStreamQualityChange(e.target.value)}
|
onChange={e => handleStreamQualityChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
|
{/* Video Enhancement Settings */}
|
||||||
|
<SettingsItem
|
||||||
|
title="Video Enhancement"
|
||||||
|
description="Adjust color settings to make the video output more vibrant and colorful"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-4 pl-4">
|
||||||
|
<SettingsItem
|
||||||
|
title="Saturation"
|
||||||
|
description={`Color saturation (${videoSaturation.toFixed(1)}x)`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max="2.0"
|
||||||
|
step="0.1"
|
||||||
|
value={videoSaturation}
|
||||||
|
onChange={e => setVideoSaturation(parseFloat(e.target.value))}
|
||||||
|
className="w-32 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
|
<SettingsItem
|
||||||
|
title="Brightness"
|
||||||
|
description={`Brightness level (${videoBrightness.toFixed(1)}x)`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max="1.5"
|
||||||
|
step="0.1"
|
||||||
|
value={videoBrightness}
|
||||||
|
onChange={e => setVideoBrightness(parseFloat(e.target.value))}
|
||||||
|
className="w-32 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
|
<SettingsItem
|
||||||
|
title="Contrast"
|
||||||
|
description={`Contrast level (${videoContrast.toFixed(1)}x)`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max="2.0"
|
||||||
|
step="0.1"
|
||||||
|
value={videoContrast}
|
||||||
|
onChange={e => setVideoContrast(parseFloat(e.target.value))}
|
||||||
|
className="w-32 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="SM"
|
||||||
|
theme="light"
|
||||||
|
text="Reset to Default"
|
||||||
|
onClick={() => {
|
||||||
|
setVideoSaturation(1.0);
|
||||||
|
setVideoBrightness(1.0);
|
||||||
|
setVideoContrast(1.0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title="EDID"
|
title="EDID"
|
||||||
description="Adjust the EDID settings for the display"
|
description="Adjust the EDID settings for the display"
|
||||||
|
|
Loading…
Reference in New Issue