feat(extensions): most of the extensions implementation, by kdb424
This commit is contained in:
102
kmk/ble.py
102
kmk/ble.py
@@ -1,102 +0,0 @@
|
||||
from adafruit_ble import BLERadio
|
||||
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
|
||||
from adafruit_ble.services.standard.hid import HIDService
|
||||
|
||||
from kmk.hid import AbstractHID
|
||||
|
||||
BLE_APPEARANCE_HID_KEYBOARD = 961
|
||||
# Hardcoded in CPy
|
||||
MAX_CONNECTIONS = 2
|
||||
|
||||
|
||||
class BLEHID(AbstractHID):
|
||||
def post_init(self, ble_name='KMK Keyboard', **kwargs):
|
||||
self.conn_id = -1
|
||||
|
||||
self.ble = BLERadio()
|
||||
self.ble.name = ble_name
|
||||
self.hid = HIDService()
|
||||
self.hid.protocol_mode = 0 # Boot protocol
|
||||
|
||||
# Security-wise this is not right. While you're away someone turns
|
||||
# on your keyboard and they can pair with it nice and clean and then
|
||||
# listen to keystrokes.
|
||||
# On the other hand we don't have LESC so it's like shouting your
|
||||
# keystrokes in the air
|
||||
if not self.ble.connected or not self.hid.devices:
|
||||
self.start_advertising()
|
||||
|
||||
self.conn_id = 0
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
'''Search through the provided list of devices to find the ones with the
|
||||
send_report attribute.'''
|
||||
if not self.ble.connected:
|
||||
return []
|
||||
|
||||
result = []
|
||||
# Security issue:
|
||||
# This introduces a race condition. Let's say you have 2 active
|
||||
# connections: Alice and Bob - Alice is connection 1 and Bob 2.
|
||||
# Now Chuck who has already paired with the device in the past
|
||||
# (this assumption is needed only in the case of LESC)
|
||||
# wants to gather the keystrokes you send to Alice. You have
|
||||
# selected right now to talk to Alice (1) and you're typing a secret.
|
||||
# If Chuck kicks Alice off and is quick enough to connect to you,
|
||||
# which means quicker than the running interval of this function,
|
||||
# he'll be earlier in the `self.hid.devices` so will take over the
|
||||
# selected 1 position in the resulted array.
|
||||
# If no LESC is in place, Chuck can sniff the keystrokes anyway
|
||||
for device in self.hid.devices:
|
||||
if hasattr(device, 'send_report'):
|
||||
result.append(device)
|
||||
|
||||
return result
|
||||
|
||||
def _check_connection(self):
|
||||
devices = self.devices
|
||||
if not devices:
|
||||
return False
|
||||
|
||||
if self.conn_id >= len(devices):
|
||||
self.conn_id = len(devices) - 1
|
||||
|
||||
if self.conn_id < 0:
|
||||
return False
|
||||
|
||||
if not devices[self.conn_id]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def hid_send(self, evt):
|
||||
if not self._check_connection():
|
||||
return
|
||||
|
||||
device = self.devices[self.conn_id]
|
||||
|
||||
while len(evt) < len(device._characteristic.value) + 1:
|
||||
evt.append(0)
|
||||
|
||||
return device.send_report(evt[1:])
|
||||
|
||||
def clear_bonds(self):
|
||||
import _bleio
|
||||
|
||||
_bleio.adapter.erase_bonding()
|
||||
|
||||
def next_connection(self):
|
||||
self.conn_id = (self.conn_id + 1) % len(self.devices)
|
||||
|
||||
def previous_connection(self):
|
||||
self.conn_id = (self.conn_id - 1) % len(self.devices)
|
||||
|
||||
def start_advertising(self):
|
||||
advertisement = ProvideServicesAdvertisement(self.hid)
|
||||
advertisement.appearance = BLE_APPEARANCE_HID_KEYBOARD
|
||||
|
||||
self.ble.start_advertising(advertisement)
|
||||
|
||||
def stop_advertising(self):
|
||||
self.ble.stop_advertising()
|
@@ -1,10 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.D9, board.D10, board.D11, board.D12, board.D13, board.SCL)
|
||||
row_pins = (board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
@@ -1,30 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (
|
||||
board.A0,
|
||||
board.A1,
|
||||
board.A2,
|
||||
board.A3,
|
||||
board.A4,
|
||||
board.A5,
|
||||
board.SCK,
|
||||
board.MOSI,
|
||||
)
|
||||
row_pins = (
|
||||
board.TX,
|
||||
board.RX,
|
||||
board.SDA,
|
||||
board.SCL,
|
||||
board.D13,
|
||||
board.D12,
|
||||
board.D11,
|
||||
board.D10,
|
||||
)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.D9
|
||||
rgb_num_pixels = 12
|
@@ -1,20 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (
|
||||
board.RX,
|
||||
board.D13,
|
||||
board.A0,
|
||||
board.D11,
|
||||
board.A4,
|
||||
board.A5,
|
||||
board.D10,
|
||||
board.D9,
|
||||
board.SCK,
|
||||
)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A1, board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.A0, board.D11, board.D10, board.D9)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [7, 7, 7, 7]
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D11, board.D10, board.D9, board.D7, board.D13)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
@@ -1,43 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
from kmk.matrix import intify_coordinate as ic
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
# Pin mappings for converter board found at hardware/README.md
|
||||
# QMK: MATRIX_COL_PINS { F6, F7, B1, B3, B2, B6 }
|
||||
# QMK: MATRIX_ROW_PINS { D7, E6, B4, D2, D4 }
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D11, board.D10, board.D9, board.RX, board.D13)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
split_flip = True
|
||||
split_offsets = (6, 6, 6, 6, 6)
|
||||
split_type = 'UART'
|
||||
uart_pin = board.SCL
|
||||
extra_data_pin = board.SDA
|
||||
rgb_pixel_pin = board.TX
|
||||
led_pin = board.D7
|
||||
|
||||
coord_mapping = []
|
||||
coord_mapping.extend(ic(0, x) for x in range(12))
|
||||
coord_mapping.extend(ic(1, x) for x in range(12))
|
||||
coord_mapping.extend(ic(2, x) for x in range(12))
|
||||
|
||||
# Buckle up friends, the bottom row of this keyboard is wild, and making
|
||||
# our layouts match, visually, what the keyboard looks like, requires some
|
||||
# surgery on the bottom two rows of coords
|
||||
|
||||
# Row index 3 is actually perfectly sane and we _could_ expose it
|
||||
# just like the above three rows, however, visually speaking, the
|
||||
# top-right thumb cluster button (when looking at the left-half PCB)
|
||||
# is more inline with R3, so we'll jam that key (and its mirror) in here
|
||||
coord_mapping.extend(ic(3, x) for x in range(6))
|
||||
coord_mapping.append(ic(4, 2))
|
||||
coord_mapping.append(ic(4, 9))
|
||||
coord_mapping.extend(ic(3, x) for x in range(6, 12)) # Now, the rest of R3
|
||||
|
||||
# And now, to handle R4, which at this point is down to just six keys
|
||||
coord_mapping.extend(ic(4, x) for x in range(3, 9))
|
@@ -1,28 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (
|
||||
board.SDA,
|
||||
board.A2,
|
||||
board.A3,
|
||||
board.A4,
|
||||
board.A5,
|
||||
board.SCK,
|
||||
board.MOSI,
|
||||
)
|
||||
row_pins = (
|
||||
board.TX,
|
||||
board.A0,
|
||||
board.RX,
|
||||
board.A1,
|
||||
board.D11,
|
||||
board.D9,
|
||||
board.D12,
|
||||
board.D10,
|
||||
)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.D13
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.A0)
|
||||
row_pins = (board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6]
|
@@ -1,18 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D13, board.D11, board.D10, board.D9)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
||||
uart_pin = board.SCL
|
||||
extra_data_pin = board.SDA
|
||||
rgb_pixel_pin = board.TX
|
||||
# led_pin = board.D7
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D13, board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
@@ -1,17 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.RX, board.A1, board.A2, board.A3, board.A4, board.A5)
|
||||
row_pins = (board.D13, board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
||||
uart_pin = board.SCL
|
||||
rgb_pixel_pin = board.TX
|
||||
extra_data_pin = board.SDA
|
@@ -1,25 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
# Will need additional work and testing
|
||||
col_pins = (
|
||||
board.A1,
|
||||
board.A2,
|
||||
board.A3,
|
||||
board.A4,
|
||||
board.A5,
|
||||
board.SCK,
|
||||
board.MOSI,
|
||||
board.D12,
|
||||
)
|
||||
row_pins = (board.A0, board.D13, board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = False
|
||||
split_offsets = [8, 8, 8, 8, 8, 8]
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D11, board.D10, board.D9, board.RX, board.D13)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
@@ -1,29 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (
|
||||
board.A0,
|
||||
board.A1,
|
||||
board.A2,
|
||||
board.A3,
|
||||
board.A4,
|
||||
board.A5,
|
||||
board.SCK,
|
||||
board.MOSI,
|
||||
)
|
||||
row_pins = (
|
||||
board.TX,
|
||||
board.RX,
|
||||
board.SDA,
|
||||
board.SCL,
|
||||
board.D9,
|
||||
board.D10,
|
||||
board.D12,
|
||||
board.D11,
|
||||
board.D13,
|
||||
)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A1, board.A2, board.A3, board.A4, board.A5, board.SCK, board.MOSI)
|
||||
row_pins = (board.D13, board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [7, 7, 7, 7, 7]
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A0, board.A1, board.A2, board.A3, board.A4, board.A5, board.SCK)
|
||||
row_pins = (board.D13, board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [7, 7, 7, 7, 7]
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.A5, board.A4, board.A3, board.A2, board.A1, board.A0)
|
||||
row_pins = (board.D7, board.D9, board.D10, board.D11)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6]
|
@@ -1,15 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (board.MOSI, board.SCK, board.A5, board.A4, board.A3, board.A2)
|
||||
row_pins = (board.D11, board.D10, board.D9, board.D7)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
rgb_pixel_pin = board.TX
|
||||
uart_pin = board.SCL
|
||||
split_type = 'UART'
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6]
|
@@ -1,42 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
from kmk.matrix import intify_coordinate as ic
|
||||
|
||||
# Implements what used to be handled by KMKKeyboard.swap_indicies for this
|
||||
# board, by flipping various row3 (bottom physical row) keys so their
|
||||
# coord_mapping matches what the user pressed (even if the wiring
|
||||
# underneath is sending different coordinates)
|
||||
_r3_swap_conversions = {3: 9, 4: 10, 5: 11, 9: 3, 10: 4, 11: 5}
|
||||
|
||||
|
||||
def r3_swap(col):
|
||||
try:
|
||||
return _r3_swap_conversions[col]
|
||||
except KeyError:
|
||||
return col
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
# physical, visible cols (SCK, MO, MI, RX, TX, D4)
|
||||
# physical, visible rows (10, 11, 12, 13) (9, 6, 5, SCL)
|
||||
col_pins = (board.SCK, board.MOSI, board.MISO, board.RX, board.TX, board.D4)
|
||||
row_pins = (
|
||||
board.D10,
|
||||
board.D11,
|
||||
board.D12,
|
||||
board.D13,
|
||||
board.D9,
|
||||
board.D6,
|
||||
board.D5,
|
||||
board.SCL,
|
||||
)
|
||||
rollover_cols_every_rows = 4
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
coord_mapping = []
|
||||
coord_mapping.extend(ic(0, x) for x in range(12))
|
||||
coord_mapping.extend(ic(1, x) for x in range(12))
|
||||
coord_mapping.extend(ic(2, x) for x in range(12))
|
||||
coord_mapping.extend(ic(3, r3_swap(x)) for x in range(12))
|
@@ -1,33 +0,0 @@
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
|
||||
from kmk.matrix import DiodeOrientation
|
||||
from kmk.matrix import intify_coordinate as ic
|
||||
|
||||
|
||||
class KMKKeyboard(_KMKKeyboard):
|
||||
col_pins = (
|
||||
board.P0_31,
|
||||
board.P0_29,
|
||||
board.P0_02,
|
||||
board.P1_15,
|
||||
board.P1_13,
|
||||
board.P1_11,
|
||||
)
|
||||
row_pins = (board.P0_22, board.P0_24, board.P1_00, board.P0_11)
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
split_type = 'UART' # TODO add bluetooth support as well
|
||||
split_flip = True
|
||||
split_offsets = [6, 6, 6, 6, 6]
|
||||
uart_pin = board.P0_08
|
||||
rgb_pixel_pin = board.P0_06
|
||||
extra_data_pin = board.SDA # TODO This is incorrect. Find better solution
|
||||
|
||||
coord_mapping = []
|
||||
coord_mapping.extend(ic(0, x) for x in range(12))
|
||||
coord_mapping.extend(ic(1, x) for x in range(12))
|
||||
coord_mapping.extend(ic(2, x) for x in range(12))
|
||||
|
||||
# And now, to handle R3, which at this point is down to just six keys
|
||||
coord_mapping.extend(ic(3, x) for x in range(3, 9))
|
@@ -1,3 +1,5 @@
|
||||
from micropython import const
|
||||
|
||||
try:
|
||||
from kmk.release_info import KMK_RELEASE
|
||||
except Exception:
|
||||
@@ -5,7 +7,7 @@ except Exception:
|
||||
|
||||
|
||||
class UnicodeMode:
|
||||
NOOP = 0
|
||||
LINUX = IBUS = 1
|
||||
MACOS = OSX = RALT = 2
|
||||
WINC = 3
|
||||
NOOP = const(0)
|
||||
LINUX = IBUS = const(1)
|
||||
MACOS = OSX = RALT = const(2)
|
||||
WINC = const(3)
|
||||
|
@@ -8,35 +8,44 @@ class Extension:
|
||||
def enable(self, keyboard):
|
||||
self._enabled = True
|
||||
|
||||
self.on_runtime_enable(self, keyboard)
|
||||
self.on_runtime_enable(keyboard)
|
||||
|
||||
def disable(self, keyboard):
|
||||
self._enabled = False
|
||||
|
||||
self.on_runtime_disable(self, keyboard)
|
||||
self.on_runtime_disable(keyboard)
|
||||
|
||||
# The below methods should be implemented by subclasses
|
||||
|
||||
def on_runtime_enable(self, keyboard):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def on_runtime_disable(self, keyboard):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be injected as an extra matrix update
|
||||
'''
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def after_matrix_scan(self, keyboard, matrix_update):
|
||||
pass
|
||||
def after_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be replace matrix update if supplied
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
59
kmk/extensions/international.py
Normal file
59
kmk/extensions/international.py
Normal file
@@ -0,0 +1,59 @@
|
||||
'''Adds international keys'''
|
||||
from kmk.extensions import Extension
|
||||
from kmk.keys import make_key
|
||||
|
||||
|
||||
class International(Extension):
|
||||
'''Adds international keys'''
|
||||
|
||||
def __init__(self):
|
||||
# International
|
||||
make_key(code=50, names=('NONUS_HASH', 'NUHS'))
|
||||
make_key(code=100, names=('NONUS_BSLASH', 'NUBS'))
|
||||
make_key(code=101, names=('APP', 'APPLICATION', 'SEL', 'WINMENU'))
|
||||
|
||||
make_key(code=135, names=('INT1', 'RO'))
|
||||
make_key(code=136, names=('INT2', 'KANA'))
|
||||
make_key(code=137, names=('INT3', 'JYEN'))
|
||||
make_key(code=138, names=('INT4', 'HENK'))
|
||||
make_key(code=139, names=('INT5', 'MHEN'))
|
||||
make_key(code=140, names=('INT6',))
|
||||
make_key(code=141, names=('INT7',))
|
||||
make_key(code=142, names=('INT8',))
|
||||
make_key(code=143, names=('INT9',))
|
||||
make_key(code=144, names=('LANG1', 'HAEN'))
|
||||
make_key(code=145, names=('LANG2', 'HAEJ'))
|
||||
make_key(code=146, names=('LANG3',))
|
||||
make_key(code=147, names=('LANG4',))
|
||||
make_key(code=148, names=('LANG5',))
|
||||
make_key(code=149, names=('LANG6',))
|
||||
make_key(code=150, names=('LANG7',))
|
||||
make_key(code=151, names=('LANG8',))
|
||||
make_key(code=152, names=('LANG9',))
|
||||
|
||||
def on_runtime_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_runtime_disable(self, sandbox):
|
||||
return
|
||||
|
||||
def during_bootup(self, sandbox):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def before_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def after_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, sandbox):
|
||||
return
|
@@ -1,111 +0,0 @@
|
||||
import gc
|
||||
|
||||
from kmk.extensions import Extension, InvalidExtensionEnvironment
|
||||
from kmk.handlers.stock import passthrough as handler_passthrough
|
||||
from kmk.keys import KC, make_key
|
||||
|
||||
|
||||
class LeaderMode:
|
||||
TIMEOUT = 0
|
||||
TIMEOUT_ACTIVE = 1
|
||||
ENTER = 2
|
||||
ENTER_ACTIVE = 3
|
||||
|
||||
|
||||
class Leader(Extension):
|
||||
def __init__(self, mode=LeaderMode.TIMEOUT, timeout=1000, sequences=None):
|
||||
if sequences is None:
|
||||
raise InvalidExtensionEnvironment(
|
||||
'sequences must be a dictionary, not None'
|
||||
)
|
||||
|
||||
self._mode = mode
|
||||
self._timeout = timeout
|
||||
self._sequences = self._compile_sequences(sequences)
|
||||
|
||||
self._leader_pending = None
|
||||
self._assembly_last_len = 0
|
||||
self._sequence_assembly = []
|
||||
|
||||
make_key(
|
||||
names=('LEADER', 'LEAD'),
|
||||
on_press=self._key_leader_pressed,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
|
||||
gc.collect()
|
||||
|
||||
def after_matrix_scan(self, keyboard_state, *args):
|
||||
if self._mode % 2 == 1:
|
||||
keys_pressed = keyboard_state._keys_pressed
|
||||
|
||||
if self._assembly_last_len and self._sequence_assembly:
|
||||
history_set = set(self._sequence_assembly)
|
||||
|
||||
keys_pressed = keys_pressed - history_set
|
||||
|
||||
self._assembly_last_len = len(keyboard_state._keys_pressed)
|
||||
|
||||
for key in keys_pressed:
|
||||
if self._mode == LeaderMode.ENTER_ACTIVE and key == KC.ENT:
|
||||
self._handle_leader_sequence(keyboard_state)
|
||||
break
|
||||
elif key == KC.ESC or key == KC.GESC:
|
||||
# Clean self and turn leader mode off.
|
||||
self._exit_leader_mode(keyboard_state)
|
||||
break
|
||||
elif key == KC.LEAD:
|
||||
break
|
||||
else:
|
||||
# Add key if not needing to escape
|
||||
# This needs replaced later with a proper debounce
|
||||
self._sequence_assembly.append(key)
|
||||
|
||||
keyboard_state._hid_pending = False
|
||||
|
||||
def _compile_sequences(self, sequences):
|
||||
gc.collect()
|
||||
|
||||
for k, v in sequences.items():
|
||||
if not isinstance(k, tuple):
|
||||
new_key = tuple(KC[c] for c in k)
|
||||
sequences[new_key] = v
|
||||
|
||||
for k, v in sequences.items():
|
||||
if not isinstance(k, tuple):
|
||||
del sequences[k]
|
||||
|
||||
gc.collect()
|
||||
|
||||
return sequences
|
||||
|
||||
def _handle_leader_sequence(self, keyboard_state):
|
||||
lmh = tuple(self._sequence_assembly)
|
||||
# Will get caught in infinite processing loops if we don't
|
||||
# exit leader mode before processing the target key
|
||||
self._exit_leader_mode(keyboard_state)
|
||||
|
||||
if lmh in self._sequences:
|
||||
# Stack depth exceeded if try to use add_key here with a unicode sequence
|
||||
keyboard_state._process_key(self._sequences[lmh], True)
|
||||
|
||||
keyboard_state._set_timeout(
|
||||
False, lambda: keyboard_state._remove_key(self._sequences[lmh])
|
||||
)
|
||||
|
||||
def _exit_leader_mode(self, keyboard_state):
|
||||
self._sequence_assembly.clear()
|
||||
self._mode -= 1
|
||||
self._assembly_last_len = 0
|
||||
keyboard_state._keys_pressed.clear()
|
||||
|
||||
def _key_leader_pressed(self, key, keyboard_state, *args, **kwargs):
|
||||
if self._mode % 2 == 0:
|
||||
keyboard_state._keys_pressed.discard(key)
|
||||
# All leader modes are one number higher when activating
|
||||
self._mode += 1
|
||||
|
||||
if self._mode == LeaderMode.TIMEOUT_ACTIVE:
|
||||
keyboard_state._set_timeout(
|
||||
self._timeout, lambda: self._handle_leader_sequence(keyboard_state)
|
||||
)
|
@@ -24,6 +24,7 @@ class LED(Extension):
|
||||
animation_mode=AnimationModes.STATIC,
|
||||
animation_speed=1,
|
||||
user_animation=None,
|
||||
val=100,
|
||||
):
|
||||
try:
|
||||
self._led = pulseio.PWMOut(led_pin)
|
||||
@@ -36,12 +37,14 @@ class LED(Extension):
|
||||
self._brightness = 0
|
||||
self._pos = 0
|
||||
self._effect_init = False
|
||||
self._enabled = True
|
||||
|
||||
self.brightness_step = brightness_step
|
||||
self.brightness_limit = brightness_limit
|
||||
self.animation_mode = animation_mode
|
||||
self.animation_speed = animation_speed
|
||||
self.breathe_center = breathe_center
|
||||
self.val = val
|
||||
|
||||
if user_animation is not None:
|
||||
self.user_animation = user_animation
|
||||
@@ -62,20 +65,51 @@ class LED(Extension):
|
||||
return 'LED({})'.format(self._to_dict())
|
||||
|
||||
def _to_dict(self):
|
||||
# TODO FIXME remove
|
||||
pass
|
||||
return {
|
||||
'_brightness': self._brightness,
|
||||
'_pos': self._pos,
|
||||
'brightness_step': self.brightness_step,
|
||||
'brightness_limit': self.brightness_limit,
|
||||
'animation_mode': self.animation_mode,
|
||||
'animation_speed': self.animation_speed,
|
||||
'breathe_center': self.breathe_center,
|
||||
'val': self.val,
|
||||
}
|
||||
|
||||
def on_runtime_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_runtime_disable(self, sandbox):
|
||||
return
|
||||
|
||||
def during_bootup(self, sandbox):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def before_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def after_hid_send(self, sandbox):
|
||||
if self._enabled and self.animation_mode:
|
||||
self.animate()
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, sandbox):
|
||||
return
|
||||
|
||||
def _init_effect(self):
|
||||
self._pos = 0
|
||||
self._effect_init = False
|
||||
return self
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
if self._enabled and self.animation_mode:
|
||||
self.animate()
|
||||
|
||||
return keyboard
|
||||
|
||||
def set_brightness(self, percent):
|
||||
self._led.duty_cycle = int(percent / 100 * 65535)
|
||||
|
||||
@@ -135,13 +169,10 @@ class LED(Extension):
|
||||
self._pos = (self._pos + self.animation_speed) % 256
|
||||
self.set_brightness(self._brightness)
|
||||
|
||||
return self
|
||||
|
||||
def effect_static(self):
|
||||
self.set_brightness(self._brightness)
|
||||
# Set animation mode to none to prevent cycles from being wasted
|
||||
self.animation_mode = None
|
||||
return self
|
||||
|
||||
def animate(self):
|
||||
'''
|
||||
@@ -160,37 +191,28 @@ class LED(Extension):
|
||||
else:
|
||||
self.off()
|
||||
|
||||
return self
|
||||
|
||||
def _key_led_tog(self, key, state, *args, **kwargs):
|
||||
def _key_led_tog(self, *args, **kwargs):
|
||||
if self.animation_mode == AnimationModes.STATIC_STANDBY:
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
|
||||
self._enabled = not self._enabled
|
||||
return state
|
||||
|
||||
def _key_led_inc(self, key, state, *args, **kwargs):
|
||||
def _key_led_inc(self, *args, **kwargs):
|
||||
self.increase_brightness()
|
||||
return state
|
||||
|
||||
def _key_led_dec(self, key, state, *args, **kwargs):
|
||||
def _key_led_dec(self, *args, **kwargs):
|
||||
self.decrease_brightness()
|
||||
return state
|
||||
|
||||
def _key_led_ani(self, key, state, *args, **kwargs):
|
||||
def _key_led_ani(self, *args, **kwargs):
|
||||
self.increase_ani()
|
||||
return state
|
||||
|
||||
def _key_led_and(self, key, state, *args, **kwargs):
|
||||
def _key_led_and(self, *args, **kwargs):
|
||||
self.decrease_ani()
|
||||
return state
|
||||
|
||||
def _key_led_mode_static(self, key, state, *args, **kwargs):
|
||||
def _key_led_mode_static(self, *args, **kwargs):
|
||||
self._effect_init = True
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
return state
|
||||
|
||||
def _key_led_mode_breathe(self, key, state, *args, **kwargs):
|
||||
def _key_led_mode_breathe(self, *args, **kwargs):
|
||||
self._effect_init = True
|
||||
self.animation_mode = AnimationModes.BREATHING
|
||||
return state
|
55
kmk/extensions/media_keys.py
Normal file
55
kmk/extensions/media_keys.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from kmk.extensions import Extension
|
||||
from kmk.keys import make_consumer_key
|
||||
|
||||
|
||||
class MediaKeys(Extension):
|
||||
def __init__(self):
|
||||
# Consumer ("media") keys. Most known keys aren't supported here. A much
|
||||
# longer list used to exist in this file, but the codes were almost certainly
|
||||
# incorrect, conflicting with each other, or otherwise 'weird'. We'll add them
|
||||
# back in piecemeal as needed. PRs welcome.
|
||||
#
|
||||
# A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php
|
||||
# Note that currently we only have the PC codes. Recent MacOS versions seem to
|
||||
# support PC media keys, so I don't know how much value we would get out of
|
||||
# adding the old Apple-specific consumer codes, but again, PRs welcome if the
|
||||
# lack of them impacts you.
|
||||
make_consumer_key(code=226, names=('AUDIO_MUTE', 'MUTE')) # 0xE2
|
||||
make_consumer_key(code=233, names=('AUDIO_VOL_UP', 'VOLU')) # 0xE9
|
||||
make_consumer_key(code=234, names=('AUDIO_VOL_DOWN', 'VOLD')) # 0xEA
|
||||
make_consumer_key(code=181, names=('MEDIA_NEXT_TRACK', 'MNXT')) # 0xB5
|
||||
make_consumer_key(code=182, names=('MEDIA_PREV_TRACK', 'MPRV')) # 0xB6
|
||||
make_consumer_key(code=183, names=('MEDIA_STOP', 'MSTP')) # 0xB7
|
||||
make_consumer_key(
|
||||
code=205, names=('MEDIA_PLAY_PAUSE', 'MPLY')
|
||||
) # 0xCD (this may not be right)
|
||||
make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8
|
||||
make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3
|
||||
make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4
|
||||
|
||||
def on_runtime_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_runtime_disable(self, sandbox):
|
||||
return
|
||||
|
||||
def during_bootup(self, sandbox):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def before_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def after_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, sandbox):
|
||||
return
|
@@ -4,6 +4,8 @@ import time
|
||||
from math import e, exp, pi, sin
|
||||
|
||||
from kmk.extensions import Extension
|
||||
from kmk.handlers.stock import passthrough as handler_passthrough
|
||||
from kmk.keys import make_key
|
||||
|
||||
rgb_config = {}
|
||||
|
||||
@@ -13,7 +15,11 @@ class AnimationModes:
|
||||
STATIC = 1
|
||||
STATIC_STANDBY = 2
|
||||
BREATHING = 3
|
||||
USER = 4
|
||||
RAINBOW = 4
|
||||
BREATHING_RAINBOW = 5
|
||||
KNIGHT = 6
|
||||
SWIRL = 7
|
||||
USER = 8
|
||||
|
||||
|
||||
class RGB(Extension):
|
||||
@@ -25,22 +31,23 @@ class RGB(Extension):
|
||||
self,
|
||||
pixel_pin,
|
||||
num_pixels=0,
|
||||
val_limit=255,
|
||||
val_limit=100,
|
||||
hue_default=0,
|
||||
sat_default=100,
|
||||
rgb_order=(1, 0, 2), # GRB WS2812
|
||||
val_default=100,
|
||||
hue_step=1,
|
||||
sat_step=1,
|
||||
val_step=1,
|
||||
hue_step=5,
|
||||
sat_step=5,
|
||||
val_step=5,
|
||||
animation_speed=1,
|
||||
breathe_center=1.5, # 1.0-2.7
|
||||
breathe_center=1, # 1.0-2.7
|
||||
knight_effect_length=3,
|
||||
animation_mode=AnimationModes.STATIC,
|
||||
effect_init=False,
|
||||
reverse_animation=False,
|
||||
user_animation=None,
|
||||
disable_auto_write=False,
|
||||
loopcounter=0,
|
||||
):
|
||||
self.neopixel = neopixel.NeoPixel(
|
||||
pixel_pin,
|
||||
@@ -49,38 +56,121 @@ class RGB(Extension):
|
||||
auto_write=not disable_auto_write,
|
||||
)
|
||||
|
||||
if len(rgb_order) == 4:
|
||||
self.rgbw = True
|
||||
self.rgbw = bool(len(rgb_order) == 4)
|
||||
|
||||
self.num_pixels = num_pixels
|
||||
self.hue_step = hue_step
|
||||
self.sat_step = sat_step
|
||||
self.val_step = val_step
|
||||
self.hue = hue_default
|
||||
self.hue_default = hue_default
|
||||
self.sat = sat_default
|
||||
self.sat_default = sat_default
|
||||
self.val = val_default
|
||||
self.val_default = val_default
|
||||
self.breathe_center = breathe_center
|
||||
self.knight_effect_length = knight_effect_length
|
||||
self.val_limit = val_limit
|
||||
self.animation_mode = animation_mode
|
||||
self.animation_speed = animation_speed
|
||||
self.effect_init = effect_init
|
||||
self.reverse_animation = reverse_animation
|
||||
self.user_animation = user_animation
|
||||
self.disable_auto_write = disable_auto_write
|
||||
self.loopcounter = loopcounter
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
pass
|
||||
make_key(
|
||||
names=('RGB_TOG',), on_press=self._rgb_tog, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_HUI',), on_press=self._rgb_hui, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_HUD',), on_press=self._rgb_hud, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_SAI',), on_press=self._rgb_sai, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_SAD',), on_press=self._rgb_sad, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_VAI',), on_press=self._rgb_vai, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_VAD',), on_press=self._rgb_vad, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_ANI',), on_press=self._rgb_ani, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_AND',), on_press=self._rgb_and, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_PLAIN', 'RGB_M_P'),
|
||||
on_press=self._rgb_mode_static,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_BREATHE', 'RGB_M_B'),
|
||||
on_press=self._rgb_mode_breathe,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_RAINBOW', 'RGB_M_R'),
|
||||
on_press=self._rgb_mode_rainbow,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'),
|
||||
on_press=self._rgb_mode_breathe_rainbow,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_SWIRL', 'RGB_M_S'),
|
||||
on_press=self._rgb_mode_swirl,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_MODE_KNIGHT', 'RGB_M_K'),
|
||||
on_press=self._rgb_mode_knight,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
make_key(
|
||||
names=('RGB_RESET', 'RGB_RST'),
|
||||
on_press=self._rgb_reset,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
if self.animation_mode:
|
||||
self.loopcounter += 1
|
||||
if self.loopcounter >= 7:
|
||||
self.animate()
|
||||
self.loopcounter = 0
|
||||
def on_runtime_enable(self, sandbox):
|
||||
return
|
||||
|
||||
return keyboard
|
||||
def on_runtime_disable(self, sandbox):
|
||||
return
|
||||
|
||||
def time_ms(self):
|
||||
def during_bootup(self, sandbox):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, sandbox):
|
||||
return
|
||||
|
||||
def before_hid_send(self, sandbox):
|
||||
return
|
||||
|
||||
def after_hid_send(self, sandbox):
|
||||
self.animate()
|
||||
|
||||
def on_powersave_enable(self, sandbox):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, sandbox):
|
||||
self._do_update()
|
||||
|
||||
@staticmethod
|
||||
def time_ms():
|
||||
return int(time.monotonic() * 1000)
|
||||
|
||||
def hsv_to_rgb(self, hue, sat, val):
|
||||
@@ -105,7 +195,7 @@ class RGB(Extension):
|
||||
|
||||
else:
|
||||
base = ((100 - sat) * val) / 100
|
||||
color = int((val - base) * ((hue % 60) / 60))
|
||||
color = (val - base) * ((hue % 60) / 60)
|
||||
|
||||
x = int(hue / 60)
|
||||
if x == 0:
|
||||
@@ -160,8 +250,6 @@ class RGB(Extension):
|
||||
else:
|
||||
self.set_rgb(self.hsv_to_rgb(hue, sat, val), index)
|
||||
|
||||
return self
|
||||
|
||||
def set_hsv_fill(self, hue, sat, val):
|
||||
'''
|
||||
Takes HSV values and displays it on all LEDs/Neopixels
|
||||
@@ -174,7 +262,6 @@ class RGB(Extension):
|
||||
self.set_rgb_fill(self.hsv_to_rgbw(hue, sat, val))
|
||||
else:
|
||||
self.set_rgb_fill(self.hsv_to_rgb(hue, sat, val))
|
||||
return self
|
||||
|
||||
def set_rgb(self, rgb, index):
|
||||
'''
|
||||
@@ -187,8 +274,6 @@ class RGB(Extension):
|
||||
if not self.disable_auto_write:
|
||||
self.neopixel.show()
|
||||
|
||||
return self
|
||||
|
||||
def set_rgb_fill(self, rgb):
|
||||
'''
|
||||
Takes an RGB or RGBW and displays it on all LEDs/Neopixels
|
||||
@@ -199,8 +284,6 @@ class RGB(Extension):
|
||||
if not self.disable_auto_write:
|
||||
self.neopixel.show()
|
||||
|
||||
return self
|
||||
|
||||
def increase_hue(self, step=None):
|
||||
'''
|
||||
Increases hue by step amount rolling at 360 and returning to 0
|
||||
@@ -214,8 +297,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def decrease_hue(self, step=None):
|
||||
'''
|
||||
Decreases hue by step amount rolling at 0 and returning to 360
|
||||
@@ -232,8 +313,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def increase_sat(self, step=None):
|
||||
'''
|
||||
Increases saturation by step amount stopping at 100
|
||||
@@ -250,8 +329,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def decrease_sat(self, step=None):
|
||||
'''
|
||||
Decreases saturation by step amount stopping at 0
|
||||
@@ -268,8 +345,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def increase_val(self, step=None):
|
||||
'''
|
||||
Increases value by step amount stopping at 100
|
||||
@@ -285,8 +360,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def decrease_val(self, step=None):
|
||||
'''
|
||||
Decreases value by step amount stopping at 0
|
||||
@@ -302,8 +375,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def increase_ani(self):
|
||||
'''
|
||||
Increases animation speed by 1 amount stopping at 10
|
||||
@@ -316,8 +387,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def decrease_ani(self):
|
||||
'''
|
||||
Decreases animation speed by 1 amount stopping at 0
|
||||
@@ -330,8 +399,6 @@ class RGB(Extension):
|
||||
if self._check_update():
|
||||
self._do_update()
|
||||
|
||||
return self
|
||||
|
||||
def off(self):
|
||||
'''
|
||||
Turns off all LEDs/Neopixels without changing stored values
|
||||
@@ -339,8 +406,6 @@ class RGB(Extension):
|
||||
if self.neopixel:
|
||||
self.set_hsv_fill(0, 0, 0)
|
||||
|
||||
return self
|
||||
|
||||
def show(self):
|
||||
'''
|
||||
Turns on all LEDs/Neopixels without changing stored values
|
||||
@@ -348,8 +413,6 @@ class RGB(Extension):
|
||||
if self.neopixel:
|
||||
self.neopixel.show()
|
||||
|
||||
return self
|
||||
|
||||
def animate(self):
|
||||
'''
|
||||
Activates a "step" in the animation based on the active mode
|
||||
@@ -358,27 +421,30 @@ class RGB(Extension):
|
||||
if self.effect_init:
|
||||
self._init_effect()
|
||||
|
||||
if self.enabled:
|
||||
if self.animation_mode == 'breathing':
|
||||
return self.effect_breathing()
|
||||
elif self.animation_mode == 'rainbow':
|
||||
return self.effect_rainbow()
|
||||
elif self.animation_mode == 'breathing_rainbow':
|
||||
return self.effect_breathing_rainbow()
|
||||
elif self.animation_mode == 'static':
|
||||
return self.effect_static()
|
||||
elif self.animation_mode == 'knight':
|
||||
return self.effect_knight()
|
||||
elif self.animation_mode == 'swirl':
|
||||
return self.effect_swirl()
|
||||
elif self.animation_mode == 'user':
|
||||
return self.user_animation(self)
|
||||
elif self.animation_mode == 'static_standby':
|
||||
pass
|
||||
else:
|
||||
self.off()
|
||||
|
||||
return self
|
||||
if self.animation_mode is not AnimationModes.STATIC_STANDBY:
|
||||
self.loopcounter += 1
|
||||
if self.loopcounter >= 7 and self.enable:
|
||||
self.loopcounter = 0
|
||||
if self.animation_mode == AnimationModes.BREATHING:
|
||||
self.effect_breathing()
|
||||
elif self.animation_mode == AnimationModes.RAINBOW:
|
||||
self.effect_rainbow()
|
||||
elif self.animation_mode == AnimationModes.BREATHING_RAINBOW:
|
||||
self.effect_breathing_rainbow()
|
||||
elif self.animation_mode == AnimationModes.STATIC:
|
||||
self.effect_static()
|
||||
elif self.animation_mode == AnimationModes.KNIGHT:
|
||||
self.effect_knight()
|
||||
elif self.animation_mode == AnimationModes.SWIRL:
|
||||
self.effect_swirl()
|
||||
elif self.animation_mode == AnimationModes.USER:
|
||||
self.user_animation(self)
|
||||
elif self.animation_mode == AnimationModes.STATIC_STANDBY:
|
||||
pass
|
||||
else:
|
||||
self.off()
|
||||
if self.loopcounter >= 7:
|
||||
self.loopcounter = 0
|
||||
|
||||
def _animation_step(self):
|
||||
interval = self.time_ms() - self.time
|
||||
@@ -387,35 +453,31 @@ class RGB(Extension):
|
||||
return max(self.intervals)
|
||||
if interval in self.intervals:
|
||||
return interval
|
||||
else:
|
||||
return False
|
||||
return None
|
||||
|
||||
def _init_effect(self):
|
||||
if (
|
||||
self.animation_mode == 'breathing'
|
||||
or self.animation_mode == 'breathing_rainbow'
|
||||
self.animation_mode == AnimationModes.BREATHING
|
||||
or self.animation_mode == AnimationModes.BREATHING_RAINBOW
|
||||
):
|
||||
self.intervals = (30, 20, 10, 5)
|
||||
elif self.animation_mode == 'swirl':
|
||||
elif self.animation_mode == AnimationModes.SWIRL:
|
||||
self.intervals = (50, 50)
|
||||
|
||||
self.pos = 0
|
||||
self.reverse_animation = False
|
||||
self.effect_init = False
|
||||
return self
|
||||
|
||||
def _check_update(self):
|
||||
if self.animation_mode == 'static_standby':
|
||||
return True
|
||||
return bool(self.animation_mode == AnimationModes.STATIC_STANDBY)
|
||||
|
||||
def _do_update(self):
|
||||
if self.animation_mode == 'static_standby':
|
||||
self.animation_mode = 'static'
|
||||
if self.animation_mode == AnimationModes.STATIC_STANDBY:
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
|
||||
def effect_static(self):
|
||||
self.set_hsv_fill(self.hue, self.sat, self.val)
|
||||
self.animation_mode = 'static_standby'
|
||||
return self
|
||||
self.animation_mode = AnimationModes.STATIC_STANDBY
|
||||
|
||||
def effect_breathing(self):
|
||||
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
|
||||
@@ -428,20 +490,14 @@ class RGB(Extension):
|
||||
self.pos = (self.pos + self.animation_speed) % 256
|
||||
self.set_hsv_fill(self.hue, self.sat, self.val)
|
||||
|
||||
return self
|
||||
|
||||
def effect_breathing_rainbow(self):
|
||||
self.increase_hue(self.animation_speed)
|
||||
self.effect_breathing()
|
||||
|
||||
return self
|
||||
|
||||
def effect_rainbow(self):
|
||||
self.increase_hue(self.animation_speed)
|
||||
self.set_hsv_fill(self.hue, self.sat, self.val)
|
||||
|
||||
return self
|
||||
|
||||
def effect_swirl(self):
|
||||
self.increase_hue(self.animation_speed)
|
||||
self.disable_auto_write = True # Turn off instantly showing
|
||||
@@ -453,7 +509,6 @@ class RGB(Extension):
|
||||
# Show final results
|
||||
self.disable_auto_write = False # Resume showing changes
|
||||
self.show()
|
||||
return self
|
||||
|
||||
def effect_knight(self):
|
||||
# Determine which LEDs should be lit up
|
||||
@@ -478,4 +533,69 @@ class RGB(Extension):
|
||||
self.disable_auto_write = False # Resume showing changes
|
||||
self.show()
|
||||
|
||||
return self
|
||||
def _rgb_tog(self, *args, **kwargs):
|
||||
if self.animation_mode == AnimationModes.STATIC:
|
||||
self.animation_mode = AnimationModes.STATIC_STANDBY
|
||||
self._do_update()
|
||||
if self.animation_mode == AnimationModes.STATIC_STANDBY:
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
self._do_update()
|
||||
if self.enable:
|
||||
self.off()
|
||||
self.enable = not self.enable
|
||||
|
||||
def _rgb_hui(self, *args, **kwargs):
|
||||
self.increase_hue()
|
||||
|
||||
def _rgb_hud(self, *args, **kwargs):
|
||||
self.decrease_hue()
|
||||
|
||||
def _rgb_sai(self, *args, **kwargs):
|
||||
self.increase_sat()
|
||||
|
||||
def _rgb_sad(self, *args, **kwargs):
|
||||
self.decrease_sat()
|
||||
|
||||
def _rgb_vai(self, *args, **kwargs):
|
||||
self.increase_val()
|
||||
|
||||
def _rgb_vad(self, *args, **kwargs):
|
||||
self.decrease_val()
|
||||
|
||||
def _rgb_ani(self, *args, **kwargs):
|
||||
self.increase_ani()
|
||||
|
||||
def _rgb_and(self, *args, **kwargs):
|
||||
self.decrease_ani()
|
||||
|
||||
def _rgb_mode_static(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
|
||||
def _rgb_mode_breathe(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.BREATHING
|
||||
|
||||
def _rgb_mode_breathe_rainbow(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.BREATHING_RAINBOW
|
||||
|
||||
def _rgb_mode_rainbow(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.RAINBOW
|
||||
|
||||
def _rgb_mode_swirl(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.SWIRL
|
||||
|
||||
def _rgb_mode_knight(self, *args, **kwargs):
|
||||
self.effect_init = True
|
||||
self.animation_mode = AnimationModes.KNIGHT
|
||||
|
||||
def _rgb_reset(self, *args, **kwargs):
|
||||
self.hue = self.hue_default
|
||||
self.sat = self.sat_default
|
||||
self.val = self.val_default
|
||||
if self.animation_mode == AnimationModes.STATIC_STANDBY:
|
||||
self.animation_mode = AnimationModes.STATIC
|
||||
self._do_update()
|
@@ -1,103 +0,0 @@
|
||||
import busio
|
||||
import gc
|
||||
|
||||
from kmk.extensions import Extension
|
||||
from kmk.kmktime import sleep_ms
|
||||
from kmk.matrix import intify_coordinate
|
||||
|
||||
|
||||
class SplitType:
|
||||
UART = 1
|
||||
I2C = 2 # unused
|
||||
ONEWIRE = 3 # unused
|
||||
BLE = 4 # unused
|
||||
|
||||
|
||||
class Split(Extension):
|
||||
def __init__(
|
||||
self,
|
||||
extra_data_pin=None,
|
||||
offsets=(),
|
||||
flip=False,
|
||||
side=None,
|
||||
stype=None,
|
||||
master_left=True,
|
||||
uart_flip=True,
|
||||
uart_pin=None,
|
||||
uart_timeout=20,
|
||||
):
|
||||
self.extra_data_pin = extra_data_pin
|
||||
self.split_offsets = offsets
|
||||
self.split_flip = flip
|
||||
self.split_side = side
|
||||
self.split_type = stype
|
||||
self.split_master_left = master_left
|
||||
self._uart = None
|
||||
self.uart_flip = uart_flip
|
||||
self.uart_pin = uart_pin
|
||||
self.uart_timeout = uart_timeout
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
if self.split_type is not None:
|
||||
try:
|
||||
# Working around https://github.com/adafruit/circuitpython/issues/1769
|
||||
keyboard._hid_helper_inst.create_report([]).send()
|
||||
self._is_master = True
|
||||
|
||||
# Sleep 2s so master portion doesn't "appear" to boot quicker than
|
||||
# dependent portions (which will take ~2s to time out on the HID send)
|
||||
sleep_ms(2000)
|
||||
except OSError:
|
||||
self._is_master = False
|
||||
|
||||
if self.split_flip and not self._is_master:
|
||||
keyboard.col_pins = list(reversed(self.col_pins))
|
||||
if self.split_side == 'Left':
|
||||
self.split_master_left = self._is_master
|
||||
elif self.split_side == 'Right':
|
||||
self.split_master_left = not self._is_master
|
||||
else:
|
||||
self._is_master = True
|
||||
|
||||
if self.uart_pin is not None:
|
||||
if self._is_master:
|
||||
self._uart = busio.UART(
|
||||
tx=None, rx=self.uart_pin, timeout=self.uart_timeout
|
||||
)
|
||||
else:
|
||||
self._uart = busio.UART(
|
||||
tx=self.uart_pin, rx=None, timeout=self.uart_timeout
|
||||
)
|
||||
|
||||
# Attempt to sanely guess a coord_mapping if one is not provided.
|
||||
if not keyboard.coord_mapping:
|
||||
keyboard.coord_mapping = []
|
||||
|
||||
rows_to_calc = len(keyboard.row_pins)
|
||||
cols_to_calc = len(keyboard.col_pins)
|
||||
|
||||
if self.split_offsets:
|
||||
rows_to_calc *= 2
|
||||
cols_to_calc *= 2
|
||||
|
||||
for ridx in range(rows_to_calc):
|
||||
for cidx in range(cols_to_calc):
|
||||
keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))
|
||||
|
||||
gc.collect()
|
||||
|
||||
def before_matrix_scan(self, keyboard_state):
|
||||
if self.split_type is not None and self._is_master:
|
||||
return self._receive_from_slave()
|
||||
|
||||
def after_matrix_scan(self, keyboard_state, matrix_update):
|
||||
if matrix_update is not None and not self._is_master:
|
||||
self._send_to_master(matrix_update)
|
||||
|
||||
def _send_to_master(self, update):
|
||||
if self.split_master_left:
|
||||
update[1] += self.split_offsets[update[0]]
|
||||
else:
|
||||
update[1] -= self.split_offsets[update[0]]
|
||||
if self._uart is not None:
|
||||
self._uart.write(update)
|
@@ -1,124 +0,0 @@
|
||||
from kmk.kmktime import ticks_diff, ticks_ms
|
||||
|
||||
|
||||
def df_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Switches the default layer
|
||||
'''
|
||||
state._active_layers[-1] = key.meta.layer
|
||||
return state
|
||||
|
||||
|
||||
def mo_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer, switches off when you let go
|
||||
'''
|
||||
state._active_layers.insert(0, key.meta.layer)
|
||||
return state
|
||||
|
||||
|
||||
def mo_released(key, state, KC, *args, **kwargs):
|
||||
# remove the first instance of the target layer
|
||||
# from the active list
|
||||
# under almost all normal use cases, this will
|
||||
# disable the layer (but preserve it if it was triggered
|
||||
# as a default layer, etc.)
|
||||
# this also resolves an issue where using DF() on a layer
|
||||
# triggered by MO() and then defaulting to the MO()'s layer
|
||||
# would result in no layers active
|
||||
try:
|
||||
del_idx = state._active_layers.index(key.meta.layer)
|
||||
del state._active_layers[del_idx]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def lm_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
state._hid_pending = True
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state._start_time['lm'] = ticks_ms()
|
||||
state._keys_pressed.add(key.meta.kc)
|
||||
return mo_pressed(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def lm_released(key, state, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
state._hid_pending = True
|
||||
state._keys_pressed.discard(key.meta.kc)
|
||||
state._start_time['lm'] = None
|
||||
return mo_released(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def lt_pressed(key, state, *args, **kwargs):
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state._start_time['lt'] = ticks_ms()
|
||||
return mo_pressed(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def lt_released(key, state, *args, **kwargs):
|
||||
# On keyup, check timer, and press key if needed.
|
||||
if state._start_time['lt'] and (
|
||||
ticks_diff(ticks_ms(), state._start_time['lt']) < state.tap_time
|
||||
):
|
||||
state._hid_pending = True
|
||||
state._tap_key(key.meta.kc)
|
||||
|
||||
mo_released(key, state, *args, **kwargs)
|
||||
state._start_time['lt'] = None
|
||||
return state
|
||||
|
||||
|
||||
def tg_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Toggles the layer (enables it if not active, and vise versa)
|
||||
'''
|
||||
# See mo_released for implementation details around this
|
||||
try:
|
||||
del_idx = state._active_layers.index(key.meta.layer)
|
||||
del state._active_layers[del_idx]
|
||||
except ValueError:
|
||||
state._active_layers.insert(0, key.meta.layer)
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def to_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Activates layer and deactivates all other layers
|
||||
'''
|
||||
state._active_layers.clear()
|
||||
state._active_layers.insert(0, key.meta.layer)
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def tt_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer if held, toggles it if tapped repeatedly
|
||||
'''
|
||||
# TODO Make this work with tap dance to function more correctly, but technically works.
|
||||
if state._start_time['tt'] is None:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state._start_time['tt'] = ticks_ms()
|
||||
return mo_pressed(key, state, *args, **kwargs)
|
||||
elif ticks_diff(ticks_ms(), state._start_time['tt']) < state.tap_time:
|
||||
state._start_time['tt'] = None
|
||||
return tg_pressed(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def tt_released(key, state, *args, **kwargs):
|
||||
tap_timed_out = ticks_diff(ticks_ms(), state._start_time['tt']) >= state.tap_time
|
||||
if state._start_time['tt'] is None or tap_timed_out:
|
||||
# On first press, works like MO. On second press, does nothing unless let up within
|
||||
# time window, then acts like TG.
|
||||
state._start_time['tt'] = None
|
||||
return mo_released(key, state, *args, **kwargs)
|
||||
|
||||
return state
|
@@ -1,23 +0,0 @@
|
||||
from kmk.kmktime import ticks_diff, ticks_ms
|
||||
|
||||
|
||||
def mt_pressed(key, state, *args, **kwargs):
|
||||
# Sets the timer start and acts like a modifier otherwise
|
||||
state._keys_pressed.add(key.meta.mods)
|
||||
|
||||
state._start_time['mod_tap'] = ticks_ms()
|
||||
return state
|
||||
|
||||
|
||||
def mt_released(key, state, *args, **kwargs):
|
||||
# On keyup, check timer, and press key if needed.
|
||||
state._keys_pressed.discard(key.meta.mods)
|
||||
timer_name = 'mod_tap'
|
||||
if state._start_time[timer_name] and (
|
||||
ticks_diff(ticks_ms(), state._start_time[timer_name]) < state.tap_time
|
||||
):
|
||||
state._hid_pending = True
|
||||
state._tap_key(key.meta.kc)
|
||||
|
||||
state._start_time[timer_name] = None
|
||||
return state
|
@@ -1,3 +1,5 @@
|
||||
import gc
|
||||
|
||||
from kmk.consts import UnicodeMode
|
||||
from kmk.handlers.stock import passthrough
|
||||
from kmk.keys import KC, make_key
|
||||
@@ -11,21 +13,21 @@ def get_wide_ordinal(char):
|
||||
return 0x10000 + (ord(char[0]) - 0xD800) * 0x400 + (ord(char[1]) - 0xDC00)
|
||||
|
||||
|
||||
def sequence_press_handler(key, state, KC, *args, **kwargs):
|
||||
old_keys_pressed = state._keys_pressed
|
||||
state._keys_pressed = set()
|
||||
def sequence_press_handler(key, keyboard, KC, *args, **kwargs):
|
||||
oldkeys_pressed = keyboard.keys_pressed
|
||||
keyboard.keys_pressed = set()
|
||||
|
||||
for ikey in key.meta.seq:
|
||||
if not getattr(ikey, 'no_press', None):
|
||||
state._process_key(ikey, True)
|
||||
state._send_hid()
|
||||
keyboard.process_key(ikey, True)
|
||||
keyboard._send_hid()
|
||||
if not getattr(ikey, 'no_release', None):
|
||||
state._process_key(ikey, False)
|
||||
state._send_hid()
|
||||
keyboard.process_key(ikey, False)
|
||||
keyboard._send_hid()
|
||||
|
||||
state._keys_pressed = old_keys_pressed
|
||||
keyboard.keys_pressed = oldkeys_pressed
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def simple_key_sequence(seq):
|
||||
@@ -59,10 +61,23 @@ RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),))
|
||||
|
||||
|
||||
def compile_unicode_string_sequences(string_table):
|
||||
for k, v in string_table.items():
|
||||
string_table[k] = unicode_string_sequence(v)
|
||||
'''
|
||||
Destructively convert ("compile") unicode strings into key sequences. This
|
||||
will, for RAM saving reasons, empty the input dictionary and trigger
|
||||
garbage collection.
|
||||
'''
|
||||
target = AttrDict()
|
||||
|
||||
return AttrDict(string_table)
|
||||
for k, v in string_table.items():
|
||||
target[k] = unicode_string_sequence(v)
|
||||
|
||||
# now loop through and kill the input dictionary to save RAM
|
||||
for k in target.keys():
|
||||
del string_table[k]
|
||||
|
||||
gc.collect()
|
||||
|
||||
return target
|
||||
|
||||
|
||||
def unicode_string_sequence(unistring):
|
||||
@@ -82,9 +97,6 @@ def generate_codepoint_keysym_seq(codepoint, expected_length=4):
|
||||
# Not sure how to send emojis on Mac/Windows like that,
|
||||
# though, since (for example) the Canadian flag is assembled
|
||||
# from two five-character codepoints, 1f1e8 and 1f1e6
|
||||
#
|
||||
# As a bonus, this function can be pretty useful for
|
||||
# leader dictionary keys as strings.
|
||||
seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))]
|
||||
|
||||
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
|
||||
@@ -93,47 +105,43 @@ def generate_codepoint_keysym_seq(codepoint, expected_length=4):
|
||||
return seq
|
||||
|
||||
|
||||
def generate_leader_dictionary_seq(string):
|
||||
return tuple(generate_codepoint_keysym_seq(string, 1))
|
||||
|
||||
|
||||
def unicode_codepoint_sequence(codepoints):
|
||||
kc_seqs = (generate_codepoint_keysym_seq(codepoint) for codepoint in codepoints)
|
||||
|
||||
kc_macros = [simple_key_sequence(kc_seq) for kc_seq in kc_seqs]
|
||||
|
||||
def _unicode_sequence(key, state, *args, **kwargs):
|
||||
if state.unicode_mode == UnicodeMode.IBUS:
|
||||
state._process_key(
|
||||
simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), True
|
||||
def _unicode_sequence(key, keyboard, *args, **kwargs):
|
||||
if keyboard.unicode_mode == UnicodeMode.IBUS:
|
||||
keyboard.process_key(
|
||||
simple_key_sequence(_ibus_unicode_sequence(kc_macros, keyboard)), True
|
||||
)
|
||||
elif state.unicode_mode == UnicodeMode.RALT:
|
||||
state._process_key(
|
||||
simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), True
|
||||
elif keyboard.unicode_mode == UnicodeMode.RALT:
|
||||
keyboard.process_key(
|
||||
simple_key_sequence(_ralt_unicode_sequence(kc_macros, keyboard)), True
|
||||
)
|
||||
elif state.unicode_mode == UnicodeMode.WINC:
|
||||
state._process_key(
|
||||
simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), True
|
||||
elif keyboard.unicode_mode == UnicodeMode.WINC:
|
||||
keyboard.process_key(
|
||||
simple_key_sequence(_winc_unicode_sequence(kc_macros, keyboard)), True
|
||||
)
|
||||
|
||||
return make_key(on_press=_unicode_sequence)
|
||||
|
||||
|
||||
def _ralt_unicode_sequence(kc_macros, state):
|
||||
def _ralt_unicode_sequence(kc_macros, keyboard):
|
||||
for kc_macro in kc_macros:
|
||||
yield RALT_DOWN_NO_RELEASE
|
||||
yield kc_macro
|
||||
yield RALT_UP_NO_PRESS
|
||||
|
||||
|
||||
def _ibus_unicode_sequence(kc_macros, state):
|
||||
def _ibus_unicode_sequence(kc_macros, keyboard):
|
||||
for kc_macro in kc_macros:
|
||||
yield IBUS_KEY_COMBO
|
||||
yield kc_macro
|
||||
yield ENTER_KEY
|
||||
|
||||
|
||||
def _winc_unicode_sequence(kc_macros, state):
|
||||
def _winc_unicode_sequence(kc_macros, keyboard):
|
||||
'''
|
||||
Send unicode sequence using WinCompose:
|
||||
|
||||
|
@@ -1,213 +1,129 @@
|
||||
from kmk.kmktime import sleep_ms
|
||||
|
||||
|
||||
def passthrough(key, state, *args, **kwargs):
|
||||
return state
|
||||
def passthrough(key, keyboard, *args, **kwargs):
|
||||
return keyboard
|
||||
|
||||
|
||||
def default_pressed(key, state, KC, coord_int=None, coord_raw=None):
|
||||
state._hid_pending = True
|
||||
def default_pressed(key, keyboard, KC, coord_int=None, coord_raw=None, *args, **kwargs):
|
||||
keyboard.hid_pending = True
|
||||
|
||||
if coord_int is not None:
|
||||
state._coord_keys_pressed[coord_int] = key
|
||||
keyboard._coordkeys_pressed[coord_int] = key
|
||||
|
||||
state._keys_pressed.add(key)
|
||||
keyboard.keys_pressed.add(key)
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def default_released(key, state, KC, coord_int=None, coord_raw=None):
|
||||
state._hid_pending = True
|
||||
state._keys_pressed.discard(key)
|
||||
def default_released(
|
||||
key, keyboard, KC, coord_int=None, coord_raw=None, *args, **kwargs # NOQA
|
||||
):
|
||||
keyboard.hid_pending = True
|
||||
keyboard.keys_pressed.discard(key)
|
||||
|
||||
if coord_int is not None:
|
||||
state._keys_pressed.discard(state._coord_keys_pressed.get(coord_int, None))
|
||||
state._coord_keys_pressed[coord_int] = None
|
||||
keyboard.keys_pressed.discard(keyboard._coordkeys_pressed.get(coord_int, None))
|
||||
keyboard._coordkeys_pressed[coord_int] = None
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def reset(*args, **kwargs):
|
||||
try:
|
||||
import machine
|
||||
import microcontroller
|
||||
|
||||
machine.reset()
|
||||
|
||||
except ImportError:
|
||||
import microcontroller
|
||||
|
||||
microcontroller.reset()
|
||||
microcontroller.reset()
|
||||
|
||||
|
||||
def bootloader(*args, **kwargs):
|
||||
try:
|
||||
import machine
|
||||
import microcontroller
|
||||
|
||||
machine.bootloader()
|
||||
|
||||
except ImportError:
|
||||
import microcontroller
|
||||
|
||||
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
|
||||
microcontroller.reset()
|
||||
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
|
||||
microcontroller.reset()
|
||||
|
||||
|
||||
def debug_pressed(key, state, KC, *args, **kwargs):
|
||||
if state.debug_enabled:
|
||||
def debug_pressed(key, keyboard, KC, *args, **kwargs):
|
||||
if keyboard.debug_enabled:
|
||||
print('DebugDisable()')
|
||||
else:
|
||||
print('DebugEnable()')
|
||||
|
||||
state.debug_enabled = not state.debug_enabled
|
||||
keyboard.debug_enabled = not keyboard.debug_enabled
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def gesc_pressed(key, state, KC, *args, **kwargs):
|
||||
def gesc_pressed(key, keyboard, KC, *args, **kwargs):
|
||||
GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI}
|
||||
|
||||
if GESC_TRIGGERS.intersection(state._keys_pressed):
|
||||
if GESC_TRIGGERS.intersection(keyboard.keys_pressed):
|
||||
# First, release GUI if already pressed
|
||||
state._send_hid()
|
||||
keyboard._send_hid()
|
||||
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
|
||||
state._keys_pressed.add(KC.GRAVE)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
keyboard.keys_pressed.add(KC.GRAVE)
|
||||
keyboard.hid_pending = True
|
||||
return keyboard
|
||||
|
||||
# else return KC_ESC
|
||||
state._keys_pressed.add(KC.ESCAPE)
|
||||
state._hid_pending = True
|
||||
keyboard.keys_pressed.add(KC.ESCAPE)
|
||||
keyboard.hid_pending = True
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def gesc_released(key, state, KC, *args, **kwargs):
|
||||
state._keys_pressed.discard(KC.ESCAPE)
|
||||
state._keys_pressed.discard(KC.GRAVE)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
def gesc_released(key, keyboard, KC, *args, **kwargs):
|
||||
keyboard.keys_pressed.discard(KC.ESCAPE)
|
||||
keyboard.keys_pressed.discard(KC.GRAVE)
|
||||
keyboard.hid_pending = True
|
||||
return keyboard
|
||||
|
||||
|
||||
def bkdl_pressed(key, state, KC, *args, **kwargs):
|
||||
def bkdl_pressed(key, keyboard, KC, *args, **kwargs):
|
||||
BKDL_TRIGGERS = {KC.LGUI, KC.RGUI}
|
||||
|
||||
if BKDL_TRIGGERS.intersection(state._keys_pressed):
|
||||
state._send_hid()
|
||||
state._keys_pressed.add(KC.DEL)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
if BKDL_TRIGGERS.intersection(keyboard.keys_pressed):
|
||||
keyboard._send_hid()
|
||||
keyboard.keys_pressed.add(KC.DEL)
|
||||
keyboard.hid_pending = True
|
||||
return keyboard
|
||||
|
||||
# else return KC_ESC
|
||||
state._keys_pressed.add(KC.BKSP)
|
||||
state._hid_pending = True
|
||||
keyboard.keys_pressed.add(KC.BKSP)
|
||||
keyboard.hid_pending = True
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def bkdl_released(key, state, KC, *args, **kwargs):
|
||||
state._keys_pressed.discard(KC.BKSP)
|
||||
state._keys_pressed.discard(KC.DEL)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
def bkdl_released(key, keyboard, KC, *args, **kwargs):
|
||||
keyboard.keys_pressed.discard(KC.BKSP)
|
||||
keyboard.keys_pressed.discard(KC.DEL)
|
||||
keyboard.hid_pending = True
|
||||
return keyboard
|
||||
|
||||
|
||||
def sleep_pressed(key, state, KC, *args, **kwargs):
|
||||
def sleep_pressed(key, keyboard, KC, *args, **kwargs):
|
||||
sleep_ms(key.meta.ms)
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def uc_mode_pressed(key, state, *args, **kwargs):
|
||||
state.unicode_mode = key.meta.mode
|
||||
def uc_mode_pressed(key, keyboard, *args, **kwargs):
|
||||
keyboard.unicode_mode = key.meta.mode
|
||||
|
||||
return state
|
||||
return keyboard
|
||||
|
||||
|
||||
def td_pressed(key, state, *args, **kwargs):
|
||||
return state._process_tap_dance(key, True)
|
||||
def td_pressed(key, keyboard, *args, **kwargs):
|
||||
return keyboard._process_tap_dance(key, True)
|
||||
|
||||
|
||||
def td_released(key, state, *args, **kwargs):
|
||||
return state._process_tap_dance(key, False)
|
||||
def td_released(key, keyboard, *args, **kwargs):
|
||||
return keyboard._process_tap_dance(key, False)
|
||||
|
||||
|
||||
def rgb_tog(key, state, *args, **kwargs):
|
||||
if state.pixels.animation_mode == 'static_standby':
|
||||
state.pixels.animation_mode = 'static'
|
||||
state.pixels.enabled = not state.pixels.enabled
|
||||
return state
|
||||
|
||||
|
||||
def rgb_hui(key, state, *args, **kwargs):
|
||||
state.pixels.increase_hue()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_hud(key, state, *args, **kwargs):
|
||||
state.pixels.decrease_hue()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_sai(key, state, *args, **kwargs):
|
||||
state.pixels.increase_sat()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_sad(key, state, *args, **kwargs):
|
||||
state.pixels.decrease_sat()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_vai(key, state, *args, **kwargs):
|
||||
state.pixels.increase_val()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_vad(key, state, *args, **kwargs):
|
||||
state.pixels.decrease_val()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_ani(key, state, *args, **kwargs):
|
||||
state.pixels.increase_ani()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_and(key, state, *args, **kwargs):
|
||||
state.pixels.decrease_ani()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_static(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'static'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_breathe(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'breathing'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_breathe_rainbow(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'breathing_rainbow'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_rainbow(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'rainbow'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_swirl(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'swirl'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_knight(key, state, *args, **kwargs):
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'knight'
|
||||
return state
|
||||
def hid_switch(key, keyboard, *args, **kwargs):
|
||||
keyboard.hid_type, keyboard.secondary_hid_type = (
|
||||
keyboard.secondary_hid_type,
|
||||
keyboard.hid_type,
|
||||
)
|
||||
keyboard._init_hid()
|
||||
return keyboard
|
||||
|
115
kmk/hid.py
115
kmk/hid.py
@@ -1,12 +1,22 @@
|
||||
import usb_hid
|
||||
from micropython import const
|
||||
|
||||
from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey
|
||||
from storage import getmount
|
||||
|
||||
try:
|
||||
from adafruit_ble import BLERadio
|
||||
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
|
||||
from adafruit_ble.services.standard.hid import HIDService
|
||||
except ImportError:
|
||||
# BLE not supported on this platform
|
||||
pass
|
||||
|
||||
|
||||
class HIDModes:
|
||||
NOOP = 0 # currently unused; for testing?
|
||||
USB = 1
|
||||
BLE = 2 # currently unused; for bluetooth
|
||||
BLE = 2
|
||||
|
||||
ALL_MODES = (NOOP, USB, BLE)
|
||||
|
||||
@@ -41,7 +51,7 @@ HID_REPORT_SIZES = {
|
||||
class AbstractHID:
|
||||
REPORT_BYTES = 8
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self):
|
||||
self._evt = bytearray(self.REPORT_BYTES)
|
||||
self.report_device = memoryview(self._evt)[0:1]
|
||||
self.report_device[0] = HIDReportTypes.KEYBOARD
|
||||
@@ -55,7 +65,7 @@ class AbstractHID:
|
||||
self.report_mods = memoryview(self._evt)[1:2]
|
||||
self.report_non_mods = memoryview(self._evt)[3:]
|
||||
|
||||
self.post_init(**kwargs)
|
||||
self.post_init()
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(REPORT_BYTES={})'.format(self.__class__.__name__, self.REPORT_BYTES)
|
||||
@@ -191,7 +201,7 @@ class AbstractHID:
|
||||
class USBHID(AbstractHID):
|
||||
REPORT_BYTES = 9
|
||||
|
||||
def post_init(self, **kwargs):
|
||||
def post_init(self):
|
||||
self.devices = {}
|
||||
|
||||
for device in usb_hid.devices:
|
||||
@@ -221,3 +231,100 @@ class USBHID(AbstractHID):
|
||||
return self.devices[reporting_device_const].send_report(
|
||||
evt[1 : HID_REPORT_SIZES[reporting_device_const] + 1]
|
||||
)
|
||||
|
||||
|
||||
class BLEHID(AbstractHID):
|
||||
BLE_APPEARANCE_HID_KEYBOARD = const(961)
|
||||
# Hardcoded in CPy
|
||||
MAX_CONNECTIONS = const(2)
|
||||
|
||||
def post_init(self, ble_name=str(getmount('/').label), **kwargs):
|
||||
self.conn_id = -1
|
||||
|
||||
self.ble = BLERadio()
|
||||
self.ble.name = ble_name
|
||||
self.hid = HIDService()
|
||||
self.hid.protocol_mode = 0 # Boot protocol
|
||||
|
||||
# Security-wise this is not right. While you're away someone turns
|
||||
# on your keyboard and they can pair with it nice and clean and then
|
||||
# listen to keystrokes.
|
||||
# On the other hand we don't have LESC so it's like shouting your
|
||||
# keystrokes in the air
|
||||
if not self.ble.connected or not self.hid.devices:
|
||||
self.start_advertising()
|
||||
|
||||
self.conn_id = 0
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
'''Search through the provided list of devices to find the ones with the
|
||||
send_report attribute.'''
|
||||
if not self.ble.connected:
|
||||
return []
|
||||
|
||||
result = []
|
||||
# Security issue:
|
||||
# This introduces a race condition. Let's say you have 2 active
|
||||
# connections: Alice and Bob - Alice is connection 1 and Bob 2.
|
||||
# Now Chuck who has already paired with the device in the past
|
||||
# (this assumption is needed only in the case of LESC)
|
||||
# wants to gather the keystrokes you send to Alice. You have
|
||||
# selected right now to talk to Alice (1) and you're typing a secret.
|
||||
# If Chuck kicks Alice off and is quick enough to connect to you,
|
||||
# which means quicker than the running interval of this function,
|
||||
# he'll be earlier in the `self.hid.devices` so will take over the
|
||||
# selected 1 position in the resulted array.
|
||||
# If no LESC is in place, Chuck can sniff the keystrokes anyway
|
||||
for device in self.hid.devices:
|
||||
if hasattr(device, 'send_report'):
|
||||
result.append(device)
|
||||
|
||||
return result
|
||||
|
||||
def _check_connection(self):
|
||||
devices = self.devices
|
||||
if not devices:
|
||||
return False
|
||||
|
||||
if self.conn_id >= len(devices):
|
||||
self.conn_id = len(devices) - 1
|
||||
|
||||
if self.conn_id < 0:
|
||||
return False
|
||||
|
||||
if not devices[self.conn_id]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def hid_send(self, evt):
|
||||
if not self._check_connection():
|
||||
return
|
||||
|
||||
device = self.devices[self.conn_id]
|
||||
|
||||
while len(evt) < len(device._characteristic.value) + 1:
|
||||
evt.append(0)
|
||||
|
||||
return device.send_report(evt[1:])
|
||||
|
||||
def clear_bonds(self):
|
||||
import _bleio
|
||||
|
||||
_bleio.adapter.erase_bonding()
|
||||
|
||||
def next_connection(self):
|
||||
self.conn_id = (self.conn_id + 1) % len(self.devices)
|
||||
|
||||
def previous_connection(self):
|
||||
self.conn_id = (self.conn_id - 1) % len(self.devices)
|
||||
|
||||
def start_advertising(self):
|
||||
advertisement = ProvideServicesAdvertisement(self.hid)
|
||||
advertisement.appearance = self.BLE_APPEARANCE_HID_KEYBOARD
|
||||
|
||||
self.ble.start_advertising(advertisement)
|
||||
|
||||
def stop_advertising(self):
|
||||
self.ble.stop_advertising()
|
||||
|
81
kmk/keys.py
81
kmk/keys.py
@@ -1,22 +1,20 @@
|
||||
import kmk.handlers.layers as layers
|
||||
import kmk.handlers.modtap as modtap
|
||||
from micropython import const
|
||||
|
||||
import kmk.handlers.stock as handlers
|
||||
from kmk.consts import UnicodeMode
|
||||
from kmk.key_validators import (
|
||||
key_seq_sleep_validator,
|
||||
layer_key_validator,
|
||||
mod_tap_validator,
|
||||
tap_dance_key_validator,
|
||||
unicode_mode_key_validator,
|
||||
)
|
||||
from kmk.types import AttrDict, UnicodeModeKeyMeta
|
||||
|
||||
FIRST_KMK_INTERNAL_KEY = 1000
|
||||
FIRST_KMK_INTERNAL_KEY = const(1000)
|
||||
NEXT_AVAILABLE_KEY = 1000
|
||||
|
||||
KEY_SIMPLE = 0
|
||||
KEY_MODIFIER = 1
|
||||
KEY_CONSUMER = 2
|
||||
KEY_SIMPLE = const(0)
|
||||
KEY_MODIFIER = const(1)
|
||||
KEY_CONSUMER = const(2)
|
||||
|
||||
# Global state, will be filled in througout this file, and
|
||||
# anywhere the user creates custom keys
|
||||
@@ -62,7 +60,7 @@ class Key:
|
||||
def __repr__(self):
|
||||
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
|
||||
|
||||
def _on_press(self, state, coord_int, coord_raw):
|
||||
def on_press(self, state, coord_int, coord_raw):
|
||||
for fn in self._pre_press_handlers:
|
||||
if not fn(self, state, KC, coord_int, coord_raw):
|
||||
return None
|
||||
@@ -74,7 +72,7 @@ class Key:
|
||||
|
||||
return ret
|
||||
|
||||
def _on_release(self, state, coord_int, coord_raw):
|
||||
def on_release(self, state, coord_int, coord_raw):
|
||||
for fn in self._pre_release_handlers:
|
||||
if not fn(self, state, KC, coord_int, coord_raw):
|
||||
return None
|
||||
@@ -202,7 +200,7 @@ class ModifierKey(Key):
|
||||
# FIXME this is atrocious to read. Please, please, please, strike down upon
|
||||
# this with great vengeance and furious anger.
|
||||
|
||||
FAKE_CODE = -1
|
||||
FAKE_CODE = const(-1)
|
||||
|
||||
def __call__(self, modified_code=None, no_press=None, no_release=None):
|
||||
if modified_code is None and no_press is None and no_release is None:
|
||||
@@ -610,66 +608,6 @@ make_key(
|
||||
on_press=handlers.gesc_pressed,
|
||||
on_release=handlers.gesc_released,
|
||||
)
|
||||
make_key(names=('RGB_TOG',), on_press=handlers.rgb_tog)
|
||||
make_key(names=('RGB_HUI',), on_press=handlers.rgb_hui)
|
||||
make_key(names=('RGB_HUD',), on_press=handlers.rgb_hud)
|
||||
make_key(names=('RGB_SAI',), on_press=handlers.rgb_sai)
|
||||
make_key(names=('RGB_SAD',), on_press=handlers.rgb_sad)
|
||||
make_key(names=('RGB_VAI',), on_press=handlers.rgb_vai)
|
||||
make_key(names=('RGB_VAD',), on_press=handlers.rgb_vad)
|
||||
make_key(names=('RGB_ANI',), on_press=handlers.rgb_ani)
|
||||
make_key(names=('RGB_AND',), on_press=handlers.rgb_and)
|
||||
make_key(names=('RGB_MODE_PLAIN', 'RGB_M_P'), on_press=handlers.rgb_mode_static)
|
||||
make_key(names=('RGB_MODE_BREATHE', 'RGB_M_B'), on_press=handlers.rgb_mode_breathe)
|
||||
make_key(names=('RGB_MODE_RAINBOW', 'RGB_M_R'), on_press=handlers.rgb_mode_rainbow)
|
||||
make_key(
|
||||
names=('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'),
|
||||
on_press=handlers.rgb_mode_breathe_rainbow,
|
||||
)
|
||||
make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=handlers.rgb_mode_swirl)
|
||||
make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=handlers.rgb_mode_knight)
|
||||
|
||||
# Layers
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('MO',),
|
||||
on_press=layers.mo_pressed,
|
||||
on_release=layers.mo_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('DF',), on_press=layers.df_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('LM',),
|
||||
on_press=layers.lm_pressed,
|
||||
on_release=layers.lm_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('LT',),
|
||||
on_press=layers.lt_pressed,
|
||||
on_release=layers.lt_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('TG',), on_press=layers.tg_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('TO',), on_press=layers.to_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('TT',),
|
||||
on_press=layers.tt_pressed,
|
||||
on_release=layers.tt_released,
|
||||
)
|
||||
|
||||
make_argumented_key(
|
||||
validator=mod_tap_validator,
|
||||
names=('MT',),
|
||||
on_press=modtap.mt_pressed,
|
||||
on_release=modtap.mt_released,
|
||||
)
|
||||
|
||||
# A dummy key to trigger a sleep_ms call in a sequence of other keys in a
|
||||
# simple sequence macro.
|
||||
@@ -711,3 +649,4 @@ make_argumented_key(
|
||||
on_press=handlers.td_pressed,
|
||||
on_release=handlers.td_released,
|
||||
)
|
||||
make_key(names=('HID_SWITCH', 'HID'), on_press=handlers.hid_switch)
|
||||
|
@@ -1,11 +1,3 @@
|
||||
# There's a chance doing preload RAM hacks this late will cause recursion
|
||||
# errors, but we'll see. I'd rather do it here than require everyone copy-paste
|
||||
# a line into their keymaps.
|
||||
import kmk.preload_imports # isort:skip # NOQA
|
||||
|
||||
import gc
|
||||
|
||||
from kmk import rgb
|
||||
from kmk.consts import KMK_RELEASE, UnicodeMode
|
||||
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
|
||||
from kmk.keys import KC
|
||||
@@ -14,62 +6,80 @@ from kmk.matrix import MatrixScanner, intify_coordinate
|
||||
from kmk.types import TapDanceKeyMeta
|
||||
|
||||
|
||||
class Sandbox:
|
||||
matrix_update = None
|
||||
secondary_matrix_update = None
|
||||
active_layers = None
|
||||
|
||||
|
||||
class KMKKeyboard:
|
||||
#####
|
||||
# User-configurable
|
||||
debug_enabled = False
|
||||
|
||||
keymap = None
|
||||
keymap = []
|
||||
coord_mapping = None
|
||||
|
||||
row_pins = None
|
||||
col_pins = None
|
||||
diode_orientation = None
|
||||
matrix = None
|
||||
matrix_scanner = MatrixScanner
|
||||
uart_buffer = []
|
||||
|
||||
unicode_mode = UnicodeMode.NOOP
|
||||
tap_time = 300
|
||||
|
||||
# RGB config
|
||||
rgb_pixel_pin = None
|
||||
rgb_config = rgb.rgb_config
|
||||
modules = []
|
||||
extensions = []
|
||||
sandbox = Sandbox()
|
||||
|
||||
#####
|
||||
# Internal State
|
||||
_keys_pressed = set()
|
||||
_coord_keys_pressed = {}
|
||||
_hid_pending = False
|
||||
keys_pressed = set()
|
||||
_coordkeys_pressed = {}
|
||||
hid_type = HIDModes.USB
|
||||
secondary_hid_type = None
|
||||
_hid_helper = None
|
||||
hid_pending = False
|
||||
state_layer_key = None
|
||||
matrix_update = None
|
||||
secondary_matrix_update = None
|
||||
_matrix_modify = None
|
||||
state_changed = False
|
||||
_old_timeouts_len = None
|
||||
_new_timeouts_len = None
|
||||
_trigger_powersave_enable = False
|
||||
_trigger_powersave_disable = False
|
||||
i2c_deinit_count = 0
|
||||
|
||||
# this should almost always be PREpended to, replaces
|
||||
# former use of reversed_active_layers which had pointless
|
||||
# overhead (the underlying list was never used anyway)
|
||||
_active_layers = [0]
|
||||
active_layers = [0]
|
||||
|
||||
_start_time = {'lt': None, 'tg': None, 'tt': None, 'lm': None}
|
||||
_timeouts = {}
|
||||
_tapping = False
|
||||
_tap_dance_counts = {}
|
||||
_tap_side_effects = {}
|
||||
|
||||
# on some M4 setups (such as klardotsh/klarank_feather_m4, CircuitPython
|
||||
# 6.0rc1) this runs out of RAM every cycle and takes down the board. no
|
||||
# real known fix yet other than turning off debug, but M4s have always been
|
||||
# tight on RAM so....
|
||||
def __repr__(self):
|
||||
return (
|
||||
'KMKKeyboard('
|
||||
'debug_enabled={} '
|
||||
'keymap=truncated '
|
||||
'coord_mapping=truncated '
|
||||
'row_pins=truncated '
|
||||
'col_pins=truncated '
|
||||
'diode_orientation={} '
|
||||
'matrix_scanner={} '
|
||||
'unicode_mode={} '
|
||||
'tap_time={} '
|
||||
'hid_helper={} '
|
||||
'_hid_helper={} '
|
||||
'keys_pressed={} '
|
||||
'coord_keys_pressed={} '
|
||||
'coordkeys_pressed={} '
|
||||
'hid_pending={} '
|
||||
'active_layers={} '
|
||||
'start_time={} '
|
||||
'timeouts={} '
|
||||
'tapping={} '
|
||||
'tap_dance_counts={} '
|
||||
@@ -77,21 +87,16 @@ class KMKKeyboard:
|
||||
')'
|
||||
).format(
|
||||
self.debug_enabled,
|
||||
# self.keymap,
|
||||
# self.coord_mapping,
|
||||
# self.row_pins,
|
||||
# self.col_pins,
|
||||
self.diode_orientation,
|
||||
self.matrix_scanner,
|
||||
self.unicode_mode,
|
||||
self.tap_time,
|
||||
self.hid_helper.__name__,
|
||||
self._hid_helper,
|
||||
# internal state
|
||||
self._keys_pressed,
|
||||
self._coord_keys_pressed,
|
||||
self._hid_pending,
|
||||
self._active_layers,
|
||||
self._start_time,
|
||||
self.keys_pressed,
|
||||
self._coordkeys_pressed,
|
||||
self.hid_pending,
|
||||
self.active_layers,
|
||||
self._timeouts,
|
||||
self._tapping,
|
||||
self._tap_dance_counts,
|
||||
@@ -99,126 +104,81 @@ class KMKKeyboard:
|
||||
)
|
||||
|
||||
def _print_debug_cycle(self, init=False):
|
||||
pre_alloc = gc.mem_alloc()
|
||||
pre_free = gc.mem_free()
|
||||
|
||||
if self.debug_enabled:
|
||||
if init:
|
||||
print('KMKInit(release={})'.format(KMK_RELEASE))
|
||||
|
||||
print(self)
|
||||
print(self)
|
||||
print(
|
||||
'GCStats(pre_alloc={} pre_free={} alloc={} free={})'.format(
|
||||
pre_alloc, pre_free, gc.mem_alloc(), gc.mem_free()
|
||||
)
|
||||
)
|
||||
|
||||
def _send_hid(self):
|
||||
self._hid_helper_inst.create_report(self._keys_pressed).send()
|
||||
self._hid_pending = False
|
||||
self._hid_helper.create_report(self.keys_pressed).send()
|
||||
self.hid_pending = False
|
||||
|
||||
def _handle_matrix_report(self, update=None):
|
||||
if update is not None:
|
||||
self._on_matrix_changed(update[0], update[1], update[2])
|
||||
self.state_changed = True
|
||||
|
||||
def _receive_from_initiator(self):
|
||||
if self.uart is not None and self.uart.in_waiting > 0 or self.uart_buffer:
|
||||
if self.uart.in_waiting >= 60:
|
||||
# This is a dirty hack to prevent crashes in unrealistic cases
|
||||
import microcontroller
|
||||
|
||||
microcontroller.reset()
|
||||
|
||||
while self._uart.in_waiting >= 3:
|
||||
self.uart_buffer.append(self._uart.read(3))
|
||||
if self.uart_buffer:
|
||||
update = bytearray(self.uart_buffer.pop(0))
|
||||
|
||||
# Built in debug mode switch
|
||||
if update == b'DEB':
|
||||
print(self._uart.readline())
|
||||
return None
|
||||
return update
|
||||
|
||||
return None
|
||||
|
||||
def _send_debug(self, message):
|
||||
'''
|
||||
Prepends DEB and appends a newline to allow debug messages to
|
||||
be detected and handled differently than typical keypresses.
|
||||
:param message: Debug message
|
||||
'''
|
||||
if self._uart is not None:
|
||||
self._uart.write('DEB')
|
||||
self._uart.write(message, '\n')
|
||||
|
||||
#####
|
||||
# SPLICE: INTERNAL STATE
|
||||
# FIXME CLEAN THIS
|
||||
#####
|
||||
|
||||
def _find_key_in_map(self, row, col):
|
||||
ic = intify_coordinate(row, col)
|
||||
|
||||
def _find_key_in_map(self, int_coord, row, col):
|
||||
self.state_layer_key = None
|
||||
try:
|
||||
idx = self.coord_mapping.index(ic)
|
||||
idx = self.coord_mapping.index(int_coord)
|
||||
except ValueError:
|
||||
if self.debug_enabled:
|
||||
print(
|
||||
'CoordMappingNotFound(ic={}, row={}, col={})'.format(ic, row, col)
|
||||
'CoordMappingNotFound(ic={}, row={}, col={})'.format(
|
||||
int_coord, row, col
|
||||
)
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
for layer in self._active_layers:
|
||||
layer_key = self.keymap[layer][idx]
|
||||
for layer in self.active_layers:
|
||||
self.state_layer_key = self.keymap[layer][idx]
|
||||
|
||||
if not layer_key or layer_key == KC.TRNS:
|
||||
if not self.state_layer_key or self.state_layer_key == KC.TRNS:
|
||||
continue
|
||||
|
||||
if self.debug_enabled:
|
||||
print('KeyResolution(key={})'.format(layer_key))
|
||||
print('KeyResolution(key={})'.format(self.state_layer_key))
|
||||
|
||||
return layer_key
|
||||
return self.state_layer_key
|
||||
|
||||
def _on_matrix_changed(self, row, col, is_pressed):
|
||||
if self.debug_enabled:
|
||||
print('MatrixChange(col={} row={} pressed={})'.format(col, row, is_pressed))
|
||||
|
||||
int_coord = intify_coordinate(row, col)
|
||||
kc_changed = self._find_key_in_map(row, col)
|
||||
kc_changed = self._find_key_in_map(int_coord, row, col)
|
||||
|
||||
if kc_changed is None:
|
||||
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
|
||||
return self
|
||||
|
||||
return self._process_key(kc_changed, is_pressed, int_coord, (row, col))
|
||||
return self.process_key(kc_changed, is_pressed, int_coord, (row, col))
|
||||
|
||||
def _process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
|
||||
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
|
||||
if self._tapping and not isinstance(key.meta, TapDanceKeyMeta):
|
||||
self._process_tap_dance(key, is_pressed)
|
||||
else:
|
||||
if is_pressed:
|
||||
key._on_press(self, coord_int, coord_raw)
|
||||
key.on_press(self, coord_int, coord_raw)
|
||||
else:
|
||||
key._on_release(self, coord_int, coord_raw)
|
||||
key.on_release(self, coord_int, coord_raw)
|
||||
|
||||
return self
|
||||
|
||||
def _remove_key(self, keycode):
|
||||
self._keys_pressed.discard(keycode)
|
||||
return self._process_key(keycode, False)
|
||||
def remove_key(self, keycode):
|
||||
self.keys_pressed.discard(keycode)
|
||||
return self.process_key(keycode, False)
|
||||
|
||||
def _add_key(self, keycode):
|
||||
self._keys_pressed.add(keycode)
|
||||
return self._process_key(keycode, True)
|
||||
def add_key(self, keycode):
|
||||
self.keys_pressed.add(keycode)
|
||||
return self.process_key(keycode, True)
|
||||
|
||||
def _tap_key(self, keycode):
|
||||
self._add_key(keycode)
|
||||
def tap_key(self, keycode):
|
||||
self.add_key(keycode)
|
||||
# On the next cycle, we'll remove the key.
|
||||
self._set_timeout(False, lambda: self._remove_key(keycode))
|
||||
self.set_timeout(False, lambda: self.remove_key(keycode))
|
||||
|
||||
return self
|
||||
|
||||
@@ -239,7 +199,7 @@ class KMKKeyboard:
|
||||
or not self._tap_dance_counts[changed_key]
|
||||
):
|
||||
self._tap_dance_counts[changed_key] = 1
|
||||
self._set_timeout(
|
||||
self.set_timeout(
|
||||
self.tap_time, lambda: self._end_tap_dance(changed_key)
|
||||
)
|
||||
self._tapping = True
|
||||
@@ -263,19 +223,19 @@ class KMKKeyboard:
|
||||
v = self._tap_dance_counts[td_key] - 1
|
||||
|
||||
if v >= 0:
|
||||
if td_key in self._keys_pressed:
|
||||
if td_key in self.keys_pressed:
|
||||
key_to_press = td_key.codes[v]
|
||||
self._add_key(key_to_press)
|
||||
self.add_key(key_to_press)
|
||||
self._tap_side_effects[td_key] = key_to_press
|
||||
self._hid_pending = True
|
||||
self.hid_pending = True
|
||||
else:
|
||||
if self._tap_side_effects[td_key]:
|
||||
self._remove_key(self._tap_side_effects[td_key])
|
||||
self.remove_key(self._tap_side_effects[td_key])
|
||||
self._tap_side_effects[td_key] = None
|
||||
self._hid_pending = True
|
||||
self.hid_pending = True
|
||||
self._cleanup_tap_dance(td_key)
|
||||
else:
|
||||
self._tap_key(td_key.codes[v])
|
||||
self.tap_key(td_key.codes[v])
|
||||
self._cleanup_tap_dance(td_key)
|
||||
|
||||
return self
|
||||
@@ -285,7 +245,7 @@ class KMKKeyboard:
|
||||
self._tapping = any(count > 0 for count in self._tap_dance_counts.values())
|
||||
return self
|
||||
|
||||
def _set_timeout(self, after_ticks, callback):
|
||||
def set_timeout(self, after_ticks, callback):
|
||||
if after_ticks is False:
|
||||
# We allow passing False as an implicit "run this on the next process timeouts cycle"
|
||||
timeout_key = ticks_ms()
|
||||
@@ -319,11 +279,6 @@ class KMKKeyboard:
|
||||
|
||||
return self
|
||||
|
||||
#####
|
||||
# SPLICE END: INTERNAL STATE
|
||||
# TODO FIXME REMOVE THIS
|
||||
#####
|
||||
|
||||
def _init_sanity_check(self):
|
||||
'''
|
||||
Ensure the provided configuration is *probably* bootable
|
||||
@@ -347,9 +302,7 @@ class KMKKeyboard:
|
||||
To save RAM on boards that don't use Split, we don't import Split
|
||||
and do an isinstance check, but instead do string detection
|
||||
'''
|
||||
if any(
|
||||
x.__class__.__module__ == 'kmk.extensions.split' for x in self._extensions
|
||||
):
|
||||
if any(x.__class__.__module__ == 'kmk.modules.split' for x in self.modules):
|
||||
return
|
||||
|
||||
if not self.coord_mapping:
|
||||
@@ -364,25 +317,14 @@ class KMKKeyboard:
|
||||
|
||||
def _init_hid(self):
|
||||
if self.hid_type == HIDModes.NOOP:
|
||||
self.hid_helper = AbstractHID
|
||||
self._hid_helper = AbstractHID
|
||||
elif self.hid_type == HIDModes.USB:
|
||||
try:
|
||||
from kmk.hid import USBHID
|
||||
|
||||
self.hid_helper = USBHID
|
||||
except ImportError:
|
||||
self.hid_helper = AbstractHID
|
||||
print('USB HID is unsupported ')
|
||||
self._hid_helper = USBHID
|
||||
elif self.hid_type == HIDModes.BLE:
|
||||
try:
|
||||
from kmk.ble import BLEHID
|
||||
|
||||
self.hid_helper = BLEHID
|
||||
except ImportError:
|
||||
self.hid_helper = AbstractHID
|
||||
print('Bluetooth is unsupported ')
|
||||
|
||||
self._hid_helper_inst = self.hid_helper(**kwargs)
|
||||
self._hid_helper = BLEHID
|
||||
else:
|
||||
self._hid_helper = AbstractHID
|
||||
self._hid_helper = self._hid_helper()
|
||||
|
||||
def _init_matrix(self):
|
||||
self.matrix = MatrixScanner(
|
||||
@@ -394,28 +336,115 @@ class KMKKeyboard:
|
||||
|
||||
return self
|
||||
|
||||
def go(self, hid_type=HIDModes.USB, **kwargs):
|
||||
self._extensions = [] + getattr(self, 'extensions', [])
|
||||
def before_matrix_scan(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.before_matrix_scan(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run pre matrix function in module: ', err, module)
|
||||
|
||||
try:
|
||||
del self.extensions
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
gc.collect()
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.before_matrix_scan(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run pre matrix function in extension: ', err, ext)
|
||||
|
||||
def after_matrix_scan(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.after_matrix_scan(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post matrix function in module: ', err, module)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.after_matrix_scan(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post matrix function in extension: ', err, ext)
|
||||
|
||||
def before_hid_send(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.before_hid_send(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run pre hid function in module: ', err, module)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.before_hid_send(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run pre hid function in extension: ', err, ext)
|
||||
|
||||
def after_hid_send(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.after_hid_send(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in module: ', err, module)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.after_hid_send(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in extension: ', err, ext)
|
||||
|
||||
def powersave_enable(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.on_powersave_enable(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in module: ', err, module)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.on_powersave_enable(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in extension: ', err, ext)
|
||||
|
||||
def powersave_disable(self):
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.on_powersave_disable(self)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in module: ', err, module)
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.on_powersave_disable(self.sandbox)
|
||||
except Exception as err:
|
||||
if self.debug_enabled:
|
||||
print('Failed to run post hid function in extension: ', err, ext)
|
||||
|
||||
def go(self, hid_type=HIDModes.USB, secondary_hid_type=None, **kwargs):
|
||||
self.hid_type = hid_type
|
||||
self.secondary_hid_type = secondary_hid_type
|
||||
|
||||
self._init_sanity_check()
|
||||
self._init_coord_mapping()
|
||||
self._init_hid()
|
||||
|
||||
for ext in self._extensions:
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.during_bootup(self)
|
||||
except Exception:
|
||||
if self.debug_enabled:
|
||||
print('Failed to load module', module)
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.during_bootup(self)
|
||||
except Exception:
|
||||
# TODO FIXME log the exceptions or something
|
||||
pass
|
||||
if self.debug_enabled:
|
||||
print('Failed to load extention', ext)
|
||||
|
||||
self._init_matrix()
|
||||
|
||||
@@ -423,48 +452,43 @@ class KMKKeyboard:
|
||||
|
||||
while True:
|
||||
self.state_changed = False
|
||||
self.sandbox.active_layers = self.active_layers.copy()
|
||||
|
||||
for ext in self._extensions:
|
||||
try:
|
||||
self._handle_matrix_report(ext.before_matrix_scan(self))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.before_matrix_scan()
|
||||
|
||||
matrix_update = self.matrix.scan_for_changes()
|
||||
self._handle_matrix_report(matrix_update)
|
||||
self.matrix_update = (
|
||||
self.sandbox.matrix_update
|
||||
) = self.matrix.scan_for_changes()
|
||||
self.sandbox.secondary_matrix_update = self.secondary_matrix_update
|
||||
|
||||
for ext in self._extensions:
|
||||
try:
|
||||
ext.after_matrix_scan(self, matrix_update)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.after_matrix_scan()
|
||||
|
||||
for ext in self._extensions:
|
||||
try:
|
||||
ext.before_hid_send(self)
|
||||
except Exception:
|
||||
# TODO FIXME log the exceptions or something
|
||||
pass
|
||||
self._handle_matrix_report(self.secondary_matrix_update)
|
||||
self.secondary_matrix_update = None
|
||||
self._handle_matrix_report(self.matrix_update)
|
||||
self.matrix_update = None
|
||||
|
||||
if self._hid_pending:
|
||||
self.before_hid_send()
|
||||
|
||||
if self.hid_pending:
|
||||
self._send_hid()
|
||||
|
||||
old_timeouts_len = len(self._timeouts)
|
||||
self._old_timeouts_len = len(self._timeouts)
|
||||
self._process_timeouts()
|
||||
new_timeouts_len = len(self._timeouts)
|
||||
self._new_timeouts_len = len(self._timeouts)
|
||||
|
||||
if old_timeouts_len != new_timeouts_len:
|
||||
if self._old_timeouts_len != self._new_timeouts_len:
|
||||
self.state_changed = True
|
||||
|
||||
if self._hid_pending:
|
||||
if self.hid_pending:
|
||||
self._send_hid()
|
||||
|
||||
for ext in self._extensions:
|
||||
try:
|
||||
ext.after_hid_send(self)
|
||||
except Exception:
|
||||
# TODO FIXME log the exceptions or something
|
||||
pass
|
||||
self.after_hid_send()
|
||||
|
||||
if self._trigger_powersave_enable:
|
||||
self.powersave_enable()
|
||||
|
||||
if self._trigger_powersave_disable:
|
||||
self.powersave_disable()
|
||||
|
||||
if self.state_changed:
|
||||
self._print_debug_cycle()
|
||||
|
@@ -1,29 +1,23 @@
|
||||
import math
|
||||
import time
|
||||
|
||||
USE_UTIME = False
|
||||
|
||||
|
||||
def sleep_ms(ms):
|
||||
'''
|
||||
Tries to sleep for a number of milliseconds in a cross-implementation
|
||||
way. Will raise an ImportError if time is not available on the platform.
|
||||
'''
|
||||
if USE_UTIME:
|
||||
return time.sleep_ms(ms)
|
||||
else:
|
||||
return time.sleep(ms / 1000)
|
||||
return time.sleep(ms / 1000)
|
||||
|
||||
|
||||
def ticks_ms():
|
||||
if USE_UTIME:
|
||||
return time.ticks_ms()
|
||||
else:
|
||||
return math.floor(time.monotonic() * 1000)
|
||||
'''Has .25s granularity, but is cheap'''
|
||||
return time.monotonic() * 1000
|
||||
|
||||
|
||||
def ticks_diff(new, old):
|
||||
if USE_UTIME:
|
||||
return time.ticks_diff(new, old)
|
||||
else:
|
||||
return new - old
|
||||
return new - old
|
||||
|
||||
|
||||
def accurate_ticks():
|
||||
'''Is more expensive, but good for time critical things'''
|
||||
return time.monotonic_ns()
|
||||
|
||||
|
||||
def accurate_ticks_diff(new, old, ms):
|
||||
return bool(new - old < ms * 1000000)
|
||||
|
40
kmk/modules/__init__.py
Normal file
40
kmk/modules/__init__.py
Normal file
@@ -0,0 +1,40 @@
|
||||
class InvalidExtensionEnvironment(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Module:
|
||||
'''
|
||||
Modules differ from extensions in that they not only can read the state, but
|
||||
are allowed to modify the state. The will be loaded on boot, and are not
|
||||
allowed to be unloaded as they are required to continue functioning in a
|
||||
consistant manner.
|
||||
'''
|
||||
|
||||
# The below methods should be implemented by subclasses
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be injected as an extra matrix update
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be replace matrix update if supplied
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
raise NotImplementedError
|
191
kmk/modules/layers.py
Normal file
191
kmk/modules/layers.py
Normal file
@@ -0,0 +1,191 @@
|
||||
'''One layer isn't enough. Adds keys to get to more of them'''
|
||||
from micropython import const
|
||||
|
||||
from kmk.key_validators import layer_key_validator
|
||||
from kmk.keys import make_argumented_key
|
||||
from kmk.kmktime import accurate_ticks, accurate_ticks_diff
|
||||
from kmk.modules import Module
|
||||
|
||||
|
||||
class LayerType:
|
||||
'''Defines layer type values for readability'''
|
||||
|
||||
MO = const(0)
|
||||
DF = const(1)
|
||||
LM = const(2)
|
||||
LT = const(3)
|
||||
TG = const(4)
|
||||
TT = const(5)
|
||||
|
||||
|
||||
class Layers(Module):
|
||||
'''Gives access to the keys used to enable the layer system'''
|
||||
|
||||
def __init__(self):
|
||||
# Layers
|
||||
self.start_time = {
|
||||
LayerType.LT: None,
|
||||
LayerType.TG: None,
|
||||
LayerType.TT: None,
|
||||
LayerType.LM: None,
|
||||
}
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('MO',),
|
||||
on_press=self._mo_pressed,
|
||||
on_release=self._mo_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('DF',), on_press=self._df_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('LM',),
|
||||
on_press=self._lm_pressed,
|
||||
on_release=self._lm_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('LT',),
|
||||
on_press=self._lt_pressed,
|
||||
on_release=self._lt_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('TG',), on_press=self._tg_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('TT',),
|
||||
on_press=self._tt_pressed,
|
||||
on_release=self._tt_released,
|
||||
)
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
return
|
||||
|
||||
def _df_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Switches the default layer
|
||||
'''
|
||||
keyboard.active_layers[-1] = key.meta.layer
|
||||
|
||||
def _mo_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer, switches off when you let go
|
||||
'''
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
|
||||
@staticmethod
|
||||
def _mo_released(key, keyboard, *args, **kwargs):
|
||||
# remove the first instance of the target layer
|
||||
# from the active list
|
||||
# under almost all normal use cases, this will
|
||||
# disable the layer (but preserve it if it was triggered
|
||||
# as a default layer, etc.)
|
||||
# this also resolves an issue where using DF() on a layer
|
||||
# triggered by MO() and then defaulting to the MO()'s layer
|
||||
# would result in no layers active
|
||||
try:
|
||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
||||
del keyboard.active_layers[del_idx]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def _lm_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
keyboard.hid_pending = True
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
keyboard.keys_pressed.add(key.meta.kc)
|
||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
||||
|
||||
def _lm_released(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
keyboard.hid_pending = True
|
||||
keyboard.keys_pressed.discard(key.meta.kc)
|
||||
self._mo_released(key, keyboard, *args, **kwargs)
|
||||
|
||||
def _lt_pressed(self, key, keyboard, *args, **kwargs):
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
self.start_time[LayerType.LT] = accurate_ticks()
|
||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
||||
|
||||
def _lt_released(self, key, keyboard, *args, **kwargs):
|
||||
# On keyup, check timer, and press key if needed.
|
||||
if self.start_time[LayerType.LT] and (
|
||||
accurate_ticks_diff(
|
||||
accurate_ticks(), self.start_time[LayerType.LT], keyboard.tap_time
|
||||
)
|
||||
):
|
||||
keyboard.hid_pending = True
|
||||
keyboard.tap_key(key.meta.kc)
|
||||
|
||||
self._mo_released(key, keyboard, *args, **kwargs)
|
||||
self.start_time[LayerType.LT] = None
|
||||
|
||||
def _tg_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Toggles the layer (enables it if not active, and vise versa)
|
||||
'''
|
||||
# See mo_released for implementation details around this
|
||||
try:
|
||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
||||
del keyboard.active_layers[del_idx]
|
||||
except ValueError:
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
|
||||
def _to_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Activates layer and deactivates all other layers
|
||||
'''
|
||||
keyboard.active_layers.clear()
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
|
||||
def _tt_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer if held, toggles it if tapped repeatedly
|
||||
'''
|
||||
if self.start_time[LayerType.TT] is None:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
self.start_time[LayerType.TT] = accurate_ticks()
|
||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
||||
elif accurate_ticks_diff(
|
||||
accurate_ticks(), self.start_time[LayerType.TT], keyboard.tap_time
|
||||
):
|
||||
self.start_time[LayerType.TT] = None
|
||||
self._tg_pressed(key, keyboard, *args, **kwargs)
|
||||
return
|
||||
return
|
||||
|
||||
def _tt_released(self, key, keyboard, *args, **kwargs):
|
||||
if self.start_time[LayerType.TT] is None or not accurate_ticks_diff(
|
||||
accurate_ticks(), self.start_time[LayerType.TT], keyboard.tap_time
|
||||
):
|
||||
# On first press, works like MO. On second press, does nothing unless let up within
|
||||
# time window, then acts like TG.
|
||||
self.start_time[LayerType.TT] = None
|
||||
self._mo_released(key, keyboard, *args, **kwargs)
|
57
kmk/modules/modtap.py
Normal file
57
kmk/modules/modtap.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from kmk.key_validators import mod_tap_validator
|
||||
from kmk.keys import make_argumented_key
|
||||
from kmk.kmktime import accurate_ticks, accurate_ticks_diff
|
||||
from kmk.modules import Module
|
||||
|
||||
|
||||
class ModTap(Module):
|
||||
def __init__(self):
|
||||
self._mod_tap_timer = None
|
||||
make_argumented_key(
|
||||
validator=mod_tap_validator,
|
||||
names=('MT',),
|
||||
on_press=self.mt_pressed,
|
||||
on_release=self.mt_released,
|
||||
)
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
return
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
return
|
||||
|
||||
def mt_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''Sets the timer start and acts like a modifier otherwise'''
|
||||
keyboard.keys_pressed.add(key.meta.mods)
|
||||
|
||||
self._mod_tap_timer = accurate_ticks()
|
||||
return keyboard
|
||||
|
||||
def mt_released(self, key, keyboard, *args, **kwargs):
|
||||
''' On keyup, check timer, and press key if needed.'''
|
||||
keyboard.keys_pressed.discard(key.meta.mods)
|
||||
if self._mod_tap_timer and (
|
||||
accurate_ticks_diff(
|
||||
accurate_ticks(), self._mod_tap_timer, keyboard.tap_time
|
||||
)
|
||||
):
|
||||
keyboard.hid_pending = True
|
||||
keyboard.tap_key(key.meta.kc)
|
||||
|
||||
self._mod_tap_timer = None
|
||||
return keyboard
|
146
kmk/modules/power.py
Normal file
146
kmk/modules/power.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import board
|
||||
import digitalio
|
||||
|
||||
from kmk.handlers.stock import passthrough as handler_passthrough
|
||||
from kmk.keys import make_key
|
||||
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
|
||||
from kmk.modules import Module
|
||||
|
||||
|
||||
class Power(Module):
|
||||
def __init__(self, powersave_pin=None):
|
||||
self.enable = False
|
||||
self.powersave_pin = powersave_pin # Powersave pin board object
|
||||
self._powersave_start = ticks_ms()
|
||||
self._usb_last_scan = ticks_ms() - 5000
|
||||
self._psp = None # Powersave pin object
|
||||
self._i2c = 0
|
||||
self._loopcounter = 0
|
||||
|
||||
make_key(
|
||||
names=('PS_TOG',), on_press=self._ps_tog, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('PS_ON',), on_press=self._ps_enable, on_release=handler_passthrough
|
||||
)
|
||||
make_key(
|
||||
names=('PS_OFF',), on_press=self._ps_disable, on_release=handler_passthrough
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f'Power({self._to_dict()})'
|
||||
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'enable': self.enable,
|
||||
'powersave_pin': self.powersave_pin,
|
||||
'_powersave_start': self._powersave_start,
|
||||
'_usb_last_scan': self._usb_last_scan,
|
||||
'_psp': self._psp,
|
||||
}
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
self._i2c_scan()
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
if keyboard.matrix_update or keyboard.secondary_matrix_update:
|
||||
self.psave_time_reset()
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
if self.enable:
|
||||
self.psleep()
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
'''Gives 10 cycles to allow other extentions to clean up before powersave'''
|
||||
if self._loopcounter > 10:
|
||||
self.enable_powersave(keyboard)
|
||||
self._loopcounter = 0
|
||||
else:
|
||||
self._loopcounter += 1
|
||||
return
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
self.disable_powersave(keyboard)
|
||||
return
|
||||
|
||||
def enable_powersave(self, keyboard):
|
||||
'''Enables power saving features'''
|
||||
if keyboard.i2c_deinit_count >= self._i2c and self.powersave_pin:
|
||||
# Allows power save to prevent RGB drain.
|
||||
# Example here https://docs.nicekeyboards.com/#/nice!nano/pinout_schematic
|
||||
|
||||
if not self._psp:
|
||||
self._psp = digitalio.DigitalInOut(self.powersave_pin)
|
||||
self._psp.direction = digitalio.Direction.OUTPUT
|
||||
if self._psp:
|
||||
self._psp.value = True
|
||||
|
||||
self.enable = True
|
||||
keyboard._trigger_powersave_enable = False
|
||||
return
|
||||
|
||||
def disable_powersave(self, keyboard):
|
||||
'''Disables power saving features'''
|
||||
if self._psp:
|
||||
self._psp.value = False
|
||||
# Allows power save to prevent RGB drain.
|
||||
# Example here https://docs.nicekeyboards.com/#/nice!nano/pinout_schematic
|
||||
|
||||
keyboard._trigger_powersave_disable = False
|
||||
self.enable = False
|
||||
return
|
||||
|
||||
def psleep(self):
|
||||
'''
|
||||
Sleeps longer and longer to save power the more time in between updates.
|
||||
'''
|
||||
if ticks_diff(ticks_ms(), self._powersave_start) <= 60000:
|
||||
sleep_ms(8)
|
||||
elif ticks_diff(ticks_ms(), self._powersave_start) >= 240000:
|
||||
sleep_ms(180)
|
||||
return
|
||||
|
||||
def psave_time_reset(self):
|
||||
self._powersave_start = ticks_ms()
|
||||
|
||||
def _i2c_scan(self):
|
||||
i2c = board.I2C()
|
||||
while not i2c.try_lock():
|
||||
pass
|
||||
try:
|
||||
self._i2c = len(i2c.scan())
|
||||
finally:
|
||||
i2c.unlock()
|
||||
return
|
||||
|
||||
def usb_rescan_timer(self):
|
||||
return bool(ticks_diff(ticks_ms(), self._usb_last_scan) > 5000)
|
||||
|
||||
def usb_time_reset(self):
|
||||
self._usb_last_scan = ticks_ms()
|
||||
return
|
||||
|
||||
def usb_scan(self):
|
||||
# TODO Add USB detection here. Currently lies that it's connected
|
||||
# https://github.com/adafruit/circuitpython/pull/3513
|
||||
return True
|
||||
|
||||
def _ps_tog(self, key, keyboard, *args, **kwargs):
|
||||
if self.enable:
|
||||
keyboard._trigger_powersave_disable = True
|
||||
else:
|
||||
keyboard._trigger_powersave_enable = True
|
||||
|
||||
def _ps_enable(self, key, keyboard, *args, **kwargs):
|
||||
if not self.enable:
|
||||
keyboard._trigger_powersave_enable = True
|
||||
|
||||
def _ps_disable(self, key, keyboard, *args, **kwargs):
|
||||
if self.enable:
|
||||
keyboard._trigger_powersave_disable = True
|
308
kmk/modules/split.py
Normal file
308
kmk/modules/split.py
Normal file
@@ -0,0 +1,308 @@
|
||||
'''Enables splitting keyboards wirelessly or wired'''
|
||||
import busio
|
||||
from micropython import const
|
||||
|
||||
from kmk.hid import HIDModes
|
||||
from kmk.kmktime import ticks_diff, ticks_ms
|
||||
from kmk.matrix import intify_coordinate
|
||||
from kmk.modules import Module
|
||||
from storage import getmount
|
||||
|
||||
|
||||
class SplitSide:
|
||||
LEFT = const(1)
|
||||
RIGHT = const(2)
|
||||
|
||||
|
||||
class SplitType:
|
||||
UART = const(1)
|
||||
I2C = const(2) # unused
|
||||
ONEWIRE = const(3) # unused
|
||||
BLE = const(4)
|
||||
|
||||
|
||||
class Split(Module):
|
||||
'''Enables splitting keyboards wirelessly, or wired'''
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
split_flip=True,
|
||||
split_side=None,
|
||||
split_type=SplitType.UART,
|
||||
split_target_left=True,
|
||||
uart_interval=20,
|
||||
data_pin=None,
|
||||
data_pin2=None,
|
||||
target_left=True,
|
||||
uart_flip=True,
|
||||
):
|
||||
self._is_target = True
|
||||
self._uart_buffer = []
|
||||
self.split_flip = split_flip
|
||||
self.split_side = split_side
|
||||
self.split_type = split_type
|
||||
self.split_target_left = split_target_left
|
||||
self.split_offset = None
|
||||
self.data_pin = data_pin
|
||||
self.data_pin2 = data_pin2
|
||||
self.target_left = target_left
|
||||
self.uart_flip = uart_flip
|
||||
self._is_target = True
|
||||
self._uart = None
|
||||
self._uart_interval = uart_interval
|
||||
self._debug_enabled = False
|
||||
if self.split_type == SplitType.BLE:
|
||||
try:
|
||||
from adafruit_ble import BLERadio
|
||||
from adafruit_ble.advertising.standard import (
|
||||
ProvideServicesAdvertisement,
|
||||
)
|
||||
from adafruit_ble.services.nordic import UARTService
|
||||
|
||||
self.ProvideServicesAdvertisement = ProvideServicesAdvertisement
|
||||
self.UARTService = UARTService
|
||||
except ImportError:
|
||||
pass # BLE isn't supported on this platform
|
||||
self._ble = BLERadio()
|
||||
self._ble_last_scan = ticks_ms() - 5000
|
||||
self._connection_count = 0
|
||||
self._uart_connection = None
|
||||
self._advertisment = None
|
||||
self._advertising = False
|
||||
self._psave_enable = False
|
||||
|
||||
def __repr__(self):
|
||||
return f'BLE_SPLIT({self._to_dict()})'
|
||||
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'_ble': self._ble,
|
||||
'_ble_last_scan': self._ble_last_scan,
|
||||
'_is_target': self._is_target,
|
||||
'uart_buffer': self._uart_buffer,
|
||||
'_split_flip': self.split_flip,
|
||||
'_split_side': self.split_side,
|
||||
}
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
# Set up name for target side detection and BLE advertisment
|
||||
name = str(getmount('/').label)
|
||||
if self.split_type == SplitType.BLE:
|
||||
self._ble.name = name
|
||||
else:
|
||||
# Try to guess data pins if not supplied
|
||||
if not self.data_pin:
|
||||
self.data_pin = keyboard.data_pin
|
||||
|
||||
# Detect split side from name
|
||||
if self.split_side is None:
|
||||
if name.endswith('L'):
|
||||
# If name ends in 'L' assume left and strip from name
|
||||
self._is_target = bool(self.split_target_left)
|
||||
self.split_side = SplitSide.LEFT
|
||||
elif name.endswith('R'):
|
||||
# If name ends in 'R' assume right and strip from name
|
||||
self._is_target = not bool(self.split_target_left)
|
||||
self.split_side = SplitSide.RIGHT
|
||||
|
||||
# if split side was given, find master from split_side.
|
||||
elif self.split_side == SplitSide.LEFT:
|
||||
self._is_target = bool(self.split_target_left)
|
||||
elif self.split_side == SplitSide.RIGHT:
|
||||
self._is_target = not bool(self.split_target_left)
|
||||
|
||||
# Flips the col pins if PCB is the same but flipped on right
|
||||
if self.split_flip and self.split_side == SplitSide.RIGHT:
|
||||
keyboard.col_pins = list(reversed(keyboard.col_pins))
|
||||
|
||||
self.split_offset = len(keyboard.col_pins)
|
||||
|
||||
if self.split_type == SplitType.UART and self.data_pin is not None:
|
||||
if self._is_target:
|
||||
self._uart = busio.UART(
|
||||
tx=self.data_pin2, rx=self.data_pin, timeout=self._uart_interval
|
||||
)
|
||||
else:
|
||||
self._uart = busio.UART(
|
||||
tx=self.data_pin, rx=self.data_pin2, timeout=self._uart_interval
|
||||
)
|
||||
|
||||
# Attempt to sanely guess a coord_mapping if one is not provided.
|
||||
if not keyboard.coord_mapping:
|
||||
keyboard.coord_mapping = []
|
||||
|
||||
rows_to_calc = len(keyboard.row_pins) * 2
|
||||
cols_to_calc = len(keyboard.col_pins) * 2
|
||||
|
||||
for ridx in range(rows_to_calc):
|
||||
for cidx in range(cols_to_calc):
|
||||
keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
if self.split_type == SplitType.BLE:
|
||||
self._check_all_connections(keyboard._hid_helper)
|
||||
self._receive_ble(keyboard)
|
||||
elif self.split_type == SplitType.UART:
|
||||
if self._is_target or self.data_pin2:
|
||||
self._receive_uart(keyboard)
|
||||
elif self.split_type == SplitType.ONEWIRE:
|
||||
pass # Protocol needs written
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
if keyboard.matrix_update:
|
||||
if self.split_type == SplitType.BLE:
|
||||
self._send_ble(keyboard.matrix_update)
|
||||
elif self.split_type == SplitType.UART and self.data_pin2:
|
||||
self._send_uart(keyboard.matrix_update)
|
||||
elif self.split_type == SplitType.ONEWIRE:
|
||||
pass # Protocol needs written
|
||||
|
||||
return
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
if self.split_type == SplitType.BLE:
|
||||
if self._uart_connection and not self._psave_enable:
|
||||
self._uart_connection.connection_interval = self._uart_interval
|
||||
self._psave_enable = True
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
if self.split_type == SplitType.BLE:
|
||||
if self._uart_connection and self._psave_enable:
|
||||
self._uart_connection.connection_interval = 11.25
|
||||
self._psave_enable = False
|
||||
|
||||
def _check_all_connections(self, hid_type):
|
||||
'''Validates the correct number of BLE connections'''
|
||||
self._connection_count = len(self._ble.connections)
|
||||
if self._is_target and hid_type == HIDModes.BLE and self._connection_count < 2:
|
||||
self._target_advertise()
|
||||
elif not self._is_target and self._connection_count < 1:
|
||||
self._initiator_scan()
|
||||
|
||||
def _initiator_scan(self):
|
||||
'''Scans for target device'''
|
||||
self._uart = None
|
||||
self._uart_connection = None
|
||||
# See if any existing connections are providing UARTService.
|
||||
self._connection_count = len(self._ble.connections)
|
||||
if self._connection_count > 0 and not self._uart:
|
||||
for connection in self._ble.connections:
|
||||
if self.UARTService in connection:
|
||||
self._uart_connection = connection
|
||||
self._uart_connection.connection_interval = 11.25
|
||||
self._uart = self._uart_connection[self.UARTService]
|
||||
break
|
||||
|
||||
if not self._uart:
|
||||
if self._debug_enabled:
|
||||
print('Scanning')
|
||||
self._ble.stop_scan()
|
||||
for adv in self._ble.start_scan(
|
||||
self.ProvideServicesAdvertisement, timeout=20
|
||||
):
|
||||
if self._debug_enabled:
|
||||
print('Scanning')
|
||||
if self.UARTService in adv.services and adv.rssi > -70:
|
||||
self._uart_connection = self._ble.connect(adv)
|
||||
self._uart_connection.connection_interval = 11.25
|
||||
self._uart = self._uart_connection[self.UARTService]
|
||||
self._ble.stop_scan()
|
||||
if self._debug_enabled:
|
||||
print('Scan complete')
|
||||
break
|
||||
self._ble.stop_scan()
|
||||
|
||||
def _target_advertise(self):
|
||||
'''Advertises the target for the initiator to find'''
|
||||
self._ble.stop_advertising()
|
||||
if self._debug_enabled:
|
||||
print('Advertising')
|
||||
# Uart must not change on this connection if reconnecting
|
||||
if not self._uart:
|
||||
self._uart = self.UARTService()
|
||||
advertisement = self.ProvideServicesAdvertisement(self._uart)
|
||||
|
||||
self._ble.start_advertising(advertisement)
|
||||
|
||||
self.ble_time_reset()
|
||||
while not self.ble_rescan_timer():
|
||||
self._connection_count = len(self._ble.connections)
|
||||
if self._connection_count > 1:
|
||||
self.ble_time_reset()
|
||||
if self._debug_enabled:
|
||||
print('Advertising complete')
|
||||
break
|
||||
self._ble.stop_advertising()
|
||||
|
||||
def ble_rescan_timer(self):
|
||||
'''If true, the rescan timer is up'''
|
||||
return bool(ticks_diff(ticks_ms(), self._ble_last_scan) > 5000)
|
||||
|
||||
def ble_time_reset(self):
|
||||
'''Resets the rescan timer'''
|
||||
self._ble_last_scan = ticks_ms()
|
||||
|
||||
def _send_ble(self, update):
|
||||
if self._uart:
|
||||
try:
|
||||
if not self._is_target:
|
||||
update[1] += self.split_offset
|
||||
self._uart.write(update)
|
||||
except OSError:
|
||||
try:
|
||||
self._uart.disconnect()
|
||||
except: # noqa: E722
|
||||
if self._debug_enabled:
|
||||
print('UART disconnect failed')
|
||||
|
||||
if self._debug_enabled:
|
||||
print('Connection error')
|
||||
self._uart_connection = None
|
||||
self._uart = None
|
||||
|
||||
def _receive_ble(self, keyboard):
|
||||
if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
|
||||
while self._uart.in_waiting >= 3:
|
||||
self._uart_buffer.append(self._uart.read(3))
|
||||
if self._uart_buffer:
|
||||
keyboard.secondary_matrix_update = bytearray(self._uart_buffer.pop(0))
|
||||
return
|
||||
|
||||
def _send_uart(self, update):
|
||||
# Change offsets depending on where the data is going to match the correct
|
||||
# matrix location of the receiever
|
||||
if self._is_target:
|
||||
if self.split_target_left:
|
||||
update[1] += self.split_offset
|
||||
else:
|
||||
update[1] -= self.split_offset
|
||||
else:
|
||||
if self.split_target_left:
|
||||
update[1] -= self.split_offset
|
||||
else:
|
||||
update[1] += self.split_offset
|
||||
|
||||
if self._uart is not None:
|
||||
self._uart.write(update)
|
||||
|
||||
def _receive_uart(self, keyboard):
|
||||
if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
|
||||
if self._uart.in_waiting >= 60:
|
||||
# This is a dirty hack to prevent crashes in unrealistic cases
|
||||
import microcontroller
|
||||
|
||||
microcontroller.reset()
|
||||
|
||||
while self._uart.in_waiting >= 3:
|
||||
self._uart_buffer.append(self._uart.read(3))
|
||||
if self._uart_buffer:
|
||||
keyboard.secondary_matrix_update = bytearray(self._uart_buffer.pop(0))
|
||||
|
||||
return
|
@@ -1,37 +0,0 @@
|
||||
# Welcome to RAM and stack size hacks central, I'm your host, klardotsh!
|
||||
# Our import structure is deeply nested enough that stuff
|
||||
# breaks in some truly bizarre ways, including:
|
||||
# - explicit RuntimeError exceptions, complaining that our
|
||||
# stack depth is too deep
|
||||
#
|
||||
# - silent hard locks of the device (basically unrecoverable without
|
||||
# UF2 flash if done in main.py, fixable with a reboot if done
|
||||
# in REPL)
|
||||
#
|
||||
# However, there's a hackaround that works for us! Because sys.modules
|
||||
# caches everything it sees (and future imports will use that cached
|
||||
# copy of the module), let's take this opportunity _way_ up the import
|
||||
# chain to import _every single thing_ KMK eventually uses in a normal
|
||||
# workflow, in nesting order
|
||||
#
|
||||
# GC runs automatically after CircuitPython imports.
|
||||
|
||||
# First, system-provided deps
|
||||
import busio
|
||||
import collections
|
||||
import gc
|
||||
import supervisor
|
||||
|
||||
# Now "light" KMK stuff with few/no external deps
|
||||
import kmk.consts # isort:skip
|
||||
import kmk.kmktime # isort:skip
|
||||
import kmk.types # isort:skip
|
||||
|
||||
# Now handlers that will be used in keys later
|
||||
import kmk.handlers.layers # isort:skip
|
||||
import kmk.handlers.stock # isort:skip
|
||||
|
||||
# Now stuff that depends on the above (and so on)
|
||||
import kmk.hid # isort:skip
|
||||
import kmk.keys # isort:skip
|
||||
import kmk.matrix # isort:skip
|
Reference in New Issue
Block a user