This commit is contained in:
Mike Trudeau
2026-02-13 20:33:17 +00:00
parent 008f60446f
commit 2d168f4a3a
64 changed files with 11570 additions and 0 deletions

106
app/pkg/device/device.go Normal file
View File

@@ -0,0 +1,106 @@
package device
import (
"errors"
"log"
"github.com/microjelly/hid"
"microjelly.com/beeon/app/pkg/device/report3"
)
var (
_devices []Info
_logLevel LogLevel = LogNone
)
// Version - returns the version of this module
func Version() string {
return "v1.0.1"
}
// UsbSupported - returns true if usb is supported on this platform
func UsbSupported() bool {
return hid.Supported()
}
// SetLogLevel - sets the logging level
func SetLogLevel(level LogLevel) {
_logLevel = level
}
// Enumerate - gets connected devices
func Enumerate(vendorID uint16, productID uint16) ([]Info, error) {
var (
_devices []Info = nil
pSN string = ""
)
for _, dev := range hid.Enumerate(vendorID, productID) {
if dev.Serial != pSN {
_devices = append(_devices, Info{
Serial: dev.Serial,
Version: dev.Release,
Manufacturer: dev.Manufacturer,
Product: dev.Product,
})
pSN = dev.Serial
}
}
return _devices, nil
}
func open(vendorID uint16, productID uint16, serial string) (*hid.Device, error) {
devices := hid.Enumerate(vendorID, productID)
for _, dev := range devices {
if (serial != "" && dev.Serial == serial) || serial == "" {
_log(LogInfo, "opening device [%s]", dev.Serial)
return dev.Open()
}
}
return nil, errors.New("device not found")
}
// ReadReport3 - get device configuration
func ReadReport3(vendorID uint16, productID uint16, serial string) (*report3.Report, error) {
report := []byte{0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
dev, err := open(vendorID, productID, serial)
if err != nil {
return nil, err
}
defer dev.Close()
_, err = dev.GetFeatureReport(report)
if err != nil {
return nil, err
}
r := report3.New(report)
_log(LogDebug, "read %v > %v", report, r)
return r, nil
}
// WriteReport3 - get device configuration
func WriteReport3(vendorID uint16, productID uint16, serial string, report []byte) error {
dev, err := open(vendorID, productID, serial)
if err != nil {
return err
}
defer dev.Close()
_log(LogDebug, "write %v > %v", report, report3.New(report))
_, err = dev.SendFeatureReport(report)
if err != nil {
return err
}
return nil
}
func _log(level LogLevel, format string, v ...interface{}) {
if level >= _logLevel {
log.Printf(format, v...)
}
}

View File

@@ -0,0 +1,175 @@
// The contents of this file is free and unencumbered software released into
// the public domain. Refer to <http://unlicense.org/> for more information.
// Src: https://github.com/yakshaveinc/go-keycodes/blob/master/keycodes.go
package keycodes
import "fmt"
type KeyCode uint8
func (k KeyCode) String() string {
if k == KeyUnset {
return "unset"
}
return fmt.Sprintf("Key(%d)", k)
}
// note: similar keys have different key codes, like Enter and
// Keypad Enter
// cross-platform key codes, compatible with SDL 2 and USB HID speccy
// https://hg.libsdl.org/SDL/file/default/include/SDL_scancode.h
// http://www.usb.org/developers/hidpage/Hut1_12v2.pdf (page 53)
// key for codes 0 - 3 are not present on keyboards, they are:
// 0 - Reserved (no event)
// 1 - ErrorRollOver
// 2 - POSTFail
// 3 - ErrorUndefined
const (
KeyA KeyCode = 4 + iota
KeyB
KeyC
KeyD
KeyE
KeyF
KeyG
KeyH
KeyI
KeyJ
KeyK
KeyL
KeyM
KeyN
KeyO
KeyP
KeyQ
KeyR
KeyS
KeyT
KeyU
KeyV
KeyW
KeyX
KeyY
KeyZ
)
const (
Key1 KeyCode = 30 + iota
Key2
Key3
Key4
Key5
Key6
Key7
Key8
Key9
Key0
)
const (
// choice is to use Enter name instead of Return
// and the key code is different from Keypad Enter
KeyEnter KeyCode = 40 + iota
KeyEscape
KeyBackspace
KeyTab
KeySpace
// keypad minus has different key code
KeyMinus
KeyEquals
KeyLeftBracket
KeyRightBracket
KeyBackslash
)
// key code number 50 is skipped, because it is unclear
// where is the key, and what is its name and function
const (
// different name from SDL2 for brevity
KeyColon KeyCode = 51 + iota
KeyApostrophe
// KeyTilde is an alias
KeyGrave
KeyCommad
// KeyDot is an alias, keypad period is a different
KeyPeriod
Slash
CapsLock
)
const KeyTilde = KeyGrave
const KeyDot = KeyPeriod
const (
KeyF1 KeyCode = 58 + iota
KeyF2
KeyF3
KeyF4
KeyF5
KeyF6
KeyF7
KeyF8
KeyF9
KeyF10
KeyF11
KeyF12
)
const (
KeyPrintScreen KeyCode = 70 + iota
KeyScrollLock
KeyPause
KeyInsert
KeyHome
KeyPageUp
KeyDelete
KeyEnd
KeyPageDown
KeyRight
KeyLeft
KeyDown
KeyUp
)
const (
KeyNumLock KeyCode = 83 + iota
KeyKpDivide
KeyKpMultiply
KeyKpMinus
KeyKpPlus
KeyKpEnter
KeyKp1
KeyKp2
KeyKp3
KeyKp4
KeyKp5
KeyKp6
KeyKp7
KeyKp8
KeyKp9
KeyKp0
// KeyKpDot is an alias
KeyKpPeriod
)
const KeyKpDot = KeyKpPeriod
// key code 100 is skipped, because I can not find the key
// key code 101 is not present on Mac
// key codes 102-223 are not present on PC
const (
KeyLCtrl KeyCode = 224 + iota
KeyLShift
KeyLAlt
// KeyLWin is an alias
KeyLGUI
KeyRCtrl
KeyRShift
KeyRAlt
// KeyRWin is an alias
KeyRGUI
)
const KeyLWin = KeyLGUI
const KeyRWin = KeyRGUI
const KeyUnset = 255

View File

@@ -0,0 +1,139 @@
package report3
import (
"fmt"
"microjelly.com/beeon/app/pkg/device/keycodes"
)
type State uint8
const (
StateIdle = State(10)
StateActive = State(20)
StateUnset = State(255)
)
func (r State) String() string {
switch r {
case StateActive:
return "active"
case StateIdle:
return "idle"
case StateUnset:
return "unset"
default:
return "unknown"
}
}
type Mode uint8
const (
ModeMouse = Mode(10)
ModeKeyboard = Mode(20)
ModeUnset = Mode(255)
)
func (r Mode) String() string {
switch r {
case ModeMouse:
return "mouse"
case ModeKeyboard:
return "keyboard"
case ModeUnset:
return "unset"
default:
return "unknown"
}
}
type Rate uint8
const (
RateFast = Rate(1)
RateNormal = Rate(8)
RateSlow = Rate(16)
RateExtraSlow = Rate(32)
RateUnset = Rate(255)
)
func (r Rate) String() string {
switch r {
case RateFast:
return "fast"
case RateNormal:
return "normal"
case RateSlow:
return "slow"
case RateExtraSlow:
return "extra-slow"
case RateUnset:
return "unset"
default:
return fmt.Sprintf("custom (%d)", r)
}
}
type MouseMode uint8
const (
MouseModeCircle = MouseMode(10)
MouseModeRandom = MouseMode(20)
MouseModeUnset = MouseMode(255)
)
func (r MouseMode) String() string {
switch r {
case MouseModeCircle:
return "circle"
case MouseModeRandom:
return "random"
case MouseModeUnset:
return "unset"
default:
return "unknown"
}
}
type Report struct {
State State
Mode Mode
Rate Rate
KeyCode keycodes.KeyCode
MouseMode MouseMode
}
func (r Report) String() string {
switch r.Mode {
case ModeMouse:
return fmt.Sprintf("{Mode: %s, MouseMode: %s, Rate: %s, State: %s}", r.Mode, r.MouseMode, r.Rate, r.State)
case ModeKeyboard:
return fmt.Sprintf("{Mode: %s, KeyCode: %s, Rate: %s, State: %s}", r.Mode, r.KeyCode, r.Rate, r.State)
case ModeUnset:
return fmt.Sprintf("{Mode: %s, MouseMode: %s, KeyCode: %s, Rate: %s, State: %s}", r.Mode, r.MouseMode, r.KeyCode, r.Rate, r.State)
default:
return "{invalid}"
}
}
func New(b []byte) *Report {
r := new(Report)
r.State = State(b[1])
r.Mode = Mode(b[2])
r.Rate = Rate(b[3])
r.KeyCode = keycodes.KeyCode(b[4])
r.MouseMode = MouseMode(b[5])
return r
}
func (r Report) Bytes() []byte {
b := make([]byte, 6)
b[0] = 3
b[1] = uint8(r.State)
b[2] = uint8(r.Mode)
b[3] = uint8(r.Rate)
b[4] = uint8(r.KeyCode)
b[5] = uint8(r.MouseMode)
return b
}

21
app/pkg/device/types.go Normal file
View File

@@ -0,0 +1,21 @@
package device
// Info - short list of details
type Info struct {
Serial string
Manufacturer string
Version uint16
Product string
}
// LogLevel - something
type LogLevel uint
// LogNone - disabled logging
// LogInfo - basic logging
// LogDebug - even more
const (
LogDebug LogLevel = iota
LogInfo
LogNone
)