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
 }