Support KEYBOARD and CONSUMER modes of HID on Feather M4 Express
This commit is contained in:
		
							
								
								
									
										39
									
								
								kmk/circuitpython/hid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								kmk/circuitpython/hid.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import usb_hid
 | 
			
		||||
from kmk.common.abstract.hid import AbstractHidHelper
 | 
			
		||||
from kmk.common.consts import (HID_REPORT_SIZES, HIDReportTypes, HIDUsage,
 | 
			
		||||
                               HIDUsagePage)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HIDHelper(AbstractHidHelper):
 | 
			
		||||
    REPORT_BYTES = 9
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        self.devices = {}
 | 
			
		||||
 | 
			
		||||
        for device in usb_hid.devices:
 | 
			
		||||
            if device.usage_page == HIDUsagePage.CONSUMER and device.usage == HIDUsage.CONSUMER:
 | 
			
		||||
                self.devices[HIDReportTypes.CONSUMER] = device
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if device.usage_page == HIDUsagePage.KEYBOARD and device.usage == HIDUsage.KEYBOARD:
 | 
			
		||||
                self.devices[HIDReportTypes.KEYBOARD] = device
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if device.usage_page == HIDUsagePage.MOUSE and device.usage == HIDUsage.MOUSE:
 | 
			
		||||
                self.devices[HIDReportTypes.MOUSE] = device
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                device.usage_page == HIDUsagePage.SYSCONTROL and
 | 
			
		||||
                device.usage == HIDUsage.SYSCONTROL
 | 
			
		||||
            ):
 | 
			
		||||
                self.devices[HIDReportTypes.SYSCONTROL] = device
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
    def hid_send(self, evt):
 | 
			
		||||
        # int, can be looked up in HIDReportTypes
 | 
			
		||||
        reporting_device_const = self.report_device[0]
 | 
			
		||||
 | 
			
		||||
        return self.devices[reporting_device_const].send_report(
 | 
			
		||||
            evt[1:HID_REPORT_SIZES[reporting_device_const] + 1],
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										155
									
								
								kmk/common/abstract/hid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								kmk/common/abstract/hid.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from kmk.common.consts import HIDReportTypes
 | 
			
		||||
from kmk.common.event_defs import HID_REPORT_EVENT
 | 
			
		||||
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
 | 
			
		||||
                                 ModifierKeycode)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractHidHelper:
 | 
			
		||||
    REPORT_BYTES = 8
 | 
			
		||||
 | 
			
		||||
    def __init__(self, store, log_level=logging.NOTSET):
 | 
			
		||||
        self.logger = logging.getLogger(__name__)
 | 
			
		||||
        self.logger.setLevel(log_level)
 | 
			
		||||
 | 
			
		||||
        self.store = store
 | 
			
		||||
        self.store.subscribe(
 | 
			
		||||
            lambda state, action: self._subscription(state, action),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self._evt = bytearray(self.REPORT_BYTES)
 | 
			
		||||
        self.report_device = memoryview(self._evt)[0:1]
 | 
			
		||||
        self.report_device[0] = HIDReportTypes.KEYBOARD
 | 
			
		||||
 | 
			
		||||
        # 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:]
 | 
			
		||||
 | 
			
		||||
        self.post_init()
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
    def hid_send(self, evt):
 | 
			
		||||
        raise NotImplementedError('hid_send(evt) must be implemented')
 | 
			
		||||
 | 
			
		||||
    def send(self):
 | 
			
		||||
        self.logger.debug('Sending HID report: {}'.format(self._evt))
 | 
			
		||||
        self.hid_send(self._evt)
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def clear_all(self):
 | 
			
		||||
        for idx, _ in enumerate(self.report_keys):
 | 
			
		||||
            self.report_keys[idx] = 0x00
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def clear_non_modifiers(self):
 | 
			
		||||
        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.report_mods[0] |= modifier.code
 | 
			
		||||
        else:
 | 
			
		||||
            self.report_mods[0] |= modifier
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def remove_modifier(self, modifier):
 | 
			
		||||
        if isinstance(modifier, ModifierKeycode):
 | 
			
		||||
            self.report_mods[0] ^= modifier.code
 | 
			
		||||
        else:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        if not placed:
 | 
			
		||||
            self.logger.warning('Out of space in HID report, could not add key')
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def remove_key(self, key):
 | 
			
		||||
        removed = False
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
            self.logger.warning('Tried to remove key that was not added')
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
@@ -5,10 +5,33 @@ class HIDReportTypes:
 | 
			
		||||
    SYSCONTROL = 4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HIDUsage:
 | 
			
		||||
    KEYBOARD = 0x06
 | 
			
		||||
    MOUSE = 0x02
 | 
			
		||||
    CONSUMER = 0x01
 | 
			
		||||
    SYSCONTROL = 0x80
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HIDUsagePage:
 | 
			
		||||
    CONSUMER = 0x0C
 | 
			
		||||
    KEYBOARD = MOUSE = SYSCONTROL = 0x01
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Currently only used by the CircuitPython HIDHelper because CircuitPython
 | 
			
		||||
# actually enforces these limits with a ValueError. Unused on PyBoard because
 | 
			
		||||
# we can happily send full reports there and it magically works.
 | 
			
		||||
HID_REPORT_SIZES = {
 | 
			
		||||
    HIDReportTypes.KEYBOARD: 8,
 | 
			
		||||
    HIDReportTypes.MOUSE: 4,
 | 
			
		||||
    HIDReportTypes.CONSUMER: 2,
 | 
			
		||||
    HIDReportTypes.SYSCONTROL: 8,  # TODO find the correct value for this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HID_REPORT_STRUCTURE = bytes([
 | 
			
		||||
    # Regular keyboard
 | 
			
		||||
    0x05, 0x01,  # Usage Page (Generic Desktop)
 | 
			
		||||
    0x09, 0x06,  # Usage (Keyboard)
 | 
			
		||||
    0x05, HIDUsagePage.KEYBOARD,  # Usage Page (Generic Desktop)
 | 
			
		||||
    0x09, HIDUsage.KEYBOARD,  # Usage (Keyboard)
 | 
			
		||||
    0xA1, 0x01,  # Collection (Application)
 | 
			
		||||
    0x85, HIDReportTypes.KEYBOARD,  #   Report ID (1)
 | 
			
		||||
    0x05, 0x07,  #   Usage Page (Keyboard)
 | 
			
		||||
@@ -39,8 +62,8 @@ HID_REPORT_STRUCTURE = bytes([
 | 
			
		||||
    0x91, 0x01,  #   Output (Constant)
 | 
			
		||||
    0xC0,        # End Collection
 | 
			
		||||
    # Regular mouse
 | 
			
		||||
    0x05, 0x01,  # Usage Page (Generic Desktop)
 | 
			
		||||
    0x09, 0x02,  # Usage (Mouse)
 | 
			
		||||
    0x05, HIDUsagePage.MOUSE,  # Usage Page (Generic Desktop)
 | 
			
		||||
    0x09, HIDUsage.MOUSE,  # Usage (Mouse)
 | 
			
		||||
    0xA1, 0x01,  # Collection (Application)
 | 
			
		||||
    0x09, 0x01,  #   Usage (Pointer)
 | 
			
		||||
    0xA1, 0x00,  #   Collection (Physical)
 | 
			
		||||
@@ -73,8 +96,8 @@ HID_REPORT_STRUCTURE = bytes([
 | 
			
		||||
    0xC0,        #   End Collection
 | 
			
		||||
    0xC0,        # End Collection
 | 
			
		||||
    # Consumer ("multimedia") keys
 | 
			
		||||
    0x05, 0x0C,        # Usage Page (Consumer)
 | 
			
		||||
    0x09, 0x01,        # Usage (Consumer Control)
 | 
			
		||||
    0x05, HIDUsagePage.CONSUMER,  # Usage Page (Consumer)
 | 
			
		||||
    0x09, HIDUsage.CONSUMER,  # Usage (Consumer Control)
 | 
			
		||||
    0xA1, 0x01,        # Collection (Application)
 | 
			
		||||
    0x85, HIDReportTypes.CONSUMER,  # Report ID (n)
 | 
			
		||||
    0x75, 0x10,        #   Report Size (16)
 | 
			
		||||
@@ -86,8 +109,8 @@ HID_REPORT_STRUCTURE = bytes([
 | 
			
		||||
    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)
 | 
			
		||||
    0x05, HIDUsagePage.SYSCONTROL,  # Usage Page (Generic Desktop Ctrls)
 | 
			
		||||
    0x09, HIDUsage.SYSCONTROL,  # Usage (Sys Control)
 | 
			
		||||
    0xA1, 0x01,        # Collection (Application)
 | 
			
		||||
    0x85, HIDReportTypes.SYSCONTROL,  # Report ID (n)
 | 
			
		||||
    0x75, 0x02,        #   Report Size (2)
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ class Store:
 | 
			
		||||
                    cb(self.state, action)
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    self.logger.error('Callback failed, moving on')
 | 
			
		||||
                    print(sys.print_exception(e), file=sys.stderr)
 | 
			
		||||
                    sys.print_exception(e)
 | 
			
		||||
 | 
			
		||||
    def get_state(self):
 | 
			
		||||
        return self.state
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import sys
 | 
			
		||||
from logging import DEBUG
 | 
			
		||||
 | 
			
		||||
from kmk.circuitpython.hid import HIDHelper
 | 
			
		||||
from kmk.circuitpython.matrix import MatrixScanner
 | 
			
		||||
from kmk.common.consts import UnicodeModes
 | 
			
		||||
from kmk.firmware import Firmware
 | 
			
		||||
@@ -23,6 +24,7 @@ def main():
 | 
			
		||||
            unicode_mode=unicode_mode,
 | 
			
		||||
            log_level=DEBUG,
 | 
			
		||||
            matrix_scanner=MatrixScanner,
 | 
			
		||||
            hid=HIDHelper,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        firmware.go()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,7 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
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, ConsumerKeycode,
 | 
			
		||||
                                 ModifierKeycode)
 | 
			
		||||
from kmk.common.abstract.hid import AbstractHidHelper
 | 
			
		||||
from kmk.common.consts import HID_REPORT_STRUCTURE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_pyb_hid_descriptor():
 | 
			
		||||
@@ -14,80 +10,20 @@ def generate_pyb_hid_descriptor():
 | 
			
		||||
    return tuple(existing_keyboard)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HIDHelper:
 | 
			
		||||
    def __init__(self, store, log_level=logging.NOTSET):
 | 
			
		||||
        self.logger = logging.getLogger(__name__)
 | 
			
		||||
        self.logger.setLevel(log_level)
 | 
			
		||||
 | 
			
		||||
        self.store = store
 | 
			
		||||
        self.store.subscribe(
 | 
			
		||||
            lambda state, action: self._subscription(state, action),
 | 
			
		||||
        )
 | 
			
		||||
class HIDHelper(AbstractHidHelper):
 | 
			
		||||
    # 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.
 | 
			
		||||
    REPORT_BYTES = 7
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        self._hid = USB_HID()
 | 
			
		||||
 | 
			
		||||
        # 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()
 | 
			
		||||
 | 
			
		||||
            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()
 | 
			
		||||
        self.hid_send = self._hid.send
 | 
			
		||||
 | 
			
		||||
    def send(self):
 | 
			
		||||
        self.logger.debug('Sending HID report: {}'.format(self._evt))
 | 
			
		||||
        self._hid.send(self._evt)
 | 
			
		||||
        self.hid_send(self._evt)
 | 
			
		||||
 | 
			
		||||
        # Without this delay, events get clobbered and you'll likely end up with
 | 
			
		||||
        # a string like `heloooooooooooooooo` rather than `hello`. This number
 | 
			
		||||
@@ -101,69 +37,3 @@ class HIDHelper:
 | 
			
		||||
        delay(5)
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def clear_all(self):
 | 
			
		||||
        for idx, _ in enumerate(self.report_keys):
 | 
			
		||||
            self.report_keys[idx] = 0x00
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def clear_non_modifiers(self):
 | 
			
		||||
        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.report_mods[0] |= modifier.code
 | 
			
		||||
        else:
 | 
			
		||||
            self.report_mods[0] |= modifier
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def remove_modifier(self, modifier):
 | 
			
		||||
        if isinstance(modifier, ModifierKeycode):
 | 
			
		||||
            self.report_mods[0] ^= modifier.code
 | 
			
		||||
        else:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        if not placed:
 | 
			
		||||
            self.logger.warning('Out of space in HID report, could not add key')
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def remove_key(self, key):
 | 
			
		||||
        removed = False
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
            self.logger.warning('Tried to remove key that was not added')
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ ANGRY_TABLE_FLIP = unicode_sequence([
 | 
			
		||||
keymap = [
 | 
			
		||||
    [
 | 
			
		||||
        [KC.GESC,              KC.A,     KC.RESET],
 | 
			
		||||
        [KC.MO(1),             KC.B,     KC.C],
 | 
			
		||||
        [KC.MO(1),             KC.B,     KC.MUTE],
 | 
			
		||||
        [KC.LT(2, KC.EXCLAIM), KC.HASH,  KC.ENTER],
 | 
			
		||||
        [KC.TT(3),             KC.SPACE, KC.LSHIFT],
 | 
			
		||||
    ],
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user