mirror of https://github.com/jetkvm/kvm.git
Merge 6ab0b7ecfa
into 3fbcb7e5c4
This commit is contained in:
commit
49283e5f05
|
@ -20,6 +20,7 @@ type Config struct {
|
||||||
CloudToken string `json:"cloud_token"`
|
CloudToken string `json:"cloud_token"`
|
||||||
GoogleIdentity string `json:"google_identity"`
|
GoogleIdentity string `json:"google_identity"`
|
||||||
JigglerEnabled bool `json:"jiggler_enabled"`
|
JigglerEnabled bool `json:"jiggler_enabled"`
|
||||||
|
JigglerConfig *JigglerConfig `json:"jiggler_config"`
|
||||||
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
||||||
IncludePreRelease bool `json:"include_pre_release"`
|
IncludePreRelease bool `json:"include_pre_release"`
|
||||||
HashedPassword string `json:"hashed_password"`
|
HashedPassword string `json:"hashed_password"`
|
||||||
|
@ -46,7 +47,12 @@ var defaultConfig = &Config{
|
||||||
DisplayMaxBrightness: 64,
|
DisplayMaxBrightness: 64,
|
||||||
DisplayDimAfterSec: 120, // 2 minutes
|
DisplayDimAfterSec: 120, // 2 minutes
|
||||||
DisplayOffAfterSec: 1800, // 30 minutes
|
DisplayOffAfterSec: 1800, // 30 minutes
|
||||||
TLSMode: "",
|
JigglerConfig: &JigglerConfig{
|
||||||
|
InactivityLimitSeconds: 20,
|
||||||
|
JitterPercentage: 0,
|
||||||
|
ScheduleCronTab: "*/20 * * * * *",
|
||||||
|
},
|
||||||
|
TLSMode: "",
|
||||||
UsbConfig: &usbgadget.Config{
|
UsbConfig: &usbgadget.Config{
|
||||||
VendorId: "0x1d6b", //The Linux Foundation
|
VendorId: "0x1d6b", //The Linux Foundation
|
||||||
ProductId: "0x0104", //Multifunction Composite Gadget
|
ProductId: "0x0104", //Multifunction Composite Gadget
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -40,11 +40,13 @@ require (
|
||||||
github.com/creack/goselect v0.1.2 // indirect
|
github.com/creack/goselect v0.1.2 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.2 // 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.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.5.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/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
@ -70,6 +72,7 @@ require (
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
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
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -35,6 +35,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.1 h1:ux/5zxVRveCaCuTtNI3DiOk581KC1KpJbpJFYUEVYwo=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.1/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
@ -58,6 +60,8 @@ github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uo
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
|
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
|
||||||
github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
|
github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
|
||||||
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
|
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
@ -137,6 +141,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
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=
|
||||||
|
|
124
jiggler.go
124
jiggler.go
|
@ -1,12 +1,21 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastUserInput = time.Now()
|
type JigglerConfig struct {
|
||||||
|
InactivityLimitSeconds int `json:"inactivity_limit_seconds"`
|
||||||
|
JitterPercentage int `json:"jitter_percentage"`
|
||||||
|
ScheduleCronTab string `json:"schedule_cron_tab"`
|
||||||
|
}
|
||||||
|
|
||||||
var jigglerEnabled = false
|
var jigglerEnabled = false
|
||||||
|
var jobDelta time.Duration = 0
|
||||||
|
var scheduler gocron.Scheduler = nil
|
||||||
|
|
||||||
func rpcSetJigglerState(enabled bool) {
|
func rpcSetJigglerState(enabled bool) {
|
||||||
jigglerEnabled = enabled
|
jigglerEnabled = enabled
|
||||||
|
@ -15,27 +24,112 @@ func rpcGetJigglerState() bool {
|
||||||
return jigglerEnabled
|
return jigglerEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rpcGetJigglerConfig() (JigglerConfig, error) {
|
||||||
|
return *config.JigglerConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcSetJigglerConfig(jigglerConfig JigglerConfig) error {
|
||||||
|
logger.Infof("jigglerConfig: %v, %v, %v", jigglerConfig.InactivityLimitSeconds, jigglerConfig.JitterPercentage, jigglerConfig.ScheduleCronTab)
|
||||||
|
config.JigglerConfig = &jigglerConfig
|
||||||
|
err := removeExistingCrobJobs(scheduler)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error removing cron jobs from scheduler %v", err)
|
||||||
|
}
|
||||||
|
err = runJigglerCronTab()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error scheduling jiggler crontab: %v", err)
|
||||||
|
}
|
||||||
|
err = SaveConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeExistingCrobJobs(s gocron.Scheduler) error {
|
||||||
|
for _, j := range s.Jobs() {
|
||||||
|
err := s.RemoveJob(j.ID())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ensureConfigLoaded()
|
ensureConfigLoaded()
|
||||||
|
err := runJigglerCronTab()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error scheduling jiggler crontab: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go runJiggler()
|
func runJigglerCronTab() error {
|
||||||
|
cronTab := config.JigglerConfig.ScheduleCronTab
|
||||||
|
s, err := gocron.NewScheduler()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
scheduler = s
|
||||||
|
_, err = s.NewJob(
|
||||||
|
gocron.CronJob(
|
||||||
|
cronTab,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
gocron.NewTask(
|
||||||
|
func() {
|
||||||
|
runJiggler()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Start()
|
||||||
|
delta, err := calculateJobDelta(s)
|
||||||
|
jobDelta = delta
|
||||||
|
logger.Infof("Time between jiggler runs: %v", jobDelta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runJiggler() {
|
func runJiggler() {
|
||||||
for {
|
if jigglerEnabled {
|
||||||
if jigglerEnabled {
|
if config.JigglerConfig.JitterPercentage != 0 {
|
||||||
if time.Since(lastUserInput) > 20*time.Second {
|
jitter := calculateJitterDuration(jobDelta)
|
||||||
//TODO: change to rel mouse
|
time.Sleep(jitter)
|
||||||
err := rpcAbsMouseReport(1, 1, 0)
|
}
|
||||||
if err != nil {
|
inactivitySeconds := config.JigglerConfig.InactivityLimitSeconds
|
||||||
logger.Warnf("Failed to jiggle mouse: %v", err)
|
timeSinceLastInput := time.Since(gadget.GetLastUserInputTime())
|
||||||
}
|
logger.Debugf("Time since last user input %v", timeSinceLastInput)
|
||||||
err = rpcAbsMouseReport(0, 0, 0)
|
if timeSinceLastInput > time.Duration(inactivitySeconds)*time.Second {
|
||||||
if err != nil {
|
logger.Debug("Jiggling mouse...")
|
||||||
logger.Warnf("Failed to reset mouse position: %v", err)
|
//TODO: change to rel mouse
|
||||||
}
|
err := rpcAbsMouseReport(1, 1, 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("Failed to jiggle mouse: %v", err)
|
||||||
|
}
|
||||||
|
err = rpcAbsMouseReport(0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("Failed to reset mouse position: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(20 * time.Second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateJobDelta(s gocron.Scheduler) (time.Duration, error) {
|
||||||
|
j := s.Jobs()[0]
|
||||||
|
runs, err := j.NextRuns(2)
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
}
|
||||||
|
return runs[1].Sub(runs[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateJitterDuration(delta time.Duration) time.Duration {
|
||||||
|
jitter := rand.Float64() * float64(config.JigglerConfig.JitterPercentage) / 100 * delta.Seconds()
|
||||||
|
return time.Duration(jitter * float64(time.Second))
|
||||||
|
}
|
||||||
|
|
|
@ -812,6 +812,8 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
||||||
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
||||||
"getJigglerState": {Func: rpcGetJigglerState},
|
"getJigglerState": {Func: rpcGetJigglerState},
|
||||||
|
"setJigglerConfig": {Func: rpcSetJigglerConfig, Params: []string{"jigglerConfig"}},
|
||||||
|
"getJigglerConfig": {Func: rpcGetJigglerConfig},
|
||||||
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
||||||
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
||||||
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
|
|
||||||
|
import { useJsonRpc } from "../hooks/useJsonRpc";
|
||||||
|
import notifications from "../notifications";
|
||||||
|
|
||||||
|
import { InputFieldWithLabel } from "./InputField";
|
||||||
|
|
||||||
|
|
||||||
|
export interface JigglerConfig {
|
||||||
|
inactivity_limit_seconds: number;
|
||||||
|
jitter_percentage: number;
|
||||||
|
schedule_cron_tab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jigglerCrontabConfigs = [
|
||||||
|
{
|
||||||
|
label: "Every 20 seconds",
|
||||||
|
value: "*/20 * * * * *",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Every 40 seconds",
|
||||||
|
value: "*/40 * * * * *",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Every 1 minute",
|
||||||
|
value: "0 * * * * *",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Every 3 minutes",
|
||||||
|
value: "0 */3 * * * *",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jigglerJitterConfigs = [
|
||||||
|
{
|
||||||
|
label: "No Jitter",
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "10%",
|
||||||
|
value: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "25%",
|
||||||
|
value: "25",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "50%",
|
||||||
|
value: "50",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jigglerInactivityConfigs = [
|
||||||
|
{
|
||||||
|
label: "20 Seconds",
|
||||||
|
value: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "40 Seconds",
|
||||||
|
value: "40",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "1 Minute",
|
||||||
|
value: "60",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "3 Minutes",
|
||||||
|
value: "180",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function JigglerSetting() {
|
||||||
|
const [send] = useJsonRpc();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [jitterPercentage, setJitterPercentage] = useState("");
|
||||||
|
const [scheduleCronTab, setScheduleCronTab] = useState("");
|
||||||
|
|
||||||
|
const [jigglerConfigState, setJigglerConfigState] = useState<JigglerConfig>({
|
||||||
|
inactivity_limit_seconds: 20,
|
||||||
|
jitter_percentage: 0,
|
||||||
|
schedule_cron_tab: "*/20 * * * * *"
|
||||||
|
});
|
||||||
|
|
||||||
|
const syncJigglerConfig = useCallback(() => {
|
||||||
|
send("getJigglerConfig", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
const result = resp.result as JigglerConfig;
|
||||||
|
setJigglerConfigState(result);
|
||||||
|
|
||||||
|
const jitterPercentage = jigglerJitterConfigs.map(u => u.value).includes(result.jitter_percentage.toString())
|
||||||
|
? result.jitter_percentage.toString()
|
||||||
|
: "custom";
|
||||||
|
setJitterPercentage(jitterPercentage)
|
||||||
|
|
||||||
|
const scheduleCronTab = jigglerCrontabConfigs.map(u => u.value).includes(result.schedule_cron_tab)
|
||||||
|
? result.schedule_cron_tab
|
||||||
|
: "custom";
|
||||||
|
setScheduleCronTab(scheduleCronTab)
|
||||||
|
});
|
||||||
|
}, [send]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
syncJigglerConfig()
|
||||||
|
}, [send, syncJigglerConfig]);
|
||||||
|
|
||||||
|
const handleJigglerInactivityLimitSecondsChange = (value: string) => {
|
||||||
|
setJigglerConfigState({ ...jigglerConfigState, inactivity_limit_seconds: Number(value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJigglerJitterPercentageChange = (value: string) => {
|
||||||
|
setJigglerConfigState({ ...jigglerConfigState, jitter_percentage: Number(value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJigglerScheduleCronTabChange = (value: string) => {
|
||||||
|
setJigglerConfigState({ ...jigglerConfigState, schedule_cron_tab: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJigglerConfigSave = useCallback(
|
||||||
|
(jigglerConfig: JigglerConfig) => {
|
||||||
|
setLoading(true);
|
||||||
|
send("setJigglerConfig", { jigglerConfig }, async resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to set jiggler config: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
notifications.success(
|
||||||
|
`Jiggler Config successfully updated`,
|
||||||
|
);
|
||||||
|
syncJigglerConfig();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[send, syncJigglerConfig],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM"
|
||||||
|
label="Schedule"
|
||||||
|
className="max-w-[192px]"
|
||||||
|
value={scheduleCronTab}
|
||||||
|
fullWidth
|
||||||
|
onChange={e => {
|
||||||
|
setScheduleCronTab(e.target.value);
|
||||||
|
if (e.target.value != "custom") {
|
||||||
|
handleJigglerScheduleCronTabChange(e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={[...jigglerCrontabConfigs, {value: "custom", label: "Custom"}]}
|
||||||
|
/>
|
||||||
|
{scheduleCronTab === "custom" && (
|
||||||
|
<InputFieldWithLabel
|
||||||
|
required
|
||||||
|
label="Jiggler Crontab"
|
||||||
|
placeholder="*/20 * * * * *"
|
||||||
|
defaultValue={jigglerConfigState.schedule_cron_tab}
|
||||||
|
onChange={e => handleJigglerScheduleCronTabChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM"
|
||||||
|
label="Jitter Percentage"
|
||||||
|
className="max-w-[192px]"
|
||||||
|
value={jitterPercentage}
|
||||||
|
fullWidth
|
||||||
|
onChange={e => {
|
||||||
|
setJitterPercentage(e.target.value);
|
||||||
|
if (e.target.value != "custom") {
|
||||||
|
handleJigglerJitterPercentageChange(e.target.value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={[...jigglerJitterConfigs, {value: "custom", label: "Custom"}]}
|
||||||
|
/>
|
||||||
|
{jitterPercentage === "custom" && (
|
||||||
|
<InputFieldWithLabel
|
||||||
|
required
|
||||||
|
label="Jitter Percentage"
|
||||||
|
placeholder="25"
|
||||||
|
defaultValue={jigglerConfigState.jitter_percentage}
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="100"
|
||||||
|
onChange={e => handleJigglerJitterPercentageChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM"
|
||||||
|
label="Inactivity Limit Seconds"
|
||||||
|
className="max-w-[192px]"
|
||||||
|
value={jigglerConfigState.inactivity_limit_seconds}
|
||||||
|
fullWidth
|
||||||
|
onChange={e => {
|
||||||
|
handleJigglerInactivityLimitSecondsChange(e.target.value);
|
||||||
|
}}
|
||||||
|
options={[...jigglerInactivityConfigs]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flex gap-x-2">
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
size="SM"
|
||||||
|
theme="primary"
|
||||||
|
text="Update Jiggler Config"
|
||||||
|
onClick={() => handleJigglerConfigSave(jigglerConfigState)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import { useDeviceSettingsStore, 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 { JigglerSetting } from "@components/JigglerSetting";
|
||||||
|
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
||||||
|
|
||||||
import { FeatureFlag } from "../components/FeatureFlag";
|
import { FeatureFlag } from "../components/FeatureFlag";
|
||||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||||
|
@ -16,6 +18,7 @@ import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
import { SettingsItem } from "./devices.$id.settings";
|
||||||
|
|
||||||
|
|
||||||
type ScrollSensitivity = "low" | "default" | "high";
|
type ScrollSensitivity = "low" | "default" | "high";
|
||||||
|
|
||||||
export default function SettingsKeyboardMouseRoute() {
|
export default function SettingsKeyboardMouseRoute() {
|
||||||
|
@ -48,6 +51,11 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
setScrollSensitivity(resp.result as ScrollSensitivity);
|
setScrollSensitivity(resp.result as ScrollSensitivity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
send("getJigglerConfig", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
setJiggler(resp.result as boolean);
|
||||||
|
});
|
||||||
}, [isScrollSensitivityEnabled, send, setScrollSensitivity]);
|
}, [isScrollSensitivityEnabled, send, setScrollSensitivity]);
|
||||||
|
|
||||||
const handleJigglerChange = (enabled: boolean) => {
|
const handleJigglerChange = (enabled: boolean) => {
|
||||||
|
@ -127,6 +135,16 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
onChange={e => handleJigglerChange(e.target.checked)}
|
onChange={e => handleJigglerChange(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
|
{jiggler && (
|
||||||
|
<>
|
||||||
|
<SettingsSectionHeader
|
||||||
|
title="Jiggler Config"
|
||||||
|
description="Control the jiggler schedule"
|
||||||
|
/>
|
||||||
|
<JigglerSetting />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsItem title="Modes" description="Choose the mouse input mode" />
|
<SettingsItem title="Modes" description="Choose the mouse input mode" />
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|
Loading…
Reference in New Issue