Merge pull request #30 from KMKfw/topic-consumer-device
HID: Support Consumer (media) keys
This commit is contained in:
commit
85cdf03572
@ -1,3 +1,109 @@
|
||||
class HIDReportTypes:
|
||||
KEYBOARD = 1
|
||||
MOUSE = 2
|
||||
CONSUMER = 3
|
||||
SYSCONTROL = 4
|
||||
|
||||
|
||||
HID_REPORT_STRUCTURE = bytes([
|
||||
# Regular keyboard
|
||||
0x05, 0x01, # Usage Page (Generic Desktop)
|
||||
0x09, 0x06, # Usage (Keyboard)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x85, HIDReportTypes.KEYBOARD, # Report ID (1)
|
||||
0x05, 0x07, # Usage Page (Keyboard)
|
||||
0x19, 224, # Usage Minimum (224)
|
||||
0x29, 231, # Usage Maximum (231)
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x25, 0x01, # Logical Maximum (1)
|
||||
0x75, 0x01, # Report Size (1)
|
||||
0x95, 0x08, # Report Count (8)
|
||||
0x81, 0x02, # Input (Data, Variable, Absolute)
|
||||
0x81, 0x01, # Input (Constant)
|
||||
0x19, 0x00, # Usage Minimum (0)
|
||||
0x29, 101, # Usage Maximum (101)
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x25, 101, # Logical Maximum (101)
|
||||
0x75, 0x08, # Report Size (8)
|
||||
0x95, 0x06, # Report Count (6)
|
||||
0x81, 0x00, # Input (Data, Array)
|
||||
0x05, 0x08, # Usage Page (LED)
|
||||
0x19, 0x01, # Usage Minimum (1)
|
||||
0x29, 0x05, # Usage Maximum (5)
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x25, 0x01, # Logical Maximum (1)
|
||||
0x75, 0x01, # Report Size (1)
|
||||
0x95, 0x05, # Report Count (5)
|
||||
0x91, 0x02, # Output (Data, Variable, Absolute)
|
||||
0x95, 0x03, # Report Count (3)
|
||||
0x91, 0x01, # Output (Constant)
|
||||
0xC0, # End Collection
|
||||
# Regular mouse
|
||||
0x05, 0x01, # Usage Page (Generic Desktop)
|
||||
0x09, 0x02, # Usage (Mouse)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x09, 0x01, # Usage (Pointer)
|
||||
0xA1, 0x00, # Collection (Physical)
|
||||
0x85, HIDReportTypes.MOUSE, # Report ID (n)
|
||||
0x05, 0x09, # Usage Page (Button)
|
||||
0x19, 0x01, # Usage Minimum (0x01)
|
||||
0x29, 0x05, # Usage Maximum (0x05)
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x25, 0x01, # Logical Maximum (1)
|
||||
0x95, 0x05, # Report Count (5)
|
||||
0x75, 0x01, # Report Size (1)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x01, # Report Count (1)
|
||||
0x75, 0x03, # Report Size (3)
|
||||
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x30, # Usage (X)
|
||||
0x09, 0x31, # Usage (Y)
|
||||
0x15, 0x81, # Logical Minimum (-127)
|
||||
0x25, 0x7F, # Logical Maximum (127)
|
||||
0x75, 0x08, # Report Size (8)
|
||||
0x95, 0x02, # Report Count (2)
|
||||
0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x38, # Usage (Wheel)
|
||||
0x15, 0x81, # Logical Minimum (-127)
|
||||
0x25, 0x7F, # Logical Maximum (127)
|
||||
0x75, 0x08, # Report Size (8)
|
||||
0x95, 0x01, # Report Count (1)
|
||||
0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, # End Collection
|
||||
0xC0, # End Collection
|
||||
# Consumer ("multimedia") keys
|
||||
0x05, 0x0C, # Usage Page (Consumer)
|
||||
0x09, 0x01, # Usage (Consumer Control)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x85, HIDReportTypes.CONSUMER, # Report ID (n)
|
||||
0x75, 0x10, # Report Size (16)
|
||||
0x95, 0x01, # Report Count (1)
|
||||
0x15, 0x01, # Logical Minimum (1)
|
||||
0x26, 0x8C, 0x02, # Logical Maximum (652)
|
||||
0x19, 0x01, # Usage Minimum (Consumer Control)
|
||||
0x2A, 0x8C, 0x02, # Usage Maximum (AC Send)
|
||||
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, # End Collection
|
||||
# Power controls
|
||||
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x80, # Usage (Sys Control)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x85, HIDReportTypes.SYSCONTROL, # Report ID (n)
|
||||
0x75, 0x02, # Report Size (2)
|
||||
0x95, 0x01, # Report Count (1)
|
||||
0x15, 0x01, # Logical Minimum (1)
|
||||
0x25, 0x03, # Logical Maximum (3)
|
||||
0x09, 0x82, # Usage (Sys Sleep)
|
||||
0x09, 0x81, # Usage (Sys Power Down)
|
||||
0x09, 0x83, # Usage (Sys Wake Up)
|
||||
0x81, 0x60, # Input (Data,Array,Abs,No Wrap,Linear,No Preferred State,Null State)
|
||||
0x75, 0x06, # Report Size (6)
|
||||
0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, # End Collection
|
||||
])
|
||||
|
||||
|
||||
class DiodeOrientation:
|
||||
'''
|
||||
Orientation of diodes on handwired boards. You can think of:
|
||||
|
@ -24,6 +24,11 @@ class ModifierKeycode:
|
||||
self.code = code
|
||||
|
||||
|
||||
class ConsumerKeycode:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
|
||||
class KeycodeCategory(type):
|
||||
@classmethod
|
||||
def to_dict(cls):
|
||||
@ -121,6 +126,9 @@ CODE_RGUI = CODE_RCMD = CODE_RWIN = 0x80
|
||||
class Keycodes(KeycodeCategory):
|
||||
'''
|
||||
A massive grouping of keycodes
|
||||
|
||||
Some of these are from http://www.freebsddiary.org/APC/usb_hid_usages.php,
|
||||
one of the most useful pages on the interwebs for HID stuff, apparently.
|
||||
'''
|
||||
class Modifiers(KeycodeCategory):
|
||||
KC_LCTRL = KC_LCTL = ModifierKeycode(CODE_LCTRL)
|
||||
@ -280,60 +288,64 @@ class Keycodes(KeycodeCategory):
|
||||
KC_LANG9 = Keycode(152)
|
||||
|
||||
class Misc(KeycodeCategory):
|
||||
KC_APPLICATION = KC_APP = Keycode(101)
|
||||
KC_POWER = Keycode(102)
|
||||
KC_EXECUTE = KC_EXEC = Keycode(116)
|
||||
KC_SYSTEM_POWER = KC_PWR = Keycode(165)
|
||||
KC_SYSTEM_SLEEP = KC_SLEP = Keycode(166)
|
||||
KC_SYSTEM_WAKE = KC_WAKE = Keycode(167)
|
||||
KC_HELP = Keycode(117)
|
||||
KC_MENU = Keycode(118)
|
||||
KC_SELECT = KC_SLCT = Keycode(119)
|
||||
KC_STOP = Keycode(120)
|
||||
KC_AGAIN = KC_AGIN = Keycode(121)
|
||||
KC_UNDO = Keycode(122)
|
||||
KC_CUT = Keycode(123)
|
||||
KC_COPY = Keycode(124)
|
||||
KC_PASTE = KC_PSTE = Keycode(125)
|
||||
KC_FIND = Keycode(126)
|
||||
KC_ALT_ERASE = KC_ERAS = Keycode(153)
|
||||
KC_SYSREQ = Keycode(154)
|
||||
KC_CANCEL = Keycode(155)
|
||||
KC_CLEAR = KC_CLR = Keycode(156)
|
||||
KC_PRIOR = Keycode(157)
|
||||
KC_RETURN = Keycode(158)
|
||||
KC_SEPERATOR = Keycode(159)
|
||||
KC_OUT = Keycode(160)
|
||||
KC_OPER = Keycode(161)
|
||||
KC_CLEAR_AGAIN = Keycode(162)
|
||||
KC_CRSEL = Keycode(163)
|
||||
KC_EXSEL = Keycode(164)
|
||||
KC_MAIL = Keycode(177)
|
||||
KC_CALCULATOR = KC_CALC = Keycode(178)
|
||||
KC_MY_COMPUTER = KC_MYCM = Keycode(179)
|
||||
KC_WWW_SEARCH = KC_WSCH = Keycode(180)
|
||||
KC_WWW_HOME = KC_WHOM = Keycode(181)
|
||||
KC_WWW_BACK = KC_WBAK = Keycode(182)
|
||||
KC_WWW_FORWARD = KC_WFWD = Keycode(183)
|
||||
KC_WWW_STOP = KC_WSTP = Keycode(184)
|
||||
KC_WWW_REFRESH = KC_WREF = Keycode(185)
|
||||
KC_WWW_FAVORITES = KC_WFAV = Keycode(186)
|
||||
KC_APPLICATION = KC_APP = ConsumerKeycode(101)
|
||||
KC_POWER = ConsumerKeycode(102)
|
||||
KC_EXECUTE = KC_EXEC = ConsumerKeycode(116)
|
||||
KC_SYSTEM_POWER = KC_PWR = ConsumerKeycode(165)
|
||||
KC_SYSTEM_SLEEP = KC_SLEP = ConsumerKeycode(166)
|
||||
KC_SYSTEM_WAKE = KC_WAKE = ConsumerKeycode(167)
|
||||
KC_HELP = ConsumerKeycode(117)
|
||||
KC_MENU = ConsumerKeycode(118)
|
||||
KC_SELECT = KC_SLCT = ConsumerKeycode(119)
|
||||
KC_STOP = ConsumerKeycode(120)
|
||||
KC_AGAIN = KC_AGIN = ConsumerKeycode(121)
|
||||
KC_UNDO = ConsumerKeycode(122)
|
||||
KC_CUT = ConsumerKeycode(123)
|
||||
KC_COPY = ConsumerKeycode(124)
|
||||
KC_PASTE = KC_PSTE = ConsumerKeycode(125)
|
||||
KC_FIND = ConsumerKeycode(126)
|
||||
KC_ALT_ERASE = KC_ERAS = ConsumerKeycode(153)
|
||||
KC_SYSREQ = ConsumerKeycode(154)
|
||||
KC_CANCEL = ConsumerKeycode(155)
|
||||
KC_CLEAR = KC_CLR = ConsumerKeycode(156)
|
||||
KC_PRIOR = ConsumerKeycode(157)
|
||||
KC_RETURN = ConsumerKeycode(158)
|
||||
KC_SEPERATOR = ConsumerKeycode(159)
|
||||
KC_OUT = ConsumerKeycode(160)
|
||||
KC_OPER = ConsumerKeycode(161)
|
||||
KC_CLEAR_AGAIN = ConsumerKeycode(162)
|
||||
KC_CRSEL = ConsumerKeycode(163)
|
||||
KC_EXSEL = ConsumerKeycode(164)
|
||||
KC_MAIL = ConsumerKeycode(177)
|
||||
KC_CALCULATOR = KC_CALC = ConsumerKeycode(178)
|
||||
KC_MY_COMPUTER = KC_MYCM = ConsumerKeycode(179)
|
||||
KC_WWW_SEARCH = KC_WSCH = ConsumerKeycode(180)
|
||||
KC_WWW_HOME = KC_WHOM = ConsumerKeycode(181)
|
||||
KC_WWW_BACK = KC_WBAK = ConsumerKeycode(182)
|
||||
KC_WWW_FORWARD = KC_WFWD = ConsumerKeycode(183)
|
||||
KC_WWW_STOP = KC_WSTP = ConsumerKeycode(184)
|
||||
KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185)
|
||||
KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186)
|
||||
|
||||
class Media(KeycodeCategory):
|
||||
KC__MUTE = Keycode(127)
|
||||
KC__VOLUP = Keycode(128)
|
||||
KC__VOLDOWN = Keycode(129)
|
||||
KC_AUDIO_MUTE = KC_MUTE = Keycode(168)
|
||||
KC_AUDIO_VOL_UP = KC_VOLU = Keycode(169)
|
||||
KC_AUDIO_VOL_DOWN = KC_VOLD = Keycode(170)
|
||||
KC_MEDIA_NEXT_TRACK = KC_MNXT = Keycode(171)
|
||||
KC_MEDIA_PREV_TRACK = KC_MPRV = Keycode(172)
|
||||
KC_MEDIA_STOP = KC_MSTP = Keycode(173)
|
||||
KC_MEDIA_PLAY_PAUSE = KC_MPLY = Keycode(174)
|
||||
KC_MEDIA_SELECT = KC_MSEL = Keycode(175)
|
||||
KC_MEDIA_EJECT = KC_EJCT = Keycode(176)
|
||||
KC_MEDIA_FAST_FORWARD = KC_MFFD = Keycode(187)
|
||||
KC_MEDIA_REWIND = KC_MRWD = Keycode(189)
|
||||
# I believe QMK used these double-underscore codes for MacOS
|
||||
# support or something. I have no idea, but modern MacOS supports
|
||||
# PC volume keys so I really don't care that these codes are the
|
||||
# same as below. If bugs arise, these codes may need to change.
|
||||
KC__MUTE = ConsumerKeycode(226)
|
||||
KC__VOLUP = ConsumerKeycode(233)
|
||||
KC__VOLDOWN = ConsumerKeycode(234)
|
||||
|
||||
KC_AUDIO_MUTE = KC_MUTE = ConsumerKeycode(226) # 0xE2
|
||||
KC_AUDIO_VOL_UP = KC_VOLU = ConsumerKeycode(233) # 0xE9
|
||||
KC_AUDIO_VOL_DOWN = KC_VOLD = ConsumerKeycode(234) # 0xEA
|
||||
KC_MEDIA_NEXT_TRACK = KC_MNXT = ConsumerKeycode(181) # 0xB5
|
||||
KC_MEDIA_PREV_TRACK = KC_MPRV = ConsumerKeycode(182) # 0xB6
|
||||
KC_MEDIA_STOP = KC_MSTP = ConsumerKeycode(183) # 0xB7
|
||||
KC_MEDIA_PLAY_PAUSE = KC_MPLY = ConsumerKeycode(205) # 0xCD (this may not be right)
|
||||
KC_MEDIA_EJECT = KC_EJCT = ConsumerKeycode(184) # 0xB8
|
||||
KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3
|
||||
KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4
|
||||
|
||||
class KMK(KeycodeCategory):
|
||||
KC_RESET = Keycode(1000)
|
||||
|
@ -1,3 +1,6 @@
|
||||
import pyb
|
||||
|
||||
pyb.usb_mode('VCP+HID', hid=pyb.hid_keyboard) # act as a serial device and a mouse
|
||||
from kmk.micropython.pyb_hid import generate_pyb_hid_descriptor
|
||||
|
||||
# act as a serial device and a KMK device
|
||||
pyb.usb_mode('VCP+HID', hid=generate_pyb_hid_descriptor())
|
||||
|
@ -1,34 +1,22 @@
|
||||
import logging
|
||||
import string
|
||||
|
||||
from pyb import USB_HID, delay
|
||||
from pyb import USB_HID, delay, hid_keyboard
|
||||
|
||||
from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes
|
||||
from kmk.common.event_defs import HID_REPORT_EVENT
|
||||
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes,
|
||||
ModifierKeycode, char_lookup)
|
||||
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
|
||||
Keycodes, ModifierKeycode, char_lookup)
|
||||
|
||||
|
||||
def generate_pyb_hid_descriptor():
|
||||
existing_keyboard = list(hid_keyboard)
|
||||
existing_keyboard[-1] = HID_REPORT_STRUCTURE
|
||||
return tuple(existing_keyboard)
|
||||
|
||||
|
||||
class HIDHelper:
|
||||
'''
|
||||
Wraps a HID reporting event. The structure of such events is (courtesy of
|
||||
http://wiki.micropython.org/USB-HID-Keyboard-mode-example-a-password-dongle):
|
||||
|
||||
>Byte 0 is for a modifier key, or combination thereof. It is used as a
|
||||
>bitmap, each bit mapped to a modifier:
|
||||
> bit 0: left control
|
||||
> bit 1: left shift
|
||||
> bit 2: left alt
|
||||
> bit 3: left GUI (Win/Apple/Meta key)
|
||||
> bit 4: right control
|
||||
> bit 5: right shift
|
||||
> bit 6: right alt
|
||||
> bit 7: right GUI
|
||||
>
|
||||
> Examples: 0x02 for Shift, 0x05 for Control+Alt
|
||||
>
|
||||
>Byte 1 is "reserved" (unused, actually)
|
||||
>Bytes 2-7 are for the actual key scancode(s) - up to 6 at a time ("chording").
|
||||
|
||||
Most methods here return `self` upon completion, allowing chaining:
|
||||
|
||||
```python
|
||||
@ -46,12 +34,52 @@ class HIDHelper:
|
||||
)
|
||||
|
||||
self._hid = USB_HID()
|
||||
self.clear_all()
|
||||
|
||||
# For some bizarre reason this can no longer be 8, it'll just fail to
|
||||
# send anything. This is almost certainly a bug in the report descriptor
|
||||
# sent over in the boot process. For now the sacrifice is that we only
|
||||
# support 5KRO until I figure this out, rather than the 6KRO HID defines.
|
||||
self._evt = bytearray(7)
|
||||
self.report_device = memoryview(self._evt)[0:1]
|
||||
|
||||
# Landmine alert for HIDReportTypes.KEYBOARD: byte index 1 of this view
|
||||
# is "reserved" and evidently (mostly?) unused. However, other modes (or
|
||||
# at least consumer, so far) will use this byte, which is the main reason
|
||||
# this view exists. For KEYBOARD, use report_mods and report_non_mods
|
||||
self.report_keys = memoryview(self._evt)[1:]
|
||||
|
||||
self.report_mods = memoryview(self._evt)[1:2]
|
||||
self.report_non_mods = memoryview(self._evt)[3:]
|
||||
|
||||
def _subscription(self, state, action):
|
||||
if action['type'] == HID_REPORT_EVENT:
|
||||
self.clear_all()
|
||||
|
||||
consumer_key = None
|
||||
for key in state.keys_pressed:
|
||||
if isinstance(key, ConsumerKeycode):
|
||||
consumer_key = key
|
||||
break
|
||||
|
||||
reporting_device = self.report_device[0]
|
||||
needed_reporting_device = HIDReportTypes.KEYBOARD
|
||||
|
||||
if consumer_key:
|
||||
needed_reporting_device = HIDReportTypes.CONSUMER
|
||||
|
||||
if reporting_device != needed_reporting_device:
|
||||
# If we are about to change reporting devices, release
|
||||
# all keys and close our proverbial tab on the existing
|
||||
# device, or keys will get stuck (mostly when releasing
|
||||
# media/consumer keys)
|
||||
self.send()
|
||||
delay(10)
|
||||
|
||||
self.report_device[0] = needed_reporting_device
|
||||
|
||||
if consumer_key:
|
||||
self.add_key(consumer_key)
|
||||
else:
|
||||
for key in state.keys_pressed:
|
||||
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
||||
continue
|
||||
@ -114,37 +142,45 @@ class HIDHelper:
|
||||
return self
|
||||
|
||||
def clear_all(self):
|
||||
self._evt = bytearray(8)
|
||||
for idx, _ in enumerate(self.report_keys):
|
||||
self.report_keys[idx] = 0x00
|
||||
|
||||
return self
|
||||
|
||||
def clear_non_modifiers(self):
|
||||
for pos in range(2, 8):
|
||||
self._evt[pos] = 0x00
|
||||
for idx, _ in enumerate(self.report_non_mods):
|
||||
self.report_non_mods[idx] = 0x00
|
||||
|
||||
return self
|
||||
|
||||
def add_modifier(self, modifier):
|
||||
if isinstance(modifier, ModifierKeycode):
|
||||
self._evt[0] |= modifier.code
|
||||
self.report_mods[0] |= modifier.code
|
||||
else:
|
||||
self._evt[0] |= modifier
|
||||
self.report_mods[0] |= modifier
|
||||
|
||||
return self
|
||||
|
||||
def remove_modifier(self, modifier):
|
||||
if isinstance(modifier, ModifierKeycode):
|
||||
self._evt[0] ^= modifier.code
|
||||
self.report_mods[0] ^= modifier.code
|
||||
else:
|
||||
self._evt[0] ^= modifier
|
||||
self.report_mods[0] ^= modifier
|
||||
|
||||
return self
|
||||
|
||||
def add_key(self, key):
|
||||
# Try to find the first empty slot in the key report, and fill it
|
||||
placed = False
|
||||
for pos in range(2, 8):
|
||||
if self._evt[pos] == 0x00:
|
||||
self._evt[pos] = key.code
|
||||
|
||||
where_to_place = self.report_non_mods
|
||||
|
||||
if self.report_device[0] == HIDReportTypes.CONSUMER:
|
||||
where_to_place = self.report_keys
|
||||
|
||||
for idx, _ in enumerate(where_to_place):
|
||||
if where_to_place[idx] == 0x00:
|
||||
where_to_place[idx] = key.code
|
||||
placed = True
|
||||
break
|
||||
|
||||
@ -155,9 +191,15 @@ class HIDHelper:
|
||||
|
||||
def remove_key(self, key):
|
||||
removed = False
|
||||
for pos in range(2, 8):
|
||||
if self._evt[pos] == key.code:
|
||||
self._evt[pos] = 0x00
|
||||
|
||||
where_to_place = self.report_non_mods
|
||||
|
||||
if self.report_device[0] == HIDReportTypes.CONSUMER:
|
||||
where_to_place = self.report_keys
|
||||
|
||||
for idx, _ in enumerate(where_to_place):
|
||||
if where_to_place[idx] == key.code:
|
||||
where_to_place[idx] = 0x00
|
||||
removed = True
|
||||
|
||||
if not removed:
|
||||
|
@ -1,7 +1,7 @@
|
||||
[flake8]
|
||||
exclude = .git,__pycache__,vendor,.venv
|
||||
max_line_length = 99
|
||||
ignore = X100
|
||||
ignore = X100, E262
|
||||
per-file-ignores =
|
||||
user_keymaps/**/*.py: F401,E501
|
||||
tests/test_data/keymaps/**/*.py: F401,E501
|
||||
|
@ -0,0 +1,14 @@
|
||||
try:
|
||||
from collections import namedtuple
|
||||
except ImportError:
|
||||
from ucollections import namedtuple
|
||||
|
||||
HIDMode = namedtuple('HIDMode', (
|
||||
'subclass',
|
||||
'protocol',
|
||||
'max_packet_length',
|
||||
'polling_interval',
|
||||
'report_descriptor',
|
||||
))
|
||||
|
||||
hid_keyboard = HIDMode(0, 0, 0, 0, bytearray(0))
|
@ -22,8 +22,8 @@ keymap = [
|
||||
[KC.F, KC.G, KC.H],
|
||||
],
|
||||
[
|
||||
[KC.X, KC.Y, KC.Z],
|
||||
[KC.TRNS, KC.PIPE, KC.O],
|
||||
[KC.R, KC.P, KC.Q],
|
||||
[KC.VOLU, KC.MUTE, KC.Z],
|
||||
[KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE],
|
||||
[KC.VOLD, KC.P, KC.Q],
|
||||
],
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user