package kvm import ( "fmt" "github.com/go-co-op/gocron/v2" "math/rand" "time" ) type JigglerConfig struct { InactivityLimitSeconds int `json:"inactivity_limit_seconds"` JitterPercentage int `json:"jitter_percentage"` ScheduleCronTab string `json:"schedule_cron_tab"` } var jigglerEnabled = false var jobDelta time.Duration = 0 var scheduler gocron.Scheduler = nil func rpcSetJigglerState(enabled bool) { jigglerEnabled = enabled } func rpcGetJigglerState() bool { 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() { ensureConfigLoaded() err := runJigglerCronTab() if err != nil { logger.Errorf("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.Infof("Time between jiggler runs: %v", jobDelta) if err != nil { return err } return nil } func runJiggler() { if jigglerEnabled { if config.JigglerConfig.JitterPercentage != 0 { jitter := calculateJitterDuration(jobDelta) time.Sleep(jitter) } inactivitySeconds := config.JigglerConfig.InactivityLimitSeconds timeSinceLastInput := time.Since(gadget.GetLastUserInputTime()) logger.Debugf("Time since last user input %v", timeSinceLastInput) if timeSinceLastInput > time.Duration(inactivitySeconds)*time.Second { logger.Debug("Jiggling mouse...") //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) } } } } 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)) }