Support KEYBOARD and CONSUMER modes of HID on Feather M4 Express
This commit is contained in:
parent
0b11f42cc2
commit
472b08d77b
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
|
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([
|
HID_REPORT_STRUCTURE = bytes([
|
||||||
# Regular keyboard
|
# Regular keyboard
|
||||||
0x05, 0x01, # Usage Page (Generic Desktop)
|
0x05, HIDUsagePage.KEYBOARD, # Usage Page (Generic Desktop)
|
||||||
0x09, 0x06, # Usage (Keyboard)
|
0x09, HIDUsage.KEYBOARD, # Usage (Keyboard)
|
||||||
0xA1, 0x01, # Collection (Application)
|
0xA1, 0x01, # Collection (Application)
|
||||||
0x85, HIDReportTypes.KEYBOARD, # Report ID (1)
|
0x85, HIDReportTypes.KEYBOARD, # Report ID (1)
|
||||||
0x05, 0x07, # Usage Page (Keyboard)
|
0x05, 0x07, # Usage Page (Keyboard)
|
||||||
@ -39,8 +62,8 @@ HID_REPORT_STRUCTURE = bytes([
|
|||||||
0x91, 0x01, # Output (Constant)
|
0x91, 0x01, # Output (Constant)
|
||||||
0xC0, # End Collection
|
0xC0, # End Collection
|
||||||
# Regular mouse
|
# Regular mouse
|
||||||
0x05, 0x01, # Usage Page (Generic Desktop)
|
0x05, HIDUsagePage.MOUSE, # Usage Page (Generic Desktop)
|
||||||
0x09, 0x02, # Usage (Mouse)
|
0x09, HIDUsage.MOUSE, # Usage (Mouse)
|
||||||
0xA1, 0x01, # Collection (Application)
|
0xA1, 0x01, # Collection (Application)
|
||||||
0x09, 0x01, # Usage (Pointer)
|
0x09, 0x01, # Usage (Pointer)
|
||||||
0xA1, 0x00, # Collection (Physical)
|
0xA1, 0x00, # Collection (Physical)
|
||||||
@ -73,8 +96,8 @@ HID_REPORT_STRUCTURE = bytes([
|
|||||||
0xC0, # End Collection
|
0xC0, # End Collection
|
||||||
0xC0, # End Collection
|
0xC0, # End Collection
|
||||||
# Consumer ("multimedia") keys
|
# Consumer ("multimedia") keys
|
||||||
0x05, 0x0C, # Usage Page (Consumer)
|
0x05, HIDUsagePage.CONSUMER, # Usage Page (Consumer)
|
||||||
0x09, 0x01, # Usage (Consumer Control)
|
0x09, HIDUsage.CONSUMER, # Usage (Consumer Control)
|
||||||
0xA1, 0x01, # Collection (Application)
|
0xA1, 0x01, # Collection (Application)
|
||||||
0x85, HIDReportTypes.CONSUMER, # Report ID (n)
|
0x85, HIDReportTypes.CONSUMER, # Report ID (n)
|
||||||
0x75, 0x10, # Report Size (16)
|
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)
|
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||||
0xC0, # End Collection
|
0xC0, # End Collection
|
||||||
# Power controls
|
# Power controls
|
||||||
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
|
0x05, HIDUsagePage.SYSCONTROL, # Usage Page (Generic Desktop Ctrls)
|
||||||
0x09, 0x80, # Usage (Sys Control)
|
0x09, HIDUsage.SYSCONTROL, # Usage (Sys Control)
|
||||||
0xA1, 0x01, # Collection (Application)
|
0xA1, 0x01, # Collection (Application)
|
||||||
0x85, HIDReportTypes.SYSCONTROL, # Report ID (n)
|
0x85, HIDReportTypes.SYSCONTROL, # Report ID (n)
|
||||||
0x75, 0x02, # Report Size (2)
|
0x75, 0x02, # Report Size (2)
|
||||||
|
@ -52,7 +52,7 @@ class Store:
|
|||||||
cb(self.state, action)
|
cb(self.state, action)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('Callback failed, moving on')
|
self.logger.error('Callback failed, moving on')
|
||||||
print(sys.print_exception(e), file=sys.stderr)
|
sys.print_exception(e)
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
return self.state
|
return self.state
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import sys
|
import sys
|
||||||
from logging import DEBUG
|
from logging import DEBUG
|
||||||
|
|
||||||
|
from kmk.circuitpython.hid import HIDHelper
|
||||||
from kmk.circuitpython.matrix import MatrixScanner
|
from kmk.circuitpython.matrix import MatrixScanner
|
||||||
from kmk.common.consts import UnicodeModes
|
from kmk.common.consts import UnicodeModes
|
||||||
from kmk.firmware import Firmware
|
from kmk.firmware import Firmware
|
||||||
@ -23,6 +24,7 @@ def main():
|
|||||||
unicode_mode=unicode_mode,
|
unicode_mode=unicode_mode,
|
||||||
log_level=DEBUG,
|
log_level=DEBUG,
|
||||||
matrix_scanner=MatrixScanner,
|
matrix_scanner=MatrixScanner,
|
||||||
|
hid=HIDHelper,
|
||||||
)
|
)
|
||||||
|
|
||||||
firmware.go()
|
firmware.go()
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from pyb import USB_HID, delay, hid_keyboard
|
from pyb import USB_HID, delay, hid_keyboard
|
||||||
|
|
||||||
from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes
|
from kmk.common.abstract.hid import AbstractHidHelper
|
||||||
from kmk.common.event_defs import HID_REPORT_EVENT
|
from kmk.common.consts import HID_REPORT_STRUCTURE
|
||||||
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
|
|
||||||
ModifierKeycode)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_pyb_hid_descriptor():
|
def generate_pyb_hid_descriptor():
|
||||||
@ -14,80 +10,20 @@ def generate_pyb_hid_descriptor():
|
|||||||
return tuple(existing_keyboard)
|
return tuple(existing_keyboard)
|
||||||
|
|
||||||
|
|
||||||
class HIDHelper:
|
class HIDHelper(AbstractHidHelper):
|
||||||
def __init__(self, store, log_level=logging.NOTSET):
|
# For some bizarre reason this can no longer be 8, it'll just fail to send
|
||||||
self.logger = logging.getLogger(__name__)
|
# anything. This is almost certainly a bug in the report descriptor sent
|
||||||
self.logger.setLevel(log_level)
|
# 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.store = store
|
REPORT_BYTES = 7
|
||||||
self.store.subscribe(
|
|
||||||
lambda state, action: self._subscription(state, action),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def post_init(self):
|
||||||
self._hid = USB_HID()
|
self._hid = USB_HID()
|
||||||
|
self.hid_send = self._hid.send
|
||||||
# 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()
|
|
||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
self.logger.debug('Sending HID report: {}'.format(self._evt))
|
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
|
# Without this delay, events get clobbered and you'll likely end up with
|
||||||
# a string like `heloooooooooooooooo` rather than `hello`. This number
|
# a string like `heloooooooooooooooo` rather than `hello`. This number
|
||||||
@ -101,69 +37,3 @@ class HIDHelper:
|
|||||||
delay(5)
|
delay(5)
|
||||||
|
|
||||||
return self
|
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 = [
|
keymap = [
|
||||||
[
|
[
|
||||||
[KC.GESC, KC.A, KC.RESET],
|
[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.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
|
||||||
[KC.TT(3), KC.SPACE, KC.LSHIFT],
|
[KC.TT(3), KC.SPACE, KC.LSHIFT],
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user