2018-10-30 15:36:10 +01:00
// +build linux
2018-06-16 11:27:56 +00:00
2018-06-05 21:12:35 +00:00
package hid
import (
2018-06-26 19:59:17 +00:00
"errors"
2018-06-05 21:12:35 +00:00
"github.com/robertkrimen/otto"
2018-06-29 12:29:19 +00:00
"fmt"
"context"
"sync"
2018-06-05 21:12:35 +00:00
"log"
"time"
)
const (
MAX_VM = 8
2018-06-15 13:26:58 +00:00
2018-06-05 21:12:35 +00:00
)
2018-06-16 11:27:56 +00:00
var (
2018-06-29 12:29:19 +00:00
globalJobList = make ( map [ * AsyncOttoJob ] bool )
globalJobListMutex = sync . Mutex { }
2018-06-26 19:59:17 +00:00
hidControllerReuse * HIDController
2018-06-29 14:17:41 +00:00
haltirq = errors . New ( "Stahp" )
ErrAbort = errors . New ( "Event listening aborted" )
ErrIrq = errors . New ( "Aborted due to interrupt request" )
2018-06-16 11:27:56 +00:00
ErrNotAllowed = errors . New ( "Calling not allowed (currently disabled)" )
2018-07-06 23:23:39 +00:00
ErrNoKeyboard = errors . New ( "Using HID keyboard not allowed (currently disabled)" )
ErrNoMouse = errors . New ( "Using HID mouse not allowed (currently disabled)" )
2018-06-16 11:27:56 +00:00
)
2018-06-05 21:12:35 +00:00
2018-06-26 19:59:17 +00:00
// 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
2018-06-15 16:26:40 +00:00
2018-06-05 21:12:35 +00:00
2018-06-15 16:26:40 +00:00
2018-06-26 19:59:17 +00:00
type HIDController struct {
Keyboard * HIDKeyboard
Mouse * Mouse
2018-06-29 20:42:55 +00:00
vmPool [ MAX_VM ] * AsyncOttoVM
2018-06-26 19:59:17 +00:00
vmMaster * otto . Otto
2018-06-29 12:29:19 +00:00
ctx context . Context
cancel context . CancelFunc
2018-07-29 02:40:23 +02:00
eventHandler EventHandler
2018-06-05 21:12:35 +00:00
}
2018-06-29 12:29:19 +00:00
func NewHIDController ( ctx context . Context , keyboardDevicePath string , keyboardMapPath string , mouseDevicePath string ) ( ctl * HIDController , err error ) {
2018-06-29 20:42:55 +00:00
//ToDo: check if hidcontroller could work with a context with cancellation, which then could be cancelled on reuse of the object
2018-06-26 19:59:17 +00:00
if hidControllerReuse == nil {
hidControllerReuse = & HIDController { }
2018-06-15 16:26:40 +00:00
2018-06-26 19:59:17 +00:00
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
2018-06-15 16:26:40 +00:00
2018-06-27 05:38:47 +00:00
hidControllerReuse . Abort ( )
hidControllerReuse . Keyboard = nil
2018-06-26 19:59:17 +00:00
hidControllerReuse . Mouse = nil
2018-06-05 21:12:35 +00:00
}
2018-06-26 16:34:32 +00:00
2018-06-29 12:29:19 +00:00
ctx , cancel := context . WithCancel ( ctx )
hidControllerReuse . ctx = ctx
hidControllerReuse . cancel = cancel
2018-06-26 16:34:32 +00:00
2018-06-15 13:26:58 +00:00
//Note: to disable mouse/keyboard support, the respective device path has to have zero length
2018-06-05 21:12:35 +00:00
//init keyboard
2018-06-15 13:26:58 +00:00
if len ( keyboardDevicePath ) > 0 {
2018-06-29 12:29:19 +00:00
hidControllerReuse . Keyboard , err = NewKeyboard ( ctx , keyboardDevicePath , keyboardMapPath )
2018-06-15 13:26:58 +00:00
if err != nil { return nil , err }
}
//init Mouse
if len ( mouseDevicePath ) > 0 {
2018-06-26 19:59:17 +00:00
hidControllerReuse . Mouse , err = NewMouse ( mouseDevicePath )
2018-06-15 13:26:58 +00:00
if err != nil { return nil , err }
}
2018-07-29 02:40:23 +02:00
hidControllerReuse . SetDefaultHandler ( )
2018-06-26 19:59:17 +00:00
return hidControllerReuse , nil
2018-06-05 21:12:35 +00:00
}
2018-07-29 02:40:23 +02:00
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 )
}
2018-06-27 05:38:47 +00:00
func ( ctl * HIDController ) Abort ( ) {
// 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
2018-06-26 16:34:32 +00:00
}
2018-10-15 12:19:22 +02:00
if hidControllerReuse . Mouse != nil {
hidControllerReuse . Mouse . Close ( )
}
2018-06-29 12:29:19 +00:00
2018-06-27 05:38:47 +00:00
// Interrupt all VMs already running
2018-06-29 12:29:19 +00:00
//hidControllerReuse.CancelAllBackgroundJobs()
2018-06-27 05:38:47 +00:00
hidControllerReuse . CancelAllBackgroundJobs ( )
2018-07-29 02:40:23 +02:00
ctl . emitEvent ( Event {
Type : EventType_CONTROLLER_ABORTED ,
Message : "Called abort on HID Controller" ,
Job : nil ,
Vm : nil ,
} )
2018-06-26 16:34:32 +00:00
}
2018-06-29 12:29:19 +00:00
func ( ctl * HIDController ) NextUnusedVM ( ) ( vm * AsyncOttoVM , err error ) {
2018-06-05 21:12:35 +00:00
//iterate over pool
2018-06-29 12:29:19 +00:00
for _ , avm := range ctl . vmPool {
2018-06-05 21:12:35 +00:00
if ! avm . IsWorking ( ) {
2018-06-07 08:14:37 +00:00
//return first non-working vm
2018-06-29 12:29:19 +00:00
return avm , nil //free to be used
2018-06-05 21:12:35 +00:00
}
}
2018-06-29 12:29:19 +00:00
return nil , errors . New ( "No free JavaScript VM available in pool" )
2018-06-05 21:12:35 +00:00
}
2018-10-15 10:26:57 +02:00
func ( ctl * HIDController ) RunScript ( ctx context . Context , script string , anonymousSelfInvoked bool ) ( val otto . Value , err error ) {
2018-06-29 20:42:55 +00:00
/ *
2018-06-05 21:12:35 +00:00
//fetch next free vm from pool
2018-06-29 12:29:19 +00:00
avm , err := ctl . NextUnusedVM ( )
2018-06-05 21:12:35 +00:00
if err != nil { return otto . Value { } , err }
2018-06-29 12:29:19 +00:00
val , err = avm . Run ( ctx , script )
2018-06-29 20:42:55 +00:00
* /
// 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)
2018-10-15 10:26:57 +02:00
job , err := ctl . StartScriptAsBackgroundJob ( ctx , script , anonymousSelfInvoked )
2018-06-29 20:42:55 +00:00
if err != nil { return val , err }
val , err = ctl . WaitBackgroundJobResult ( ctx , job )
2018-06-05 21:12:35 +00:00
return
}
2018-06-29 12:29:19 +00:00
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
}
}
2018-07-29 02:40:23 +02:00
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" )
}
}
2018-10-15 10:26:57 +02:00
func ( ctl * HIDController ) StartScriptAsBackgroundJob ( ctx context . Context , script string , anonymousSelfInvoked bool ) ( job * AsyncOttoJob , err error ) {
2018-06-05 21:12:35 +00:00
//fetch next free vm from pool
2018-06-29 12:29:19 +00:00
avm , err := ctl . NextUnusedVM ( )
2018-07-29 02:40:23 +02:00
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
}
2018-06-05 21:12:35 +00:00
//try to run script async
2018-10-15 10:26:57 +02:00
job , err = avm . RunAsync ( ctx , script , anonymousSelfInvoked )
2018-06-29 12:29:19 +00:00
if err != nil { return nil , err }
2018-06-05 21:12:35 +00:00
2018-07-29 02:40:23 +02:00
ctl . emitEvent ( Event {
Job : job ,
Vm : avm ,
Type : EventType_JOB_STARTED ,
2018-08-05 16:04:27 +02:00
Message : script , //pack script source into message
2018-07-29 02:40:23 +02:00
//ScriptSource:script,
} )
2018-06-29 12:29:19 +00:00
//add job to global list
globalJobListMutex . Lock ( )
globalJobList [ job ] = true
globalJobListMutex . Unlock ( )
2018-06-05 21:12:35 +00:00
2018-06-30 05:31:55 +00:00
//ad go routine which monitors context of the job, to remove it from global list, once done or interrupted
go func ( ) {
2018-07-29 02:40:23 +02:00
//fmt.Printf("StartScriptAsBackgroundJob: started finish watcher for job %d\n",job.Id)
2018-06-30 05:31:55 +00:00
select {
case <- job . ctx . Done ( ) :
fmt . Printf ( "StartScriptAsBackgroundJob: Job %d finished or interrupted, removing from global list\n" , job . Id )
2018-07-29 02:40:23 +02:00
ctl . emitEvent ( Event {
Job : job ,
Vm : avm ,
Type : EventType_JOB_STOPPED ,
Message : "Script stopped" ,
//ScriptSource:script,
} )
2018-06-30 05:31:55 +00:00
//remove job from global list after result has been retrieved
globalJobListMutex . Lock ( )
delete ( globalJobList , job )
globalJobListMutex . Unlock ( )
}
} ( )
2018-06-29 12:29:19 +00:00
return
2018-06-05 21:12:35 +00:00
}
2018-06-29 20:42:55 +00:00
func ( ctl * HIDController ) WaitBackgroundJobResult ( ctx context . Context , job * AsyncOttoJob ) ( val otto . Value , err error ) {
2018-06-29 12:29:19 +00:00
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 ) )
2018-06-05 21:12:35 +00:00
}
2018-06-29 12:29:19 +00:00
globalJobListMutex . Unlock ( )
if err != nil { return }
2018-06-05 21:12:35 +00:00
2018-06-29 20:42:55 +00:00
//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 )
2018-06-29 12:29:19 +00:00
val , err = job . WaitResult ( ) //Blocking result wait
2018-06-05 21:12:35 +00:00
return
}
2018-06-26 19:59:17 +00:00
2018-06-29 12:29:19 +00:00
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 ( )
2018-10-30 15:36:10 +01:00
/ *
2018-07-29 02:40:23 +02:00
ctl . emitEvent ( Event {
Job : job ,
Vm : job . executingVM ,
Type : EventType_JOB_CANCELLED ,
Message : "Script execution cancelled" ,
} )
2018-10-30 15:36:10 +01:00
* /
2018-06-05 21:12:35 +00:00
}
2018-06-29 12:29:19 +00:00
globalJobList = make ( map [ * AsyncOttoJob ] bool ) //Create new empty list
globalJobListMutex . Unlock ( )
2018-06-05 21:12:35 +00:00
}
2018-07-06 22:40:08 +00:00
2018-08-05 16:04:27 +02:00
func ( ctl * HIDController ) GetAllBackgroundJobs ( ) ( jobs [ ] * AsyncOttoJob , err error ) {
2018-07-06 22:40:08 +00:00
globalJobListMutex . Lock ( )
for job , _ := range globalJobList {
2018-08-05 16:04:27 +02:00
jobs = append ( jobs , job )
2018-07-06 22:40:08 +00:00
}
globalJobListMutex . Unlock ( )
return jobs , nil
}
2018-06-05 21:12:35 +00:00
//Function declarations for master VM
2018-06-08 16:09:12 +00:00
func ( ctl * HIDController ) jsType ( call otto . FunctionCall ) ( res otto . Value ) {
2018-07-06 23:23:39 +00:00
if ctl . Keyboard == nil { log . Println ( ErrNoKeyboard ) ; oErr , _ := otto . ToValue ( ErrNoKeyboard ) ; return oErr }
2018-06-05 21:12:35 +00:00
arg0 := call . Argument ( 0 )
2018-06-08 12:03:24 +00:00
//fmt.Printf("JS type() called with: `%s` (%s)\n", arg0, arg0)
2018-06-05 21:12:35 +00:00
if ! arg0 . IsString ( ) {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript type: Wrong argument, 'type' accepts a single argument of type string. Error location: %v\n" , call . CallerLocation ( ) )
2018-06-05 21:12:35 +00:00
return
}
outStr , err := arg0 . ToString ( )
if err != nil {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript type: couldn't convert '%s' to UTF-8 string\n" , arg0 )
2018-06-05 21:12:35 +00:00
return
}
2018-06-27 05:38:47 +00:00
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 )
2018-06-08 12:03:24 +00:00
err = ctl . Keyboard . StringToPressKeySequence ( outStr )
2018-06-05 21:12:35 +00:00
if err != nil {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript type: Couldn't type out `%s` on %v\n" , outStr , ctl . Keyboard . DevicePath )
2018-06-05 21:12:35 +00:00
return
}
return
}
2018-06-08 16:09:12 +00:00
func ( ctl * HIDController ) jsLayout ( call otto . FunctionCall ) ( res otto . Value ) {
2018-07-06 23:23:39 +00:00
if ctl . Keyboard == nil { log . Println ( ErrNoKeyboard ) ; oErr , _ := otto . ToValue ( ErrNoKeyboard ) ; return oErr }
2018-06-08 16:09:12 +00:00
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
}
2018-06-15 13:26:58 +00:00
func ( ctl * HIDController ) jsTypingSpeed ( call otto . FunctionCall ) ( res otto . Value ) {
2018-07-06 23:23:39 +00:00
if ctl . Keyboard == nil { log . Println ( ErrNoKeyboard ) ; oErr , _ := otto . ToValue ( ErrNoKeyboard ) ; return oErr }
2018-06-15 13:26:58 +00:00
typeDelay := call . Argument ( 0 ) //delay between keypresses in milliseconds
typeJitter := call . Argument ( 0 ) //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
}
2018-06-08 16:09:12 +00:00
2018-06-05 21:12:35 +00:00
func ( ctl * HIDController ) jsDelay ( call otto . FunctionCall ) ( res otto . Value ) {
arg0 := call . Argument ( 0 )
2018-06-08 12:03:24 +00:00
//fmt.Printf("JS delay() called with: `%s` (%s)\n", arg0, arg0)
2018-06-05 21:12:35 +00:00
if ! arg0 . IsNumber ( ) {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript delay: Wrong argument, delay accepts a single argument ot type number. Error location: %v\n" , call . CallerLocation ( ) )
2018-06-05 21:12:35 +00:00
return
}
fDelay , err := arg0 . ToFloat ( )
if err != nil {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript delay: Error couldn't convert `%v` to float\n" , arg0 )
2018-06-05 21:12:35 +00:00
return
}
delay := int ( fDelay )
2018-06-29 20:42:55 +00:00
// log.Printf("HIDScript delay: Sleeping `%v` milliseconds\n", delay)
2018-06-05 21:12:35 +00:00
2018-06-29 14:17:41 +00:00
select {
case <- time . After ( time . Millisecond * time . Duration ( int ( delay ) ) ) :
return
case irq := <- call . Otto . Interrupt :
irq ( )
}
2018-06-05 21:12:35 +00:00
return
}
2018-06-08 12:03:24 +00:00
//for pressing key combos
func ( ctl * HIDController ) jsPress ( call otto . FunctionCall ) ( res otto . Value ) {
2018-07-06 23:23:39 +00:00
if ctl . Keyboard == nil { log . Println ( ErrNoKeyboard ) ; oErr , _ := otto . ToValue ( ErrNoKeyboard ) ; return oErr }
2018-06-08 12:03:24 +00:00
arg0 := call . Argument ( 0 )
//fmt.Printf("JS delay() called with: `%s` (%s)\n", arg0, arg0)
if ! arg0 . IsString ( ) {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript press: Wrong argument for 'press'. 'press' accepts a single argument of type string.\n\tError location: %v\n" , call . CallerLocation ( ) )
2018-06-08 12:03:24 +00:00
return
}
comboStr , err := arg0 . ToString ( )
if err != nil {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript press: Error couldn't convert '%v' to string\n" , arg0 )
2018-06-08 12:03:24 +00:00
return
}
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript press: Pressing combo '%s'\n" , comboStr )
2018-06-08 12:03:24 +00:00
err = ctl . Keyboard . StringToPressKeyCombo ( comboStr )
if err != nil {
2018-06-08 16:09:12 +00:00
log . Printf ( "HIDScript press: Error couldn't convert `%v` to string\n" , arg0 )
2018-06-08 12:03:24 +00:00
oErr , vErr := otto . ToValue ( err )
if vErr == nil { return oErr }
return
}
return
}
2018-06-30 05:31:55 +00:00
2018-06-08 16:09:12 +00:00
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 )
2018-06-30 05:31:55 +00:00
// log.Printf("HIDScript: Called WaitLED(%v, %v)\n", arg0, arg1)
2018-06-08 16:09:12 +00:00
maskInt , err := arg0 . ToInteger ( )
2018-06-30 05:31:55 +00:00
if err != nil || ! arg0 . IsNumber ( ) || ! ( maskInt >= 0 && maskInt <= MaskAnyOrNone ) {
2018-06-08 16:09:12 +00:00
//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 {
2018-06-27 05:38:47 +00:00
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 )
2018-06-08 16:09:12 +00:00
return
}
2018-06-27 05:38:47 +00:00
timeout = time . Duration ( timeoutInt ) * time . Millisecond
2018-06-08 16:09:12 +00:00
default :
2018-06-27 05:38:47 +00:00
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" )
2018-06-08 16:09:12 +00:00
return
}
2018-07-06 23:23:39 +00:00
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 )
}
2018-06-29 14:17:41 +00:00
}
2018-06-08 16:09:12 +00:00
2018-07-06 23:23:39 +00:00
2018-06-16 11:27:56 +00:00
errStr := ""
if err != nil { errStr = fmt . Sprintf ( "%v" , err ) }
2018-07-29 02:40:23 +02:00
resStruct := struct {
2018-06-16 11:27:56 +00:00
ERROR bool
ERRORTEXT string
2018-06-08 16:09:12 +00:00
TIMEOUT bool
NUM bool
CAPS bool
SCROLL bool
COMPOSE bool
KANA bool
} {
2018-06-16 11:27:56 +00:00
ERROR : err != nil ,
ERRORTEXT : errStr ,
2018-06-08 16:09:12 +00:00
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 ,
2018-07-29 02:40:23 +02:00
}
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)" ,
} )
}
2018-06-08 16:09:12 +00:00
return
}
2018-06-05 21:12:35 +00:00
2018-06-11 15:34:47 +00:00
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 ( )
2018-06-30 05:31:55 +00:00
if err != nil || ! arg0 . IsNumber ( ) || ! ( maskInt >= 0 && maskInt <= MaskAnyOrNone ) {
2018-06-11 15:34:47 +00:00
//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 {
2018-06-27 05:38:47 +00:00
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 )
2018-06-11 15:34:47 +00:00
return
}
2018-06-27 05:38:47 +00:00
timeout = time . Duration ( timeoutInt ) * time . Millisecond
2018-06-11 15:34:47 +00:00
default :
2018-06-27 05:38:47 +00:00
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" )
2018-06-11 15:34:47 +00:00
return
}
2018-06-29 14:17:41 +00:00
2018-07-06 23:23:39 +00:00
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 )
}
2018-06-29 14:17:41 +00:00
}
2018-06-11 15:34:47 +00:00
2018-07-06 23:23:39 +00:00
2018-06-16 11:27:56 +00:00
errStr := ""
if err != nil { errStr = fmt . Sprintf ( "%v" , err ) }
2018-07-29 02:40:23 +02:00
resStruct := struct {
2018-06-16 11:27:56 +00:00
ERROR bool
ERRORTEXT string
2018-06-11 15:34:47 +00:00
TIMEOUT bool
NUM bool
CAPS bool
SCROLL bool
COMPOSE bool
KANA bool
} {
2018-06-16 11:27:56 +00:00
ERROR : err != nil ,
ERRORTEXT : errStr ,
2018-06-11 15:34:47 +00:00
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 ,
2018-07-29 02:40:23 +02:00
}
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)" ,
} )
}
2018-06-11 15:34:47 +00:00
return
}
2018-06-15 13:26:58 +00:00
// Move mouse relative in given mouse units (-127 to +127 per axis)
func ( ctl * HIDController ) jsMove ( call otto . FunctionCall ) ( res otto . Value ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
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 ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
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 ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
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 ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
//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 ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
//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 ) {
2018-07-06 23:23:39 +00:00
if ctl . Mouse == nil { log . Println ( ErrNoMouse ) ; return }
2018-06-15 13:26:58 +00:00
//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
}
2018-06-05 21:12:35 +00:00
func ( ctl * HIDController ) initMasterVM ( ) ( err error ) {
ctl . vmMaster = otto . New ( )
2018-06-26 16:34:32 +00:00
ctl . initVM ( ctl . vmMaster )
return nil
}
func ( ctl * HIDController ) initVM ( vm * otto . Otto ) ( err error ) {
err = vm . Set ( "NUM" , MaskNumLock )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "CAPS" , MaskCapsLock )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "SCROLL" , MaskScrollLock )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "COMPOSE" , MaskCompose )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "KANA" , MaskKana )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "ANY" , MaskAny )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-30 05:31:55 +00:00
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 }
2018-06-08 16:09:12 +00:00
2018-06-26 16:34:32 +00:00
err = vm . Set ( "BT1" , BUTTON1 )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "BT2" , BUTTON2 )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "BT3" , BUTTON3 )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "BTNONE" , 0 )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "typingSpeed" , ctl . jsTypingSpeed ) //This function influences all scripts
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-08 16:09:12 +00:00
2018-06-26 16:34:32 +00:00
err = vm . Set ( "type" , ctl . jsType )
2018-06-05 21:12:35 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "delay" , ctl . jsDelay )
2018-06-05 21:12:35 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "press" , ctl . jsPress )
2018-06-08 12:03:24 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "waitLED" , ctl . jsWaitLED )
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "waitLEDRepeat" , ctl . jsWaitLEDRepeat )
2018-06-11 15:34:47 +00:00
if err != nil { return err }
2018-06-29 20:42:55 +00:00
err = vm . Set ( "layout" , ctl . jsLayout ) // this influences all scripts
2018-06-08 16:09:12 +00:00
if err != nil { return err }
2018-06-15 13:26:58 +00:00
2018-06-26 16:34:32 +00:00
err = vm . Set ( "move" , ctl . jsMove )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "moveStepped" , ctl . jsMoveStepped )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "moveTo" , ctl . jsMoveTo )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "button" , ctl . jsButton )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "click" , ctl . jsClick )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-26 16:34:32 +00:00
err = vm . Set ( "doubleClick" , ctl . jsDoubleClick )
2018-06-15 13:26:58 +00:00
if err != nil { return err }
2018-06-05 21:12:35 +00:00
return nil
}