Added mouse functionality to HID scripting engine + test from external (modifyable) script file

This commit is contained in:
mame82 2018-06-15 13:26:58 +00:00
parent 84867d2ef6
commit 9fc612932a
7 changed files with 527 additions and 24 deletions

View File

@ -11,6 +11,7 @@ import (
const (
MAX_VM = 8
)
var halt = errors.New("Stahp")
@ -117,15 +118,28 @@ func (avm *AsyncOttoVM) Cancel() error {
type HIDController struct {
Keyboard *HIDKeyboard
Mouse *Mouse
vmPool [MAX_VM]*AsyncOttoVM //ToDo: check if this could be changed to sync.Pool
vmMaster *otto.Otto
}
func NewHIDController(keyboardDevicePath string, keyboardMapPath string, mouseDevicePath string) (ctl *HIDController, err error) {
ctl = &HIDController{}
//Note: to disable mouse/keyboard support, the respective device path has to have zero length
//init keyboard
ctl.Keyboard, err = NewKeyboard(keyboardDevicePath, keyboardMapPath)
if err != nil { return nil, err }
if len(keyboardDevicePath) > 0 {
ctl.Keyboard, err = NewKeyboard(keyboardDevicePath, keyboardMapPath)
if err != nil { return nil, err }
}
//init Mouse
if len(mouseDevicePath) > 0 {
ctl.Mouse,err = NewMouse(mouseDevicePath)
if err != nil { return nil, err }
}
//init master otto vm
@ -266,6 +280,26 @@ func (ctl *HIDController) jsLayout(call otto.FunctionCall) (res otto.Value) {
return
}
func (ctl *HIDController) jsTypingSpeed(call otto.FunctionCall) (res otto.Value) {
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
}
func (ctl *HIDController) jsDelay(call otto.FunctionCall) (res otto.Value) {
@ -467,6 +501,132 @@ func (ctl *HIDController) jsWaitLEDRepeat(call otto.FunctionCall) (res otto.Valu
return
}
// Move mouse relative in given mouse units (-127 to +127 per axis)
func (ctl *HIDController) jsMove(call otto.FunctionCall) (res otto.Value) {
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) {
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) {
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) {
//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) {
//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) {
//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()
@ -483,6 +643,18 @@ func (ctl *HIDController) initMasterVM() (err error) {
err = ctl.vmMaster.Set("ANY", MaskAny)
if err != nil { return err }
err = ctl.vmMaster.Set("BT1", BUTTON1)
if err != nil { return err }
err = ctl.vmMaster.Set("BT2", BUTTON2)
if err != nil { return err }
err = ctl.vmMaster.Set("BT3", BUTTON3)
if err != nil { return err }
err = ctl.vmMaster.Set("BTNONE", 0)
if err != nil { return err }
err = ctl.vmMaster.Set("typingSpeed", ctl.jsTypingSpeed) //This function influences all scripts
if err != nil { return err }
err = ctl.vmMaster.Set("type", ctl.jsType)
if err != nil { return err }
@ -496,5 +668,19 @@ func (ctl *HIDController) initMasterVM() (err error) {
if err != nil { return err }
err = ctl.vmMaster.Set("layout", ctl.jsLayout)
if err != nil { return err }
err = ctl.vmMaster.Set("move", ctl.jsMove)
if err != nil { return err }
err = ctl.vmMaster.Set("moveStepped", ctl.jsMoveStepped)
if err != nil { return err }
err = ctl.vmMaster.Set("moveTo", ctl.jsMoveTo)
if err != nil { return err }
err = ctl.vmMaster.Set("button", ctl.jsButton)
if err != nil { return err }
err = ctl.vmMaster.Set("click", ctl.jsClick)
if err != nil { return err }
err = ctl.vmMaster.Set("doubleClick", ctl.jsDoubleClick)
if err != nil { return err }
return nil
}

View File

@ -42,6 +42,8 @@ type HIDKeyboard struct {
func NewKeyboard(devicePath string, resourcePath string) (keyboard *HIDKeyboard, err error) {
//ToDo: check existence of deviceFile (+ is writable)
keyboard = &HIDKeyboard{}
keyboard.DevicePath = devicePath
keyboard.KeyDelay = 0

167
hid/mouse.go Normal file
View File

@ -0,0 +1,167 @@
package hid
import (
"encoding/binary"
"io/ioutil"
"os"
"math"
)
const (
BUTTON1 = byte(1 << 0)
BUTTON2 = byte(1 << 1)
BUTTON3 = byte(1 << 2)
)
//mat.Round() doesn't exist before go 1.10
func round(f float64) float64 {
return math.Floor(f + .5)
}
type Mouse struct {
lastChangeWasAbsolute bool
buttons [3]bool
axis [2]int
devicePath string
}
func NewMouse(devicePath string) (mouse *Mouse, err error) {
//ToDo: check existence of deviceFile (+ is writable)
return &Mouse{
devicePath: devicePath,
}, nil
}
func (m *Mouse) writeReportToFile(file string) error {
report, err := generateMouseReport(m.lastChangeWasAbsolute, m.buttons, m.axis)
if err != nil { return err }
//fmt.Printf("Writing %+v to %s\n", report, file)
return ioutil.WriteFile(file, report, os.ModePerm) //Serialize Report and write to specified file
}
func (m* Mouse) SetButtons(bt1,bt2,bt3 bool) (err error) {
change := false
if m.buttons[0] != bt1 {
m.buttons[0] = bt1
change = true
}
if m.buttons[1] != bt2 {
m.buttons[1] = bt2
change = true
}
if m.buttons[2] != bt3 {
m.buttons[2] = bt3
change = true
}
if change {
m.axis[0] = 0 //No (repeated) movement on button change
m.axis[1] = 0 //No (repeated) movement on button change
return m.writeReportToFile(m.devicePath)
} else {
//no state change, no new mouse report
return nil
}
}
func (m* Mouse) Click(bt1,bt2,bt3 bool) (err error) {
m.SetButtons(bt1,bt2,bt3)
m.SetButtons(false,false,false ) //release all button (including other buttons in pressed state, before doing the click)
return
}
func (m* Mouse) DoubleClick(bt1,bt2,bt3 bool) (err error) {
m.Click(bt1,bt2,bt3)
m.Click(bt1,bt2,bt3)
return
}
func (m* Mouse) Move(x,y int8) (err error) {
m.axis[0] = int(x)
m.axis[1] = int(y)
m.lastChangeWasAbsolute = false
return m.writeReportToFile(m.devicePath)
}
func scaleAbs(fVal float64) int {
ival := int(float64(0xFFFF) * fVal)
ival -= 32768
if ival < -32768 { ival = -32768 }
if ival < 32767 { ival = 32767 }
return ival
}
func (m* Mouse) MoveTo(x,y float64) (err error) {
m.axis[0] = scaleAbs(x)
m.axis[1] = scaleAbs(y)
m.lastChangeWasAbsolute = true
return m.writeReportToFile(m.devicePath)
}
func (m* Mouse) MoveStepped(x,y int16) (err error) {
xf := float64(x)
yf := float64(y)
steps := math.Max(math.Abs(xf), math.Abs(yf))
dx := xf / steps
dy := yf / steps
curX := int16(0)
curY := int16(0)
for curStep := 1; curStep <= int(steps); curStep++ {
desiredX := int16(round(dx * float64(curStep)))
desiredY := int16(round(dy * float64(curStep)))
stepX := desiredX - curX
stepY := desiredY - curY
//start Lock here
m.axis[0] = int(stepX)
m.axis[1] = int(stepY)
m.lastChangeWasAbsolute = false
err = m.writeReportToFile(m.devicePath)
if err != nil {
m.axis[0] = 0
m.axis[1] = 0
//unlock
return err
}
//unlock
curX += stepX
curY += stepY
}
//Lock
m.axis[0] = 0
m.axis[1] = 0
//Unlock
return nil
}
func generateMouseReport(absolute bool, buttons [3]bool, axis [2]int) (report []byte, err error) {
var outdata [6]byte
if absolute {
outdata[0] = 0x02
} else {
outdata[0] = 0x01
}
if buttons[0] { outdata[1] |= BUTTON1 }
if buttons[1] { outdata[1] |= BUTTON2 }
if buttons[2] { outdata[1] |= BUTTON3 }
if absolute {
binary.LittleEndian.PutUint16(outdata[2:], uint16(axis[0]))
binary.LittleEndian.PutUint16(outdata[4:], uint16(axis[1]))
} else {
outdata[2] = uint8(axis[0])
outdata[3] = uint8(axis[1])
}
return outdata[:], nil
}

55
hidtest1.js Normal file
View File

@ -0,0 +1,55 @@
//Log something to internal console
console.log("HID testscript");
layout("US"); //set US layout
//Natural typing speed (100 ms between keys + additional jitter up to 200 ms)
typingSpeed(100,200);
type("Typing in natural speed");
layout("DE"); //Switching language layout, while script still running
//Fastest typing speed (no delays)
typingSpeed(0,0);
type("Typing fast, including unicode: üÜöÖäÄ");
//Do some relative mouse movement
for (var i = 0; i<10; i++) {
x = Math.random() * 256 - 128; //x, scaled between -128 and 127
y = Math.random() * 256 - 128; //y, scaled between -128 and 127
move(x,y);
delay(500); //wait a half a second
}
//Do some relative mouse movement, but devide it into 1 DPI substeps (pixel perfect mouse move, but slow)
for (var i = 0; i<10; i++) {
x = Math.random() * 256 - 128; //x, scaled between -128 and 127
y = Math.random() * 256 - 128; //y, scaled between -128 and 127
moveStepped(x,y);
delay(500); //wait a half a second
}
//Do some absolute Mouse positioning (not stepped, mouse moves immediately, thus delays are added)
moveTo(0.2,0.2);
delay(1000);
moveTo(0.8,0.2);
delay(1000);
moveTo(0.8,0.8);
delay(1000);
moveTo(0.2,0.8);
delay(1000);
//press button 1, move mouse stepped, release button 1
console.log("Moving mouse with button 1 pressed");
button(BT1);
moveStepped(20,0);
button(BTNONE);
delay(500);
//Click button 2
console.log("Click button 2");
click(BT2);
//Doubleclick button 1
console.log("Double click button 2");
doubleClick(BT1);

View File

@ -67,7 +67,7 @@ func GetDefaultGadgetSettings() (res pb.GadgetSettings) {
Use_CDC_ECM: false,
Use_RNDIS: true,
Use_HID_KEYBOARD: true,
Use_HID_MOUSE: false,
Use_HID_MOUSE: true,
Use_HID_RAW: false,
Use_UMS: false,
Use_SERIAL: false,

View File

@ -6,6 +6,8 @@ import(
"log"
"fmt"
"time"
"math"
"io/ioutil"
)
var (
@ -128,29 +130,35 @@ func TestStringTyping(hidCtl *hid.HIDController) {
if err != nil { fmt.Println(err)}
}
func main() {
/*
*/
hidCtl, err := hid.NewHIDController("/dev/hidg0", "keymaps", "")
if err != nil {panic(err)}
hidCtl.Keyboard.KeyDelay = 100
// hidCtl.Keyboard.KeyDelayJitter = 200
fmt.Printf("Available language maps:\n%v\n",hidCtl.Keyboard.ListLanguageMapNames())
err = hidCtl.Keyboard.SetActiveLanguageMap("DE") //first loaded language map is set by default
if err != nil { fmt.Println(err)}
fmt.Printf("Chosen keyboard language mapping '%s'\n", hidCtl.Keyboard.ActiveLanguageLayout.Name)
/* tests */
//TestComboPress(hidCtl)
//TestLEDTriggers(hidCtl)
//TestStringTyping(hidCtl)
//TestConcurrentLEDTrigges(hidCtl)
func TestCombinedScript(hidCtl *hid.HIDController) (err error) {
testcript := `
console.log("HID Script test for P4wnP1 rework"); //Print to internal console
for (var i = 0; i<5; i++) {
move(128, 0);
delay(500);
move(0, -100.1);
delay(500);
move(-100, 0);
delay(500);
move(0, 100);
delay(500);
}
console.log("HID Script test for P4wnP1 rework"); //Print to internal console
for (var i = 0; i<5; i++) {
moveTo(0.0, 0.0);
delay(500);
moveTo(0.8, 0.0);
delay(500);
moveTo(0.8, 0.8);
delay(500);
moveTo(0.8, 0.8);
delay(500);
}
waitLED(ANY)
layout("US"); //Switch to US keyboard layout
type("Some ASCII test text QWERTZ\n") //Type text to target ('\n' translates to RETURN key)
@ -193,6 +201,92 @@ func main() {
_,err = hidCtl.RunScript(testcript)
if err != nil {panic(err)}
return
}
func TestMouseNoScript(hidCtl *hid.HIDController) (err error) {
hidCtl.Mouse.MoveStepped(100,0)
hidCtl.Mouse.MoveStepped(0,-100)
hidCtl.Mouse.MoveStepped(0,100)
time.Sleep(2*time.Second)
hidCtl.Mouse.SetButtons(true, false, false)
for alpha := 0.0; alpha < 8*math.Pi; alpha+=(math.Pi/180) {
cos := int16(math.Cos(6.0*alpha) * 5)
sin := int16(math.Sin(alpha) * 5)
hidCtl.Mouse.MoveStepped(sin,cos)
}
hidCtl.Mouse.SetButtons(false, false, false)
return nil
}
func TestMouseCircle(hidCtl *hid.HIDController) {
scriptMouse := `
//circular mouse movement with rotating vector
turns = 2
degree = Math.PI/180.0
scale = 4
for (var alpha = 0; alpha < 2 * Math.PI * turns; alpha += degree) {
vecx = Math.cos(alpha) * scale
vecy = Math.sin(alpha) * scale
moveStepped(vecx, vecy);
}
`
_,err := hidCtl.RunScript(scriptMouse)
if err != nil { panic(err)}
}
func main() {
/*
*/
/*
for x:=0; x<50; x++ {
err := mouse.MoveTo(-12,int16(x))
if err != nil { panic(err) }
time.Sleep(100 * time.Millisecond)
}
*/
hidCtl, err := hid.NewHIDController("/dev/hidg0", "keymaps", "/dev/hidg1")
if err != nil {panic(err)}
hidCtl.Keyboard.KeyDelay = 100
// hidCtl.Keyboard.KeyDelayJitter = 200
fmt.Printf("Available language maps:\n%v\n",hidCtl.Keyboard.ListLanguageMapNames())
err = hidCtl.Keyboard.SetActiveLanguageMap("DE") //first loaded language map is set by default
if err != nil { fmt.Println(err)}
fmt.Printf("Chosen keyboard language mapping '%s'\n", hidCtl.Keyboard.ActiveLanguageLayout.Name)
/* tests */
//TestComboPress(hidCtl)
//TestLEDTriggers(hidCtl)
//TestStringTyping(hidCtl)
//TestConcurrentLEDTrigges(hidCtl)
//TestMouseNoScript(hidCtl)
//TestCombinedScript(hidCtl)
//TestMouseCircle(hidCtl)
//try to load script file
filepath := "./hidtest1.js"
if scriptFile, err := ioutil.ReadFile(filepath); err != nil {
log.Printf("Couldn't load HIDScript testfile: %s\n", filepath)
} else {
_,err = hidCtl.RunScript(string(scriptFile))
if err != nil { panic(err)}
}
/*

View File

@ -1 +0,0 @@
package main