P4wnP1_aloa/hid/controller.go
2018-11-23 17:18:51 +01:00

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
}