From e58aec83e83d86d5b76ec43af4eb809a50aa4952 Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 14 Nov 2023 09:17:53 +0900 Subject: [PATCH 1/2] WIP: BLEHID --- ble/ble.go | 194 +++++++++++++ ble/blesplit.go | 89 ++++++ ble/keyboard.go | 495 +++++++++++++++++++++++++++++++++ ble/keycode.go | 534 ++++++++++++++++++++++++++++++++++++ buffer.go | 65 +++++ go.mod | 15 +- go.sum | 62 +++++ kbblesplit.go | 218 +++++++++++++++ keyboard.go | 2 + targets/sgkey-ble/ble.go | 37 +++ targets/sgkey-ble/def.go | 9 + targets/sgkey-ble/main.go | 313 +++++++++++++++++++++ targets/sgkey-ble/vial.json | 12 + 13 files changed, 2043 insertions(+), 2 deletions(-) create mode 100644 ble/ble.go create mode 100644 ble/blesplit.go create mode 100644 ble/keyboard.go create mode 100644 ble/keycode.go create mode 100644 buffer.go create mode 100644 kbblesplit.go create mode 100644 targets/sgkey-ble/ble.go create mode 100644 targets/sgkey-ble/def.go create mode 100644 targets/sgkey-ble/main.go create mode 100644 targets/sgkey-ble/vial.json diff --git a/ble/ble.go b/ble/ble.go new file mode 100644 index 0000000..421e851 --- /dev/null +++ b/ble/ble.go @@ -0,0 +1,194 @@ +//go:build tinygo && nrf52840 + +package ble + +import ( + "machine/usb/descriptor" + k "machine/usb/hid/keyboard" + + "tinygo.org/x/bluetooth" +) + +var adapter = bluetooth.DefaultAdapter +var reportIn bluetooth.Characteristic +var rx bluetooth.DeviceCharacteristic + +var reportMap = descriptor.CDCHID.HID[2] + +func init() { + adapter.Enable() +} + +type bleKeyboard struct { + keyboard + Name string + report [9]byte + connected bool +} + +func NewKeyboard(name string) *bleKeyboard { + return &bleKeyboard{ + Name: name, + } +} + +func (k *bleKeyboard) Connect() error { + var err error + + bluetooth.SetSecParamsBonding() + bluetooth.SetSecCapabilities(bluetooth.NoneGapIOCapability) + + name := k.Name + if len(name) > 14 { + name = name[:14] + } + + adv := adapter.DefaultAdvertisement() + adv.Configure(bluetooth.AdvertisementOptions{ + LocalName: name, + ServiceUUIDs: []bluetooth.UUID{ + bluetooth.ServiceUUIDDeviceInformation, + bluetooth.ServiceUUIDBattery, + bluetooth.ServiceUUIDHumanInterfaceDevice, + }, + }) + + err = adv.Start() + if err != nil { + return err + } + + k.registerHID() + + return nil +} + +func (k *bleKeyboard) registerHID() error { + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDDeviceInformation, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDManufacturerNameString, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("Nice Keyboards"), + }, + { + UUID: bluetooth.CharacteristicUUIDModelNumberString, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("nice!nano"), + }, + { + UUID: bluetooth.CharacteristicUUIDPnPID, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte{0x02, 0x8a, 0x24, 0x66, 0x82, 0x34, 0x36}, + //Value: []byte{0x02, uint8(0x10C4 >> 8), uint8(0x10C4 & 0xff), uint8(0x0001 >> 8), uint8(0x0001 & 0xff)}, + }, + }, + }) + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDBattery, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDBatteryLevel, + Value: []byte{80}, + Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, + }, + }, + }) + // gacc + /* + device name r + apperance r + peripheral prefreed connection + + */ + + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDGenericAccess, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDDeviceName, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("tinygo-corne"), + }, + { + + UUID: bluetooth.New16BitUUID(0x2A01), + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte{uint8(0x03c4 >> 8), uint8(0x03c4 & 0xff)}, /// []byte(strconv.Itoa(961)), + }, + // { + // UUID: bluetooth.CharacteristicUUIDPeripheralPreferredConnectionParameters, + // Flags: bluetooth.CharacteristicReadPermission, + // Value: []byte{0x02}, + // }, + + // // // + }, + }) + + // hid + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDHumanInterfaceDevice, + /* + - hid information r + - report map r + - report nr + - client charecteristic configuration + - report reference + - report nr + - client charecteristic configuration + - report reference + - hid control point wnr + */ + Characteristics: []bluetooth.CharacteristicConfig{ + // { + // UUID: bluetooth.CharacteristicUUIDHIDInformation, + // Flags: bluetooth.CharacteristicReadPermission, + // Value: []byte{uint8(0x0111 >> 8), uint8(0x0111 & 0xff), uint8(0x0002 >> 8), uint8(0x0002 & 0xff)}, + // }, + { + //Handle: &reportmap, + UUID: bluetooth.CharacteristicUUIDReportMap, + Flags: bluetooth.CharacteristicReadPermission, + Value: reportMap, + }, + { + + Handle: &reportIn, + UUID: bluetooth.CharacteristicUUIDReport, + Value: k.report[:], + Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, + }, + { + // protocl mode + UUID: bluetooth.New16BitUUID(0x2A4E), + Flags: bluetooth.CharacteristicWriteWithoutResponsePermission | bluetooth.CharacteristicReadPermission, + // Value: []byte{uint8(1)}, + // WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { + // print("protocol mode") + // }, + }, + { + UUID: bluetooth.CharacteristicUUIDHIDControlPoint, + Flags: bluetooth.CharacteristicWriteWithoutResponsePermission, + // Value: []byte{0x02}, + }, + }, + }) + + return nil +} + +func (k *bleKeyboard) Up(c k.Keycode) error { + return k.keyboard.Up(Keycode(c)) +} + +func (k *bleKeyboard) Down(c k.Keycode) error { + return k.keyboard.Down(Keycode(c)) +} + +func sendBLEPacket(b []byte) error { + _, err := reportIn.Write(b) + return err +} diff --git a/ble/blesplit.go b/ble/blesplit.go new file mode 100644 index 0000000..d33d9bd --- /dev/null +++ b/ble/blesplit.go @@ -0,0 +1,89 @@ +//go:build tinygo && nrf52840 + +package ble + +import ( + k "machine/usb/hid/keyboard" + + "tinygo.org/x/bluetooth" +) + +var tx = &bluetooth.Characteristic{} + +type bleSplitKeyboard struct { + keyboard + Name string + report [9]byte + pressed []k.Keycode + connected bool +} + +func NewSplitKeyboard(name string) *bleSplitKeyboard { + return &bleSplitKeyboard{ + Name: name, + } +} + +func (k *bleSplitKeyboard) Connect() error { + var err error + + name := k.Name + if len(name) > 14 { + name = name[:14] + } + adapter.SetConnectHandler(func(device bluetooth.Address, connected bool) { + println("connected:", connected) + }) + + adv := adapter.DefaultAdvertisement() + err = adv.Configure(bluetooth.AdvertisementOptions{ + LocalName: name, + }) + err = adv.Start() + if err != nil { + return err + } + + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDNordicUART, + Characteristics: []bluetooth.CharacteristicConfig{ + { + Handle: tx, + UUID: bluetooth.CharacteristicUUIDUARTTX, + Value: k.report[:3], + Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, + }, + }, + }) + return nil +} + +func (k *bleSplitKeyboard) Up(c k.Keycode) error { + for i, p := range k.pressed { + if c == p { + k.pressed = append(k.pressed[:i], k.pressed[i+1:]...) + row := byte(c >> 8) + col := byte(c) + _, err := tx.Write([]byte{0x55, byte(row), byte(col)}) + return err + } + } + return nil +} + +func (k *bleSplitKeyboard) Down(c k.Keycode) error { + found := false + for _, p := range k.pressed { + if c == p { + found = true + } + } + if !found { + k.pressed = append(k.pressed, c) + row := byte(c >> 8) + col := byte(c) + _, err := tx.Write([]byte{0xAA, byte(row), byte(col)}) + return err + } + return nil +} diff --git a/ble/keyboard.go b/ble/keyboard.go new file mode 100644 index 0000000..e2d8422 --- /dev/null +++ b/ble/keyboard.go @@ -0,0 +1,495 @@ +//go:build tinygo + +package ble + +import ( + "errors" + "machine/usb/hid" +) + +// from usb-hid-keyboard.go +var ( + ErrInvalidCodepoint = errors.New("invalid Unicode codepoint") + ErrInvalidKeycode = errors.New("invalid keyboard keycode") + ErrInvalidUTF8 = errors.New("invalid UTF-8 encoding") + ErrKeypressMaximum = errors.New("maximum keypresses exceeded") +) + +var Keyboard *keyboard + +// Keyboard represents a USB HID keyboard device with support for international +// layouts and various control, system, multimedia, and consumer keycodes. +// +// Keyboard implements the io.Writer interface that translates UTF-8 encoded +// byte strings into sequences of keypress events. +type keyboard struct { + // led holds the current state of all keyboard LEDs: + // 1=NumLock 2=CapsLock 4=ScrollLock 8=Compose 16=Kana + led uint8 + + // mod holds the current state of all keyboard modifier keys: + // 1=LeftCtrl 2=LeftShift 4=LeftAlt 8=LeftGUI + // 16=RightCtrl 32=RightShift 64=RightAlt 128=RightGUI + mod uint8 + + // key holds a list of all keyboard keys currently pressed. + key [hidKeyboardKeyCount]uint8 + con [hidKeyboardConCount]uint16 + sys [hidKeyboardSysCount]uint8 + + // decode holds the current state of the UTF-8 decoder. + decode decodeState + + // wideChar holds high bits for the UTF-8 decoder. + wideChar uint16 + + buf *hid.RingBuffer + waitTxc bool +} + +// decodeState represents a state in the UTF-8 decode state machine. +type decodeState uint8 + +// Constant enumerated values of type decodeState. +const ( + decodeReset decodeState = iota + decodeByte1 + decodeByte2 + decodeByte3 +) + +// New returns the USB hid-keyboard port. +// Deprecated, better to just use Port() +func New() *keyboard { + return Port() +} + +// Port returns the USB hid-keyboard port. +func Port() *keyboard { + return Keyboard +} + +func newKeyboard() *keyboard { + return &keyboard{ + buf: hid.NewRingBuffer(), + } +} + +func (kb *keyboard) tx(b []byte) { + sendBLEPacket(b) +} + +func (kb *keyboard) NumLockLed() bool { + return kb.led&1 != 0 +} + +func (kb *keyboard) CapsLockLed() bool { + return kb.led&2 != 0 +} + +func (kb *keyboard) ScrollLockLed() bool { + return kb.led&4 != 0 +} + +func (kb *keyboard) ready() bool { + return true +} + +// Write transmits press-and-release key sequences for each Keycode translated +// from the given UTF-8 byte string. Write implements the io.Writer interface +// and conforms to all documented conventions for arguments and return values. +func (kb *keyboard) Write(b []byte) (n int, err error) { + for _, c := range b { + if err = kb.WriteByte(c); nil != err { + break + } + n += 1 + } + return +} + +// WriteByte processes a single byte from a UTF-8 byte string. This method is a +// stateful method with respect to the receiver Keyboard, meaning that its exact +// behavior will depend on the current state of its UTF-8 decode state machine: +// +// 1. If the given byte is a valid ASCII encoding (0-127), then a keypress +// sequence is immediately transmitted for the respective Keycode. +// 2. If the given byte represents the final byte in a multi-byte codepoint, +// then a keypress sequence is immediately transmitted by translating the +// multi-byte codepoint to its respective Keycode. +// 3. If the given byte appears to represent high bits for a multi-byte +// codepoint, then the bits are copied to the receiver's internal state +// machine buffer for use by a subsequent call to WriteByte() (or Write()) +// that completes the codepoint. +// 4. If the given byte is out of range, or contains illegal bits for the +// current state of the UTF-8 decoder, then the UTF-8 decode state machine +// is reset to its initial state. +// +// In cases 3 and 4, a keypress sequence is not generated and no data is +// transmitted. In case 3, additional bytes must be received via WriteByte() +// (or Write()) to complete or discard the current codepoint. +func (kb *keyboard) WriteByte(b byte) error { + switch { + case b < 0x80: + // 1-byte encoding (0x00-0x7F) + kb.decode = decodeByte1 + return kb.write(uint16(b)) + + case b < 0xC0: + // 2nd, 3rd, or 4th byte (0x80-0xBF) + b = Keycode(b).key() + switch kb.decode { + case decodeByte2: + kb.decode = decodeByte1 + return kb.write(kb.wideChar | uint16(b)) + case decodeByte3: + kb.decode = decodeByte2 + kb.wideChar |= uint16(b) << 6 + } + + case b < 0xE0: + // 2-byte encoding (0xC2-0xDF), or illegal byte 2 (0xC0-0xC1) + kb.decode = decodeByte2 + kb.wideChar = uint16(b&0x1F) << 6 + + case b < 0xF0: + // 3-byte encoding (0xE0-0xEF) + kb.decode = decodeByte3 + kb.wideChar = uint16(b&0x0F) << 12 + + default: + // 4-byte encoding unsupported (0xF0-0xF4), or illegal byte 4 (0xF5-0xFF) + kb.decode = decodeReset + return ErrInvalidUTF8 + } + return nil +} + +func (kb *keyboard) write(p uint16) error { + c := keycode(p) + if 0 == c { + return ErrInvalidCodepoint + } + if d := deadkey(c); 0 != d { + if err := kb.writeKeycode(d); nil != err { + return err + } + } + return kb.writeKeycode(c) +} + +func (kb *keyboard) writeKeycode(c Keycode) error { + var b [9]byte + b[0] = 0x02 + b[1] = c.mod() + b[2] = 0 + b[3] = c.key() + b[4] = 0 + b[5] = 0 + b[6] = 0 + b[7] = 0 + b[8] = 0 + if !kb.sendKey(false, b[:]) { + return hid.ErrHIDReportTransfer + } + + b[1] = 0 + b[3] = 0 + if !kb.sendKey(false, b[:]) { + return hid.ErrHIDReportTransfer + } + return nil +} + +// Press transmits a press-and-release sequence for the given Keycode, which +// simulates a discrete keypress event. +// +// The following values of Keycode are supported: +// +// 0x0020 - 0x007F ASCII (U+0020 to U+007F) [USES LAYOUT] +// 0x0080 - 0xC1FF Unicode (U+0080 to U+C1FF) [USES LAYOUT] +// 0xC200 - 0xDFFF UTF-8 packed (U+0080 to U+07FF) [USES LAYOUT] +// 0xE000 - 0xE0FF Modifier key (bitmap, 8 keys, Shift/Ctrl/Alt/GUI) +// 0xE200 - 0xE2FF System key (HID usage code, page 1) +// 0xE400 - 0xE7FF Media/Consumer key (HID usage code, page 12) +// 0xF000 - 0xFFFF Normal key (HID usage code, page 7) +func (kb *keyboard) Press(c Keycode) error { + if err := kb.Down(c); nil != err { + return err + } + return kb.Up(c) +} + +func (kb *keyboard) sendKey(consumer bool, b []byte) bool { + kb.tx(b) + return true +} + +func (kb *keyboard) keyboardSendKeys(consumer bool) bool { + var b [9]byte + + if !consumer { + b[0] = 0x02 // REPORT_ID + b[1] = kb.mod + b[2] = 0x02 + b[3] = kb.key[0] + b[4] = kb.key[1] + b[5] = kb.key[2] + b[6] = kb.key[3] + b[7] = kb.key[4] + b[8] = kb.key[5] + return kb.sendKey(consumer, b[:]) + + } else { + b[0] = 0x03 // REPORT_ID + b[1] = uint8(kb.con[0]) + b[2] = uint8((kb.con[0] & 0x0300) >> 8) + + return kb.sendKey(consumer, b[:3]) + } +} + +// Down transmits a key-down event for the given Keycode. +// +// The host will interpret the key as being held down continuously until a +// corresponding key-up event is transmitted, e.g., via method Up(). +// +// See godoc comment on method Press() for details on what input is accepted and +// how it is interpreted. +func (kb *keyboard) Down(c Keycode) error { + var res uint8 + msb := c >> 8 + if msb >= 0xC2 { + if msb < 0xE0 { + c = ((msb & 0x1F) << 6) | Keycode(c.key()) + } else { + switch msb { + case 0xF0: + return kb.down(uint8(c), 0) + + case 0xE0: + return kb.down(0, uint8(c)) + + case 0xE2: + return kb.downSys(uint8(c)) + + default: + if 0xE4 <= msb && msb <= 0xE7 { + return kb.downCon(uint16(c & 0x03FF)) + } + return ErrInvalidKeycode + } + } + } + c = keycode(uint16(c)) + if 0 == c { + return ErrInvalidCodepoint + } + if d := deadkey(c); 0 != d { + res = kb.mod + if 0 != res { + kb.mod = 0 + kb.keyboardSendKeys(false) + } + kb.down(d.key(), d.mod()) + kb.up(d.key(), d.mod()) + } + return kb.down(c.key(), c.mod()|res) +} + +func (kb *keyboard) down(key uint8, mod uint8) error { + send := false + if 0 != mod { + if kb.mod&mod != mod { + kb.mod |= mod + send = true + } + } + if 0 != key { + for _, k := range kb.key { + if k == key { + goto end + } + } + for i, k := range kb.key { + if 0 == k { + kb.key[i] = key + send = true + goto end + } + } + return ErrKeypressMaximum + } +end: + if send { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) downCon(key uint16) error { + if 0 == key { + return ErrInvalidKeycode + } + for _, k := range kb.con { + if key == k { + return nil // already pressed + } + } + for i, k := range kb.con { + if 0 == k { + kb.con[i] = key + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return ErrKeypressMaximum +} + +func (kb *keyboard) downSys(key uint8) error { + if 0 == key { + return ErrInvalidKeycode + } + for _, k := range kb.sys { + if key == k { + return nil // already pressed + } + } + for i, k := range kb.sys { + if 0 == k { + kb.sys[i] = key + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return ErrKeypressMaximum +} + +// Up transmits a key-up event for the given Keycode. +// +// See godoc comment on method Press() for details on what input is accepted and +// how it is interpreted. +func (kb *keyboard) Up(c Keycode) error { + msb := c >> 8 + if msb >= 0xC2 { + if msb < 0xE0 { + c = ((msb & 0x1F) << 6) | Keycode(c.key()) + } else { + switch msb { + case 0xF0: + return kb.up(uint8(c), 0) + + case 0xE0: + return kb.up(0, uint8(c)) + + case 0xE2: + return kb.upSys(uint8(c)) + + default: + if 0xE4 <= msb && msb <= 0xE7 { + return kb.upCon(uint16(c & 0x03FF)) + } + return ErrInvalidKeycode + } + } + } + c = keycode(uint16(c)) + if 0 == c { + return ErrInvalidCodepoint + } + return kb.up(c.key(), c.mod()) +} + +// Release transmits a key-up event for all keyboard keys currently pressed as +// if the user removed his/her hands from the keyboard entirely. +func (kb *keyboard) Release() error { + + bits := uint16(kb.mod) + kb.mod = 0 + for i, k := range kb.key { + bits |= uint16(k) + kb.key[i] = 0 + } + if 0 != bits { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + bits = 0 + for i, k := range kb.con { + bits |= k + kb.con[i] = 0 + } + for i, k := range kb.sys { + bits |= uint16(k) + kb.sys[i] = 0 + } + if 0 != bits { + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) up(key uint8, mod uint8) error { + send := false + if 0 != mod { + if kb.mod&mod != 0 { + kb.mod &^= mod + send = true + } + } + if 0 != key { + for i, k := range kb.key { + if key == k { + kb.key[i] = 0 + send = true + } + } + } + if send { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) upCon(key uint16) error { + if 0 == key { + return ErrInvalidKeycode + } + for i, k := range kb.con { + if key == k { + kb.con[i] = 0 + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return nil +} + +func (kb *keyboard) upSys(key uint8) error { + if 0 == key { + return ErrInvalidKeycode + } + for i, k := range kb.sys { + if key == k { + kb.sys[i] = 0 + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return nil +} diff --git a/ble/keycode.go b/ble/keycode.go new file mode 100644 index 0000000..7725bcd --- /dev/null +++ b/ble/keycode.go @@ -0,0 +1,534 @@ +package ble + +// Keycode is a package-defined bitmap used to encode the value of a given key. +type Keycode uint16 + +// keycode returns the given Unicode codepoint translated to a Keycode sequence. +// Unicode codepoints greater than U+FFFF are unsupported. +// +//go:inline +func keycode(p uint16) Keycode { + if p < 0x80 { + return ascii[p] + } else if p >= 0xA0 && p < 0x0100 { + return iso88591[p-0xA0] + } else if uint16(UNICODE20AC) == p { + return UNICODE20AC.mask() + } + return 0 +} + +//go:inline +func deadkey(c Keycode) Keycode { + switch c & deadkeysMask { + case acuteAccentBits: + return deadkeyAcuteAccent + case circumflexBits: + return deadkeyCircumflex + case diaeresisBits: + return deadkeyDiaeresis + case graveAccentBits: + return deadkeyGraveAccent + case tildeBits: + return deadkeyTilde + } + return 0 +} + +//go:inline +func (c Keycode) mask() Keycode { return c & keycodeMask } + +//go:inline +func (c Keycode) key() uint8 { return uint8(c & keyMask) } + +//go:inline +func (c Keycode) mod() uint8 { + var m Keycode + if 0 != c&shiftMask { + m |= KeyModifierShift + } + if 0 != c&altgrMask { + m |= KeyModifierRightAlt + } + return uint8(m) +} + +//go:inline +func (c Keycode) Shift() Keycode { return c | KeyModifierShift } + +const ( + hidKeyboardKeyCount = 6 // Max number of simultaneous keypresses + hidKeyboardSysCount = 3 + hidKeyboardConCount = 4 +) + +// Keycodes common to all Keyboard layouts +const ( + KeyModifierCtrl Keycode = 0x01 | 0xE000 + KeyModifierShift Keycode = 0x02 | 0xE000 + KeyModifierAlt Keycode = 0x04 | 0xE000 + KeyModifierGUI Keycode = 0x08 | 0xE000 + KeyModifierLeftCtrl Keycode = 0x01 | 0xE000 + KeyModifierLeftShift Keycode = 0x02 | 0xE000 + KeyModifierLeftAlt Keycode = 0x04 | 0xE000 + KeyModifierLeftGUI Keycode = 0x08 | 0xE000 + KeyModifierRightCtrl Keycode = 0x10 | 0xE000 + KeyModifierRightShift Keycode = 0x20 | 0xE000 + KeyModifierRightAlt Keycode = 0x40 | 0xE000 + KeyModifierRightGUI Keycode = 0x80 | 0xE000 + + // KeySystemXXX is not supported now + KeySystemPowerDown Keycode = 0x81 | 0xE200 + KeySystemSleep Keycode = 0x82 | 0xE200 + KeySystemWakeUp Keycode = 0x83 | 0xE200 + + KeyMediaPlay Keycode = 0xB0 | 0xE400 + KeyMediaPause Keycode = 0xB1 | 0xE400 + KeyMediaRecord Keycode = 0xB2 | 0xE400 + KeyMediaFastForward Keycode = 0xB3 | 0xE400 + KeyMediaRewind Keycode = 0xB4 | 0xE400 + KeyMediaNextTrack Keycode = 0xB5 | 0xE400 + KeyMediaPrevTrack Keycode = 0xB6 | 0xE400 + KeyMediaStop Keycode = 0xB7 | 0xE400 + KeyMediaEject Keycode = 0xB8 | 0xE400 + KeyMediaRandomPlay Keycode = 0xB9 | 0xE400 + KeyMediaPlayPause Keycode = 0xCD | 0xE400 + KeyMediaPlaySkip Keycode = 0xCE | 0xE400 + KeyMediaMute Keycode = 0xE2 | 0xE400 + KeyMediaVolumeInc Keycode = 0xE9 | 0xE400 + KeyMediaVolumeDec Keycode = 0xEA | 0xE400 + + KeyA Keycode = 4 | 0xF000 + KeyB Keycode = 5 | 0xF000 + KeyC Keycode = 6 | 0xF000 + KeyD Keycode = 7 | 0xF000 + KeyE Keycode = 8 | 0xF000 + KeyF Keycode = 9 | 0xF000 + KeyG Keycode = 10 | 0xF000 + KeyH Keycode = 11 | 0xF000 + KeyI Keycode = 12 | 0xF000 + KeyJ Keycode = 13 | 0xF000 + KeyK Keycode = 14 | 0xF000 + KeyL Keycode = 15 | 0xF000 + KeyM Keycode = 16 | 0xF000 + KeyN Keycode = 17 | 0xF000 + KeyO Keycode = 18 | 0xF000 + KeyP Keycode = 19 | 0xF000 + KeyQ Keycode = 20 | 0xF000 + KeyR Keycode = 21 | 0xF000 + KeyS Keycode = 22 | 0xF000 + KeyT Keycode = 23 | 0xF000 + KeyU Keycode = 24 | 0xF000 + KeyV Keycode = 25 | 0xF000 + KeyW Keycode = 26 | 0xF000 + KeyX Keycode = 27 | 0xF000 + KeyY Keycode = 28 | 0xF000 + KeyZ Keycode = 29 | 0xF000 + Key1 Keycode = 30 | 0xF000 + Key2 Keycode = 31 | 0xF000 + Key3 Keycode = 32 | 0xF000 + Key4 Keycode = 33 | 0xF000 + Key5 Keycode = 34 | 0xF000 + Key6 Keycode = 35 | 0xF000 + Key7 Keycode = 36 | 0xF000 + Key8 Keycode = 37 | 0xF000 + Key9 Keycode = 38 | 0xF000 + Key0 Keycode = 39 | 0xF000 + KeyEnter Keycode = 40 | 0xF000 + KeyEsc Keycode = 41 | 0xF000 + KeyBackspace Keycode = 42 | 0xF000 + KeyTab Keycode = 43 | 0xF000 + KeySpace Keycode = 44 | 0xF000 + KeyMinus Keycode = 45 | 0xF000 + KeyEqual Keycode = 46 | 0xF000 + KeyLeftBrace Keycode = 47 | 0xF000 + KeyRightBrace Keycode = 48 | 0xF000 + KeyBackslash Keycode = 49 | 0xF000 + KeyNonUsNum Keycode = 50 | 0xF000 + KeySemicolon Keycode = 51 | 0xF000 + KeyQuote Keycode = 52 | 0xF000 + KeyTilde Keycode = 53 | 0xF000 + KeyComma Keycode = 54 | 0xF000 + KeyPeriod Keycode = 55 | 0xF000 + KeySlash Keycode = 56 | 0xF000 + KeyCapsLock Keycode = 57 | 0xF000 + KeyF1 Keycode = 58 | 0xF000 + KeyF2 Keycode = 59 | 0xF000 + KeyF3 Keycode = 60 | 0xF000 + KeyF4 Keycode = 61 | 0xF000 + KeyF5 Keycode = 62 | 0xF000 + KeyF6 Keycode = 63 | 0xF000 + KeyF7 Keycode = 64 | 0xF000 + KeyF8 Keycode = 65 | 0xF000 + KeyF9 Keycode = 66 | 0xF000 + KeyF10 Keycode = 67 | 0xF000 + KeyF11 Keycode = 68 | 0xF000 + KeyF12 Keycode = 69 | 0xF000 + KeyPrintscreen Keycode = 70 | 0xF000 + KeyScrollLock Keycode = 71 | 0xF000 + KeyPause Keycode = 72 | 0xF000 + KeyInsert Keycode = 73 | 0xF000 + KeyHome Keycode = 74 | 0xF000 + KeyPageUp Keycode = 75 | 0xF000 + KeyDelete Keycode = 76 | 0xF000 + KeyEnd Keycode = 77 | 0xF000 + KeyPageDown Keycode = 78 | 0xF000 + KeyRight Keycode = 79 | 0xF000 + KeyLeft Keycode = 80 | 0xF000 + KeyDown Keycode = 81 | 0xF000 + KeyUp Keycode = 82 | 0xF000 + KeyNumLock Keycode = 83 | 0xF000 + KeypadSlash Keycode = 84 | 0xF000 + KeypadAsterisk Keycode = 85 | 0xF000 + KeypadMinus Keycode = 86 | 0xF000 + KeypadPlus Keycode = 87 | 0xF000 + KeypadEnter Keycode = 88 | 0xF000 + Keypad1 Keycode = 89 | 0xF000 + Keypad2 Keycode = 90 | 0xF000 + Keypad3 Keycode = 91 | 0xF000 + Keypad4 Keycode = 92 | 0xF000 + Keypad5 Keycode = 93 | 0xF000 + Keypad6 Keycode = 94 | 0xF000 + Keypad7 Keycode = 95 | 0xF000 + Keypad8 Keycode = 96 | 0xF000 + Keypad9 Keycode = 97 | 0xF000 + Keypad0 Keycode = 98 | 0xF000 + KeypadPeriod Keycode = 99 | 0xF000 + KeyNonUSBS Keycode = 100 | 0xF000 + KeyMenu Keycode = 101 | 0xF000 + KeyF13 Keycode = 104 | 0xF000 + KeyF14 Keycode = 105 | 0xF000 + KeyF15 Keycode = 106 | 0xF000 + KeyF16 Keycode = 107 | 0xF000 + KeyF17 Keycode = 108 | 0xF000 + KeyF18 Keycode = 109 | 0xF000 + KeyF19 Keycode = 110 | 0xF000 + KeyF20 Keycode = 111 | 0xF000 + KeyF21 Keycode = 112 | 0xF000 + KeyF22 Keycode = 113 | 0xF000 + KeyF23 Keycode = 114 | 0xF000 + KeyF24 Keycode = 115 | 0xF000 + + KeyUpArrow Keycode = KeyUp + KeyDownArrow Keycode = KeyDown + KeyLeftArrow Keycode = KeyLeft + KeyRightArrow Keycode = KeyRight + KeyReturn Keycode = KeyEnter + KeyLeftCtrl Keycode = KeyModifierLeftCtrl + KeyLeftShift Keycode = KeyModifierLeftShift + KeyLeftAlt Keycode = KeyModifierLeftAlt + KeyLeftGUI Keycode = KeyModifierLeftGUI + KeyRightCtrl Keycode = KeyModifierRightCtrl + KeyRightShift Keycode = KeyModifierRightShift + KeyRightAlt Keycode = KeyModifierRightAlt + KeyRightGUI Keycode = KeyModifierRightGUI +) + +// Keycodes for layout US English (0x0904) +const ( + keycodeMask Keycode = 0x07FF + keyMask Keycode = 0x003F + + shiftMask Keycode = 0x0040 + altgrMask Keycode = 0x0080 + deadkeysMask Keycode = 0x0700 + circumflexBits Keycode = 0x0100 + acuteAccentBits Keycode = 0x0200 + graveAccentBits Keycode = 0x0300 + tildeBits Keycode = 0x0400 + diaeresisBits Keycode = 0x0500 + deadkeyCircumflex Keycode = Key6 | shiftMask + deadkeyAcuteAccent Keycode = KeyQuote + deadkeyGraveAccent Keycode = KeyTilde + deadkeyTilde Keycode = KeyTilde | shiftMask + deadkeyDiaeresis Keycode = KeyQuote | shiftMask + + ASCII00 Keycode = 0 // 0 NUL + ASCII01 Keycode = 0 // 1 SOH + ASCII02 Keycode = 0 // 2 STX + ASCII03 Keycode = 0 // 3 ETX + ASCII04 Keycode = 0 // 4 EOT + ASCII05 Keycode = 0 // 5 ENQ + ASCII06 Keycode = 0 // 6 ACK + ASCII07 Keycode = 0 // 7 BEL + ASCII08 Keycode = KeyBackspace // 8 BS + ASCII09 Keycode = KeyTab // 9 TAB + ASCII0A Keycode = KeyEnter // 10 LF + ASCII0B Keycode = 0 // 11 VT + ASCII0C Keycode = 0 // 12 FF + ASCII0D Keycode = 0 // 13 CR + ASCII0E Keycode = 0 // 14 SO + ASCII0F Keycode = 0 // 15 SI + ASCII10 Keycode = 0 // 16 DEL + ASCII11 Keycode = 0 // 17 DC1 + ASCII12 Keycode = 0 // 18 DC2 + ASCII13 Keycode = 0 // 19 DC3 + ASCII14 Keycode = 0 // 20 DC4 + ASCII15 Keycode = 0 // 21 NAK + ASCII16 Keycode = 0 // 22 SYN + ASCII17 Keycode = 0 // 23 ETB + ASCII18 Keycode = 0 // 24 CAN + ASCII19 Keycode = 0 // 25 EM + ASCII1A Keycode = 0 // 26 SUB + ASCII1B Keycode = 0 // 27 ESC + ASCII1C Keycode = 0 // 28 FS + ASCII1D Keycode = 0 // 29 GS + ASCII1E Keycode = 0 // 30 RS + ASCII1F Keycode = 0 // 31 US + + ASCII20 Keycode = KeySpace // 32 SPACE + ASCII21 Keycode = Key1 | shiftMask // 33 ! + ASCII22 Keycode = diaeresisBits | KeySpace // 34 " + ASCII23 Keycode = Key3 | shiftMask // 35 # + ASCII24 Keycode = Key4 | shiftMask // 36 $ + ASCII25 Keycode = Key5 | shiftMask // 37 % + ASCII26 Keycode = Key7 | shiftMask // 38 & + ASCII27 Keycode = acuteAccentBits | KeySpace // 39 ' + ASCII28 Keycode = Key9 | shiftMask // 40 ( + ASCII29 Keycode = Key0 | shiftMask // 41 ) + ASCII2A Keycode = Key8 | shiftMask // 42 * + ASCII2B Keycode = KeyEqual | shiftMask // 43 + + ASCII2C Keycode = KeyComma // 44 , + ASCII2D Keycode = KeyMinus // 45 - + ASCII2E Keycode = KeyPeriod // 46 . + ASCII2F Keycode = KeySlash // 47 / + ASCII30 Keycode = Key0 // 48 0 + ASCII31 Keycode = Key1 // 49 1 + ASCII32 Keycode = Key2 // 50 2 + ASCII33 Keycode = Key3 // 51 3 + ASCII34 Keycode = Key4 // 52 4 + ASCII35 Keycode = Key5 // 53 5 + ASCII36 Keycode = Key6 // 54 6 + ASCII37 Keycode = Key7 // 55 7 + ASCII38 Keycode = Key8 // 55 8 + ASCII39 Keycode = Key9 // 57 9 + ASCII3A Keycode = KeySemicolon | shiftMask // 58 : + ASCII3B Keycode = KeySemicolon // 59 ; + ASCII3C Keycode = KeyComma | shiftMask // 60 < + ASCII3D Keycode = KeyEqual // 61 = + ASCII3E Keycode = KeyPeriod | shiftMask // 62 > + ASCII3F Keycode = KeySlash | shiftMask // 63 ? + ASCII40 Keycode = Key2 | shiftMask // 64 @ + ASCII41 Keycode = KeyA | shiftMask // 65 A + ASCII42 Keycode = KeyB | shiftMask // 66 B + ASCII43 Keycode = KeyC | shiftMask // 67 C + ASCII44 Keycode = KeyD | shiftMask // 68 D + ASCII45 Keycode = KeyE | shiftMask // 69 E + ASCII46 Keycode = KeyF | shiftMask // 70 F + ASCII47 Keycode = KeyG | shiftMask // 71 G + ASCII48 Keycode = KeyH | shiftMask // 72 H + ASCII49 Keycode = KeyI | shiftMask // 73 I + ASCII4A Keycode = KeyJ | shiftMask // 74 J + ASCII4B Keycode = KeyK | shiftMask // 75 K + ASCII4C Keycode = KeyL | shiftMask // 76 L + ASCII4D Keycode = KeyM | shiftMask // 77 M + ASCII4E Keycode = KeyN | shiftMask // 78 N + ASCII4F Keycode = KeyO | shiftMask // 79 O + ASCII50 Keycode = KeyP | shiftMask // 80 P + ASCII51 Keycode = KeyQ | shiftMask // 81 Q + ASCII52 Keycode = KeyR | shiftMask // 82 R + ASCII53 Keycode = KeyS | shiftMask // 83 S + ASCII54 Keycode = KeyT | shiftMask // 84 T + ASCII55 Keycode = KeyU | shiftMask // 85 U + ASCII56 Keycode = KeyV | shiftMask // 86 V + ASCII57 Keycode = KeyW | shiftMask // 87 W + ASCII58 Keycode = KeyX | shiftMask // 88 X + ASCII59 Keycode = KeyY | shiftMask // 89 Y + ASCII5A Keycode = KeyZ | shiftMask // 90 Z + ASCII5B Keycode = KeyLeftBrace // 91 [ + ASCII5C Keycode = KeyBackslash // 92 \ + ASCII5D Keycode = KeyRightBrace // 93 ] + ASCII5E Keycode = circumflexBits | KeySpace // 94 ^ + ASCII5F Keycode = KeyMinus | shiftMask // 95 + ASCII60 Keycode = graveAccentBits | KeySpace // 96 ` + ASCII61 Keycode = KeyA // 97 a + ASCII62 Keycode = KeyB // 98 b + ASCII63 Keycode = KeyC // 99 c + ASCII64 Keycode = KeyD // 100 d + ASCII65 Keycode = KeyE // 101 e + ASCII66 Keycode = KeyF // 102 f + ASCII67 Keycode = KeyG // 103 g + ASCII68 Keycode = KeyH // 104 h + ASCII69 Keycode = KeyI // 105 i + ASCII6A Keycode = KeyJ // 106 j + ASCII6B Keycode = KeyK // 107 k + ASCII6C Keycode = KeyL // 108 l + ASCII6D Keycode = KeyM // 109 m + ASCII6E Keycode = KeyN // 110 n + ASCII6F Keycode = KeyO // 111 o + ASCII70 Keycode = KeyP // 112 p + ASCII71 Keycode = KeyQ // 113 q + ASCII72 Keycode = KeyR // 114 r + ASCII73 Keycode = KeyS // 115 s + ASCII74 Keycode = KeyT // 116 t + ASCII75 Keycode = KeyU // 117 u + ASCII76 Keycode = KeyV // 118 v + ASCII77 Keycode = KeyW // 119 w + ASCII78 Keycode = KeyX // 120 x + ASCII79 Keycode = KeyY // 121 y + ASCII7A Keycode = KeyZ // 122 z + ASCII7B Keycode = KeyLeftBrace | shiftMask // 123 { + ASCII7C Keycode = KeyBackslash | shiftMask // 124 | + ASCII7D Keycode = KeyRightBrace | shiftMask // 125 } + ASCII7E Keycode = tildeBits | KeySpace // 126 ~ + ASCII7F Keycode = KeyBackspace // 127 DEL + ISO88591A0 Keycode = KeySpace // 160 Nonbreakng Space + ISO88591A1 Keycode = Key1 | altgrMask // 161 ¡ Inverted Exclamation + ISO88591A2 Keycode = KeyC | altgrMask | shiftMask // 162 ¢ Cent SIGN + ISO88591A3 Keycode = Key4 | altgrMask | shiftMask // 163 £ Pound Sign + ISO88591A4 Keycode = Key4 | altgrMask // 164 ¤ Currency or Euro Sign + ISO88591A5 Keycode = KeyMinus | altgrMask // 165 ¥ YEN SIGN + ISO88591A6 Keycode = KeyBackslash | altgrMask | shiftMask // 166 ¦ BROKEN BAR ?? + ISO88591A7 Keycode = KeyS | altgrMask | shiftMask // 167 § SECTION SIGN + ISO88591A8 Keycode = KeyQuote | altgrMask | shiftMask // 168 ¨ DIAERESIS + ISO88591A9 Keycode = KeyC | altgrMask // 169 © COPYRIGHT SIGN + ISO88591AA Keycode = 0 // 170 ª FEMININE ORDINAL + ISO88591AB Keycode = KeyLeftBrace | altgrMask // 171 « LEFT DOUBLE ANGLE QUOTE + ISO88591AC Keycode = KeyBackslash | altgrMask // 172 ¬ NOT SIGN ?? + ISO88591AD Keycode = 0 // 173 SOFT HYPHEN + ISO88591AE Keycode = KeyR | altgrMask // 174 ® REGISTERED SIGN + ISO88591AF Keycode = 0 // 175 ¯ MACRON + ISO88591B0 Keycode = KeySemicolon | altgrMask | shiftMask // 176 ° DEGREE SIGN + ISO88591B1 Keycode = 0 // 177 ± PLUS-MINUS SIGN + ISO88591B2 Keycode = Key2 | altgrMask // 178 ² SUPERSCRIPT TWO + ISO88591B3 Keycode = Key3 | altgrMask // 179 ³ SUPERSCRIPT THREE + ISO88591B4 Keycode = KeyQuote | altgrMask // 180 ´ ACUTE ACCENT + ISO88591B5 Keycode = KeyM | altgrMask // 181 µ MICRO SIGN + ISO88591B6 Keycode = KeySemicolon | altgrMask // 182 ¶ PILCROW SIGN + ISO88591B7 Keycode = 0 // 183 · MIDDLE DOT + ISO88591B8 Keycode = 0 // 184 ¸ CEDILLA + ISO88591B9 Keycode = Key1 | altgrMask | shiftMask // 185 ¹ SUPERSCRIPT ONE + ISO88591BA Keycode = 0 // 186 º MASCULINE ORDINAL + ISO88591BB Keycode = KeyRightBrace | altgrMask // 187 » RIGHT DOUBLE ANGLE QUOTE + ISO88591BC Keycode = Key6 | altgrMask // 188 ¼ FRACTION ONE QUARTER + ISO88591BD Keycode = Key7 | altgrMask // 189 ½ FRACTION ONE HALF + ISO88591BE Keycode = Key8 | altgrMask // 190 ¾ FRACTION THREE QUARTERS + ISO88591BF Keycode = KeySlash | altgrMask // 191 ¿ INVERTED QUESTION MARK + ISO88591C0 Keycode = graveAccentBits | KeyA | shiftMask // 192 À A GRAVE + ISO88591C1 Keycode = KeyA | altgrMask | shiftMask // 193 Á A ACUTE + ISO88591C2 Keycode = circumflexBits | KeyA | shiftMask // 194 Â A CIRCUMFLEX + ISO88591C3 Keycode = tildeBits | KeyA | shiftMask // 195 Ã A TILDE + ISO88591C4 Keycode = KeyQ | altgrMask | shiftMask // 196 Ä A DIAERESIS + ISO88591C5 Keycode = KeyW | altgrMask | shiftMask // 197 Å A RING ABOVE + ISO88591C6 Keycode = KeyZ | altgrMask | shiftMask // 198 Æ AE + ISO88591C7 Keycode = KeyComma | altgrMask | shiftMask // 199 Ç C CEDILLA + ISO88591C8 Keycode = graveAccentBits | KeyE | shiftMask // 200 È E GRAVE + ISO88591C9 Keycode = KeyE | altgrMask | shiftMask // 201 É E ACUTE + ISO88591CA Keycode = circumflexBits | KeyE | shiftMask // 202 Ê E CIRCUMFLEX + ISO88591CB Keycode = diaeresisBits | KeyE | shiftMask // 203 Ë E DIAERESIS + ISO88591CC Keycode = graveAccentBits | KeyI | shiftMask // 204 Ì I GRAVE + ISO88591CD Keycode = KeyI | altgrMask | shiftMask // 205 Í I ACUTE + ISO88591CE Keycode = circumflexBits | KeyI | shiftMask // 206 Î I CIRCUMFLEX + ISO88591CF Keycode = diaeresisBits | KeyI | shiftMask // 207 Ï I DIAERESIS + ISO88591D0 Keycode = KeyD | altgrMask | shiftMask // 208 Ð ETH + ISO88591D1 Keycode = KeyN | altgrMask | shiftMask // 209 Ñ N TILDE + ISO88591D2 Keycode = graveAccentBits | KeyO | shiftMask // 210 Ò O GRAVE + ISO88591D3 Keycode = KeyO | altgrMask | shiftMask // 211 Ó O ACUTE + ISO88591D4 Keycode = circumflexBits | KeyO | shiftMask // 212 Ô O CIRCUMFLEX + ISO88591D5 Keycode = tildeBits | KeyO | shiftMask // 213 Õ O TILDE + ISO88591D6 Keycode = KeyP | altgrMask | shiftMask // 214 Ö O DIAERESIS + ISO88591D7 Keycode = KeyEqual | altgrMask // 215 × MULTIPLICATION + ISO88591D8 Keycode = KeyL | altgrMask | shiftMask // 216 Ø O STROKE + ISO88591D9 Keycode = graveAccentBits | KeyU | shiftMask // 217 Ù U GRAVE + ISO88591DA Keycode = KeyU | altgrMask | shiftMask // 218 Ú U ACUTE + ISO88591DB Keycode = circumflexBits | KeyU | shiftMask // 219 Û U CIRCUMFLEX + ISO88591DC Keycode = KeyY | altgrMask | shiftMask // 220 Ü U DIAERESIS + ISO88591DD Keycode = acuteAccentBits | KeyY | shiftMask // 221 Ý Y ACUTE + ISO88591DE Keycode = KeyT | altgrMask | shiftMask // 222 Þ THORN + ISO88591DF Keycode = KeyS | altgrMask // 223 ß SHARP S + ISO88591E0 Keycode = graveAccentBits | KeyA // 224 à a GRAVE + ISO88591E1 Keycode = KeyA | altgrMask // 225 á a ACUTE + ISO88591E2 Keycode = circumflexBits | KeyA // 226 â a CIRCUMFLEX + ISO88591E3 Keycode = tildeBits | KeyA // 227 ã a TILDE + ISO88591E4 Keycode = diaeresisBits | KeyA // 228 ä a DIAERESIS + ISO88591E5 Keycode = KeyW | altgrMask // 229 å a RING ABOVE + ISO88591E6 Keycode = KeyZ | altgrMask // 230 æ ae + ISO88591E7 Keycode = KeyComma | altgrMask // 231 ç c CEDILLA + ISO88591E8 Keycode = graveAccentBits | KeyE // 232 è e GRAVE + ISO88591E9 Keycode = acuteAccentBits | KeyE // 233 é e ACUTE + ISO88591EA Keycode = circumflexBits | KeyE // 234 ê e CIRCUMFLEX + ISO88591EB Keycode = diaeresisBits | KeyE // 235 ë e DIAERESIS + ISO88591EC Keycode = graveAccentBits | KeyI // 236 ì i GRAVE + ISO88591ED Keycode = KeyI | altgrMask // 237 í i ACUTE + ISO88591EE Keycode = circumflexBits | KeyI // 238 î i CIRCUMFLEX + ISO88591EF Keycode = diaeresisBits | KeyI // 239 ï i DIAERESIS + ISO88591F0 Keycode = KeyD | altgrMask // 240 ð ETH + ISO88591F1 Keycode = KeyN | altgrMask // 241 ñ n TILDE + ISO88591F2 Keycode = graveAccentBits | KeyO // 242 ò o GRAVE + ISO88591F3 Keycode = KeyO | altgrMask // 243 ó o ACUTE + ISO88591F4 Keycode = circumflexBits | KeyO // 244 ô o CIRCUMFLEX + ISO88591F5 Keycode = tildeBits | KeyO // 245 õ o TILDE + ISO88591F6 Keycode = KeyP | altgrMask // 246 ö o DIAERESIS + ISO88591F7 Keycode = KeyEqual | altgrMask | shiftMask // 247 ÷ DIVISION + ISO88591F8 Keycode = KeyL | altgrMask // 248 ø o STROKE + ISO88591F9 Keycode = graveAccentBits | KeyU // 249 ù u GRAVE + ISO88591FA Keycode = KeyU | altgrMask // 250 ú u ACUTE + ISO88591FB Keycode = circumflexBits | KeyU // 251 û u CIRCUMFLEX + ISO88591FC Keycode = KeyY | altgrMask // 252 ü u DIAERESIS + ISO88591FD Keycode = acuteAccentBits | KeyY // 253 ý y ACUTE + ISO88591FE Keycode = KeyT | altgrMask // 254 þ THORN + ISO88591FF Keycode = diaeresisBits | KeyY // 255 ÿ y DIAERESIS + UNICODE20AC Keycode = Key5 | altgrMask // 20AC € Euro Sign +) + +var ascii = [...]Keycode{ + ASCII00.mask(), ASCII01.mask(), ASCII02.mask(), ASCII03.mask(), + ASCII04.mask(), ASCII05.mask(), ASCII06.mask(), ASCII07.mask(), + ASCII08.mask(), ASCII09.mask(), ASCII0A.mask(), ASCII0B.mask(), + ASCII0C.mask(), ASCII0D.mask(), ASCII0E.mask(), ASCII0F.mask(), + ASCII10.mask(), ASCII11.mask(), ASCII12.mask(), ASCII13.mask(), + ASCII14.mask(), ASCII15.mask(), ASCII16.mask(), ASCII17.mask(), + ASCII18.mask(), ASCII19.mask(), ASCII1A.mask(), ASCII1B.mask(), + ASCII1C.mask(), ASCII1D.mask(), ASCII1E.mask(), ASCII1F.mask(), + ASCII20.mask(), ASCII21.mask(), ASCII22.mask(), ASCII23.mask(), + ASCII24.mask(), ASCII25.mask(), ASCII26.mask(), ASCII27.mask(), + ASCII28.mask(), ASCII29.mask(), ASCII2A.mask(), ASCII2B.mask(), + ASCII2C.mask(), ASCII2D.mask(), ASCII2E.mask(), ASCII2F.mask(), + ASCII30.mask(), ASCII31.mask(), ASCII32.mask(), ASCII33.mask(), + ASCII34.mask(), ASCII35.mask(), ASCII36.mask(), ASCII37.mask(), + ASCII38.mask(), ASCII39.mask(), ASCII3A.mask(), ASCII3B.mask(), + ASCII3C.mask(), ASCII3D.mask(), ASCII3E.mask(), ASCII3F.mask(), + ASCII40.mask(), ASCII41.mask(), ASCII42.mask(), ASCII43.mask(), + ASCII44.mask(), ASCII45.mask(), ASCII46.mask(), ASCII47.mask(), + ASCII48.mask(), ASCII49.mask(), ASCII4A.mask(), ASCII4B.mask(), + ASCII4C.mask(), ASCII4D.mask(), ASCII4E.mask(), ASCII4F.mask(), + ASCII50.mask(), ASCII51.mask(), ASCII52.mask(), ASCII53.mask(), + ASCII54.mask(), ASCII55.mask(), ASCII56.mask(), ASCII57.mask(), + ASCII58.mask(), ASCII59.mask(), ASCII5A.mask(), ASCII5B.mask(), + ASCII5C.mask(), ASCII5D.mask(), ASCII5E.mask(), ASCII5F.mask(), + ASCII60.mask(), ASCII61.mask(), ASCII62.mask(), ASCII63.mask(), + ASCII64.mask(), ASCII65.mask(), ASCII66.mask(), ASCII67.mask(), + ASCII68.mask(), ASCII69.mask(), ASCII6A.mask(), ASCII6B.mask(), + ASCII6C.mask(), ASCII6D.mask(), ASCII6E.mask(), ASCII6F.mask(), + ASCII70.mask(), ASCII71.mask(), ASCII72.mask(), ASCII73.mask(), + ASCII74.mask(), ASCII75.mask(), ASCII76.mask(), ASCII77.mask(), + ASCII78.mask(), ASCII79.mask(), ASCII7A.mask(), ASCII7B.mask(), + ASCII7C.mask(), ASCII7D.mask(), ASCII7E.mask(), ASCII7F.mask(), +} + +var iso88591 = [...]Keycode{ + ISO88591A0.mask(), ISO88591A1.mask(), ISO88591A2.mask(), ISO88591A3.mask(), + ISO88591A4.mask(), ISO88591A5.mask(), ISO88591A6.mask(), ISO88591A7.mask(), + ISO88591A8.mask(), ISO88591A9.mask(), ISO88591AA.mask(), ISO88591AB.mask(), + ISO88591AC.mask(), ISO88591AD.mask(), ISO88591AE.mask(), ISO88591AF.mask(), + ISO88591B0.mask(), ISO88591B1.mask(), ISO88591B2.mask(), ISO88591B3.mask(), + ISO88591B4.mask(), ISO88591B5.mask(), ISO88591B6.mask(), ISO88591B7.mask(), + ISO88591B8.mask(), ISO88591B9.mask(), ISO88591BA.mask(), ISO88591BB.mask(), + ISO88591BC.mask(), ISO88591BD.mask(), ISO88591BE.mask(), ISO88591BF.mask(), + ISO88591C0.mask(), ISO88591C1.mask(), ISO88591C2.mask(), ISO88591C3.mask(), + ISO88591C4.mask(), ISO88591C5.mask(), ISO88591C6.mask(), ISO88591C7.mask(), + ISO88591C8.mask(), ISO88591C9.mask(), ISO88591CA.mask(), ISO88591CB.mask(), + ISO88591CC.mask(), ISO88591CD.mask(), ISO88591CE.mask(), ISO88591CF.mask(), + ISO88591D0.mask(), ISO88591D1.mask(), ISO88591D2.mask(), ISO88591D3.mask(), + ISO88591D4.mask(), ISO88591D5.mask(), ISO88591D6.mask(), ISO88591D7.mask(), + ISO88591D8.mask(), ISO88591D9.mask(), ISO88591DA.mask(), ISO88591DB.mask(), + ISO88591DC.mask(), ISO88591DD.mask(), ISO88591DE.mask(), ISO88591DF.mask(), + ISO88591E0.mask(), ISO88591E1.mask(), ISO88591E2.mask(), ISO88591E3.mask(), + ISO88591E4.mask(), ISO88591E5.mask(), ISO88591E6.mask(), ISO88591E7.mask(), + ISO88591E8.mask(), ISO88591E9.mask(), ISO88591EA.mask(), ISO88591EB.mask(), + ISO88591EC.mask(), ISO88591ED.mask(), ISO88591EE.mask(), ISO88591EF.mask(), + ISO88591F0.mask(), ISO88591F1.mask(), ISO88591F2.mask(), ISO88591F3.mask(), + ISO88591F4.mask(), ISO88591F5.mask(), ISO88591F6.mask(), ISO88591F7.mask(), + ISO88591F8.mask(), ISO88591F9.mask(), ISO88591FA.mask(), ISO88591FB.mask(), + ISO88591FC.mask(), ISO88591FD.mask(), ISO88591FE.mask(), ISO88591FF.mask(), +} diff --git a/buffer.go b/buffer.go new file mode 100644 index 0000000..f45c20c --- /dev/null +++ b/buffer.go @@ -0,0 +1,65 @@ +//go:build tinygo + +package keyboard + +import ( + "runtime/volatile" +) + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type RingBuffer[T any] struct { + buffer []T + head volatile.Register8 + tail volatile.Register8 +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer[T any](buf []T) *RingBuffer[T] { + return &RingBuffer[T]{ + buffer: buf, + } +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer[T]) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer[T]) Put(val T) bool { + if rb.Used() != uint8(len(rb.buffer)) { + rb.head.Set(rb.head.Get() + 1) + rb.buffer[rb.head.Get()%uint8(len(rb.buffer))] = val + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer[T]) Get() (T, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.buffer[rb.tail.Get()%uint8(len(rb.buffer))], true + } + var ret T + return ret, false +} + +// Peek peeks a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer[T]) Peek() (T, bool) { + if rb.Used() != 0 { + return rb.buffer[(rb.tail.Get()+1)%uint8(len(rb.buffer))], true + } + var ret T + return ret, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer[T]) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/go.mod b/go.mod index 36ad461..7f05530 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,18 @@ require ( golang.org/x/exp v0.0.0-20231226003508-02704c960a9b tinygo.org/x/drivers v0.27.0 tinygo.org/x/tinydraw v0.3.0 - tinygo.org/x/tinyfont v0.3.0 + tinygo.org/x/tinyfont v0.4.0 ) -require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect +require ( + github.com/fatih/structs v1.1.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 // indirect + github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tinygo-org/cbgo v0.0.4 // indirect + golang.org/x/sys v0.11.0 // indirect + tinygo.org/x/bluetooth v0.8.0 // indirect +) diff --git a/go.sum b/go.sum index dace614..4fddd5d 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,85 @@ github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/itchio/lzma v0.0.0-20190703113020-d3e24e3e3d49 h1:+YrBMf3rkLjkT10zIHyVE4S7ma4hqvfjl6XgnzZwS6o= github.com/itchio/lzma v0.0.0-20190703113020-d3e24e3e3d49/go.mod h1:avNrevQMli1pYPsz1+HIHMvx95pk6O+6otbWqCZPeZI= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 h1:BuVRHr4HHJbk1DHyWkArJ7E8J/VA8ncCr/VLnQFazBo= +github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1 h1:L2YoWezgwpAZ2SEKjXk6yLnwOkM3u7mXq/mKuJeEpFM= +github.com/saltosystems/winrt-go v0.0.0-20230921082907-2ab5b7d431e1/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= +github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU= +github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tinygo.org/x/bluetooth v0.8.0 h1:WmuRebsODcUUIlGhesyuNRIAEIUCErhKlrZ9K9aimdI= +tinygo.org/x/bluetooth v0.8.0/go.mod h1:cfsVc0/nGo3nzi6+CeQaXb+anNlmEnSABkKsxer8OAE= tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= @@ -30,5 +91,6 @@ tinygo.org/x/tinydraw v0.3.0/go.mod h1:Yz0vLSP2rHsIKpLYkEmLnE+2zyhhITu2LxiVtLRiW tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM= tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= +tinygo.org/x/tinyfont v0.4.0/go.mod h1:7nVj3j3geqBoPDzpFukAhF1C8AP9YocMsZy0HSAcGCA= tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= diff --git a/kbblesplit.go b/kbblesplit.go new file mode 100644 index 0000000..32d9f77 --- /dev/null +++ b/kbblesplit.go @@ -0,0 +1,218 @@ +//go:build tinygo && nrf52840 + +package keyboard + +import ( + "tinygo.org/x/bluetooth" +) + +type BleSplitKeyboard struct { + State []State + Keys [][]Keycode + callback Callback + + connectTo string + adapter *bluetooth.Adapter + ringbuf *RingBuffer[bleKeyEvent] + buf []byte + processed []int +} + +type bleKeyEvent struct { + index int + isHigh bool +} + +func (d *Device) AddBleSplitKeyboard(size int, adapter *bluetooth.Adapter, connectTo string, keys [][]Keycode, opt ...Option) *BleSplitKeyboard { + state := make([]State, size) + + keydef := make([][]Keycode, LayerCount) + for l := 0; l < len(keydef); l++ { + keydef[l] = make([]Keycode, len(state)) + } + for l := 0; l < len(keys); l++ { + for kc := 0; kc < len(keys[l]); kc++ { + keydef[l][kc] = keys[l][kc] + } + } + + var vb [32]bleKeyEvent + k := &BleSplitKeyboard{ + State: state, + Keys: keydef, + callback: func(layer, index int, state State) {}, + + adapter: adapter, + connectTo: connectTo, + ringbuf: NewRingBuffer(vb[:]), + buf: make([]byte, 3), + processed: make([]int, 0, 8), + } + + d.kb = append(d.kb, k) + return k +} + +func (d *BleSplitKeyboard) SetCallback(fn Callback) { + d.callback = fn +} + +func (d *BleSplitKeyboard) Callback(layer, index int, state State) { + if d.callback != nil { + d.callback(layer, index, state) + } +} + +func (d *BleSplitKeyboard) Get() []State { + for i := range d.State { + switch d.State[i] { + case NoneToPress: + d.State[i] = Press + case PressToRelease: + d.State[i] = None + } + } + + d.processed = d.processed[:0] + + cont := true + for cont { + b, ok := d.ringbuf.Peek() + if !ok { + return d.State + } + index := b.index + current := b.isHigh + + for _, idx := range d.processed { + if index == idx { + return d.State + } + } + d.processed = append(d.processed, index) + + d.ringbuf.Get() + + switch d.State[index] { + case None: + if current { + d.State[index] = NoneToPress + d.callback(0, index, Press) + } else { + } + case NoneToPress: + if current { + d.State[index] = Press + } else { + d.State[index] = PressToRelease + d.callback(0, index, PressToRelease) + } + case Press: + if current { + } else { + d.State[index] = PressToRelease + d.callback(0, index, PressToRelease) + } + case PressToRelease: + if current { + d.State[index] = NoneToPress + d.callback(0, index, Press) + } else { + d.State[index] = None + } + } + + } + + return d.State +} + +func (d *BleSplitKeyboard) Key(layer, index int) Keycode { + if layer >= LayerCount { + return 0 + } + if index >= len(d.Keys[layer]) { + return 0 + } + return d.Keys[layer][index] +} + +func (d *BleSplitKeyboard) SetKeycode(layer, index int, key Keycode) { + if layer >= LayerCount { + return + } + if index >= len(d.Keys[layer]) { + return + } + d.Keys[layer][index] = key +} + +func (d *BleSplitKeyboard) GetKeyCount() int { + return len(d.State) +} + +func (d *BleSplitKeyboard) Init() error { + d.adapter.SetConnectHandler(func(device bluetooth.Address, connected bool) { + println("connected:", connected) + }) + + return d.connectToPeriph() +} + +func (d *BleSplitKeyboard) connectToPeriph() error { + var foundDevice bluetooth.ScanResult + name := d.connectTo + if len(name) > 14 { + name = name[:14] + } + + err := d.adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) { + println(result.Address.String(), result.LocalName()) + if result.LocalName() != name { + return + } + foundDevice = result + + // Stop the scan. + err := d.adapter.StopScan() + if err != nil { + return + } + }) + if err != nil { + return err + } + println("connected") + device, err := d.adapter.Connect(foundDevice.Address, bluetooth.ConnectionParams{}) + if err != nil { + return err + } + services, err := device.DiscoverServices([]bluetooth.UUID{bluetooth.ServiceUUIDNordicUART}) + if err != nil { + return err + } + service := services[0] + chars, err := service.DiscoverCharacteristics([]bluetooth.UUID{bluetooth.CharacteristicUUIDUARTTX}) + if err != nil { + return err + } + + rx := chars[0] + rx.EnableNotifications( + func(buf []byte) { + //println("received buf len:", len(buf), ":", buf[0], buf[1], buf[2]) + current := false + if buf[0] == 0xAA { + current = true + } + index := (int(buf[1]) << 8) + int(buf[2]) + + d.ringbuf.Put(bleKeyEvent{ + index: index, + isHigh: current, + }) + }, + ) + + return nil +} diff --git a/keyboard.go b/keyboard.go index 62d48d2..562d321 100644 --- a/keyboard.go +++ b/keyboard.go @@ -8,6 +8,7 @@ import ( "machine" k "machine/usb/hid/keyboard" "machine/usb/hid/mouse" + "runtime" "time" "github.com/sago35/tinygo-keyboard/keycodes" @@ -367,6 +368,7 @@ func (d *Device) Loop(ctx context.Context) error { if err != nil { return err } + runtime.Gosched() } return nil diff --git a/targets/sgkey-ble/ble.go b/targets/sgkey-ble/ble.go new file mode 100644 index 0000000..2c06a66 --- /dev/null +++ b/targets/sgkey-ble/ble.go @@ -0,0 +1,37 @@ +//go:build tinygo && nrf52840 + +package main + +import ( + "tinygo.org/x/bluetooth" +) + +var adapter = bluetooth.DefaultAdapter +var reportIn bluetooth.Characteristic + +var reportMap = []byte{ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x06, // USAGE (Keyboard) + 0xa1, 0x01, // COLLECTION (Application) + 0x85, 0x01, // REPORT_ID (1) + 0x05, 0x07, // USAGE_PAGE (Keyboard) + 0x19, 0x01, // USAGE_MINIMUM + 0x29, 0x7f, // USAGE_MAXIMUM + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x01, // INPUT (Cnst,Ary,Abs) + 0x95, 0x06, // REPORT_COUNT (6) + 0x75, 0x08, // REPORT_SIZE (8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x65, // LOGICAL_MAXIMUM (101) + 0x05, 0x07, // USAGE_PAGE (Keyboard) + 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) + 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) + 0x81, 0x00, // INPUT (Data,Ary,Abs) + 0xc0, // END_COLLECTION +} diff --git a/targets/sgkey-ble/def.go b/targets/sgkey-ble/def.go new file mode 100644 index 0000000..3cd08db --- /dev/null +++ b/targets/sgkey-ble/def.go @@ -0,0 +1,9 @@ +package main + +import keyboard "github.com/sago35/tinygo-keyboard" + +func loadKeyboardDef() { + keyboard.KeyboardDef = []byte{ + 0x5D, 0x00, 0x00, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3D, 0x82, 0x80, 0x19, 0x1B, 0x9A, 0xE8, 0xB8, 0xA7, 0x6E, 0x26, 0x77, 0x5D, 0x26, 0xFB, 0x93, 0xC2, 0xE2, 0xE7, 0x46, 0x70, 0xE9, 0xC5, 0x5C, 0x25, 0x18, 0xAC, 0xC0, 0x02, 0xFD, 0x58, 0x39, 0x21, 0x5D, 0xCD, 0x75, 0xE8, 0x8B, 0x63, 0x00, 0xDB, 0x30, 0x34, 0xCC, 0x0F, 0x1B, 0xD8, 0x91, 0xE9, 0x98, 0x70, 0x7A, 0x6A, 0x9C, 0x4F, 0x71, 0xC3, 0x6C, 0xBE, 0xF6, 0x64, 0xAC, 0x6A, 0x5D, 0xE6, 0xAE, 0xEC, 0x22, 0x30, 0x1D, 0x96, 0x95, 0x6B, 0xBA, 0xC6, 0xA4, 0x9C, 0xC1, 0xD4, 0xCA, 0x47, 0xCE, 0x7E, 0x3C, 0x18, 0x38, 0xAA, 0x93, 0x88, 0x30, 0x3C, 0xB6, 0x9F, 0x66, 0x6D, 0x54, 0xE4, 0x7C, 0x28, 0x18, 0x6A, 0x5C, 0xA6, 0xD0, 0x1B, 0xDD, 0x25, 0xB3, 0x1B, 0x4F, 0x5D, 0xEC, 0x32, 0xE4, 0x35, 0xB8, 0x8A, 0xC2, 0x8C, 0x5D, 0xF1, 0x7F, 0x09, 0x77, 0xDF, 0x51, 0x63, 0x77, 0x5F, 0xE4, 0x1E, 0xAE, 0x91, 0x4A, 0x91, 0x71, 0xF8, 0xE5, 0xAF, 0x25, 0xFB, 0x9E, 0xFF, 0xAA, 0x78, 0x5F, 0x80, + } +} diff --git a/targets/sgkey-ble/main.go b/targets/sgkey-ble/main.go new file mode 100644 index 0000000..3209b25 --- /dev/null +++ b/targets/sgkey-ble/main.go @@ -0,0 +1,313 @@ +package main + +import ( + "context" + _ "embed" + "fmt" + "log" + "machine" + "machine/usb" + "runtime" + + keyboard "github.com/sago35/tinygo-keyboard" + "github.com/sago35/tinygo-keyboard/keycodes/jp" + "tinygo.org/x/bluetooth" +) + +func main() { + usb.Product = "sgkey-0.1.0" + + machine.InitSerial() + var err error + println("enabling adapter") + err = adapter.Enable() + if err != nil { + log.Fatal(err) + } + err = connect() + if err != nil { + println("failed to establish LESC connection:", err.Error()) + log.Fatal(err) + } + println("esteblished LESC connection") + registerHID() + println("registered HID") + + err = run() + if err != nil { + log.Fatal(err) + } +} + +type RCS struct { + row, col int + state keyboard.State +} + +type KeyEvent struct { + layer, indx int + state keyboard.State +} + +func run() error { + d := keyboard.New() + + colPins := []machine.Pin{ + machine.D8, + machine.D9, + machine.D10, + } + + rowPins := []machine.Pin{ + machine.D1, + machine.D2, + } + + keyChan := make(chan KeyEvent, 5) + // -------------------------------------------------- + + matrixKeyCodes := [][]keyboard.Keycode{ + { + jp.KeyT, jp.KeyI, jp.KeyN, + jp.KeyY, jp.KeyG, jp.KeyO, + }, + } + mk := d.AddMatrixKeyboard(colPins, rowPins, matrixKeyCodes) + mk.SetCallback(func(layer, index int, state keyboard.State) { + keyChan <- KeyEvent{layer: layer, indx: index, state: state} + }) + + go func() { + for { + select { + case x := <-keyChan: + fmt.Printf("key %#v\n", x) + var report []byte + if x.state == keyboard.PressToRelease { + report = []byte{0x01, + 0x00, + 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + } else { + // TODO: actually move special keys to mod bits in array + key := matrixKeyCodes[x.layer][x.indx] + report = []byte{0x01, + 0x00, + 0x00, + uint8(key), 0x00, 0x00, 0x00, 0x00, 0x00} + } + + _, err := reportIn.Write(report) + + if err != nil { + println("xxx failed to send key:", err.Error()) + } + } + runtime.Gosched() + } + }() + + // for Vial + loadKeyboardDef() + + d.Debug = true + return d.Loop(context.Background()) +} + +func connect() error { + + // peerPKey := make([]byte, 0, 64) + // privLesc, err := ecdh.P256().GenerateKey(rand.Reader) + // if err != nil { + // return err + // } + // lescChan := make(chan struct{}) + bluetooth.SetSecParamsBonding() + //bluetooth.SetSecParamsLesc() + bluetooth.SetSecCapabilities(bluetooth.NoneGapIOCapability) + // time.Sleep(4 * time.Second) + // println("getting own pub key") + // var key []byte + + // pk := privLesc.PublicKey().Bytes() + // pubKey := swapEndinan(pk[1:]) + //bluetooth.SetLesPublicKey(swapEndinan(privLesc.PublicKey().Bytes()[1:])) + // pubKey = nil + //println(" key is set") + + // println("register lesc callback") + // adapter.SetLescRequestHandler( + // func(pubKey []byte) { + // peerPKey = pubKey + // close(lescChan) + // }, + // ) + + println("def adv") + adv := adapter.DefaultAdvertisement() + println("adv config") + adv.Configure(bluetooth.AdvertisementOptions{ + LocalName: "tinygo-corne", + ServiceUUIDs: []bluetooth.UUID{ + bluetooth.ServiceUUIDDeviceInformation, + bluetooth.ServiceUUIDBattery, + bluetooth.ServiceUUIDHumanInterfaceDevice, + }, + }) + println("adv start") + return adv.Start() + + // select { + // case <-lescChan: + // peerPKey = append([]byte{0x04}, swapEndinan(peerPKey)...) + // p, err := ecdh.P256().NewPublicKey(peerPKey) + // if err != nil { + // println("failed on parsing pub:", err.Error()) + // return err + // } + // println("calculating ecdh") + // key, err = privLesc.ECDH(p) + // if err != nil { + // println("failed on curving:", err.Error()) + // return errfffffff + // } + // println("key len:", len(key)) + // return bluetooth.ReplyLesc(swapEndinan(key)) + // } + +} + +func swapEndinan(in []byte) []byte { + var reverse = make([]byte, len(in)) + for i, b := range in[:32] { + + reverse[31-i] = b + } + if len(in) > 32 { + for i, b := range in[32:] { + reverse[63-i] = b + } + } + + return reverse +} + +func registerHID() { + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDDeviceInformation, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDManufacturerNameString, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("Nice Keyboards"), + }, + { + UUID: bluetooth.CharacteristicUUIDModelNumberString, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("nice!nano"), + }, + { + UUID: bluetooth.CharacteristicUUIDPnPID, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte{0x02, 0x8a, 0x24, 0x66, 0x82, 0x34, 0x36}, + //Value: []byte{0x02, uint8(0x10C4 >> 8), uint8(0x10C4 & 0xff), uint8(0x0001 >> 8), uint8(0x0001 & 0xff)}, + }, + }, + }) + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDBattery, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDBatteryLevel, + Value: []byte{80}, + Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, + }, + }, + }) + // gacc + /* + device name r + apperance r + peripheral prefreed connection + + */ + + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDGenericAccess, + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.CharacteristicUUIDDeviceName, + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte("tinygo-corne"), + }, + { + + UUID: bluetooth.New16BitUUID(0x2A01), + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte{uint8(0x03c4 >> 8), uint8(0x03c4 & 0xff)}, /// []byte(strconv.Itoa(961)), + }, + // { + // UUID: bluetooth.CharacteristicUUIDPeripheralPreferredConnectionParameters, + // Flags: bluetooth.CharacteristicReadPermission, + // Value: []byte{0x02}, + // }, + + // // // + }, + }) + + //v := []byte{0x85, 0x02} // 0x85, 0x02 + reportValue := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + //var reportmap bluetooth.Characteristic + + // hid + adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.ServiceUUIDHumanInterfaceDevice, + /* + - hid information r + - report map r + - report nr + - client charecteristic configuration + - report reference + - report nr + - client charecteristic configuration + - report reference + - hid control point wnr + */ + Characteristics: []bluetooth.CharacteristicConfig{ + // { + // UUID: bluetooth.CharacteristicUUIDHIDInformation, + // Flags: bluetooth.CharacteristicReadPermission, + // Value: []byte{uint8(0x0111 >> 8), uint8(0x0111 & 0xff), uint8(0x0002 >> 8), uint8(0x0002 & 0xff)}, + // }, + { + //Handle: &reportmap, + UUID: bluetooth.CharacteristicUUIDReportMap, + Flags: bluetooth.CharacteristicReadPermission, + Value: reportMap, + }, + { + + Handle: &reportIn, + UUID: bluetooth.CharacteristicUUIDReport, + Value: reportValue[:], + Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, + }, + { + // protocl mode + UUID: bluetooth.New16BitUUID(0x2A4E), + Flags: bluetooth.CharacteristicWriteWithoutResponsePermission | bluetooth.CharacteristicReadPermission, + // Value: []byte{uint8(1)}, + // WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { + // print("protocol mode") + // }, + }, + { + UUID: bluetooth.CharacteristicUUIDHIDControlPoint, + Flags: bluetooth.CharacteristicWriteWithoutResponsePermission, + // Value: []byte{0x02}, + }, + }, + }) +} diff --git a/targets/sgkey-ble/vial.json b/targets/sgkey-ble/vial.json new file mode 100644 index 0000000..7ea9337 --- /dev/null +++ b/targets/sgkey-ble/vial.json @@ -0,0 +1,12 @@ +{ + "name": "tinygo-sgkey", + "vendorId": "0x2e8a", + "productId": "0x000a", + "matrix": {"rows": 1, "cols": 6}, + "layouts": { + "keymap": [ + ["0,0","0,1","0,2"], + ["0,3","0,4","0,5"] + ] + } +} From 125735fd660ca7d81564a221e449c984b2c4b360 Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 19 Mar 2024 11:25:23 +0900 Subject: [PATCH 2/2] support newer tinygo-org/bluetooth --- kbblesplit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kbblesplit.go b/kbblesplit.go index 32d9f77..e3ee034 100644 --- a/kbblesplit.go +++ b/kbblesplit.go @@ -152,7 +152,7 @@ func (d *BleSplitKeyboard) GetKeyCount() int { } func (d *BleSplitKeyboard) Init() error { - d.adapter.SetConnectHandler(func(device bluetooth.Address, connected bool) { + d.adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) { println("connected:", connected) })