Merge pull request #30 from KMKfw/topic-consumer-device
HID: Support Consumer (media) keys
This commit is contained in:
		@@ -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,24 +34,64 @@ 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 key.code >= FIRST_KMK_INTERNAL_KEYCODE:
 | 
			
		||||
                    continue
 | 
			
		||||
                if isinstance(key, ConsumerKeycode):
 | 
			
		||||
                    consumer_key = key
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                if isinstance(key, ModifierKeycode):
 | 
			
		||||
                    self.add_modifier(key)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.add_key(key)
 | 
			
		||||
            reporting_device = self.report_device[0]
 | 
			
		||||
            needed_reporting_device = HIDReportTypes.KEYBOARD
 | 
			
		||||
 | 
			
		||||
                    if key.has_modifiers:
 | 
			
		||||
                        for mod in key.has_modifiers:
 | 
			
		||||
                            self.add_modifier(mod)
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
                    if isinstance(key, ModifierKeycode):
 | 
			
		||||
                        self.add_modifier(key)
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.add_key(key)
 | 
			
		||||
 | 
			
		||||
                        if key.has_modifiers:
 | 
			
		||||
                            for mod in key.has_modifiers:
 | 
			
		||||
                                self.add_modifier(mod)
 | 
			
		||||
 | 
			
		||||
            self.send()
 | 
			
		||||
 | 
			
		||||
@@ -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],
 | 
			
		||||
    ],
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user