added JigglerConfig struct

added jiggler scheduler
jitter randomizes jiggler activation
This commit is contained in:
JackTheRooster 2025-03-23 23:56:30 -05:00
parent ec5226ebdb
commit a982ec571f
6 changed files with 170 additions and 16 deletions

View File

@ -20,6 +20,7 @@ type Config struct {
CloudToken string `json:"cloud_token"`
GoogleIdentity string `json:"google_identity"`
JigglerEnabled bool `json:"jiggler_enabled"`
JigglerConfig *JigglerConfig `json:"jiggler_config"`
AutoUpdateEnabled bool `json:"auto_update_enabled"`
IncludePreRelease bool `json:"include_pre_release"`
HashedPassword string `json:"hashed_password"`
@ -46,7 +47,13 @@ var defaultConfig = &Config{
DisplayMaxBrightness: 64,
DisplayDimAfterSec: 120, // 2 minutes
DisplayOffAfterSec: 1800, // 30 minutes
TLSMode: "",
JigglerConfig: &JigglerConfig{
ActiveAfterSeconds: 20,
JitterEnabled: false,
JitterPercentage: .25,
ScheduleCronTab: "*/5 * * * * *",
},
TLSMode: "",
UsbConfig: &usbgadget.Config{
VendorId: "0x1d6b", //The Linux Foundation
ProductId: "0x0104", //Multifunction Composite Gadget

3
go.mod
View File

@ -40,11 +40,13 @@ require (
github.com/creack/goselect v0.1.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // 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-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // 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/klauspost/compress v1.17.11 // 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/prometheus/client_model v0.6.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/ugorji/go/codec v1.2.12 // indirect
github.com/vishvananda/netns v0.0.4 // indirect

6
go.sum
View File

@ -33,6 +33,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-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
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/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@ -56,6 +58,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/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
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/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
@ -135,6 +139,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/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
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/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -1,12 +1,22 @@
package kvm
import (
"github.com/go-co-op/gocron/v2"
"math/rand"
"time"
)
var lastUserInput = time.Now()
type JigglerConfig struct {
ActiveAfterSeconds int `json:"active_after_seconds"`
JitterEnabled bool `json:"jitter_enabled"`
JitterPercentage float64 `json:"jitter_percentage"`
ScheduleCronTab string `json:"schedule_cron_tab"`
}
var lastUserInput = time.Now()
var jigglerEnabled = false
var jobDelta time.Duration = 0
var scheduler gocron.Scheduler = nil
func rpcSetJigglerState(enabled bool) {
jigglerEnabled = enabled
@ -15,27 +25,105 @@ func rpcGetJigglerState() bool {
return jigglerEnabled
}
func rpcGetJigglerConfig() (JigglerConfig, error) {
return *config.JigglerConfig, nil
}
func rpcSetJigglerConfig(jigglerConfig JigglerConfig) {
config.JigglerConfig = &jigglerConfig
err := removeExistingCrobJobs(scheduler)
if err != nil {
logger.Errorf("Error removing cron jobs from scheduler %v", err)
return
}
err = runJigglerCronTab()
if err != nil {
logger.Errorf("Error scheduling jiggler crontab: %v", err)
return
}
}
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() {
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
if err != nil {
return err
}
return nil
}
func runJiggler() {
for {
if jigglerEnabled {
if time.Since(lastUserInput) > 20*time.Second {
//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)
}
if jigglerEnabled {
if config.JigglerConfig.JitterEnabled {
jitter := calculateJitterDuration(jobDelta)
logger.Infof("Jitter enabled, Sleeping for %v", jitter)
time.Sleep(jitter)
}
activeAfterSeconds := config.JigglerConfig.ActiveAfterSeconds
if time.Since(lastUserInput) > time.Duration(activeAfterSeconds)*time.Second {
//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() * config.JigglerConfig.JitterPercentage * delta.Seconds()
return time.Duration(jitter * float64(time.Second))
}

View File

@ -807,6 +807,8 @@ var rpcHandlers = map[string]RPCHandler{
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
"getJigglerState": {Func: rpcGetJigglerState},
"setJigglerConfig": {Func: rpcSetJigglerConfig, Params: []string{"setJigglerConfig"}},
"getJigglerConfig": {Func: rpcGetJigglerConfig},
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},

View File

@ -12,9 +12,17 @@ import { FeatureFlag } from "../components/FeatureFlag";
import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { useFeatureFlag } from "../hooks/useFeatureFlag";
import { SettingsItem } from "./devices.$id.settings";
import {InputFieldWithLabel} from "@components/InputField";
type ScrollSensitivity = "low" | "default" | "high";
export interface JigglerConfig {
active_after_seconds: number;
jitter_enabled: boolean;
jitter_percentage: number;
schedule_cron_tab: string;
}
export default function SettingsKeyboardMouseRoute() {
const hideCursor = useSettingsStore(state => state.isCursorHidden);
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
@ -30,6 +38,12 @@ export default function SettingsKeyboardMouseRoute() {
const { isEnabled: isScrollSensitivityEnabled } = useFeatureFlag("0.3.8");
const [jiggler, setJiggler] = useState(false);
const [jigglerConfig, setJigglerConfig] = useState<JigglerConfig>({
active_after_seconds: 0,
jitter_enabled: false,
jitter_percentage: 0.0,
schedule_cron_tab: "*/20 * * * * *"
});
const [send] = useJsonRpc();
@ -45,6 +59,11 @@ export default function SettingsKeyboardMouseRoute() {
setScrollSensitivity(resp.result as ScrollSensitivity);
});
}
send("getJigglerConfig", {}, resp => {
if ("error" in resp) return;
setJiggler(resp.result as boolean);
});
}, [isScrollSensitivityEnabled, send, setScrollSensitivity]);
const handleJigglerChange = (enabled: boolean) => {
@ -59,6 +78,23 @@ export default function SettingsKeyboardMouseRoute() {
});
};
// const handleJigglerActiveAfterSecondsChange = (value: number) => {
// setJigglerConfig({ ...jigglerConfig, active_after_seconds: value });
// };
//
// const handleJigglerJitterEnabledChange = (value: boolean) => {
// setJigglerConfig({ ...jigglerConfig, jitter_enabled: value });
// };
//
// const handleJigglerJitterPercentageChange = (value: number) => {
// setJigglerConfig({ ...jigglerConfig, jitter_percentage: value });
// };
const handleJigglerScheduleCronTabChange = (value: string) => {
setJigglerConfig({ ...jigglerConfig, schedule_cron_tab: value });
};
const onScrollSensitivityChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const sensitivity = e.target.value as ScrollSensitivity;
@ -124,6 +160,18 @@ export default function SettingsKeyboardMouseRoute() {
onChange={e => handleJigglerChange(e.target.checked)}
/>
</SettingsItem>
<SettingsItem
title="Jiggler Schedule"
description="Schedule for jiggler being triggered. Uses standard crontab format."
>
<InputFieldWithLabel
required
label="Jiggler Schedule"
placeholder="Enter Crontab"
defaultValue={jigglerConfig?.schedule_cron_tab}
onChange={e => handleJigglerScheduleCronTabChange(e.target.value)}
/>
</SettingsItem>
<div className="space-y-4">
<SettingsItem title="Modes" description="Choose the mouse input mode" />
<div className="flex items-center gap-4">