Compare commits

..

No commits in common. "main" and "release/0.4.5-rc1" have entirely different histories.

12 changed files with 42 additions and 170 deletions

View File

@ -2,8 +2,8 @@ BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
BUILDDATE ?= $(shell date -u +%FT%T%z) BUILDDATE ?= $(shell date -u +%FT%T%z)
BUILDTS ?= $(shell date -u +%s) BUILDTS ?= $(shell date -u +%s)
REVISION ?= $(shell git rev-parse HEAD) REVISION ?= $(shell git rev-parse HEAD)
VERSION_DEV ?= 0.4.6-dev$(shell date +%Y%m%d%H%M) VERSION_DEV ?= 0.4.5-dev$(shell date +%Y%m%d%H%M)
VERSION ?= 0.4.5 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

View File

@ -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

View File

@ -143,21 +143,15 @@ func (u *UsbGadget) listenKeyboardEvents() {
default: default:
l.Trace().Msg("reading from keyboard") l.Trace().Msg("reading from keyboard")
if u.keyboardHidFile == nil { if u.keyboardHidFile == nil {
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil") l.Error().Msg("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 {
u.logWithSupression("keyboardHidFileRead", 100, &l, err, "failed to read") l.Error().Err(err).Msg("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")
@ -201,12 +195,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.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0") u.log.Error().Err(err).Msg("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
} }

View File

@ -12,7 +12,7 @@ var absoluteMouseConfig = gadgetConfigItem{
configPath: []string{"hid.usb1"}, configPath: []string{"hid.usb1"},
attrs: gadgetAttributes{ attrs: gadgetAttributes{
"protocol": "2", "protocol": "2",
"subclass": "0", "subclass": "1",
"report_length": "6", "report_length": "6",
}, },
reportDesc: absoluteMouseCombinedReportDesc, reportDesc: absoluteMouseCombinedReportDesc,
@ -75,12 +75,11 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
_, err := u.absMouseHidFile.Write(data) _, err := u.absMouseHidFile.Write(data)
if err != nil { if err != nil {
u.logWithSupression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1") u.log.Error().Err(err).Msg("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
} }

View File

@ -65,12 +65,11 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
_, err := u.relMouseHidFile.Write(data) _, err := u.relMouseHidFile.Write(data)
if err != nil { if err != nil {
u.logWithSupression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2") u.log.Error().Err(err).Msg("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
} }

View File

@ -79,8 +79,6 @@ 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"
@ -128,8 +126,6 @@ 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 {

View File

@ -6,8 +6,6 @@ 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 {
@ -80,27 +78,3 @@ 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
}
}

View File

@ -8,7 +8,6 @@ import (
"io" "io"
"net" "net"
"os" "os"
"os/exec"
"sync" "sync"
"time" "time"
@ -42,11 +41,6 @@ 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()
@ -135,26 +129,16 @@ func StartNativeSocketServer(socketPath string, handleClient func(net.Conn), isC
scopedLogger.Info().Msg("server listening") scopedLogger.Info().Msg("server listening")
go func() { go func() {
for { conn, err := listener.Accept()
conn, err := listener.Accept() listener.Close()
if err != nil {
if err != nil { scopedLogger.Warn().Err(err).Msg("failed to accept socket")
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
@ -251,51 +235,6 @@ 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 {
@ -307,28 +246,12 @@ 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 := startNativeBinaryWithLock(binaryPath) cmd, err := startNativeBinary(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)
} }
// check if the binary is still running every 10 seconds //TODO: add auto restart
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")

View File

@ -21,7 +21,6 @@ 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(),
@ -55,6 +54,14 @@ 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)
}
}, },
}) })

View File

@ -115,18 +115,9 @@ export default function WebRTCVideo() {
const isFullscreenEnabled = document.fullscreenEnabled; const isFullscreenEnabled = document.fullscreenEnabled;
const checkNavigatorPermissions = useCallback(async (permissionName: string) => { const checkNavigatorPermissions = useCallback(async (permissionName: string) => {
if (!navigator.permissions || !navigator.permissions.query) { const name = permissionName as PermissionName;
return false; // if can't query permissions, assume NOT granted const { state } = await navigator.permissions.query({ name });
} 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 () => {
@ -137,11 +128,7 @@ export default function WebRTCVideo() {
const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock"); const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock");
if (isPointerLockGranted && settings.mouseMode === "relative") { if (isPointerLockGranted && settings.mouseMode === "relative") {
try { await videoElm.current.requestPointerLock();
await videoElm.current.requestPointerLock();
} catch {
// ignore errors
}
} }
}, [checkNavigatorPermissions, isPointerLockPossible, settings.mouseMode]); }, [checkNavigatorPermissions, isPointerLockPossible, settings.mouseMode]);
@ -149,13 +136,10 @@ export default function WebRTCVideo() {
if (videoElm.current === null) return; if (videoElm.current === null) return;
const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock"); const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock");
if (isKeyboardLockGranted) {
if (isKeyboardLockGranted && "keyboard" in navigator) { if ("keyboard" in navigator) {
try {
// @ts-expect-error - keyboard lock is not supported in all browsers // @ts-expect-error - keyboard lock is not supported in all browsers
await navigator.keyboard.lock(); await navigator.keyboard.lock();
} catch {
// ignore errors
} }
} }
}, [checkNavigatorPermissions]); }, [checkNavigatorPermissions]);
@ -164,12 +148,8 @@ export default function WebRTCVideo() {
if (videoElm.current === null || document.fullscreenElement !== videoElm.current) return; if (videoElm.current === null || document.fullscreenElement !== videoElm.current) return;
if ("keyboard" in navigator) { if ("keyboard" in navigator) {
try { // @ts-expect-error - keyboard unlock is not supported in all browsers
// @ts-expect-error - keyboard unlock is not supported in all browsers await navigator.keyboard.unlock();
await navigator.keyboard.unlock();
} catch {
// ignore errors
}
} }
}, []); }, []);

View File

@ -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(() => {

View File

@ -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 } })