mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-03-17 13:21:50 +01:00
908 lines
30 KiB
Go
908 lines
30 KiB
Go
// +build linux
|
|
|
|
package hid
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/robertkrimen/otto"
|
|
"fmt"
|
|
"context"
|
|
"sync"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
|
|
const (
|
|
MAX_VM = 8
|
|
|
|
)
|
|
|
|
var (
|
|
globalJobList = make(map[*AsyncOttoJob]bool)
|
|
globalJobListMutex = sync.Mutex{}
|
|
|
|
hidControllerReuse *HIDController
|
|
|
|
haltirq = errors.New("Stahp")
|
|
ErrAbort = errors.New("Event listening aborted")
|
|
ErrIrq = errors.New("Aborted due to interrupt request")
|
|
ErrNotAllowed = errors.New("Calling not allowed (currently disabled)")
|
|
ErrNoKeyboard = errors.New("Using HID keyboard not allowed (currently disabled)")
|
|
ErrNoMouse = errors.New("Using HID mouse not allowed (currently disabled)")
|
|
)
|
|
|
|
// Note: The HID Controller uses multiple otto.Otto VMs
|
|
// The HIDController object can't be used, once the USB stack re-initializes, as the HID keyboard and mouse
|
|
// depend on the device file currently in use. Thus I started multiple attempts to cleanly destroy the otto VMs
|
|
// in order to allow creating a new HIDController object. This didn't succeed, as no matter what I tried, the
|
|
// (no more used and referenced) otto VMs leak memory. To cope with that, the max amount of concurrent existing
|
|
// otto VMs is limited to MAX_VM. The VMs (clone from a master VM, providing all the functionality) have to be
|
|
// reused. This again means, the HIDController object exists during the whole runtime and needs a method to be
|
|
// reconfigured. The cause of a reconfiguration is a change in the HID functions, leading to the need of using
|
|
// different device files for keyboard / mouse communication (f.e. a setup with keyboard + mouse has /dev/hidg0
|
|
// and /dev/hidg1, while a changed setup with mouse or keyboard only use /dev/hidg0 for the respective device -
|
|
// there wouldn't exist a /dev/hidg1).
|
|
// In order to reuse the HIDController, there has to be some kind of Reset() method, fulfilling the following tasks:
|
|
// - init a new HIDKeyboard if necessary, based on the new devicepath
|
|
// - the HIDkeyboard uses a KeyboardLEDStateWatcher, which constantly reads the device file (receives USB HID
|
|
// output reports) and thus needs to be stopped and reinitialized
|
|
// - init a new Mouse if necessary, there should be no problem overwriting the former Mouse object and let the GC
|
|
// do its work
|
|
// - interrupt all script currently running on the ottoVMs
|
|
|
|
|
|
|
|
type HIDController struct {
|
|
Keyboard *HIDKeyboard
|
|
Mouse *Mouse
|
|
vmPool [MAX_VM]*AsyncOttoVM
|
|
vmMaster *otto.Otto
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
eventHandler EventHandler
|
|
|
|
abortLock *sync.Mutex
|
|
isAborted bool
|
|
}
|
|
|
|
func NewHIDController(ctx context.Context, keyboardDevicePath string, keyboardMapPath string, mouseDevicePath string) (ctl *HIDController, err error) {
|
|
//ToDo: check if hidcontroller could work with a context with cancellation, which then could be cancelled on reuse of the object
|
|
if hidControllerReuse == nil {
|
|
hidControllerReuse = &HIDController{}
|
|
hidControllerReuse.abortLock = &sync.Mutex{}
|
|
hidControllerReuse.isAborted = true
|
|
|
|
hidControllerReuse.initMasterVM()
|
|
//clone VM to pool
|
|
for i:=0; i< len(hidControllerReuse.vmPool); i++ {
|
|
hidControllerReuse.vmPool[i] = NewAsyncOttoVMClone(hidControllerReuse.vmMaster)
|
|
}
|
|
} else {
|
|
// an old HIDController object is reused and has to be cleaned
|
|
|
|
hidControllerReuse.Abort()
|
|
hidControllerReuse.Keyboard = nil
|
|
hidControllerReuse.Mouse = nil
|
|
}
|
|
|
|
|
|
hidControllerReuse.abortLock.Lock()
|
|
defer hidControllerReuse.abortLock.Unlock()
|
|
|
|
hidControllerReuse.isAborted = false
|
|
|
|
ctx,cancel := context.WithCancel(ctx)
|
|
hidControllerReuse.ctx = ctx
|
|
hidControllerReuse.cancel = cancel
|
|
|
|
//Note: to disable mouse/keyboard support, the respective device path has to have zero length
|
|
|
|
//init keyboard
|
|
if len(keyboardDevicePath) > 0 {
|
|
hidControllerReuse.Keyboard, err = NewKeyboard(ctx, keyboardDevicePath, keyboardMapPath)
|
|
if err != nil { return nil, err }
|
|
}
|
|
|
|
//init Mouse
|
|
if len(mouseDevicePath) > 0 {
|
|
hidControllerReuse.Mouse,err = NewMouse(mouseDevicePath)
|
|
if err != nil { return nil, err }
|
|
}
|
|
|
|
hidControllerReuse.SetDefaultHandler()
|
|
return hidControllerReuse, nil
|
|
}
|
|
|
|
func (ctl *HIDController) SetEventHandler(handler EventHandler) {
|
|
ctl.eventHandler = handler
|
|
// set same handler for all child VMs
|
|
for _,vm := range ctl.vmPool { vm.SetEventHandler(handler) }
|
|
}
|
|
|
|
func (ctl *HIDController) HandleEvent(event Event) {
|
|
fmt.Printf("!!! HID Controller Event: %+v\n", event)
|
|
}
|
|
|
|
func (ctl *HIDController) SetDefaultHandler() {
|
|
ctl.SetEventHandler(ctl)
|
|
}
|
|
|
|
func (ctl *HIDController) emitEvent(event Event) {
|
|
if ctl.eventHandler == nil { return }
|
|
ctl.eventHandler.HandleEvent(event)
|
|
|
|
}
|
|
|
|
func (ctl *HIDController) Abort() {
|
|
ctl.abortLock.Lock()
|
|
defer ctl.abortLock.Unlock()
|
|
|
|
if ctl.isAborted {
|
|
return
|
|
}
|
|
ctl.isAborted = true
|
|
|
|
// stop the old LED reader if present
|
|
// !! Keyboard.Close() has to be called before sending IRQ to VMs (there're JS function which register
|
|
// blocking callbacks to the Keyboard's LED reader, which would hinder the VM interrupt in triggering)
|
|
if hidControllerReuse.Keyboard != nil {
|
|
hidControllerReuse.Keyboard.Close() //interrupts go routines reading from device file and lets LEDStateListeners die
|
|
}
|
|
|
|
if hidControllerReuse.Mouse != nil {
|
|
hidControllerReuse.Mouse.Close()
|
|
}
|
|
|
|
// Interrupt all VMs already running
|
|
//hidControllerReuse.CancelAllBackgroundJobs()
|
|
hidControllerReuse.CancelAllBackgroundJobs()
|
|
|
|
ctl.emitEvent(Event{
|
|
Type: EventType_CONTROLLER_ABORTED,
|
|
Message: "Called abort on HID Controller",
|
|
Job: nil,
|
|
Vm: nil,
|
|
})
|
|
}
|
|
|
|
func (ctl *HIDController) NextUnusedVM() (vm *AsyncOttoVM, err error) {
|
|
//iterate over pool
|
|
for _,avm := range ctl.vmPool {
|
|
if !avm.IsWorking() {
|
|
//return first non-working vm
|
|
return avm, nil //free to be used
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("No free JavaScript VM available in pool")
|
|
}
|
|
|
|
func (ctl *HIDController) RunScript(ctx context.Context, script string, anonymousSelfInvoked bool) (val otto.Value, err error) {
|
|
/*
|
|
//fetch next free vm from pool
|
|
avm,err := ctl.NextUnusedVM()
|
|
if err != nil { return otto.Value{}, err }
|
|
|
|
val, err = avm.Run(ctx, script)
|
|
*/
|
|
|
|
// use backround job and wait, to force keeping track of job in joblist, in case the result is never fetched due to
|
|
// remote CLI abort after running endless script (wouldn't cancel the script)
|
|
job,err := ctl.StartScriptAsBackgroundJob(ctx, script, anonymousSelfInvoked)
|
|
if err != nil { return val,err }
|
|
val,err = ctl.WaitBackgroundJobResult(ctx, job)
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) GetBackgroundJobByID(id int) (job *AsyncOttoJob, err error) {
|
|
globalJobListMutex.Lock()
|
|
for j,_ := range globalJobList {
|
|
if j.Id == id {
|
|
job = j
|
|
break
|
|
}
|
|
}
|
|
globalJobListMutex.Unlock()
|
|
|
|
if job == nil {
|
|
return nil, errors.New(fmt.Sprintf("Job with ID %d not found in list of background jobs\n", id))
|
|
} else {
|
|
return job,nil
|
|
}
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) retrieveJobFromOtto(vm *otto.Otto) (job *AsyncOttoJob, runVM *AsyncOttoVM, err error) {
|
|
found := false
|
|
globalJobListMutex.Lock()
|
|
for cJob,_ := range globalJobList {
|
|
vme := cJob.executingVM
|
|
if vme == nil { continue }
|
|
if vme.vm == vm {
|
|
found = true
|
|
job = cJob
|
|
runVM = vme
|
|
}
|
|
|
|
}
|
|
globalJobListMutex.Unlock()
|
|
if found {
|
|
return
|
|
} else {
|
|
return nil,nil,errors.New("Couldn't retrieve job of this Otto VM")
|
|
}
|
|
}
|
|
|
|
func (ctl *HIDController) StartScriptAsBackgroundJob(ctx context.Context,script string, anonymousSelfInvoked bool) (job *AsyncOttoJob, err error) {
|
|
//fetch next free vm from pool
|
|
avm,err := ctl.NextUnusedVM()
|
|
if err != nil {
|
|
ctl.emitEvent(Event{
|
|
Job:job,
|
|
Message:"No free Java VM available to run the script",
|
|
Type:EventType_JOB_NO_FREE_VM,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
//try to run script async
|
|
job,err = avm.RunAsync(ctx,script,anonymousSelfInvoked)
|
|
if err != nil { return nil, err }
|
|
|
|
ctl.emitEvent(Event{
|
|
Job:job,
|
|
Vm:avm,
|
|
Type:EventType_JOB_STARTED,
|
|
Message:script, //pack script source into message
|
|
|
|
//ScriptSource:script,
|
|
})
|
|
|
|
//add job to global list
|
|
globalJobListMutex.Lock()
|
|
globalJobList[job] = true
|
|
globalJobListMutex.Unlock()
|
|
|
|
//ad go routine which monitors context of the job, to remove it from global list, once done or interrupted
|
|
go func() {
|
|
//fmt.Printf("StartScriptAsBackgroundJob: started finish watcher for job %d\n",job.Id)
|
|
select {
|
|
case <- job.ctx.Done():
|
|
fmt.Printf("StartScriptAsBackgroundJob: Job %d finished or interrupted, removing from global list\n",job.Id)
|
|
|
|
ctl.emitEvent(Event{
|
|
Job:job,
|
|
Vm:avm,
|
|
Type:EventType_JOB_STOPPED,
|
|
Message:"Script stopped",
|
|
//ScriptSource:script,
|
|
})
|
|
|
|
//remove job from global list after result has been retrieved
|
|
globalJobListMutex.Lock()
|
|
delete(globalJobList,job)
|
|
globalJobListMutex.Unlock()
|
|
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) WaitBackgroundJobResult(ctx context.Context, job *AsyncOttoJob) (val otto.Value, err error) {
|
|
globalJobListMutex.Lock()
|
|
if !globalJobList[job] {
|
|
err = errors.New(fmt.Sprintf("Tried to retrieve results of job with id %d failed, because it is not in the list of background jobs", job.Id))
|
|
}
|
|
globalJobListMutex.Unlock()
|
|
if err != nil {return}
|
|
|
|
//cancel job when provided context is done
|
|
// ToDo: do profiling to check if this go routine stays open, in case ctx is never done
|
|
go func(ctx context.Context, job *AsyncOttoJob) {
|
|
select {
|
|
case <- ctx.Done():
|
|
job.Cancel()
|
|
}
|
|
}(ctx,job)
|
|
|
|
val,err = job.WaitResult() //Blocking result wait
|
|
|
|
return
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) CancelAllBackgroundJobs() {
|
|
globalJobListMutex.Lock()
|
|
oldList := globalJobList
|
|
// cancel known jobs
|
|
for job,_ := range oldList {
|
|
fmt.Printf("Cancelling Job %d\n", job.Id)
|
|
job.Cancel()
|
|
/*
|
|
ctl.emitEvent(Event{
|
|
Job:job,
|
|
Vm:job.executingVM,
|
|
Type:EventType_JOB_CANCELLED,
|
|
Message:"Script execution cancelled",
|
|
})
|
|
*/
|
|
}
|
|
globalJobList = make(map[*AsyncOttoJob]bool) //Create new empty list
|
|
globalJobListMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) GetAllBackgroundJobs() (jobs []*AsyncOttoJob, err error) {
|
|
globalJobListMutex.Lock()
|
|
|
|
for job,_ := range globalJobList {
|
|
jobs = append(jobs, job)
|
|
}
|
|
|
|
globalJobListMutex.Unlock()
|
|
return jobs, nil
|
|
}
|
|
|
|
|
|
//Function declarations for master VM
|
|
func (ctl *HIDController) jsType(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Keyboard == nil { log.Println(ErrNoKeyboard); oErr,_ := otto.ToValue(ErrNoKeyboard); return oErr }
|
|
arg0 := call.Argument(0)
|
|
//fmt.Printf("JS type() called with: `%s` (%s)\n", arg0, arg0)
|
|
|
|
if !arg0.IsString() {
|
|
log.Printf("HIDScript type: Wrong argument, 'type' accepts a single argument of type string. Error location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
outStr,err := arg0.ToString()
|
|
if err != nil {
|
|
log.Printf("HIDScript type: couldn't convert '%s' to UTF-8 string\n", arg0)
|
|
return
|
|
}
|
|
var partial string
|
|
if len(outStr) > 15 {
|
|
partial = outStr[:15]
|
|
} else {
|
|
partial = outStr
|
|
}
|
|
log.Printf("HIDScript type: Typing '%s ...' on HID keyboard device '%s'\n", partial, ctl.Keyboard.DevicePath)
|
|
err = ctl.Keyboard.StringToPressKeySequence(outStr)
|
|
if err != nil {
|
|
log.Printf("HIDScript type: Couldn't type out `%s` on %v\n", outStr, ctl.Keyboard.DevicePath)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsLayout(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Keyboard == nil { log.Println(ErrNoKeyboard); oErr,_ := otto.ToValue(ErrNoKeyboard); return oErr }
|
|
arg0 := call.Argument(0)
|
|
//fmt.Printf("JS type() called with: `%s` (%s)\n", arg0, arg0)
|
|
|
|
if !arg0.IsString() {
|
|
log.Printf("HIDScript layout: Wrong argument, 'layout' accepts a single argument of type string. Error location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
layoutName,err := arg0.ToString()
|
|
if err != nil {
|
|
//shouldn't happen
|
|
log.Printf("HIDScript layout: couldn't convert '%s' to string\n", arg0)
|
|
return
|
|
}
|
|
|
|
log.Printf("HIDScript layout: Setting layout to '%s'\n", layoutName)
|
|
err = ctl.Keyboard.SetActiveLanguageMap(layoutName)
|
|
if err != nil {
|
|
log.Printf("HIDScript layout: Couldn't set layout `%s`: %v\n", layoutName, err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsTypingSpeed(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Keyboard == nil { log.Println(ErrNoKeyboard); oErr,_ := otto.ToValue(ErrNoKeyboard); return oErr }
|
|
typeDelay := call.Argument(0) //delay between keypresses in milliseconds
|
|
typeJitter := call.Argument(1) //additional random jitter between keypresses, maximum in milliseconds
|
|
|
|
if delay,err:= typeDelay.ToInteger();err != nil || delay < 0 {
|
|
log.Printf("HIDScript typingSpeed: First argument has to be positive integer, representing the delay between key presses in milliseconds\n")
|
|
return
|
|
} else {
|
|
//ToDo: this isn't thread safe at all, additionally it influences type speed of every other running Script
|
|
ctl.Keyboard.KeyDelay = int(delay)
|
|
}
|
|
if jitter,err:= typeJitter.ToInteger();err != nil || jitter < 0 {
|
|
log.Printf("HIDScript typingSpeed: Second argument has to be positive integer, representing the maximum of an additional random jitter in milliseconds\n")
|
|
return
|
|
} else {
|
|
//ToDo: this isn't thread safe at all, additionally it influences type speed of every other running Script
|
|
ctl.Keyboard.KeyDelayJitter = int(jitter)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsDelay(call otto.FunctionCall) (res otto.Value) {
|
|
arg0 := call.Argument(0)
|
|
//fmt.Printf("JS delay() called with: `%s` (%s)\n", arg0, arg0)
|
|
|
|
if !arg0.IsNumber() {
|
|
log.Printf("HIDScript delay: Wrong argument, delay accepts a single argument ot type number. Error location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
fDelay,err := arg0.ToFloat()
|
|
if err != nil {
|
|
log.Printf("HIDScript delay: Error couldn't convert `%v` to float\n", arg0)
|
|
return
|
|
}
|
|
delay := int(fDelay)
|
|
// log.Printf("HIDScript delay: Sleeping `%v` milliseconds\n", delay)
|
|
|
|
select {
|
|
case <- time.After(time.Millisecond * time.Duration(int(delay))):
|
|
return
|
|
case irq := <-call.Otto.Interrupt:
|
|
irq()
|
|
}
|
|
return
|
|
}
|
|
|
|
//for pressing key combos
|
|
func (ctl *HIDController) jsPress(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Keyboard == nil { log.Println(ErrNoKeyboard); oErr,_ := otto.ToValue(ErrNoKeyboard); return oErr }
|
|
arg0 := call.Argument(0)
|
|
//fmt.Printf("JS delay() called with: `%s` (%s)\n", arg0, arg0)
|
|
|
|
if !arg0.IsString() {
|
|
log.Printf("HIDScript press: Wrong argument for 'press'. 'press' accepts a single argument of type string.\n\tError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
comboStr,err := arg0.ToString()
|
|
if err != nil {
|
|
log.Printf("HIDScript press: Error couldn't convert '%v' to string\n", arg0)
|
|
return
|
|
}
|
|
log.Printf("HIDScript press: Pressing combo '%s'\n", comboStr)
|
|
err = ctl.Keyboard.StringToPressKeyCombo(comboStr)
|
|
if err != nil {
|
|
log.Printf("HIDScript press: Error couldn't convert `%v` to string\n", arg0)
|
|
oErr,vErr := otto.ToValue(err)
|
|
if vErr == nil { return oErr}
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) jsWaitLED(call otto.FunctionCall) (res otto.Value) {
|
|
//arg0 has to be of type number, representing an LED MASK
|
|
//arg1 is optional and represents the timeout in seconds, in case it isn't present, we set timeout to a year (=infinite in our context ;-))
|
|
arg0 := call.Argument(0)
|
|
arg1 := call.Argument(1)
|
|
// log.Printf("HIDScript: Called WaitLED(%v, %v)\n", arg0, arg1)
|
|
maskInt, err := arg0.ToInteger()
|
|
if err != nil || !arg0.IsNumber() || !(maskInt >= 0 && maskInt <= MaskAnyOrNone) {
|
|
//We don't mention KANA and COMPOSE in the error message
|
|
log.Printf("HIDScript WaitLED: First argument for `waitLED` has to be a bitmask representing LEDs (NUM | CAPS | SCROLL | ANY).\nError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
mask := byte(maskInt)
|
|
//fmt.Printf("Mask: %d\n", mask )
|
|
|
|
timeout := time.Hour * 24 * 365
|
|
switch {
|
|
case arg1.IsUndefined():
|
|
log.Printf("HIDScript WaitLED: No timeout given setting to a year\n")
|
|
case arg1.IsNumber():
|
|
// log.Printf("Timeout given: %v\n", arg1)
|
|
timeoutInt, err := arg1.ToInteger()
|
|
if err != nil || timeoutInt < 0 {
|
|
log.Printf("HIDScript WaitLED: Second argument for `waitLED` is the timeout in milliseconds and has to be given as positive interger, but '%d' was given!\n", arg1)
|
|
return
|
|
}
|
|
timeout = time.Duration(timeoutInt) * time.Millisecond
|
|
default:
|
|
log.Printf("HIDScript WaitLED: Second argument for `waitLED` is the timeout in milliseconds and has to be given as interger or omitted for infinite timeout\n")
|
|
return
|
|
}
|
|
|
|
var changed *HIDLEDState
|
|
if ctl.Keyboard == nil { err = ErrNoKeyboard } else {
|
|
changed,err = ctl.Keyboard.WaitLEDStateChange(call.Otto.Interrupt, mask, timeout)
|
|
if err == ErrIrq {
|
|
panic(haltirq)
|
|
}
|
|
}
|
|
|
|
|
|
errStr := ""
|
|
if err != nil {errStr = fmt.Sprintf("%v",err)}
|
|
resStruct := struct{
|
|
ERROR bool
|
|
ERRORTEXT string
|
|
TIMEOUT bool
|
|
NUM bool
|
|
CAPS bool
|
|
SCROLL bool
|
|
COMPOSE bool
|
|
KANA bool
|
|
}{
|
|
ERROR: err != nil,
|
|
ERRORTEXT: errStr,
|
|
TIMEOUT: err == ErrTimeout,
|
|
NUM: err == nil && changed.NumLock,
|
|
CAPS: err == nil && changed.CapsLock,
|
|
SCROLL: err == nil && changed.ScrollLock,
|
|
COMPOSE: err == nil && changed.Compose,
|
|
KANA: err == nil && changed.Kana,
|
|
}
|
|
res,_ = call.Otto.ToValue(resStruct)
|
|
|
|
//Event generation
|
|
resText := "WaitLED ended:"
|
|
if resStruct.ERROR { resText = " ERROR " + errStr} else {
|
|
if resStruct.NUM { resText += " NUM" }
|
|
if resStruct.CAPS { resText += " CAPS" }
|
|
if resStruct.SCROLL { resText += " SCROLL" }
|
|
if resStruct.COMPOSE { resText += " COMPOSE" }
|
|
if resStruct.KANA { resText += " KANA" }
|
|
}
|
|
sJob, sVM, err := ctl.retrieveJobFromOtto(call.Otto)
|
|
if err == nil {
|
|
ctl.emitEvent(Event{
|
|
Type: EventType_JOB_WAIT_LED_FINISHED,
|
|
Job: sJob,
|
|
Vm: sVM,
|
|
Message: resText,
|
|
})
|
|
} else {
|
|
ctl.emitEvent(Event{
|
|
Type: EventType_JOB_WAIT_LED_FINISHED,
|
|
Message: resText + " (unknown Job)",
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsWaitLEDRepeat(call otto.FunctionCall) (res otto.Value) {
|
|
//arg0 has to be of type number, representing an LED MASK
|
|
//arg1 repeat delay (number float)
|
|
//arg2 repeat count (number integer)
|
|
//arg3 is optional and represents the timeout in seconds, in case it isn't present, we set timeout to a year (=infinite in our context ;-))
|
|
arg0 := call.Argument(0) //trigger mask
|
|
arg1 := call.Argument(1) //minimum repeat count till trigger
|
|
arg2 := call.Argument(2) //maximum interval between LED changes of same LED, to be considered as repeat
|
|
arg3 := call.Argument(3) //timeout
|
|
log.Printf("HIDScript: Called WaitLEDRepeat(%v, %v, %v, %v)\n", arg0, arg1, arg2, arg3)
|
|
|
|
//arg0: Typecheck trigger mask
|
|
maskInt, err := arg0.ToInteger()
|
|
if err != nil || !arg0.IsNumber() || !(maskInt >= 0 && maskInt <= MaskAnyOrNone) {
|
|
//We don't mention KANA and COMPOSE in the error message
|
|
log.Printf("HIDScript WaitLEDRepeat: First argument for `waitLED` has to be a bitmask representing LEDs (NUM | CAPS | SCROLL | ANY).\nError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
mask := byte(maskInt)
|
|
|
|
//arg1: repeat count (positive int > 0)
|
|
repeatCount := 3 //default (first LED change is usually too slow to count, thus we need 4 changes and ultimately end up initial LED state)
|
|
switch {
|
|
case arg1.IsUndefined():
|
|
log.Printf("HIDScript WaitLEDRepeat: No repeat count given, defaulting to '%v' led changes\n", repeatCount)
|
|
case arg1.IsNumber():
|
|
repeatInt, err := arg1.ToInteger()
|
|
if err != nil || repeatInt < 1 {
|
|
log.Printf("HIDScript WaitLEDRepeat: Second argument for `waitLEDRepeat` is the repeat count and has to be provided as positive interger, but '%d' was given!\n", arg1)
|
|
return
|
|
}
|
|
repeatCount = int(repeatInt)
|
|
default:
|
|
log.Printf("HIDScript WaitLEDRepeat: Second argument for `waitLEDRepeat` is the repeat count and has to be provided as positive interger or omitted for default of '%v'\n", repeatCount)
|
|
return
|
|
}
|
|
|
|
|
|
//arg2: //maximum interval between LED changes of same LED in milliseconds, to be considered as repeat
|
|
maxInterval := 800 * time.Millisecond //default 800 ms
|
|
switch {
|
|
case arg2.IsUndefined():
|
|
log.Printf("HIDScript WaitLEDRepeat: No maximum interval given (time allowed between LED changes, to be considered as repeat). Using default of %v\n", maxInterval)
|
|
case arg2.IsNumber():
|
|
// log.Printf("Timeout given: %v\n", arg1)
|
|
maxIntervalInt, err := arg2.ToInteger()
|
|
if err != nil || maxInterval < 0 {
|
|
log.Printf("HIDScript WaitLEDRepeat: Third argument for `waitLEDRepeat` is the maximum interval between LED changes in milliseconds and has to be provided as positive interger, but '%d' was given!\n", arg1)
|
|
return
|
|
}
|
|
maxInterval = time.Duration(maxIntervalInt) * time.Millisecond
|
|
default:
|
|
log.Printf("HIDScript WaitLEDRepeat: Third argument for `waitLEDRepeat` is the maximum interval between LED changes in milliseconds and has to be provided as positive interger or omitted to default to '%d'\n", maxInterval)
|
|
return
|
|
}
|
|
|
|
|
|
//arg3: Typecheck timeout (positive integer or undefined)
|
|
timeout := time.Hour * 24 * 365
|
|
switch {
|
|
case arg3.IsUndefined():
|
|
log.Printf("HIDScript WaitLEDRepeat: No timeout given setting to a year\n")
|
|
case arg3.IsNumber():
|
|
// log.Printf("Timeout given: %v\n", arg1)
|
|
timeoutInt, err := arg3.ToInteger()
|
|
if err != nil || timeoutInt < 0 {
|
|
log.Printf("HIDScript WaitLEDRepeat: Second argument for `waitLED` is the timeout in milliseconds and has to be given as positive interger, but '%d' was given!\n", arg1)
|
|
return
|
|
}
|
|
timeout = time.Duration(timeoutInt) * time.Millisecond
|
|
default:
|
|
log.Printf("HIDScript WaitLEDRepeat: Second argument for `waitLED` is the timeout in milliseconds and has to be given as interger or omitted for infinite timeout\n")
|
|
return
|
|
}
|
|
|
|
|
|
var changed *HIDLEDState
|
|
if ctl.Keyboard == nil { err = ErrNoKeyboard } else {
|
|
log.Printf("HIDScript: Waiting for repeated LED change. Mask for considered LEDs: %v, Minimum repeat count: %v, Maximum repeat delay: %v, Timeout: %v\n", mask, repeatCount, maxInterval, timeout)
|
|
changed,err = ctl.Keyboard.WaitLEDStateChangeRepeated(call.Otto.Interrupt, mask, repeatCount, maxInterval, timeout)
|
|
if err == ErrIrq {
|
|
panic(haltirq)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
errStr := ""
|
|
if err != nil {errStr = fmt.Sprintf("%v",err)}
|
|
resStruct := struct{
|
|
ERROR bool
|
|
ERRORTEXT string
|
|
TIMEOUT bool
|
|
NUM bool
|
|
CAPS bool
|
|
SCROLL bool
|
|
COMPOSE bool
|
|
KANA bool
|
|
}{
|
|
ERROR: err != nil,
|
|
ERRORTEXT: errStr,
|
|
TIMEOUT: err == ErrTimeout,
|
|
NUM: err == nil && changed.NumLock,
|
|
CAPS: err == nil && changed.CapsLock,
|
|
SCROLL: err == nil && changed.ScrollLock,
|
|
COMPOSE: err == nil && changed.Compose,
|
|
KANA: err == nil && changed.Kana,
|
|
}
|
|
res,_ = call.Otto.ToValue(resStruct)
|
|
|
|
//Event generation
|
|
resText := "WaitLED ended:"
|
|
if resStruct.ERROR { resText = " ERROR " + errStr} else {
|
|
if resStruct.NUM { resText += " NUM" }
|
|
if resStruct.CAPS { resText += " CAPS" }
|
|
if resStruct.SCROLL { resText += " SCROLL" }
|
|
if resStruct.COMPOSE { resText += " COMPOSE" }
|
|
if resStruct.KANA { resText += " KANA" }
|
|
}
|
|
sJob, sVM, err := ctl.retrieveJobFromOtto(call.Otto)
|
|
if err == nil {
|
|
ctl.emitEvent(Event{
|
|
Type: EventType_JOB_WAIT_LED_REPEATED_FINISHED,
|
|
Job: sJob,
|
|
Vm: sVM,
|
|
Message: resText,
|
|
})
|
|
} else {
|
|
ctl.emitEvent(Event{
|
|
Type: EventType_JOB_WAIT_LED_REPEATED_FINISHED,
|
|
Message: resText + " (unknown Job)",
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// Move mouse relative in given mouse units (-127 to +127 per axis)
|
|
func (ctl *HIDController) jsMove(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
|
|
argx := call.Argument(0)
|
|
argy := call.Argument(1)
|
|
log.Printf("HIDScript: Called move(%v, %v)\n", argx, argy)
|
|
|
|
var x,y int
|
|
if lx,err:= argx.ToInteger();err != nil || lx < -127 || lx > 127 {
|
|
log.Printf("HIDScript move: First argument has to be integer between -127 and +127 describing relative mouse movement on x-axis\n")
|
|
return
|
|
} else { x = int(lx) }
|
|
if ly,err:= argy.ToInteger();err != nil || ly < -127 || ly > 127 {
|
|
log.Printf("HIDScript move: Second argument has to be integer between -127 and +127 describing relative mouse movement on y-axis\n")
|
|
return
|
|
} else { y = int(ly) }
|
|
x8 := int8(x)
|
|
y8 := int8(y)
|
|
ctl.Mouse.Move(x8,y8)
|
|
return
|
|
}
|
|
|
|
// Move mouse relative in across given distance in mouse units, devide into substeps of 1 DPI per step (parameters uint6 -32768 to +32767 per axis)
|
|
func (ctl *HIDController) jsMoveStepped(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
argx := call.Argument(0)
|
|
argy := call.Argument(1)
|
|
// log.Printf("HIDScript: Called moveStepped(%v, %v)\n", argx, argy)
|
|
|
|
var x,y int
|
|
if lx,err:= argx.ToInteger();err != nil || lx < -32768 || lx > 32767 {
|
|
log.Printf("HIDScript moveStepped: First argument has to be integer between -32768 and +32767 describing relative mouse movement on x-axis\n")
|
|
return
|
|
} else { x = int(lx) }
|
|
if ly,err:= argy.ToInteger();err != nil || ly < -32768 || ly > 32767 {
|
|
log.Printf("HIDScript moveStepped: Second argument has to be integer between -32768 and +32767 describing relative mouse movement on y-axis\n")
|
|
return
|
|
} else { y = int(ly) }
|
|
x16 := int16(x)
|
|
y16 := int16(y)
|
|
ctl.Mouse.MoveStepped(x16,y16)
|
|
return
|
|
}
|
|
|
|
|
|
// Move mouse to absolute position (-1.0 to +1.0 per axis)
|
|
func (ctl *HIDController) jsMoveTo(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
argx := call.Argument(0)
|
|
argy := call.Argument(1)
|
|
log.Printf("HIDScript: Called moveTo(%v, %v)\n", argx, argy)
|
|
|
|
var x,y float64
|
|
if lx,err:= argx.ToFloat();err != nil || lx < -1.0 || lx > 1.0 {
|
|
log.Printf("HIDScript move: First argument has to be a float between -1.0 and +1.0 describing relative mouse movement on x-axis\n")
|
|
return
|
|
} else { x = float64(lx) }
|
|
if ly,err:= argy.ToFloat();err != nil || ly < -1.0 || ly > 1.0 {
|
|
log.Printf("HIDScript move: Second argument has to be a float between -1.0 and +1.0 describing relative mouse movement on y-axis\n")
|
|
return
|
|
} else { y = float64(ly) }
|
|
ctl.Mouse.MoveTo(x,y)
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsButton(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
//arg0 has to be of type number, representing a bitmask for BUTTON1..3
|
|
arg0 := call.Argument(0)
|
|
log.Printf("HIDScript: Called button(%v)\n", arg0)
|
|
maskInt, err := arg0.ToInteger()
|
|
maskByte := byte(maskInt)
|
|
if err != nil || !arg0.IsNumber() || maskInt != int64(maskByte) || !(maskByte >= 0 && maskByte <= BUTTON3) {
|
|
log.Printf("HIDScript button: Argument has to be a bitmask representing Buttons (BT1 || BT2 || BT3).\nError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
|
|
var bt [3]bool
|
|
if maskByte & BUTTON1 > 0 { bt[0] = true}
|
|
if maskByte & BUTTON2 > 0 { bt[1] = true}
|
|
if maskByte & BUTTON3 > 0 { bt[2] = true}
|
|
err = ctl.Mouse.SetButtons(bt[0], bt[1], bt[2])
|
|
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsClick(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
//arg0 has to be of type number, representing a bitmask for BUTTON1..3
|
|
arg0 := call.Argument(0)
|
|
log.Printf("HIDScript: Called click(%v)\n", arg0)
|
|
maskInt, err := arg0.ToInteger()
|
|
maskByte := byte(maskInt)
|
|
if err != nil || !arg0.IsNumber() || maskInt != int64(maskByte) || !(maskByte >= 0 && maskByte <= BUTTON3) {
|
|
log.Printf("HIDScript click: Argument has to be a bitmask representing Buttons (BT1 || BT2 || BT3).\nError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
|
|
var bt [3]bool
|
|
if maskByte & BUTTON1 > 0 { bt[0] = true}
|
|
if maskByte & BUTTON2 > 0 { bt[1] = true}
|
|
if maskByte & BUTTON3 > 0 { bt[2] = true}
|
|
err = ctl.Mouse.Click(bt[0], bt[1], bt[2])
|
|
|
|
return
|
|
}
|
|
|
|
func (ctl *HIDController) jsDoubleClick(call otto.FunctionCall) (res otto.Value) {
|
|
if ctl.Mouse == nil { log.Println(ErrNoMouse); return }
|
|
|
|
//arg0 has to be of type number, representing a bitmask for BUTTON1..3
|
|
arg0 := call.Argument(0)
|
|
log.Printf("HIDScript: Called doubleClick(%v)\n", arg0)
|
|
maskInt, err := arg0.ToInteger()
|
|
maskByte := byte(maskInt)
|
|
if err != nil || !arg0.IsNumber() || maskInt != int64(maskByte) || !(maskByte >= 0 && maskByte <= BUTTON3) {
|
|
log.Printf("HIDScript doubleClick: Argument has to be a bitmask representing Buttons (BT1 || BT2 || BT3).\nError location: %v\n", call.CallerLocation())
|
|
return
|
|
}
|
|
|
|
|
|
var bt [3]bool
|
|
if maskByte & BUTTON1 > 0 { bt[0] = true}
|
|
if maskByte & BUTTON2 > 0 { bt[1] = true}
|
|
if maskByte & BUTTON3 > 0 { bt[2] = true}
|
|
err = ctl.Mouse.DoubleClick(bt[0], bt[1], bt[2])
|
|
|
|
return
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) initMasterVM() (err error) {
|
|
ctl.vmMaster = otto.New()
|
|
ctl.initVM(ctl.vmMaster)
|
|
return nil
|
|
}
|
|
|
|
|
|
func (ctl *HIDController) initVM(vm *otto.Otto) (err error) {
|
|
err = vm.Set("NUM", MaskNumLock)
|
|
if err != nil { return err }
|
|
err = vm.Set("CAPS", MaskCapsLock)
|
|
if err != nil { return err }
|
|
err = vm.Set("SCROLL", MaskScrollLock)
|
|
if err != nil { return err }
|
|
err = vm.Set("COMPOSE", MaskCompose)
|
|
if err != nil { return err }
|
|
err = vm.Set("KANA", MaskKana)
|
|
if err != nil { return err }
|
|
err = vm.Set("ANY", MaskAny)
|
|
if err != nil { return err }
|
|
err = vm.Set("ANY_OR_NONE", MaskAnyOrNone) //special masking, report back LED state updates with no change (re-attachment of Keyboard on Win/Linux)
|
|
if err != nil { return err }
|
|
|
|
err = vm.Set("BT1", BUTTON1)
|
|
if err != nil { return err }
|
|
err = vm.Set("BT2", BUTTON2)
|
|
if err != nil { return err }
|
|
err = vm.Set("BT3", BUTTON3)
|
|
if err != nil { return err }
|
|
err = vm.Set("BTNONE", 0)
|
|
if err != nil { return err }
|
|
|
|
|
|
err = vm.Set("typingSpeed", ctl.jsTypingSpeed) //This function influences all scripts
|
|
if err != nil { return err }
|
|
|
|
err = vm.Set("type", ctl.jsType)
|
|
if err != nil { return err }
|
|
err = vm.Set("delay", ctl.jsDelay)
|
|
if err != nil { return err }
|
|
err = vm.Set("press", ctl.jsPress)
|
|
if err != nil { return err }
|
|
err = vm.Set("waitLED", ctl.jsWaitLED)
|
|
if err != nil { return err }
|
|
err = vm.Set("waitLEDRepeat", ctl.jsWaitLEDRepeat)
|
|
if err != nil { return err }
|
|
err = vm.Set("layout", ctl.jsLayout) // this influences all scripts
|
|
if err != nil { return err }
|
|
|
|
err = vm.Set("move", ctl.jsMove)
|
|
if err != nil { return err }
|
|
err = vm.Set("moveStepped", ctl.jsMoveStepped)
|
|
if err != nil { return err }
|
|
err = vm.Set("moveTo", ctl.jsMoveTo)
|
|
if err != nil { return err }
|
|
|
|
err = vm.Set("button", ctl.jsButton)
|
|
if err != nil { return err }
|
|
err = vm.Set("click", ctl.jsClick)
|
|
if err != nil { return err }
|
|
err = vm.Set("doubleClick", ctl.jsDoubleClick)
|
|
if err != nil { return err }
|
|
return nil
|
|
}
|