mirror of https://github.com/jetkvm/kvm.git
Added crontab scheduler for jiggler (#316)
This commit is contained in:
parent
33ac9fe0b6
commit
f7b8efde7c
|
@ -82,6 +82,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"`
|
||||||
|
@ -117,7 +118,13 @@ var defaultConfig = &Config{
|
||||||
DisplayMaxBrightness: 64,
|
DisplayMaxBrightness: 64,
|
||||||
DisplayDimAfterSec: 120, // 2 minutes
|
DisplayDimAfterSec: 120, // 2 minutes
|
||||||
DisplayOffAfterSec: 1800, // 30 minutes
|
DisplayOffAfterSec: 1800, // 30 minutes
|
||||||
TLSMode: "",
|
// This is the "Standard" jiggler option in the UI
|
||||||
|
JigglerConfig: &JigglerConfig{
|
||||||
|
InactivityLimitSeconds: 60,
|
||||||
|
JitterPercentage: 25,
|
||||||
|
ScheduleCronTab: "0 * * * * *",
|
||||||
|
},
|
||||||
|
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
|
||||||
|
|
9
go.mod
9
go.mod
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/gin-contrib/logger v1.2.6
|
github.com/gin-contrib/logger v1.2.6
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.3
|
||||||
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-20250601184604-370a9a75f341
|
github.com/gwatts/rootcerts v0.0.0-20250601184604-370a9a75f341
|
||||||
|
@ -28,9 +29,9 @@ require (
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
go.bug.st/serial v1.6.4
|
go.bug.st/serial v1.6.4
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.40.0
|
||||||
golang.org/x/net v0.41.0
|
golang.org/x/net v0.41.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.34.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
|
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
|
||||||
|
@ -50,6 +51,7 @@ require (
|
||||||
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/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/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
|
||||||
|
@ -75,6 +77,7 @@ require (
|
||||||
github.com/pion/turn/v4 v4.0.2 // 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.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.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.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
@ -82,7 +85,7 @@ require (
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
golang.org/x/arch v0.18.0 // indirect
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.27.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
|
||||||
)
|
)
|
||||||
|
|
20
go.sum
20
go.sum
|
@ -38,6 +38,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.3 h1:kYqukZqBa8RC2+AFAHnunmKcs9GRTjwBo8WRF3I6cbI=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.16.3/go.mod h1:aTf7/+5Jo2E+cyAqq625UQ6DzpkV96b22VHIUAt6l3c=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
@ -62,6 +64,8 @@ github.com/gwatts/rootcerts v0.0.0-20250601184604-370a9a75f341 h1:zPrkLSKi7kKJoN
|
||||||
github.com/gwatts/rootcerts v0.0.0-20250601184604-370a9a75f341/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
github.com/gwatts/rootcerts v0.0.0-20250601184604-370a9a75f341/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
||||||
github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs=
|
github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs=
|
||||||
github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
|
github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
|
||||||
|
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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
@ -146,6 +150,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
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/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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
|
@ -175,10 +181,12 @@ 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.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
|
@ -188,10 +196,10 @@ 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=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
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=
|
||||||
|
|
127
jiggler.go
127
jiggler.go
|
@ -1,12 +1,22 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
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,25 +25,112 @@ func rpcGetJigglerState() bool {
|
||||||
return jigglerEnabled
|
return jigglerEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rpcGetJigglerConfig() (JigglerConfig, error) {
|
||||||
|
return *config.JigglerConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcSetJigglerConfig(jigglerConfig JigglerConfig) error {
|
||||||
|
logger.Info().Msgf("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 initJiggler() {
|
func initJiggler() {
|
||||||
go runJiggler()
|
ensureConfigLoaded()
|
||||||
|
err := runJigglerCronTab()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msgf("Error scheduling jiggler crontab: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Info().Msgf("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.Warn().Err(err).Msg("Failed to jiggle mouse")
|
timeSinceLastInput := time.Since(gadget.GetLastUserInputTime())
|
||||||
}
|
logger.Debug().Msgf("Time since last user input %v", timeSinceLastInput)
|
||||||
err = rpcAbsMouseReport(0, 0, 0)
|
if timeSinceLastInput > time.Duration(inactivitySeconds)*time.Second {
|
||||||
if err != nil {
|
logger.Debug().Msg("Jiggling mouse...")
|
||||||
logger.Warn().Err(err).Msg("Failed to reset mouse position")
|
//TODO: change to rel mouse
|
||||||
}
|
err := rpcAbsMouseReport(1, 1, 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Msgf("Failed to jiggle mouse: %v", err)
|
||||||
|
}
|
||||||
|
err = rpcAbsMouseReport(0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Msgf("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))
|
||||||
|
}
|
||||||
|
|
|
@ -1056,6 +1056,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"}},
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function FieldLabel({
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{description && (
|
{description && (
|
||||||
<span className="my-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
<span className="mb-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -36,11 +36,11 @@ export default function FieldLabel({
|
||||||
} else if (as === "span") {
|
} else if (as === "span") {
|
||||||
return (
|
return (
|
||||||
<div className="flex select-none flex-col">
|
<div className="flex select-none flex-col">
|
||||||
<span className="font-display text-[13px] font-medium leading-snug text-black dark:text-white">
|
<span className="font-display text-[13px] font-semibold leading-snug text-black dark:text-white">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
{description && (
|
{description && (
|
||||||
<span className="my-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
<span className="mb-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -49,4 +49,4 @@ export default function FieldLabel({
|
||||||
} else {
|
} else {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ type InputFieldProps = {
|
||||||
|
|
||||||
type InputFieldWithLabelProps = InputFieldProps & {
|
type InputFieldWithLabelProps = InputFieldProps & {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
description?: string | null;
|
description?: React.ReactNode | string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputField(
|
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputField(
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
|
||||||
|
import { InputFieldWithLabel } from "./InputField";
|
||||||
|
import ExtLink from "./ExtLink";
|
||||||
|
|
||||||
|
export interface JigglerConfig {
|
||||||
|
inactivity_limit_seconds: number;
|
||||||
|
jitter_percentage: number;
|
||||||
|
schedule_cron_tab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JigglerSetting({
|
||||||
|
onSave,
|
||||||
|
}: {
|
||||||
|
onSave: (jigglerConfig: JigglerConfig) => void;
|
||||||
|
}) {
|
||||||
|
const [jigglerConfigState, setJigglerConfigState] = useState<JigglerConfig>({
|
||||||
|
inactivity_limit_seconds: 20,
|
||||||
|
jitter_percentage: 0,
|
||||||
|
schedule_cron_tab: "*/20 * * * * *",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="grid max-w-sm grid-cols-1 items-end gap-y-2">
|
||||||
|
<InputFieldWithLabel
|
||||||
|
required
|
||||||
|
size="SM"
|
||||||
|
label="Cron Schedule"
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
Generate with{" "}
|
||||||
|
<ExtLink className="text-blue-700 underline" href="https://crontab.guru/">
|
||||||
|
crontab.guru
|
||||||
|
</ExtLink>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placeholder="*/20 * * * * *"
|
||||||
|
defaultValue={jigglerConfigState.schedule_cron_tab}
|
||||||
|
onChange={e =>
|
||||||
|
setJigglerConfigState({
|
||||||
|
...jigglerConfigState,
|
||||||
|
schedule_cron_tab: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputFieldWithLabel
|
||||||
|
size="SM"
|
||||||
|
label="Inactivity Limit Seconds"
|
||||||
|
description="Seconds of inactivity before triggering a jiggle again"
|
||||||
|
value={jigglerConfigState.inactivity_limit_seconds}
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="100"
|
||||||
|
onChange={e =>
|
||||||
|
setJigglerConfigState({
|
||||||
|
...jigglerConfigState,
|
||||||
|
inactivity_limit_seconds: Number(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputFieldWithLabel
|
||||||
|
required
|
||||||
|
size="SM"
|
||||||
|
label="Random delay"
|
||||||
|
description="To avoid recognizable patterns"
|
||||||
|
placeholder="25"
|
||||||
|
TrailingElm={<span className="px-2 text-xs text-slate-500">%</span>}
|
||||||
|
defaultValue={jigglerConfigState.jitter_percentage}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
onChange={e =>
|
||||||
|
setJigglerConfigState({
|
||||||
|
...jigglerConfigState,
|
||||||
|
jitter_percentage: Number(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 flex gap-x-2">
|
||||||
|
<Button
|
||||||
|
size="SM"
|
||||||
|
theme="primary"
|
||||||
|
text="Save Jiggler Config"
|
||||||
|
onClick={() => onSave(jigglerConfigState)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ type SelectMenuProps = Pick<
|
||||||
|
|
||||||
const sizes = {
|
const sizes = {
|
||||||
XS: "h-[24.5px] pl-3 pr-8 text-xs",
|
XS: "h-[24.5px] pl-3 pr-8 text-xs",
|
||||||
SM: "h-[32px] pl-3 pr-8 text-[13px]",
|
SM: "h-[36px] pl-3 pr-8 text-[13px]",
|
||||||
MD: "h-[40px] pl-4 pr-10 text-sm",
|
MD: "h-[40px] pl-4 pr-10 text-sm",
|
||||||
LG: "h-[48px] pl-4 pr-10 px-5 text-base",
|
LG: "h-[48px] pl-4 pr-10 px-5 text-base",
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,7 @@ export const SelectMenuBasic = React.forwardRef<HTMLSelectElement, SelectMenuPro
|
||||||
"text-sm",
|
"text-sm",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label && <FieldLabel label={label} id={id} as="span" />}
|
{label && <FieldLabel label={label} id={id} />}
|
||||||
<Card className="w-auto border! border-solid border-slate-800/30! shadow-xs outline-0 dark:border-slate-300/30!">
|
<Card className="w-auto border! border-solid border-slate-800/30! shadow-xs outline-0 dark:border-slate-300/30!">
|
||||||
<select
|
<select
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default function SettingsNestedSection({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="ml-2 border-l border-slate-800/30 pl-4 dark:border-slate-300/30">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { CheckCircleIcon } from "@heroicons/react/16/solid";
|
import { CheckCircleIcon } from "@heroicons/react/16/solid";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import MouseIcon from "@/assets/mouse-icon.svg";
|
import MouseIcon from "@/assets/mouse-icon.svg";
|
||||||
import PointingFinger from "@/assets/pointing-finger.svg";
|
import PointingFinger from "@/assets/pointing-finger.svg";
|
||||||
|
@ -7,14 +7,63 @@ import { GridCard } from "@/components/Card";
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import notifications from "@/notifications";
|
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
|
|
||||||
import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
|
||||||
import { cx } from "../cva.config";
|
import { cx } from "../cva.config";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
import { SettingsItem } from "./devices.$id.settings";
|
||||||
|
import notifications from "../notifications";
|
||||||
|
import SettingsNestedSection from "../components/SettingsNestedSection";
|
||||||
|
import { JigglerSetting } from "@components/JigglerSetting";
|
||||||
|
|
||||||
|
export interface JigglerConfig {
|
||||||
|
inactivity_limit_seconds: number;
|
||||||
|
jitter_percentage: number;
|
||||||
|
schedule_cron_tab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jigglerOptions = [
|
||||||
|
{ value: "disabled", label: "Disabled", config: null },
|
||||||
|
{
|
||||||
|
value: "frequent",
|
||||||
|
label: "Frequent - 30s",
|
||||||
|
config: {
|
||||||
|
inactivity_limit_seconds: 30,
|
||||||
|
jitter_percentage: 25,
|
||||||
|
schedule_cron_tab: "*/30 * * * * *",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "standard",
|
||||||
|
label: "Standard - 1m",
|
||||||
|
config: {
|
||||||
|
inactivity_limit_seconds: 60,
|
||||||
|
jitter_percentage: 25,
|
||||||
|
schedule_cron_tab: "0 * * * * *",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "light",
|
||||||
|
label: "Light - 5m",
|
||||||
|
config: {
|
||||||
|
inactivity_limit_seconds: 300,
|
||||||
|
jitter_percentage: 25,
|
||||||
|
schedule_cron_tab: "0 */5 * * * *",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "business_hours",
|
||||||
|
label: "Business Hours - 1m - (Mon-Fri 9-17)",
|
||||||
|
config: {
|
||||||
|
inactivity_limit_seconds: 60,
|
||||||
|
jitter_percentage: 25,
|
||||||
|
schedule_cron_tab: "0 * 9-17 * * 1-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type JigglerValues = (typeof jigglerOptions)[number]["value"] | "custom";
|
||||||
|
|
||||||
export default function SettingsMouseRoute() {
|
export default function SettingsMouseRoute() {
|
||||||
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
||||||
|
@ -23,14 +72,11 @@ export default function SettingsMouseRoute() {
|
||||||
const mouseMode = useSettingsStore(state => state.mouseMode);
|
const mouseMode = useSettingsStore(state => state.mouseMode);
|
||||||
const setMouseMode = useSettingsStore(state => state.setMouseMode);
|
const setMouseMode = useSettingsStore(state => state.setMouseMode);
|
||||||
|
|
||||||
const { isEnabled: isScrollSensitivityEnabled } = useFeatureFlag("0.3.8");
|
|
||||||
|
|
||||||
const [jiggler, setJiggler] = useState(false);
|
|
||||||
|
|
||||||
const scrollThrottling = useSettingsStore(state => state.scrollThrottling);
|
const scrollThrottling = useSettingsStore(state => state.scrollThrottling);
|
||||||
const setScrollThrottling = useSettingsStore(
|
const setScrollThrottling = useSettingsStore(state => state.setScrollThrottling);
|
||||||
state => state.setScrollThrottling,
|
|
||||||
);
|
const [selectedJigglerOption, setSelectedJigglerOption] =
|
||||||
|
useState<JigglerValues | null>(null);
|
||||||
|
|
||||||
const scrollThrottlingOptions = [
|
const scrollThrottlingOptions = [
|
||||||
{ value: "0", label: "Off" },
|
{ value: "0", label: "Off" },
|
||||||
|
@ -42,23 +88,85 @@ export default function SettingsMouseRoute() {
|
||||||
|
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
useEffect(() => {
|
const syncJigglerSettings = useCallback(() => {
|
||||||
send("getJigglerState", {}, resp => {
|
send("getJigglerState", {}, resp => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
setJiggler(resp.result as boolean);
|
const isEnabled = resp.result as boolean;
|
||||||
});
|
|
||||||
}, [isScrollSensitivityEnabled, send]);
|
|
||||||
|
|
||||||
const handleJigglerChange = (enabled: boolean) => {
|
// If the jiggler is disabled, set the selected option to "disabled" and nothing else
|
||||||
send("setJigglerState", { enabled }, resp => {
|
if (!isEnabled) return setSelectedJigglerOption("disabled");
|
||||||
if ("error" in resp) {
|
|
||||||
notifications.error(
|
send("getJigglerConfig", {}, resp => {
|
||||||
`Failed to set jiggler state: ${resp.error.data || "Unknown error"}`,
|
if ("error" in resp) return;
|
||||||
);
|
const result = resp.result as JigglerConfig;
|
||||||
return;
|
const value = jigglerOptions.find(
|
||||||
}
|
o =>
|
||||||
setJiggler(enabled);
|
o?.config?.inactivity_limit_seconds === result.inactivity_limit_seconds &&
|
||||||
|
o?.config?.jitter_percentage === result.jitter_percentage &&
|
||||||
|
o?.config?.schedule_cron_tab === result.schedule_cron_tab,
|
||||||
|
)?.value;
|
||||||
|
|
||||||
|
setSelectedJigglerOption(value || "custom");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}, [send]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
syncJigglerSettings();
|
||||||
|
}, [syncJigglerSettings]);
|
||||||
|
|
||||||
|
const saveJigglerConfig = useCallback(
|
||||||
|
(jigglerConfig: JigglerConfig) => {
|
||||||
|
// We assume the jiggler should be set to enabled if the config is being updated
|
||||||
|
send("setJigglerState", { enabled: true }, async resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
return notifications.error(
|
||||||
|
`Failed to set jiggler state: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
send("setJigglerConfig", { jigglerConfig }, async resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
return notifications.error(
|
||||||
|
`Failed to set jiggler config: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.success(`Jiggler Config successfully updated`);
|
||||||
|
syncJigglerSettings();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[send, syncJigglerSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleJigglerChange = (option: JigglerValues) => {
|
||||||
|
if (option === "custom") {
|
||||||
|
setSelectedJigglerOption("custom");
|
||||||
|
// We don't need to sync the jiggler settings when the option is "custom". The user will press "Save" to save the custom settings.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to update the device jiggler state when the option is "disabled"
|
||||||
|
if (option === "disabled") {
|
||||||
|
send("setJigglerState", { enabled: false }, async resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
return notifications.error(
|
||||||
|
`Failed to set jiggler state: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notifications.success(`Jiggler Config successfully updated`);
|
||||||
|
return setSelectedJigglerOption("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
const jigglerConfig = jigglerOptions.find(o => o.value === option)?.config;
|
||||||
|
if (!jigglerConfig) {
|
||||||
|
return notifications.error("There was an error setting the jiggler config");
|
||||||
|
}
|
||||||
|
|
||||||
|
saveJigglerConfig(jigglerConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -79,30 +187,50 @@ export default function SettingsMouseRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title="Scroll Throttling"
|
title="Scroll Throttling"
|
||||||
description="Reduce the frequency of scroll events"
|
description="Reduce the frequency of scroll events"
|
||||||
>
|
>
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
size="SM"
|
size="SM"
|
||||||
label=""
|
label=""
|
||||||
className="max-w-[292px]"
|
className="max-w-[292px]"
|
||||||
value={scrollThrottling}
|
value={scrollThrottling}
|
||||||
fullWidth
|
fullWidth
|
||||||
onChange={e => setScrollThrottling(parseInt(e.target.value))}
|
onChange={e => setScrollThrottling(parseInt(e.target.value))}
|
||||||
options={scrollThrottlingOptions}
|
options={scrollThrottlingOptions}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</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"
|
||||||
>
|
>
|
||||||
<Checkbox
|
<SelectMenuBasic
|
||||||
checked={jiggler}
|
size="SM"
|
||||||
onChange={e => handleJigglerChange(e.target.checked)}
|
label=""
|
||||||
|
value={selectedJigglerOption || "disabled"}
|
||||||
|
options={[
|
||||||
|
...jigglerOptions.map(option => ({
|
||||||
|
value: option.value,
|
||||||
|
label: option.label,
|
||||||
|
})),
|
||||||
|
{ value: "custom", label: "Custom" },
|
||||||
|
]}
|
||||||
|
onChange={e => {
|
||||||
|
handleJigglerChange(
|
||||||
|
e.target.value as (typeof jigglerOptions)[number]["value"],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
|
{selectedJigglerOption === "custom" && (
|
||||||
|
<SettingsNestedSection>
|
||||||
|
<JigglerSetting onSave={saveJigglerConfig} />
|
||||||
|
</SettingsNestedSection>
|
||||||
|
)}
|
||||||
<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