316 lines
7.3 KiB
Go
316 lines
7.3 KiB
Go
package hid
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type syncer interface {
|
|
Sync() error
|
|
}
|
|
|
|
// A Key is a USB HID value
|
|
type Key struct {
|
|
Modifier []string `json:"modifier"`
|
|
Decimal byte `json:"decimal"`
|
|
PressDelayDelimiter bool `json:"pressDelayDelimiter,omitempty"`
|
|
ReleaseDelayDelimiter bool `json:"releaseDelayDelimiter,omitempty"`
|
|
Comment bool `json:"comment,omitempty"`
|
|
}
|
|
|
|
// A Keymap is a json representation of the unicode rune mapped to its USB HID value
|
|
type Keymap map[string]Key
|
|
|
|
type Keyboard struct {
|
|
PressDelay time.Duration // PressDelay is the time in ms to delay before sending a press event
|
|
ReleaseDelay time.Duration // ReleaseDelay is the time in ms to wait before sending the release event
|
|
KeymapOrder []string // Keymap Order is the order in which the specified keymaps cycle on the computer
|
|
KeymapShortcut [8]byte // KeymapShortcut is the key combo that will cycle the current keymap by one
|
|
ErrOnUnknownKey bool // ErrOnUnknownKey whether or not to fail if the unicode rune is invalid or is not in the specified keymaps
|
|
KeymapPath string // KeymapPath is the pathe to where the keymap files are stored
|
|
currentKeyMap int
|
|
keymaps map[string]Keymap
|
|
flags map[string]byte
|
|
Hidg0 io.Writer
|
|
}
|
|
|
|
// bit flag of modifier keys
|
|
const (
|
|
LCTRL byte = 1 << iota
|
|
LSHIFT
|
|
LALT
|
|
LSUPER
|
|
RCTRL
|
|
RSHIFT
|
|
RALT
|
|
RSUPER
|
|
NONE = 0
|
|
)
|
|
|
|
var (
|
|
Modifiers = map[string]byte{
|
|
"LSHIFT": LSHIFT,
|
|
"LCTRL": LCTRL,
|
|
"LALT": LALT,
|
|
"LSUPER": LSUPER,
|
|
"RSHIFT": RSHIFT,
|
|
"RCTRL": RCTRL,
|
|
"RALT": RALT,
|
|
"RSUPER": RSUPER,
|
|
"NONE": NONE,
|
|
}
|
|
)
|
|
|
|
func NewKeyboard(Modifiers map[string]byte, kemapOrder []string, KeymapPath string, hidg0 io.Writer) *Keyboard {
|
|
return &Keyboard{
|
|
flags: Modifiers,
|
|
KeymapOrder: kemapOrder,
|
|
KeymapPath: KeymapPath,
|
|
Hidg0: hidg0,
|
|
}
|
|
}
|
|
|
|
// io.writer probably isn't the best interface to use for this
|
|
func (k *Keyboard) Write(p []byte) (int, error) {
|
|
var (
|
|
index int
|
|
err error
|
|
)
|
|
for index < len(p) {
|
|
var (
|
|
r rune
|
|
s int
|
|
flag byte
|
|
report [8]byte
|
|
)
|
|
press:
|
|
for i := 2; i < 8 && index < len(p); i++ {
|
|
var (
|
|
mod byte
|
|
)
|
|
r, s = utf8.DecodeRune(p[index:])
|
|
if r == utf8.RuneError {
|
|
return index, fmt.Errorf("invalid rune: 0x%X", p[index]) // This probably screws things up if the last rune in 'p' is incomplete
|
|
}
|
|
cur, ok := k.CurrentKeymap()[string(r)]
|
|
if !ok {
|
|
if i == 3 { // can't press two keys from different keymaps
|
|
ok, err = k.changeKeymap(r)
|
|
if !ok && k.ErrOnUnknownKey {
|
|
if err != nil {
|
|
return index, err
|
|
}
|
|
return index, fmt.Errorf("rune not in keymap: %c", r)
|
|
}
|
|
} else {
|
|
break press
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case cur.PressDelayDelimiter:
|
|
var n int
|
|
n, k.PressDelay = parseDelay(p[index+s:])
|
|
index += s + n
|
|
break press
|
|
|
|
case cur.ReleaseDelayDelimiter:
|
|
var n int
|
|
n, k.ReleaseDelay = parseDelay(p[index+s:])
|
|
index += s + n
|
|
break press
|
|
|
|
case cur.Comment:
|
|
var n int
|
|
n = bytes.Index(p[index+s:], []byte("\n")) + 1
|
|
if n < 0 {
|
|
n = 0
|
|
}
|
|
index += s + n
|
|
break press
|
|
case r == '␀':
|
|
// Causes immediate key press useful for modifier keys
|
|
index += s
|
|
break press
|
|
|
|
default:
|
|
// Calculate next modifier byte
|
|
for _, v := range cur.Modifier {
|
|
mod |= k.flags[v]
|
|
}
|
|
|
|
// Set the modifier if it is the first key otherwise
|
|
// check if the next modifier byte is the same
|
|
if i == 2 {
|
|
flag = mod
|
|
} else if flag != mod {
|
|
// This is the second key press if the previous one was a modifier only Decimal == 0 then take the current key as well
|
|
if report[i-1] != 0 {
|
|
break press
|
|
}
|
|
// Add the modifier of the current key eg 'D' adds shift 'd' does not
|
|
flag |= mod
|
|
fmt.Printf("this press is modified %d %v\n", flag, cur)
|
|
}
|
|
|
|
// Check for duplicate key press. You can't press a key if it is already pressed, unless it is 0 indicating a modifier.
|
|
for u := 2; u < i; u++ {
|
|
if cur.Decimal == report[u] && cur.Decimal != 0 {
|
|
break press
|
|
}
|
|
}
|
|
}
|
|
report[i] = cur.Decimal
|
|
index += s
|
|
if k.PressDelay > 0 {
|
|
// This is the first key press if this is a modifier only Decimal == 0 then take the next key as well
|
|
if report[i] != 0 {
|
|
break press
|
|
}
|
|
fmt.Printf("this press is a modifier %v\n", cur)
|
|
}
|
|
}
|
|
report[0] = flag
|
|
err = k.Press(report, k.Hidg0)
|
|
if err != nil {
|
|
return index, err
|
|
}
|
|
k.delay(k.PressDelay)
|
|
}
|
|
err = k.keymapto0() // To make it reproducible
|
|
if err != nil {
|
|
return index, err
|
|
}
|
|
return index, nil
|
|
}
|
|
|
|
func parseDelay(buffer []byte) (count int, delay time.Duration) {
|
|
var index int
|
|
sb := strings.Builder{}
|
|
for index < len(buffer) {
|
|
r, s := utf8.DecodeRune(buffer[index:])
|
|
if unicode.IsDigit(r) {
|
|
sb.WriteRune(r)
|
|
index += s
|
|
} else {
|
|
if r == '\r' {
|
|
index += s
|
|
r, s = utf8.DecodeRune(buffer[index:])
|
|
}
|
|
if r == '\n' {
|
|
index += s
|
|
}
|
|
break
|
|
}
|
|
}
|
|
i, err := strconv.Atoi(sb.String())
|
|
if err == nil || err == strconv.ErrRange {
|
|
return index, time.Millisecond * time.Duration(i)
|
|
}
|
|
return 0, 0
|
|
}
|
|
|
|
func (k *Keyboard) delay(Delay time.Duration) {
|
|
if Delay > 0 {
|
|
if syncCheck, ok := k.Hidg0.(syncer); ok {
|
|
_ = syncCheck.Sync()
|
|
}
|
|
time.Sleep(Delay)
|
|
}
|
|
}
|
|
|
|
func (k *Keyboard) Press(press [8]byte, file io.Writer) error {
|
|
_, err1 := file.Write(press[:])
|
|
k.delay(k.ReleaseDelay)
|
|
_, err2 := file.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (k *Keyboard) Hold(press [8]byte, file io.Writer) error {
|
|
_, err := file.Write(press[:])
|
|
return err
|
|
}
|
|
|
|
func (k *Keyboard) keymapto0() error {
|
|
if len(k.KeymapOrder) > 1 {
|
|
for i := 0; i < len(k.KeymapOrder)-(k.currentKeyMap); i++ {
|
|
err := k.Press([8]byte{LALT, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00}, k.Hidg0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
k.currentKeyMap = 0
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (k *Keyboard) changeKeymap(r rune) (bool, error) {
|
|
var err error
|
|
buf := bytes.NewBuffer(make([]byte, 0, 8*len(k.KeymapOrder))) // To batch shortcut presses
|
|
|
|
for i := 0; i < len(k.KeymapOrder); i++ {
|
|
_, ok := k.CurrentKeymap()[string(r)]
|
|
if ok {
|
|
_, err = k.Hidg0.Write(buf.Bytes())
|
|
return true, err
|
|
}
|
|
err = k.Press(k.KeymapShortcut, buf)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if k.currentKeyMap == len(k.KeymapOrder)-1 {
|
|
k.currentKeyMap = 0
|
|
} else {
|
|
k.currentKeyMap++
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (k *Keyboard) CurrentKeymap() Keymap {
|
|
keymap, ok := k.keymaps[k.KeymapOrder[k.currentKeyMap]]
|
|
if ok {
|
|
return keymap
|
|
}
|
|
if k.keymaps == nil {
|
|
k.keymaps = make(map[string]Keymap)
|
|
}
|
|
k.keymaps[k.KeymapOrder[k.currentKeyMap]] = LoadKeymap(k.KeymapOrder[k.currentKeyMap], k.KeymapPath)
|
|
return k.keymaps[k.KeymapOrder[k.currentKeyMap]]
|
|
}
|
|
|
|
func LoadKeymap(keymapName string, KeymapPath string) Keymap {
|
|
var (
|
|
err error
|
|
content []byte
|
|
file = path.Join(KeymapPath, keymapName+".json")
|
|
tmp = make(Keymap)
|
|
)
|
|
fmt.Println(file)
|
|
content, err = ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
err = json.Unmarshal(content, &tmp)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return tmp
|
|
}
|