From 8f553db5c36b7a935b67890dbd77c2d3a98210e8 Mon Sep 17 00:00:00 2001 From: MaMe82 <mame8282@googlemail.com> Date: Sun, 29 Jul 2018 02:40:23 +0200 Subject: [PATCH] Added interface for callback events to HID --- hid/AsyncOtto.go | 40 ++++++++++--- hid/HIDEvent.go | 46 +++++++++++++++ hid/controller.go | 142 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 hid/HIDEvent.go diff --git a/hid/AsyncOtto.go b/hid/AsyncOtto.go index c1bd3a1..31eea5e 100644 --- a/hid/AsyncOtto.go +++ b/hid/AsyncOtto.go @@ -57,13 +57,29 @@ func (job *AsyncOttoJob) ResultJsonString() (string, error) { return string(json),nil } - - type AsyncOttoVM struct { vm *otto.Otto isWorking bool sync.Mutex Id int + eventHandler EventHandler +} + +func (avm *AsyncOttoVM) HandleEvent(event Event) { + fmt.Printf("!!! AsyncOtto Event: %+v\n", event) +} + +func (avm *AsyncOttoVM) SetEventHandler(handler EventHandler) { + avm.eventHandler = handler +} + +func (avm *AsyncOttoVM) SetDefaultHandler() { + avm.SetEventHandler(avm) +} + +func (avm *AsyncOttoVM) emitEvent(event Event) { + if avm.eventHandler == nil { return } + avm.eventHandler.HandleEvent(event) } func NewAsyncOttoVM(vm *otto.Otto) *AsyncOttoVM { @@ -73,6 +89,7 @@ func NewAsyncOttoVM(vm *otto.Otto) *AsyncOttoVM { vm: vm, Id: vmNum, } + res.SetDefaultHandler() vmNum++ return res } @@ -133,7 +150,7 @@ func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}) (job *Asy }(avm) go func(avm *AsyncOttoVM) { - defer func() { //runs after avm.vm.Run() returns (because script finished a was interrupted) + defer func() { //runs after avm.vm.Run() returns (because script finished or was interrupted) defer avm.SetWorking(false) if caught := recover(); caught != nil { fmt.Printf("VM %d CAUGHT INTERRUPT, ENDING JOB %d\n", avm.Id, job.Id) @@ -157,17 +174,26 @@ func (avm *AsyncOttoVM) RunAsync(ctx context.Context, src interface{}) (job *Asy job.ResultValue, job.ResultErr = avm.vm.Run(job.Source) //store result job.SetFinished() // signal job finished - //DEBUG + //Emit event + print DEBUG + evRes := Event{ Vm: avm, Job: job } + //evRes.ScriptSource,_ = job.Source.(string) //if string, attach source to event if job.ResultErr == nil { jRes,jErr := job.ResultJsonString() if jErr == nil { - fmt.Printf("JOB %d on VM %d SUCCEEDED WITH RESULT: %s\n", job.Id, avm.Id, jRes) + evRes.Type = EventType_JOB_SUCCEEDED + evRes.Message = fmt.Sprintf("JOB %d on VM %d SUCCEEDED WITH RESULT: %s", job.Id, avm.Id, jRes) + fmt.Println(evRes.Message) } else { - fmt.Printf("JOB %d on VM %d SUCCEEDED BUT RESULT COULDN'T BE MARSHALED TO JSON: %v\n", job.Id, avm.Id, jErr) + evRes.Type = EventType_JOB_SUCCEEDED + evRes.Message = fmt.Sprintf("JOB %d on VM %d SUCCEEDED BUT RESULT COULDN'T BE MARSHALED TO JSON: %v", job.Id, avm.Id, jErr) + fmt.Println(evRes.Message) } } else { - fmt.Printf("JOB %d on VM %d FAILED: %v\n", job.Id, avm.Id, job.ResultErr) + evRes.Type = EventType_JOB_FAILED + evRes.Message = fmt.Sprintf("JOB %d on VM %d FAILED: %v", job.Id, avm.Id, job.ResultErr) + fmt.Println(evRes.Message) } + avm.emitEvent(evRes) }(avm) diff --git a/hid/HIDEvent.go b/hid/HIDEvent.go new file mode 100644 index 0000000..da55689 --- /dev/null +++ b/hid/HIDEvent.go @@ -0,0 +1,46 @@ +package hid + +type EventType int32 + +const ( + EventType_JOB_STARTED EventType = 0 + EventType_JOB_STOPPED EventType = 1 + EventType_CONTROLLER_ABORTED EventType = 2 + EventType_JOB_CANCELLED EventType = 3 + EventType_JOB_SUCCEEDED EventType = 4 + EventType_JOB_SUCCEEDED_NO_RESULT EventType = 5 + EventType_JOB_FAILED EventType = 6 + EventType_JOB_WAIT_LED_FINISHED EventType = 7 + EventType_JOB_WAIT_LED_REPEATED_FINISHED EventType = 8 + EventType_JOB_NO_FREE_VM EventType = 9 + +) + +/* +var EventType_name = map[int32]string{ + 0: "JOB_STARTED", + 1: "JOB_STOPPED", + 2: "CONTROLLER_ABORTED", + 3: "JOB_CANCELLED", + +} +var EventType_value = map[string]int32{ + "JOB_STARTED": 0, + "JOB_STOPPED": 1, + "CONTROLLER_ABORTED": 2, + "JOB_CANCELLED": 3, +} +*/ + +type Event struct { + Type EventType + Job *AsyncOttoJob + Vm *AsyncOttoVM + Message string + //ScriptSource string +} + + +type EventHandler interface { + HandleEvent(event Event) +} diff --git a/hid/controller.go b/hid/controller.go index bb71452..1cfb8ce 100644 --- a/hid/controller.go +++ b/hid/controller.go @@ -59,6 +59,7 @@ type HIDController struct { vmMaster *otto.Otto ctx context.Context cancel context.CancelFunc + eventHandler EventHandler } func NewHIDController(ctx context.Context, keyboardDevicePath string, keyboardMapPath string, mouseDevicePath string) (ctl *HIDController, err error) { @@ -98,9 +99,30 @@ func NewHIDController(ctx context.Context, keyboardDevicePath string, keyboardMa 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() { // stop the old LED reader if present // !! Keyboard.Close() has to be called before sending IRQ to VMs (there're JS function which register @@ -113,6 +135,13 @@ func (ctl *HIDController) Abort() { // 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) { @@ -161,15 +190,52 @@ func (ctl *HIDController) GetBackgroundJobByID(id int) (job *AsyncOttoJob, err e } } + +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) (job *AsyncOttoJob, err error) { //fetch next free vm from pool avm,err := ctl.NextUnusedVM() - if err != nil { return nil, err } + 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) if err != nil { return nil, err } + ctl.emitEvent(Event{ + Job:job, + Vm:avm, + Type:EventType_JOB_STARTED, + Message:"Script started", + //ScriptSource:script, + }) + //add job to global list globalJobListMutex.Lock() globalJobList[job] = true @@ -177,11 +243,19 @@ func (ctl *HIDController) StartScriptAsBackgroundJob(ctx context.Context,script //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) + //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) @@ -223,6 +297,12 @@ func (ctl *HIDController) CancelAllBackgroundJobs() { 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() @@ -419,7 +499,7 @@ func (ctl *HIDController) jsWaitLED(call otto.FunctionCall) (res otto.Value) { errStr := "" if err != nil {errStr = fmt.Sprintf("%v",err)} - res,_ = call.Otto.ToValue(struct{ + resStruct := struct{ ERROR bool ERRORTEXT string TIMEOUT bool @@ -437,7 +517,32 @@ func (ctl *HIDController) jsWaitLED(call otto.FunctionCall) (res otto.Value) { 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 } @@ -530,7 +635,7 @@ func (ctl *HIDController) jsWaitLEDRepeat(call otto.FunctionCall) (res otto.Valu errStr := "" if err != nil {errStr = fmt.Sprintf("%v",err)} - res,_ = call.Otto.ToValue(struct{ + resStruct := struct{ ERROR bool ERRORTEXT string TIMEOUT bool @@ -548,7 +653,32 @@ func (ctl *HIDController) jsWaitLEDRepeat(call otto.FunctionCall) (res otto.Valu 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 }