mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-03-18 13:52:04 +01:00
634 lines
20 KiB
Go
634 lines
20 KiB
Go
package hid
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"fmt"
|
|
"time"
|
|
"math/rand"
|
|
"regexp"
|
|
"path/filepath"
|
|
"sync"
|
|
"context"
|
|
)
|
|
|
|
var (
|
|
KeyboardReportEmpty = NewKeyboardOutReport(0)
|
|
ErrTimeout = errors.New("Timeout reached")
|
|
)
|
|
|
|
var (
|
|
//regex
|
|
rpSplit = regexp.MustCompile("(?m)\\s+")
|
|
rpSingleUpperLetter = regexp.MustCompile("(?m)^[A-Z]$")
|
|
)
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
type HIDKeyboard struct {
|
|
lock *sync.Mutex
|
|
DevicePath string
|
|
ActiveLanguageLayout *HIDKeyboardLanguageMap
|
|
LanguageMaps map[string]*HIDKeyboardLanguageMap //available language maps
|
|
LEDWatcher *KeyboardLEDStateWatcher
|
|
KeyDelay int
|
|
KeyDelayJitter int
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
kbdOutFile *os.File
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewKeyboard(ctx context.Context, devicePath string, resourcePath string) (keyboard *HIDKeyboard, err error) {
|
|
//ToDo: check existence of deviceFile (+ is writable)
|
|
|
|
ctx,cancel := context.WithCancel(ctx)
|
|
|
|
keyboard = &HIDKeyboard{
|
|
lock: &sync.Mutex{},
|
|
DevicePath: devicePath,
|
|
KeyDelay: 0,
|
|
KeyDelayJitter: 0,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
|
|
//Load available language maps
|
|
err = keyboard.LoadLanguageMapDir(resourcePath)
|
|
if err != nil {return nil, err}
|
|
|
|
//Init LED sate
|
|
keyboard.LEDWatcher, err = NewLEDStateWatcher(ctx, devicePath)
|
|
if err != nil {return nil, err}
|
|
|
|
//Open dev file for writing
|
|
keyboard.kbdOutFile, err = os.OpenFile(devicePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
|
if err != nil {
|
|
return nil,err
|
|
}
|
|
|
|
|
|
return
|
|
}
|
|
|
|
func (kbd *HIDKeyboard) Close() {
|
|
kbd.kbdOutFile.Close()
|
|
kbd.LEDWatcher.Stop()
|
|
}
|
|
|
|
|
|
func (kbd *HIDKeyboard) LoadLanguageMapDir(dirpath string) (err error) {
|
|
folder,err := filepath.Abs(dirpath)
|
|
if err != nil { return err }
|
|
var mapFiles []string
|
|
err = filepath.Walk(string(folder), func(path string, info os.FileInfo, err error) error {
|
|
if err != nil { return err } // prevent panic due to access failures
|
|
if !info.IsDir() && (strings.ToLower(filepath.Ext(info.Name())) == ".json") {
|
|
fp := path
|
|
abs,pErr := filepath.Abs(fp)
|
|
if pErr == nil {
|
|
mapFiles = append(mapFiles, abs)
|
|
} else {
|
|
//print warning
|
|
log.Printf("Ignoring map file '%s', retrieving absolute path failed!\n", fp)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil { return err }
|
|
|
|
//mapFiles contains all absolute path of files with extension ".json"
|
|
var commonMAP *HIDKeyboardLanguageMap
|
|
for _,mapFile := range mapFiles {
|
|
kbdmap, lErr := loadKeyboardLanguageMapFromFile(mapFile)
|
|
if lErr != nil {
|
|
//Warn on error
|
|
log.Printf("Skipping language map file '%s' due to load error: %v\n", mapFile, lErr)
|
|
continue
|
|
}
|
|
if strings.ToUpper(kbdmap.Name) == "COMMON" {
|
|
//this is the map with common keys
|
|
//mapping in this file will be reflected to all other maps, in case the ARE NOT ALREADY PRESENT
|
|
commonMAP = kbdmap
|
|
} else {
|
|
if kbd.LanguageMaps == nil {
|
|
kbd.LanguageMaps = make(map[string]*HIDKeyboardLanguageMap)
|
|
}
|
|
kbd.LanguageMaps[strings.ToUpper(kbdmap.Name)] = kbdmap
|
|
|
|
if kbd.ActiveLanguageLayout == nil && kbdmap.Name != "COMMON" {
|
|
kbd.ActiveLanguageLayout = kbdmap
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no language maps beside "COMMON" have been loaded, return error
|
|
if len(kbd.LanguageMaps) == 0 {
|
|
if commonMAP == nil {
|
|
return errors.New("Couldn't load any language map")
|
|
} else {
|
|
return errors.New("Couldn't load any language map, beside 'COMMON' map")
|
|
}
|
|
|
|
}
|
|
|
|
// At this point, all map files not named "COMMON" should be added to kbd.LanguageMaps
|
|
// In case a map with name "COMMON" was found, it should be stored in commonMap
|
|
// If commonMap was found, the contained mappings are added to the other language maps,
|
|
// in case the dedicated mapping doesn't exist already. F.e. the mapping for "F1" is only
|
|
// needed in map "COMMON" and added to all other maps, except they specify the "F1" mapping
|
|
// themselves.
|
|
if commonMAP != nil {
|
|
//iterate over all common mappings
|
|
for name,reports := range commonMAP.Mapping {
|
|
//iterate over all loaded maps
|
|
for _,lMap := range kbd.LanguageMaps {
|
|
//check if the mapping isn't already present and add it if needed
|
|
if _,alreadyPresent := lMap.Mapping[name]; !alreadyPresent {
|
|
lMap.Mapping[name] = reports
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (kbd *HIDKeyboard) LoadLanguageMapFromFile(filepath string) (err error) {
|
|
//if this is the first map loaded, set as active Map
|
|
kbdmap, err := loadKeyboardLanguageMapFromFile(filepath)
|
|
if err != nil { return err }
|
|
|
|
if kbd.LanguageMaps == nil {
|
|
kbd.LanguageMaps = make(map[string]*HIDKeyboardLanguageMap)
|
|
}
|
|
kbd.LanguageMaps[strings.ToUpper(kbdmap.Name)] = kbdmap
|
|
|
|
if kbd.ActiveLanguageLayout == nil && kbdmap.Name != "COMMON" {
|
|
kbd.ActiveLanguageLayout = kbdmap
|
|
}
|
|
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kbd HIDKeyboard) ListLanguageMapNames() (mapNames []string) {
|
|
mapNames = make([]string, len(kbd.LanguageMaps))
|
|
|
|
i := 0
|
|
for k := range kbd.LanguageMaps {
|
|
mapNames[i] = k
|
|
i++
|
|
}
|
|
return mapNames
|
|
}
|
|
|
|
func (kbd *HIDKeyboard) SetActiveLanguageMap(name string) (err error) {
|
|
if m, ok := kbd.LanguageMaps[strings.ToUpper(name)]; ok {
|
|
kbd.ActiveLanguageLayout = m
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Language map with name '%s' isn't loaded!", name))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (kbd *HIDKeyboard) StringToPressKeyCombo(comboStr string) (err error) {
|
|
report,err := kbd.StringToKeyCombo(comboStr)
|
|
if err != nil { return err }
|
|
seq := []KeyboardOutReport{*report} //convert to single report sequence
|
|
err = kbd.PressKeySequence(seq)
|
|
return
|
|
}
|
|
|
|
|
|
func (kbd *HIDKeyboard) StringToKeyCombo(comboStr string) (result *KeyboardOutReport, err error) {
|
|
//ToDo: Check if keyboard device file exists
|
|
if kbd.ActiveLanguageLayout == nil {
|
|
return nil,errors.New("No language mapping active, couldn't send key combo!")
|
|
}
|
|
|
|
//split key sequence describe by string into single key names
|
|
keyNames := rpSplit.Split(comboStr, -1)
|
|
if len(keyNames) == 0 {
|
|
return nil,errors.New("No keys to press")
|
|
}
|
|
if len(keyNames) == 1 && len(keyNames[0]) == 0 {
|
|
return nil,errors.New("No keys to press")
|
|
}
|
|
|
|
//fmt.Printf("KeyNames %d: %+v\n", len(keyNames), keyNames)
|
|
|
|
//try to convert splitted keynames to reports
|
|
comboReports := make([]*KeyboardOutReport,0)
|
|
for _,keyname := range keyNames {
|
|
if len(keyname) == 0 { continue } //ignore empty keynames
|
|
report,err := kbd.mapKeyNameToReport(keyname)
|
|
if err == nil {
|
|
//fmt.Printf("Keyname '%s' mapped to report %+v\n", keyname, report)
|
|
comboReports = append(comboReports, report)
|
|
} else {
|
|
return nil,errors.New(fmt.Sprintf("Couldn't build key combo '%s' because of mapping error in keyname '%s': %v", comboStr, keyname, err))
|
|
}
|
|
}
|
|
|
|
//fmt.Printf("Combo reports for '%s': %+v\n", comboStr, comboReports)
|
|
|
|
//combine reports
|
|
result,err = combineReports(comboReports)
|
|
return
|
|
}
|
|
|
|
func (kbd *HIDKeyboard) StringToPressKeySequence(str string) (err error) {
|
|
|
|
//ToDo: Check if keyboard device file exists
|
|
if kbd.ActiveLanguageLayout == nil {
|
|
return errors.New("No language mapping active, couldn't send key sequence!")
|
|
}
|
|
for _,runeVal := range str {
|
|
strRune := string(runeVal)
|
|
if reports,found := kbd.ActiveLanguageLayout.Mapping[strRune]; found {
|
|
//log.Printf("Sending reports (%T): %v\n", reports, reports)
|
|
err = kbd.PressKeySequence(reports)
|
|
if err != nil {
|
|
//Abort typing
|
|
return err
|
|
}
|
|
} else {
|
|
log.Printf("HID keyboard warning: Couldn't send charcter '%q' (0x%x) because it is not defined in language map '%s', skipping ...", strRune, strRune, kbd.ActiveLanguageLayout.Name)
|
|
}
|
|
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// mapKeyNameToReport tries to translate a key expressed by a string description to a SINGLE
|
|
// report (with respect to the chosen language map), which could be sent to a keyboard device.
|
|
// Most printable characters like 'a' or 'A' could be represented by a single rune (f.e. `a` or `A`).
|
|
// The parameter `keyName` is of type string (instead of rune) because there're keys which
|
|
// couldn't be described with a single rune, for example: 'F1', 'ESCAPE' ...
|
|
//
|
|
// mapKeyNameToReport translates uppercase alphabetical keys [A-Z] to the respective lower case,
|
|
// before trying to map, in order to avoid fetching reports with [SHIFT] modifiers (this assures
|
|
// that 'A' gets mapped to the USB key KEY_A, not to the USB key KEY_A combined with the [SHIFT]
|
|
// modifier).
|
|
//
|
|
// There're result reports containing only modifiers (f.e. if keyName = 'CTRL'). Such reports could be used
|
|
// to build KeyCombos, by mixing them together.
|
|
//
|
|
// The language maps consist mappings from UTF-8 runes to reports sequences and keyNames to single reports.
|
|
// Examples for sequences are mostly printable runes which are built from multiple sequential key presses
|
|
// (mostly started with DEAD KEYS) like the `^` rune on a german keyboard layout, which has to be created by
|
|
// pressing [^] followed by [SPACE].
|
|
// The purpose of mapKeyNameToReport is to resolve the given keyname to A SINGLE REPORT, thus report sequences
|
|
// are truncated to the first report only, before returned. This is the trade-off between assuring to return a
|
|
// single report per keyname versus managing separated mapping files for rune-mapping and key-mapping.
|
|
func (kbd *HIDKeyboard) mapKeyNameToReport(keyName string) (report *KeyboardOutReport,err error) {
|
|
// Assure keyName contains no spaces, else error
|
|
r := rpSplit.Split(keyName, -1)
|
|
if len(r) > 1 {
|
|
return nil, errors.New(fmt.Sprintf("Error mapping keyName '%s', unallowed contains spaces!", keyName))
|
|
}
|
|
keyName = strings.ToUpper(r[0]) //reassign trimmed, upper case version
|
|
|
|
|
|
// If keyName consists of a single upper case letter, translate to lowercase
|
|
if rpSingleUpperLetter.MatchString(keyName) { keyName = strings.ToLower(keyName)}
|
|
|
|
// Try to find a matching mapping in current language map
|
|
if kbd.ActiveLanguageLayout == nil {
|
|
return nil, errors.New("No language mapping selected")
|
|
}
|
|
if reports,found := kbd.ActiveLanguageLayout.Mapping[keyName]; found {
|
|
//report(s) found, return only first one
|
|
if len(reports) > 0 {
|
|
return &reports[0], nil
|
|
} else {
|
|
return nil, errors.New(fmt.Sprintf("Mapping for key '%s' found in language map named '%s', but mapping is empty!", keyName, kbd.ActiveLanguageLayout.Name))
|
|
}
|
|
} else {
|
|
return nil, errors.New(fmt.Sprintf("Couldn't find mapping for key '%s' in language map named '%s'!", keyName, kbd.ActiveLanguageLayout.Name))
|
|
}
|
|
}
|
|
|
|
// combineReports combines a slice of output reports into a single report (for key combinations).
|
|
// The following rules apply:
|
|
// 1) Modifiers are combined with logical or
|
|
// 2) Unique keys are filled into the keys array, one-by-one
|
|
// 3) Duplicated keys are ignore
|
|
// 4) Only the first 6 keys are regarded (without duplicates), the rest is ignored
|
|
func combineReports(reports []*KeyboardOutReport) (result *KeyboardOutReport, err error) {
|
|
r := KeyboardOutReport{}
|
|
keys := make(map[byte]bool)
|
|
keyCount := 0
|
|
maxKeys := 6
|
|
|
|
ADDREPORTLOOP:
|
|
for _,report := range reports {
|
|
//Add modifiers
|
|
r.Modifiers |= report.Modifiers
|
|
|
|
// add keys to map
|
|
// Note: This could be interrupted in the middle of a report if too many keys are contained, while the
|
|
// modifiers of this report are already applied. This is a corner case, we don't take care of (happens f.e.
|
|
// if the first report contains 2 keys and the second one 6 keys with modifiers)
|
|
for _,key := range report.Keys {
|
|
if key != 0 { //Ignore "no key"
|
|
if !keys[key] {
|
|
keys[key] = true
|
|
keyCount++
|
|
if keyCount >= maxKeys {
|
|
break ADDREPORTLOOP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//keys should contain maxKeys at max
|
|
keyCount = 0
|
|
for k,_ := range keys {
|
|
r.Keys[keyCount] = k
|
|
keyCount++
|
|
if keyCount >= maxKeys { break }
|
|
}
|
|
|
|
return &r, nil
|
|
}
|
|
|
|
|
|
|
|
// PressKeySequence writes the output reports given in `reports` to the keyboard device in sequential
|
|
// order. A all empty report is automatically appended in order to release all keys after finishing
|
|
// the sequence (press in contrast to hold).
|
|
//
|
|
// There's a clear reason to use sequences of reports. For example the character 'à' on a German keyboard
|
|
// layout, is created by pressing the key with [`] in combination with [SHIFT], followed by the key [A].
|
|
// To represent the underlying key sequence three reports are needed:
|
|
// 1) A report containing the key [`] (key equal from US layout) along with the [SHIFT] modifier
|
|
// 2) A report containing the key [A] (the [A] key results in lower case 'a', as no [SHIFT] modifier is used)
|
|
// 3) A report containing no key and no modifier representing the release of all keys
|
|
//
|
|
// It is worth mentioning, that a single report could hold 8 different modifiers and up to 6 dedicated keys,
|
|
// for the keyboard type used here. Anyway, packing the keys [A] and [`] in a single report, along with the
|
|
// [SHIFT] modifier, would lead to a different result (if there's a result at all). The reason is, that the
|
|
// pressing order of the two keys [A] and [`] couldn't be determined anymore, neither would it be possible
|
|
// to distinguish if the [SHIFT] modifier should be combined with [A], [`] or both.
|
|
//
|
|
// As shown, even a single character could be represented by several reports in sequential order! This is why
|
|
// PressKeySequence is needed.
|
|
//
|
|
// A key combination, in contrast to a sequence, combines several keys in a single report (f.e. CTRL+ALT+A)
|
|
func (kbd *HIDKeyboard) PressKeySequence(reports []KeyboardOutReport) (err error) {
|
|
//Synchronize the whole sequence output, as this is considered atomar.
|
|
// f.e. This is used to type out character which are brought up a sequence consisting of
|
|
// [some deadkey + modifiers, some normal key + modifier, zero report for key release]
|
|
// if another key is pressed right after the deadkey, by a different go routine, we end
|
|
// up with unpredictable output
|
|
kbd.lock.Lock()
|
|
defer kbd.lock.Unlock()
|
|
|
|
//iterate over reports and send them
|
|
for _,rep := range reports {
|
|
//err = rep.WriteTo(kbd.DevicePath)
|
|
err = rep.WriteToFile(kbd.kbdOutFile)
|
|
if err != nil { return err }
|
|
}
|
|
//append an empty report to release all keys
|
|
//err = KeyboardReportEmpty.WriteTo(kbd.DevicePath)
|
|
err = KeyboardReportEmpty.WriteToFile(kbd.kbdOutFile)
|
|
if err != nil { return err }
|
|
|
|
//Delay after keypress
|
|
delay := kbd.KeyDelay
|
|
if kbd.KeyDelayJitter > 0 { delay += rand.Intn(kbd.KeyDelayJitter)}
|
|
if delay > 0 { time.Sleep(time.Millisecond * time.Duration(delay)) }
|
|
return nil
|
|
}
|
|
|
|
|
|
|
|
type HIDKeyboardLanguageMap struct {
|
|
Name string
|
|
Description string
|
|
Mapping map[string][]KeyboardOutReport
|
|
}
|
|
|
|
func (klm *HIDKeyboardLanguageMap) StoreToFile(filePath string) (err error) {
|
|
//create JSON representation
|
|
mapJson, err := json.MarshalIndent(klm, "", "\t")
|
|
if err != nil { return err }
|
|
//Write to file
|
|
return ioutil.WriteFile(filePath, mapJson, os.ModePerm)
|
|
}
|
|
|
|
func loadKeyboardLanguageMapFromFile(filePath string) (result *HIDKeyboardLanguageMap, err error) {
|
|
result = &HIDKeyboardLanguageMap{}
|
|
mapJson, err := ioutil.ReadFile(filePath)
|
|
if err != nil { return nil,err }
|
|
err = json.Unmarshal(mapJson, result)
|
|
return
|
|
}
|
|
|
|
type KeyboardOutReport struct {
|
|
Modifiers byte
|
|
//Reserved byte
|
|
Keys [6]byte
|
|
}
|
|
|
|
|
|
func (kr *KeyboardOutReport) UnmarshalJSON(b []byte) error {
|
|
var o map[string]interface{}
|
|
if err := json.Unmarshal(b,&o); err != nil {
|
|
return err
|
|
}
|
|
|
|
|
|
for k,v := range o {
|
|
//log.Printf("key: %v, val %v (%T)\n", k, v, v)
|
|
|
|
switch strings.ToLower(k) {
|
|
case "modifiers":
|
|
switch vv := v.(type) {
|
|
case []interface{}:
|
|
for _, modValIface := range vv {
|
|
//convert modifier back from string to uint8 representation
|
|
|
|
switch modVal := modValIface.(type) {
|
|
case string:
|
|
if modInt, ok := StringToUsbModKey[modVal]; ok {
|
|
kr.Modifiers |= modInt
|
|
} else {
|
|
return errors.New(fmt.Sprintf("The value '%s' couldn't be translated to a valid modifier key\n", modVal))
|
|
}
|
|
//log.Printf("Mod: %v (%T)", modVal, modVal)
|
|
case float64:
|
|
modInt := uint8(modVal)
|
|
if _,ok := UsbModKeyToString[modInt]; !ok && modInt != 0 {
|
|
return errors.New(fmt.Sprintf("The value '%v' isn't valid for a modifier key\n", modVal))
|
|
}
|
|
|
|
kr.Modifiers |= modInt
|
|
default:
|
|
return errors.New(fmt.Sprintf("The value '%v' of type '%T' isn't a valid type for a modifier key\n", modVal, modVal))
|
|
}
|
|
}
|
|
default:
|
|
return errors.New(fmt.Sprintf("Unintended type for 'Modifiers', has to be array of modifier strings, but %v was given\n", vv))
|
|
}
|
|
case "keys":
|
|
switch vv := v.(type) {
|
|
case []interface{}:
|
|
for i, keyValIface := range vv {
|
|
if i > len(kr.Keys) - 1 {
|
|
return errors.New(fmt.Sprintf("The key '%v' at index %d exceeds the maximum key count per report, which is 6!\n", keyValIface, i))
|
|
}
|
|
switch keyVal := keyValIface.(type) {
|
|
case string:
|
|
|
|
if keyInt, ok := StringToUsbKey[keyVal]; ok {
|
|
kr.Keys[i] = keyInt
|
|
} else {
|
|
return errors.New(fmt.Sprintf("The value '%s' couldn't be translated to a valid key\n", keyVal))
|
|
}
|
|
|
|
//log.Printf("Key '%s' (%T) at index %d\n", keyVal, keyVal, i)
|
|
case float64:
|
|
keyInt := uint8(keyVal)
|
|
if _,ok := UsbKeyToString[keyInt]; !ok && keyInt != 0 {
|
|
return errors.New(fmt.Sprintf("The value '%v' isn't valid for a key\n", keyVal))
|
|
}
|
|
|
|
kr.Keys[i] = keyInt
|
|
default:
|
|
return errors.New(fmt.Sprintf("The value '%v' of type '%T' at index %d isn't a valid type for a key array\n", keyVal, keyVal, i))
|
|
}
|
|
}
|
|
default:
|
|
return errors.New(fmt.Sprintf("Unintended type in for 'Keys', has to be array of key strings, but %v was given\n", vv))
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kr KeyboardOutReport) String() string {
|
|
bytes,err := kr.MarshalJSON()
|
|
if err == nil {
|
|
return string(bytes)
|
|
} else {
|
|
return fmt.Sprintf("%+v", kr) //ToDo: check if this works or calls a loop
|
|
}
|
|
}
|
|
|
|
|
|
func (kr *KeyboardOutReport) MarshalJSON() ([]byte, error) {
|
|
keys := []string{}
|
|
modifiers := []string{}
|
|
|
|
if kr.Modifiers & HID_MOD_KEY_LEFT_CONTROL > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_LEFT_CONTROL]) }
|
|
if kr.Modifiers & HID_MOD_KEY_LEFT_SHIFT > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_LEFT_SHIFT]) }
|
|
if kr.Modifiers & HID_MOD_KEY_LEFT_ALT > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_LEFT_ALT]) }
|
|
if kr.Modifiers & HID_MOD_KEY_LEFT_GUI > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_LEFT_GUI]) }
|
|
if kr.Modifiers & HID_MOD_KEY_RIGHT_CONTROL > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_RIGHT_CONTROL]) }
|
|
if kr.Modifiers & HID_MOD_KEY_RIGHT_SHIFT > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_RIGHT_SHIFT]) }
|
|
if kr.Modifiers & HID_MOD_KEY_RIGHT_ALT > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_RIGHT_ALT]) }
|
|
if kr.Modifiers & HID_MOD_KEY_RIGHT_GUI > 0 { modifiers = append(modifiers, UsbModKeyToString[HID_MOD_KEY_RIGHT_GUI]) }
|
|
|
|
for _,key := range kr.Keys {
|
|
if key == 0 {break} //abort on first 0x00 key code
|
|
if keyStr, ok := UsbKeyToString[uint8(key)]; ok {
|
|
keys = append(keys, keyStr)
|
|
//log.Println(keyStr)
|
|
} else {
|
|
log.Printf("Warning: No string representation for USB key with value '%d', key ignored during JSON marshaling.\n", key)
|
|
}
|
|
}
|
|
|
|
result := struct{
|
|
Modifiers []string
|
|
Keys []string
|
|
}{
|
|
Keys:keys,
|
|
Modifiers:modifiers,
|
|
}
|
|
return json.Marshal(result)
|
|
}
|
|
|
|
|
|
|
|
func (rep KeyboardOutReport) Serialize() (out []byte) {
|
|
out = []byte {
|
|
rep.Modifiers,
|
|
byte(0),
|
|
rep.Keys[0],
|
|
rep.Keys[1],
|
|
rep.Keys[2],
|
|
rep.Keys[3],
|
|
rep.Keys[4],
|
|
rep.Keys[5],
|
|
}
|
|
return
|
|
}
|
|
|
|
func (rep KeyboardOutReport) Deserialize(data []byte) (err error) {
|
|
if len(data) != 8 {
|
|
err = errors.New("Wrong data length, keyboard out report has to be 8 bytes in length")
|
|
}
|
|
rep = KeyboardOutReport{
|
|
Modifiers: data[0],
|
|
//data[1] should be empty, we ignore it
|
|
Keys: [6]byte{
|
|
data[2],
|
|
data[3],
|
|
data[4],
|
|
data[5],
|
|
data[6],
|
|
data[7],
|
|
},
|
|
}
|
|
return
|
|
}
|
|
|
|
func (rep KeyboardOutReport) WriteTo(filePath string) (err error) {
|
|
return ioutil.WriteFile(filePath, rep.Serialize(), os.ModePerm) //Serialize Report and write to specified file
|
|
}
|
|
|
|
// Accepts *os.File, in contrast to WriteTo() this allows keeping the file open
|
|
func (rep KeyboardOutReport) WriteToFile(file *os.File) (err error) {
|
|
data := rep.Serialize()
|
|
n, err := file.Write(data)
|
|
if err == nil && n < len(data) {
|
|
err = io.ErrShortWrite
|
|
}
|
|
return err
|
|
}
|
|
|
|
|
|
|
|
func NewKeyboardOutReport(modifiers byte, keys ...byte) (res KeyboardOutReport) {
|
|
res = KeyboardOutReport{
|
|
Keys: [6]byte {0, 0, 0, 0, 0, 0,},
|
|
}
|
|
res.Modifiers = modifiers
|
|
for i, key := range keys {
|
|
if i < 6 {
|
|
res.Keys[i] = key
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|