diff --git a/hid/controller.go b/hid/controller.go index 5472473..bc4b2f0 100644 --- a/hid/controller.go +++ b/hid/controller.go @@ -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 } diff --git a/hid/keyboard.go b/hid/keyboard.go index b2e021c..7865ec1 100644 --- a/hid/keyboard.go +++ b/hid/keyboard.go @@ -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 diff --git a/hid/mouse.go b/hid/mouse.go new file mode 100644 index 0000000..c289b0f --- /dev/null +++ b/hid/mouse.go @@ -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 +} + + + diff --git a/hidtest1.js b/hidtest1.js new file mode 100644 index 0000000..fab4c35 --- /dev/null +++ b/hidtest1.js @@ -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); diff --git a/service/defaults.go b/service/defaults.go index e6604ca..ba9419d 100644 --- a/service/defaults.go +++ b/service/defaults.go @@ -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, diff --git a/testhid.go b/testhid.go index 80faa9b..21d7068 100644 --- a/testhid.go +++ b/testhid.go @@ -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)} + } /* diff --git a/testhidscript.go b/testhidscript.go deleted file mode 100644 index 06ab7d0..0000000 --- a/testhidscript.go +++ /dev/null @@ -1 +0,0 @@ -package main