summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@dailly.me>2023-05-22 08:40:47 +0200
committerSébastien Dailly <sebastien@dailly.me>2023-05-22 08:40:47 +0200
commit597b007333d8ec0d9cfd29e6941fcbe57379108a (patch)
tree0cf87e1ac487e7deb91acf7f2bec70bd4dd06703
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--src/Makefile9
-rw-r--r--src/actions.py72
-rw-r--r--src/boot.py20
-rw-r--r--src/code.py158
-rw-r--r--src/keyboard_layout_win_frnb.py580
-rw-r--r--src/keycode_win_frnb.py165
-rw-r--r--src/lib/adafruit_bitmap_font/__init__.py0
-rw-r--r--src/lib/adafruit_display_shapes/__init__.py0
-rw-r--r--src/macros/1-ff.py83
-rw-r--r--src/macros/2-num.py103
-rw-r--r--src/macros/3-fkeys.py87
-rw-r--r--src/macros/4-move_mouse.py54
-rw-r--r--src/macros/5-irssi.py49
-rw-r--r--src/macros/99-none.py3
-rw-r--r--src/macros/99-wm.py62
-rw-r--r--src/macros/olympia.py91
-rw-r--r--src/macros/santana.py47
-rw-r--r--src/macros/teams.py44
-rw-r--r--src/macros/zim.py53
-rw-r--r--src/menu.py23
-rw-r--r--src/readme.rst73
-rw-r--r--src/skeleton.py199
-rw-r--r--src/ticks.py21
24 files changed, 1997 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4815d20
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.mpy
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..ecb9a82
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,9 @@
+.phony: all
+all: skeleton.mpy keycode_win_frnb.mpy actions.mpy
+
+%.mpy: %.py
+ .././mpy-cross.static-amd64-linux-8.0.3 $<
+
+.phony: clean
+clean:
+ rm *.mpy
diff --git a/src/actions.py b/src/actions.py
new file mode 100644
index 0000000..1e40bbd
--- /dev/null
+++ b/src/actions.py
@@ -0,0 +1,72 @@
+from supervisor import ticks_ms
+import ticks
+import time
+from random import randrange
+
+class Action(object):
+ def __new__(cls):
+ if not hasattr(cls, 'instance'):
+ cls.instance = super(Action, cls).__new__(cls)
+ cls.instance.events = []
+ return cls.instance
+
+
+ def __init__(self):
+ """ Do nothing and do not get any constructor.
+ This is intended because it allow to call the class directly like :
+ Action().key( … )
+ """
+ pass
+
+ def set_macropad(self, macropad):
+ self.macropad = macropad
+
+ def key(self, pressed, sequence):
+ """ Send a key to the host
+ """
+
+ if pressed:
+ action = self.macropad.keyboard.press
+ s = sequence
+ else:
+ action = self.macropad.keyboard.release
+ s = reversed(sequence)
+ for item in s:
+ action(item)
+
+ def sequence(self, keys):
+ """ Send a sequence of keys to the host
+ """
+ for key in keys:
+ self.key(True, key)
+ time.sleep( randrange(25, 74)/400. )
+ self.key(False, key)
+
+ def delay(self, duration, callback):
+ "Register a delayed task"
+ time = ticks_ms()
+ next_tick = ticks.add(
+ ticks_ms(),
+ duration
+ )
+ self.events.append((next_tick, callback))
+
+ def flash(self, key, release_color):
+ "Flash the key with a high light for 10ms"
+ def restore():
+ if self.macropad.pixels[key] == (127, 127, 127):
+ self.macropad.pixels[key] = release_color
+ self.macropad.pixels.show()
+ self.delay(10, restore)
+ self.macropad.pixels[key] = (127, 127, 127)
+
+ def collect_tasks(self):
+ "Execute all the delayed tasks"
+ remain = []
+ time = ticks_ms()
+ for event in self.events:
+ if not ticks.less(event[0]):
+ event[1]()
+ else:
+ remain.append(event)
+ self.events = remain
diff --git a/src/boot.py b/src/boot.py
new file mode 100644
index 0000000..0a30e17
--- /dev/null
+++ b/src/boot.py
@@ -0,0 +1,20 @@
+# This example is for the MacroPad,
+# or any board with buttons that are connected to ground when pressed.
+
+import usb_cdc
+import storage
+import board, digitalio
+
+# On the Macropad, pressing a key grounds it. You need to set a pull-up.
+# If not pressed, the key will be at +V (due to the pull-up).
+# The KEY12 is the RightBotom key in the keypad
+button = digitalio.DigitalInOut(board.KEY12)
+button.pull = digitalio.Pull.UP
+
+usb_cdc.enable(console=True, data=True)
+# Disable devices only if button is not pressed.
+if button.value:
+ print("KEY12 Unpressed, starting without storage")
+ storage.disable_usb_drive()
+else:
+ print("Mounting USB storage")
diff --git a/src/code.py b/src/code.py
new file mode 100644
index 0000000..813574a
--- /dev/null
+++ b/src/code.py
@@ -0,0 +1,158 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+A macro/hotkey program for Adafruit MACROPAD. Macro setups are stored in the
+/macros folder (configurable below), load up just the ones you're likely to
+use. Plug into computer's USB port, use dial to select an application macro
+set, press MACROPAD keys to send key sequences and other USB protocols.
+"""
+
+# pylint: disable=import-error, unused-import, too-few-public-methods
+
+import os
+import time
+import displayio
+import terminalio
+
+import usb_cdc
+import json
+
+#from adafruit_display_shapes.rect import Rect
+#from adafruit_display_text import label
+from adafruit_macropad import MacroPad
+
+from supervisor import runtime
+
+from actions import Action
+import skeleton
+import menu
+
+# CONFIGURABLES ------------------------
+
+MACRO_FOLDER = '/macros'
+
+
+# INITIALIZATION -----------------------
+
+# Do not reload the application when the files are changed
+runtime.autoreload = False
+
+macropad = MacroPad()
+macropad.display.auto_refresh = False
+macropad.pixels.auto_write = False
+
+Action().set_macropad(macropad)
+
+# Load all the macro key setups from .py files in MACRO_FOLDER
+apps = []
+files = os.listdir(MACRO_FOLDER)
+files.sort()
+for filename in files:
+ if filename.startswith('._'):
+ continue
+ if filename.endswith('.py'):
+ module_name = filename[:-3]
+ elif filename.endswith('.mpy'):
+ module_name = filename[:-4]
+ else:
+ continue
+
+ try:
+ module = __import__(MACRO_FOLDER + '/' + module_name)
+ apps.append(module.configuration)
+ except (SyntaxError, ImportError, AttributeError, KeyError, NameError,
+ IndexError, TypeError) as err:
+ print("ERROR in", filename)
+ import traceback
+ traceback.print_exception(err, err, err.__traceback__)
+
+if not apps:
+ group[13].text = 'NO MACRO FILES FOUND'
+ macropad.display.refresh()
+ while True:
+ pass
+
+app_index = 0
+
+def run_application(app):
+ # Read encoder position. If it's changed, declare it as a button.
+ position = app.macropad.encoder
+ if position > app.position:
+ app.position = position
+ key_number = 12
+ pressed = True
+ elif position < app.position:
+ app.position = position
+ key_number = 13
+ pressed = True
+ else:
+ event = app.macropad.keys.events.get()
+ if not event:
+ app.tick()
+ return # No key events
+ key_number = event.key_number
+ pressed = event.pressed
+
+ return app.execute_action(key_number, pressed or False)
+
+def switch_layout(configuration):
+ global app
+ app = skeleton.Handler(macropad, configuration)
+ app.start()
+
+
+def search_and_switch(layout):
+ found_application = False
+ for local_app in apps:
+ if local_app.title == layout and app is not local_app:
+ found_application = True
+ switch_layout(local_app)
+ break
+
+def send_layout(_any):
+ if usb_cdc.data.connected:
+ usb_cdc.data.write(bytes("%s\n" % app.name, "utf-8"))
+
+actions = {
+ "layout" : search_and_switch,
+ "get_layout" : send_layout,
+ }
+
+switch_layout(apps[app_index])
+
+# MAIN LOOP ----------------------------
+
+while True:
+
+ # First read from the serial
+ # If we have an application name from the command line, switch to this app.
+ if usb_cdc.data.connected and usb_cdc.data.in_waiting != 0:
+ line = usb_cdc.data.read(usb_cdc.data.in_waiting).strip()
+ commands = json.loads(line)
+ for command in commands:
+ for key, value in command.items():
+ actions[key](value)
+ continue
+
+ # Handle encoder button. If state has changed, and if there's a
+ # corresponding macro, set up variables to act on this just like
+ # the keypad keys, as if it were a 13th key/macro.
+ app.macropad.encoder_switch_debounced.update()
+ encoder_switch = app.macropad.encoder_switch_debounced.pressed
+ # On the encoder button, switch to the next mode
+ if encoder_switch:
+
+ config = menu.build_application(apps)
+ menu_app = skeleton.Handler(macropad, config)
+ menu_app.start()
+ while True:
+ configuration = run_application(menu_app)
+ if configuration is None: continue
+ switch_layout(configuration)
+ break
+ send_layout(None)
+
+ run_application(app)
+ continue
diff --git a/src/keyboard_layout_win_frnb.py b/src/keyboard_layout_win_frnb.py
new file mode 100644
index 0000000..51d8bc2
--- /dev/null
+++ b/src/keyboard_layout_win_frnb.py
@@ -0,0 +1,580 @@
+# SPDX-FileCopyrightText: 2022 Neradoc NeraOnGit@ri1.fr
+# SPDX-License-Identifier: MIT
+"""
+This file was automatically generated using Circuitpython_Keyboard_Layouts
+"""
+from adafruit_hid.keyboard_layout_base import KeyboardLayoutBase
+
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/Neradoc/Circuitpython_Keyboard_Layouts.git"
+
+
+class KeyboardLayout(KeyboardLayoutBase):
+ ASCII_TO_KEYCODE = (
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x2a' # BACKSPACE
+ b'\x2b' # '\t'
+ b'\x28' # '\n'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x29' # ESC
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x00'
+ b'\x2c' # ' '
+ b'\x9c' # '!'
+ b'\x1e' # '"'
+ b'\xb5' # '#'
+ b'\x35' # '$'
+ b'\x2e' # '%'
+ b'\x08' # '&'
+ b'\x0a' # "'"
+ b'\x21' # '('
+ b'\x22' # ')'
+ b'\x27' # '*'
+ b'\x24' # '+'
+ b'\x0a' # ','
+ b'\x00' # '-' (Dead key)
+ b'\x00' # '.' (Dead key)
+ b'\x00' # '/' (Dead key)
+ b'\xa7' # '0'
+ b'\x9e' # '1'
+ b'\x9f' # '2'
+ b'\xa0' # '3'
+ b'\xa1' # '4'
+ b'\xa2' # '5'
+ b'\xa3' # '6'
+ b'\xa4' # '7'
+ b'\xa5' # '8'
+ b'\xa6' # '9'
+ b'\x99' # ':'
+ b'\x8a' # ';'
+ b'\x1f' # '<'
+ b'\x2d' # '='
+ b'\x20' # '>'
+ b'\x91' # '?'
+ b'\x23' # '@'
+ b'\x84' # 'A'
+ b'\x94' # 'B'
+ b'\x8b' # 'C'
+ b'\x8c' # 'D'
+ b'\x89' # 'E'
+ b'\xb8' # 'F'
+ b'\xb6' # 'G'
+ b'\xb7' # 'H'
+ b'\x87' # 'I'
+ b'\x93' # 'J'
+ b'\x85' # 'K'
+ b'\x92' # 'L'
+ b'\xb4' # 'M'
+ b'\xb3' # 'N'
+ b'\x95' # 'O'
+ b'\x88' # 'P'
+ b'\x90' # 'Q'
+ b'\x8f' # 'R'
+ b'\x8e' # 'S'
+ b'\x8d' # 'T'
+ b'\x96' # 'U'
+ b'\x98' # 'V'
+ b'\xb0' # 'W'
+ b'\x86' # 'X'
+ b'\x9b' # 'Y'
+ b'\xaf' # 'Z'
+ b'\x21' # '['
+ b'\x1d' # '\\'
+ b'\x22' # ']'
+ b'\x23' # '^'
+ b'\x2c' # '_'
+ b'\x00' # '`' (Dead key)
+ b'\x04' # 'a'
+ b'\x14' # 'b'
+ b'\x0b' # 'c'
+ b'\x0c' # 'd'
+ b'\x09' # 'e'
+ b'\x38' # 'f'
+ b'\x36' # 'g'
+ b'\x37' # 'h'
+ b'\x07' # 'i'
+ b'\x13' # 'j'
+ b'\x05' # 'k'
+ b'\x12' # 'l'
+ b'\x34' # 'm'
+ b'\x33' # 'n'
+ b'\x15' # 'o'
+ b'\x08' # 'p'
+ b'\x10' # 'q'
+ b'\x0f' # 'r'
+ b'\x0e' # 's'
+ b'\x0d' # 't'
+ b'\x16' # 'u'
+ b'\x18' # 'v'
+ b'\x30' # 'w'
+ b'\x06' # 'x'
+ b'\x1b' # 'y'
+ b'\x2f' # 'z'
+ b'\x1b' # '{'
+ b'\x14' # '|'
+ b'\x06' # '}'
+ b'\x00' # '~' (Dead key)
+ b'\x00'
+ )
+ NEED_ALTGR = "&'<>[\\]^_{|}¡±¿×æ÷ùœ–—…‰€−≠"
+ HIGHER_ASCII = {
+ 0x2014: 0x1e, # '—'
+ 0xab: 0x1f, # '«'
+ 0xbb: 0x20, # '»'
+ 0xb1: 0x24, # '±'
+ 0x2212: 0x25, # '−'
+ 0xf7: 0x26, # '÷'
+ 0xd7: 0x27, # '×'
+ 0xb0: 0xad, # '°'
+ 0x2260: 0x2d, # '≠'
+ 0x2030: 0x2e, # '‰'
+ 0xe9: 0x1a, # 'é'
+ 0xc9: 0x9a, # 'É'
+ 0x153: 0x15, # 'œ'
+ 0xe8: 0x17, # 'è'
+ 0xc8: 0x97, # 'È'
+ 0xa1: 0x1c, # '¡'
+ 0xe6: 0x04, # 'æ'
+ 0xf9: 0x16, # 'ù'
+ 0x20ac: 0x09, # '€'
+ 0x2013: 0x35, # '–'
+ 0xe7: 0x31, # 'ç'
+ 0xc7: 0xb1, # 'Ç'
+ 0xe0: 0x1d, # 'à'
+ 0xc0: 0x9d, # 'À'
+ 0x2026: 0x19, # '…'
+ 0x2019: 0x11, # '’'
+ 0xbf: 0x11, # '¿'
+ 0x202f: 0xac, # '\u202f'
+ 0xea: 0x64, # 'ê'
+ 0xca: 0xe4, # 'Ê'
+ }
+ COMBINED_KEYS = {
+ 0xc1: 0x1ac1, # 'Á'
+ 0x106: 0x1ac3, # 'Ć'
+ 0xc9: 0x1ac5, # 'É'
+ 0x1f4: 0x1ac7, # 'Ǵ'
+ 0xcd: 0x1ac9, # 'Í'
+ 0x1e30: 0x1acb, # 'Ḱ'
+ 0x139: 0x1acc, # 'Ĺ'
+ 0x1e3e: 0x1acd, # 'Ḿ'
+ 0x143: 0x1ace, # 'Ń'
+ 0xd3: 0x1acf, # 'Ó'
+ 0x1e54: 0x1ad0, # 'Ṕ'
+ 0x154: 0x1ad2, # 'Ŕ'
+ 0x15a: 0x1ad3, # 'Ś'
+ 0xda: 0x1ad5, # 'Ú'
+ 0x1d7: 0x1ad6, # 'Ǘ'
+ 0x1e82: 0x1ad7, # 'Ẃ'
+ 0xdd: 0x1ad9, # 'Ý'
+ 0x179: 0x1ada, # 'Ź'
+ 0x1fc: 0x1ac6, # 'Ǽ'
+ 0xe1: 0x1ae1, # 'á'
+ 0x107: 0x1ae3, # 'ć'
+ 0xe9: 0x1ae5, # 'é'
+ 0x1f5: 0x1ae7, # 'ǵ'
+ 0xed: 0x1ae9, # 'í'
+ 0x1e31: 0x1aeb, # 'ḱ'
+ 0x13a: 0x1aec, # 'ĺ'
+ 0x1e3f: 0x1aed, # 'ḿ'
+ 0x144: 0x1aee, # 'ń'
+ 0xf3: 0x1aef, # 'ó'
+ 0x1e55: 0x1af0, # 'ṕ'
+ 0x155: 0x1af2, # 'ŕ'
+ 0x15b: 0x1af3, # 'ś'
+ 0xfa: 0x1af5, # 'ú'
+ 0x1d8: 0x1af6, # 'ǘ'
+ 0x1e83: 0x1af7, # 'ẃ'
+ 0xfd: 0x1af9, # 'ý'
+ 0x17a: 0x1afa, # 'ź'
+ 0x1fd: 0x1ae6, # 'ǽ'
+ 0xb4: 0x1aa0, # '´'
+ 0xc0: 0x17c1, # 'À'
+ 0xc8: 0x17c5, # 'È'
+ 0xcc: 0x17c9, # 'Ì'
+ 0x1f8: 0x17ce, # 'Ǹ'
+ 0xd2: 0x17cf, # 'Ò'
+ 0xd9: 0x17d5, # 'Ù'
+ 0x1e80: 0x17d7, # 'Ẁ'
+ 0x1ef2: 0x17d9, # 'Ỳ'
+ 0xe0: 0x17e1, # 'à'
+ 0xe8: 0x17e5, # 'è'
+ 0xec: 0x17e9, # 'ì'
+ 0x1f9: 0x17ee, # 'ǹ'
+ 0xf2: 0x17ef, # 'ò'
+ 0xf9: 0x17f5, # 'ù'
+ 0x1e81: 0x17f7, # 'ẁ'
+ 0x1ef3: 0x17f9, # 'ỳ'
+ 0x60: 0x17a0, # '`'
+ 0xc2: 0x1c41, # 'Â'
+ 0x108: 0x1c43, # 'Ĉ'
+ 0xca: 0x1c45, # 'Ê'
+ 0x11c: 0x1c47, # 'Ĝ'
+ 0x124: 0x1c48, # 'Ĥ'
+ 0xce: 0x1c49, # 'Î'
+ 0x134: 0x1c4a, # 'Ĵ'
+ 0xd4: 0x1c4f, # 'Ô'
+ 0x15c: 0x1c53, # 'Ŝ'
+ 0xdb: 0x1c55, # 'Û'
+ 0x174: 0x1c57, # 'Ŵ'
+ 0x176: 0x1c59, # 'Ŷ'
+ 0x1e90: 0x1c5a, # 'Ẑ'
+ 0xe2: 0x1c61, # 'â'
+ 0x109: 0x1c63, # 'ĉ'
+ 0xea: 0x1c65, # 'ê'
+ 0x11d: 0x1c67, # 'ĝ'
+ 0x125: 0x1c68, # 'ĥ'
+ 0xee: 0x1c69, # 'î'
+ 0x135: 0x1c6a, # 'ĵ'
+ 0xf4: 0x1c6f, # 'ô'
+ 0x15d: 0x1c73, # 'ŝ'
+ 0xfb: 0x1c75, # 'û'
+ 0x175: 0x1c77, # 'ŵ'
+ 0x177: 0x1c79, # 'ŷ'
+ 0x1e91: 0x1c7a, # 'ẑ'
+ 0x5e: 0x1c20, # '^'
+ 0x1cd: 0x18c1, # 'Ǎ'
+ 0x10c: 0x18c3, # 'Č'
+ 0x10e: 0x18c4, # 'Ď'
+ 0x11a: 0x18c5, # 'Ě'
+ 0x1e6: 0x18c7, # 'Ǧ'
+ 0x21e: 0x18c8, # 'Ȟ'
+ 0x1cf: 0x18c9, # 'Ǐ'
+ 0x1e8: 0x18cb, # 'Ǩ'
+ 0x13d: 0x18cc, # 'Ľ'
+ 0x147: 0x18ce, # 'Ň'
+ 0x1d1: 0x18cf, # 'Ǒ'
+ 0x158: 0x18d2, # 'Ř'
+ 0x160: 0x18d3, # 'Š'
+ 0x164: 0x18d4, # 'Ť'
+ 0x1d3: 0x18d5, # 'Ǔ'
+ 0x17d: 0x18da, # 'Ž'
+ 0x1ee: 0x181b7, # 'Ǯ'
+ 0x1ce: 0x18e1, # 'ǎ'
+ 0x10d: 0x18e3, # 'č'
+ 0x10f: 0x18e4, # 'ď'
+ 0x11b: 0x18e5, # 'ě'
+ 0x1e7: 0x18e7, # 'ǧ'
+ 0x21f: 0x18e8, # 'ȟ'
+ 0x1d0: 0x18e9, # 'ǐ'
+ 0x1f0: 0x18ea, # 'ǰ'
+ 0x1e9: 0x18eb, # 'ǩ'
+ 0x13e: 0x18ec, # 'ľ'
+ 0x148: 0x18ee, # 'ň'
+ 0x1d2: 0x18ef, # 'ǒ'
+ 0x159: 0x18f2, # 'ř'
+ 0x161: 0x18f3, # 'š'
+ 0x165: 0x18f4, # 'ť'
+ 0x1d4: 0x18f5, # 'ǔ'
+ 0x17e: 0x18fa, # 'ž'
+ 0x1ef: 0x18292, # 'ǯ'
+ 0x2c7: 0x18a0, # 'ˇ'
+ 0x221e: 0x0ca0, # '∞'
+ 0x221a: 0x0cd6, # '√'
+ 0x2264: 0x0c2afd, # '≤'
+ 0x2265: 0x0c2afe, # '≥'
+ 0x23a: 0x12c1, # 'Ⱥ'
+ 0x23b: 0x12c3, # 'Ȼ'
+ 0x246: 0x12c5, # 'Ɇ'
+ 0x141: 0x12cc, # 'Ł'
+ 0xd8: 0x12cf, # 'Ø'
+ 0x23e: 0x12d4, # 'Ⱦ'
+ 0x23c: 0x12e3, # 'ȼ'
+ 0x247: 0x12e5, # 'ɇ'
+ 0x142: 0x12ec, # 'ł'
+ 0xf8: 0x12ef, # 'ø'
+ 0x1e9c: 0x121ff, # 'ẜ'
+ 0x2260: 0x12bd, # '≠'
+ 0x2244: 0x1222c3, # '≄'
+ 0x226e: 0x12bc, # '≮'
+ 0x226f: 0x12be, # '≯'
+ 0x2270: 0x122afd, # '≰'
+ 0x2271: 0x122afe, # '≱'
+ 0x2f: 0x12a0, # '/'
+ 0x243: 0x2fc2, # 'Ƀ'
+ 0x110: 0x2fc4, # 'Đ'
+ 0x1e4: 0x2fc7, # 'Ǥ'
+ 0x126: 0x2fc8, # 'Ħ'
+ 0x197: 0x2fc9, # 'Ɨ'
+ 0x248: 0x2fca, # 'Ɉ'
+ 0x23d: 0x2fcc, # 'Ƚ'
+ 0x24c: 0x2fd2, # 'Ɍ'
+ 0x166: 0x2fd4, # 'Ŧ'
+ 0x244: 0x2fd5, # 'Ʉ'
+ 0x24e: 0x2fd9, # 'Ɏ'
+ 0x1b5: 0x2fda, # 'Ƶ'
+ 0x180: 0x2fe2, # 'ƀ'
+ 0x111: 0x2fe4, # 'đ'
+ 0x1e5: 0x2fe7, # 'ǥ'
+ 0x127: 0x2fe8, # 'ħ'
+ 0x249: 0x2fea, # 'ɉ'
+ 0x19a: 0x2fec, # 'ƚ'
+ 0x24d: 0x2ff2, # 'ɍ'
+ 0x167: 0x2ff4, # 'ŧ'
+ 0x24f: 0x2ff9, # 'ɏ'
+ 0x1b6: 0x2ffa, # 'ƶ'
+ 0x2d: 0x2fa0, # '-'
+ 0xc4: 0x07c1, # 'Ä'
+ 0xcb: 0x07c5, # 'Ë'
+ 0x1e26: 0x07c8, # 'Ḧ'
+ 0xcf: 0x07c9, # 'Ï'
+ 0xd6: 0x07cf, # 'Ö'
+ 0xdc: 0x07d5, # 'Ü'
+ 0x1e84: 0x07d7, # 'Ẅ'
+ 0x1e8c: 0x07d8, # 'Ẍ'
+ 0x178: 0x07d9, # 'Ÿ'
+ 0xe4: 0x07e1, # 'ä'
+ 0xeb: 0x07e5, # 'ë'
+ 0x1e27: 0x07e8, # 'ḧ'
+ 0xef: 0x07e9, # 'ï'
+ 0xf6: 0x07ef, # 'ö'
+ 0x1e97: 0x07f4, # 'ẗ'
+ 0xfc: 0x07f5, # 'ü'
+ 0x1e85: 0x07f7, # 'ẅ'
+ 0x1e8d: 0x07f8, # 'ẍ'
+ 0xff: 0x07f9, # 'ÿ'
+ 0xa8: 0x07a0, # '¨'
+ 0xc7: 0x0bc3, # 'Ç'
+ 0x1e10: 0x0bc4, # 'Ḑ'
+ 0x228: 0x0bc5, # 'Ȩ'
+ 0x122: 0x0bc7, # 'Ģ'
+ 0x1e28: 0x0bc8, # 'Ḩ'
+ 0x136: 0x0bcb, # 'Ķ'
+ 0x13b: 0x0bcc, # 'Ļ'
+ 0x145: 0x0bce, # 'Ņ'
+ 0x156: 0x0bd2, # 'Ŗ'
+ 0x15e: 0x0bd3, # 'Ş'
+ 0x162: 0x0bd4, # 'Ţ'
+ 0xe7: 0x0be3, # 'ç'
+ 0x1e11: 0x0be4, # 'ḑ'
+ 0x229: 0x0be5, # 'ȩ'
+ 0x123: 0x0be7, # 'ģ'
+ 0x1e29: 0x0be8, # 'ḩ'
+ 0x137: 0x0beb, # 'ķ'
+ 0x13c: 0x0bec, # 'ļ'
+ 0x146: 0x0bee, # 'ņ'
+ 0x157: 0x0bf2, # 'ŗ'
+ 0x15f: 0x0bf3, # 'ş'
+ 0x163: 0x0bf4, # 'ţ'
+ 0xb8: 0x0ba0, # '¸'
+ 0x2070: 0x0db0, # '⁰'
+ 0xb9: 0x0db1, # '¹'
+ 0xb2: 0x0db2, # '²'
+ 0xb3: 0x0db3, # '³'
+ 0x2074: 0x0db4, # '⁴'
+ 0x2075: 0x0db5, # '⁵'
+ 0x2076: 0x0db6, # '⁶'
+ 0x2077: 0x0db7, # '⁷'
+ 0x2078: 0x0db8, # '⁸'
+ 0x2079: 0x0db9, # '⁹'
+ 0x207d: 0x0da8, # '⁽'
+ 0x207e: 0x0da9, # '⁾'
+ 0x207a: 0x0dab, # '⁺'
+ 0x207b: 0x0dad, # '⁻'
+ 0x207c: 0x0dbd, # '⁼'
+ 0x1d49: 0x0da0, # 'ᵉ'
+ 0x2039: 0x0eab, # '‹'
+ 0x203a: 0x0ebb, # '›'
+ 0xad: 0x0ead, # '\xad'
+ 0xf0: 0x0ee4, # 'ð'
+ 0xd0: 0x0ec4, # 'Ð'
+ 0x133: 0x0eea, # 'ij'
+ 0x132: 0x0eca, # 'IJ'
+ 0x259: 0x0ee5, # 'ə'
+ 0x18f: 0x0ec5, # 'Ə'
+ 0x3b8: 0x36f5, # 'θ'
+ 0x3f4: 0x0ec3, # 'ϴ'
+ 0xfe: 0x0ef4, # 'þ'
+ 0xde: 0x0ed4, # 'Þ'
+ 0xdf: 0x0ea0, # 'ß'
+ 0x1e9e: 0x0e1ff, # 'ẞ'
+ 0x14b: 0x0eee, # 'ŋ'
+ 0x14a: 0x0ece, # 'Ŋ'
+ 0xba: 0x0eaf, # 'º'
+ 0x2bb: 0x0e2098, # 'ʻ'
+ 0x2bc: 0x0e2099, # 'ʼ'
+ 0xaa: 0x0e2db, # 'ª'
+ 0x2003: 0x0e20af, # '\u2003'
+ 0x102: 0x0fc1, # 'Ă'
+ 0x114: 0x0fc5, # 'Ĕ'
+ 0x11e: 0x0fc7, # 'Ğ'
+ 0x12c: 0x0fc9, # 'Ĭ'
+ 0x14e: 0x0fcf, # 'Ŏ'
+ 0x16c: 0x0fd5, # 'Ŭ'
+ 0x103: 0x0fe1, # 'ă'
+ 0x115: 0x0fe5, # 'ĕ'
+ 0x11f: 0x0fe7, # 'ğ'
+ 0x12d: 0x0fe9, # 'ĭ'
+ 0x14f: 0x0fef, # 'ŏ'
+ 0x16d: 0x0ff5, # 'ŭ'
+ 0x2d8: 0x0fa0, # '˘'
+ 0xc3: 0x33c1, # 'Ã'
+ 0x1ebc: 0x33c5, # 'Ẽ'
+ 0x128: 0x33c9, # 'Ĩ'
+ 0xd1: 0x33ce, # 'Ñ'
+ 0xd5: 0x33cf, # 'Õ'
+ 0x168: 0x33d5, # 'Ũ'
+ 0x1e7c: 0x33d6, # 'Ṽ'
+ 0x1ef8: 0x33d9, # 'Ỹ'
+ 0xe3: 0x33e1, # 'ã'
+ 0x1ebd: 0x33e5, # 'ẽ'
+ 0x129: 0x33e9, # 'ĩ'
+ 0xf1: 0x33ee, # 'ñ'
+ 0xf5: 0x33ef, # 'õ'
+ 0x169: 0x33f5, # 'ũ'
+ 0x1e7d: 0x33f6, # 'ṽ'
+ 0x1ef9: 0x33f9, # 'ỹ'
+ 0x2243: 0x33ad, # '≃'
+ 0x2248: 0x33bd, # '≈'
+ 0x2272: 0x33bc, # '≲'
+ 0x2273: 0x33be, # '≳'
+ 0x7e: 0x33a0, # '~'
+ 0x100: 0x34c1, # 'Ā'
+ 0x112: 0x34c5, # 'Ē'
+ 0x1e20: 0x34c7, # 'Ḡ'
+ 0x12a: 0x34c9, # 'Ī'
+ 0x14c: 0x34cf, # 'Ō'
+ 0x16a: 0x34d5, # 'Ū'
+ 0x232: 0x34d9, # 'Ȳ'
+ 0x101: 0x34e1, # 'ā'
+ 0x113: 0x34e5, # 'ē'
+ 0x1e21: 0x34e7, # 'ḡ'
+ 0x12b: 0x34e9, # 'ī'
+ 0x14d: 0x34ef, # 'ō'
+ 0x16b: 0x34f5, # 'ū'
+ 0x233: 0x34f9, # 'ȳ'
+ 0x1e2: 0x34c6, # 'Ǣ'
+ 0x1e3: 0x34e6, # 'ǣ'
+ 0xaf: 0x34a0, # '¯'
+ 0xc5: 0x10c1, # 'Å'
+ 0x16e: 0x10d5, # 'Ů'
+ 0xe5: 0x10e1, # 'å'
+ 0x16f: 0x10f5, # 'ů'
+ 0x1e98: 0x10f7, # 'ẘ'
+ 0x1e99: 0x10f9, # 'ẙ'
+ 0x2da: 0x10a0, # '˚'
+ 0x391: 0x36c1, # 'Α'
+ 0x392: 0x36c2, # 'Β'
+ 0x3a8: 0x36c3, # 'Ψ'
+ 0x394: 0x36c4, # 'Δ'
+ 0x395: 0x36c5, # 'Ε'
+ 0x3a6: 0x36c6, # 'Φ'
+ 0x393: 0x36c7, # 'Γ'
+ 0x397: 0x36c8, # 'Η'
+ 0x399: 0x36c9, # 'Ι'
+ 0x39e: 0x36ca, # 'Ξ'
+ 0x39a: 0x36cb, # 'Κ'
+ 0x39b: 0x36cc, # 'Λ'
+ 0x39c: 0x36cd, # 'Μ'
+ 0x39d: 0x36ce, # 'Ν'
+ 0x39f: 0x36cf, # 'Ο'
+ 0x3a0: 0x36d0, # 'Π'
+ 0x3a1: 0x36d2, # 'Ρ'
+ 0x3a3: 0x36d3, # 'Σ'
+ 0x3a4: 0x36d4, # 'Τ'
+ 0x398: 0x36d5, # 'Θ'
+ 0x3a9: 0x36d6, # 'Ω'
+ 0x3a7: 0x36d8, # 'Χ'
+ 0x3a5: 0x36d9, # 'Υ'
+ 0x396: 0x36da, # 'Ζ'
+ 0x3b1: 0x36e1, # 'α'
+ 0x3b2: 0x36e2, # 'β'
+ 0x3c8: 0x36e3, # 'ψ'
+ 0x3b4: 0x36e4, # 'δ'
+ 0x3b5: 0x36e5, # 'ε'
+ 0x3c6: 0x36e6, # 'φ'
+ 0x3b3: 0x36e7, # 'γ'
+ 0x3b7: 0x36e8, # 'η'
+ 0x3b9: 0x36e9, # 'ι'
+ 0x3be: 0x36ea, # 'ξ'
+ 0x3ba: 0x36eb, # 'κ'
+ 0x3bb: 0x36ec, # 'λ'
+ 0x3bc: 0x36ed, # 'μ'
+ 0x3bd: 0x36ee, # 'ν'
+ 0x3bf: 0x36ef, # 'ο'
+ 0x3c0: 0x36f0, # 'π'
+ 0x3c1: 0x36f2, # 'ρ'
+ 0x3c3: 0x36f3, # 'σ'
+ 0x3c4: 0x36f4, # 'τ'
+ 0x3c9: 0x36f6, # 'ω'
+ 0x3c2: 0x36f7, # 'ς'
+ 0x3c7: 0x36f8, # 'χ'
+ 0x3c5: 0x36f9, # 'υ'
+ 0x3b6: 0x36fa, # 'ζ'
+ 0xb5: 0x36a0, # 'µ'
+ 0x1ea0: 0x37c1, # 'Ạ'
+ 0x1e04: 0x37c2, # 'Ḅ'
+ 0x1e0c: 0x37c4, # 'Ḍ'
+ 0x1eb8: 0x37c5, # 'Ẹ'
+ 0x1e24: 0x37c8, # 'Ḥ'
+ 0x1eca: 0x37c9, # 'Ị'
+ 0x1e32: 0x37cb, # 'Ḳ'
+ 0x1e36: 0x37cc, # 'Ḷ'
+ 0x1e42: 0x37cd, # 'Ṃ'
+ 0x1e46: 0x37ce, # 'Ṇ'
+ 0x1ecc: 0x37cf, # 'Ọ'
+ 0x1e5a: 0x37d2, # 'Ṛ'
+ 0x1e62: 0x37d3, # 'Ṣ'
+ 0x1e6c: 0x37d4, # 'Ṭ'
+ 0x1ee4: 0x37d5, # 'Ụ'
+ 0x1e7e: 0x37d6, # 'Ṿ'
+ 0x1e88: 0x37d7, # 'Ẉ'
+ 0x1ef4: 0x37d9, # 'Ỵ'
+ 0x1e92: 0x37da, # 'Ẓ'
+ 0x1ea1: 0x37e1, # 'ạ'
+ 0x1e05: 0x37e2, # 'ḅ'
+ 0x1e0d: 0x37e4, # 'ḍ'
+ 0x1eb9: 0x37e5, # 'ẹ'
+ 0x1e25: 0x37e8, # 'ḥ'
+ 0x1ecb: 0x37e9, # 'ị'
+ 0x1e33: 0x37eb, # 'ḳ'
+ 0x1e37: 0x37ec, # 'ḷ'
+ 0x1e43: 0x37ed, # 'ṃ'
+ 0x1e47: 0x37ee, # 'ṇ'
+ 0x1ecd: 0x37ef, # 'ọ'
+ 0x1e5b: 0x37f2, # 'ṛ'
+ 0x1e63: 0x37f3, # 'ṣ'
+ 0x1e6d: 0x37f4, # 'ṭ'
+ 0x1ee5: 0x37f5, # 'ụ'
+ 0x1e7f: 0x37f6, # 'ṿ'
+ 0x1e89: 0x37f7, # 'ẉ'
+ 0x1ef5: 0x37f9, # 'ỵ'
+ 0x1e93: 0x37fa, # 'ẓ'
+ 0x2e: 0x37a0, # '.'
+ 0x104: 0x38c1, # 'Ą'
+ 0x118: 0x38c5, # 'Ę'
+ 0x12e: 0x38c9, # 'Į'
+ 0x1ea: 0x38cf, # 'Ǫ'
+ 0x172: 0x38d5, # 'Ų'
+ 0x105: 0x38e1, # 'ą'
+ 0x119: 0x38e5, # 'ę'
+ 0x12f: 0x38e9, # 'į'
+ 0x1eb: 0x38ef, # 'ǫ'
+ 0x173: 0x38f5, # 'ų'
+ 0x2db: 0x38a0, # '˛'
+ }
diff --git a/src/keycode_win_frnb.py b/src/keycode_win_frnb.py
new file mode 100644
index 0000000..2cab58a
--- /dev/null
+++ b/src/keycode_win_frnb.py
@@ -0,0 +1,165 @@
+# SPDX-FileCopyrightText: 2022 Neradoc NeraOnGit@ri1.fr
+# SPDX-License-Identifier: MIT
+"""
+This file was automatically generated using Circuitpython_Keyboard_Layouts
+"""
+
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/Neradoc/Circuitpython_Keyboard_Layouts.git"
+
+
+class Keycode:
+ A = 0x04
+ B = 0x14
+ C = 0x0b
+ D = 0x0c
+ E = 0x09
+ F = 0x38
+ G = 0x36
+ H = 0x37
+ I = 0x07
+ J = 0x13
+ K = 0x05
+ L = 0x12
+ M = 0x34
+ N = 0x33
+ O = 0x15
+ P = 0x08
+ Q = 0x10
+ R = 0x0f
+ S = 0x0e
+ T = 0x0d
+ U = 0x16
+ V = 0x18
+ W = 0x30
+ X = 0x06
+ Y = 0x1b
+ Z = 0x2f
+ ALT = 0xe2
+ END = 0x4d
+ F1 = 0x3a
+ F2 = 0x3b
+ F3 = 0x3c
+ F4 = 0x3d
+ F5 = 0x3e
+ F6 = 0x3f
+ F7 = 0x40
+ F8 = 0x41
+ F9 = 0x42
+ F10 = 0x43
+ F11 = 0x44
+ F12 = 0x45
+ F13 = 0x68
+ F14 = 0x69
+ F15 = 0x6a
+ F16 = 0x6b
+ F17 = 0x6c
+ F18 = 0x6d
+ F19 = 0x6e
+ F20 = 0x6f
+ F21 = 0x70
+ F22 = 0x71
+ F23 = 0x72
+ F24 = 0x73
+ GUI = 0xe3
+ ONE = 0x1e
+ SIX = 0x23
+ TAB = 0x2b
+ TWO = 0x1f
+ FIVE = 0x22
+ FOUR = 0x21
+ HOME = 0x4a
+ NINE = 0x26
+ ZERO = 0x27
+ ALTGR = 0xe6
+ Brève = 0x0f
+ COMMA = 0x0a
+ Caron = 0x18
+ EIGHT = 0x25
+ ENTER = 0x28
+ MINUS = 0x2d
+ PAUSE = 0x48
+ QUOTE = 0x35
+ SEVEN = 0x24
+ SHIFT = 0xe1
+ SPACE = 0x2c
+ THREE = 0x20
+ Tilde = 0x33
+ Tréma = 0x07
+ APPLICATION = 0x65
+ Accent_aigu = 0x1a
+ Accent_circonflexe = 0x1c
+ Accent_grave = 0x17
+ BACKSLASH = 0x31
+ BACKSPACE = 0x2a
+ Barre_couvrante = 0x2f
+ CAPS_LOCK = 0x39
+ COMMAND = 0xe3
+ CONTROL = 0xe0
+ Cédille = 0x0b
+ DELETE = 0x4c
+ DOWN_ARROW = 0x51
+ EQUALS = 0x2e
+ ESCAPE = 0x29
+ Exposants = 0x0d
+ FORWARD_SLASH = 0x17
+ GRAVE_ACCENT = 0x11
+ INSERT = 0x49
+ KEYPAD_ASTERISK = 0x55
+ KEYPAD_EIGHT = 0x60
+ KEYPAD_FIVE = 0x5d
+ KEYPAD_FORWARD_SLASH = 0x54
+ KEYPAD_FOUR = 0x5c
+ KEYPAD_MINUS = 0x56
+ KEYPAD_NINE = 0x61
+ KEYPAD_NUMLOCK = 0x53
+ KEYPAD_ONE = 0x59
+ KEYPAD_PERIOD = 0x63
+ KEYPAD_PLUS = 0x57
+ KEYPAD_SEVEN = 0x5f
+ KEYPAD_SIX = 0x5e
+ KEYPAD_THREE = 0x5b
+ KEYPAD_TWO = 0x5a
+ KEYPAD_ZERO = 0x62
+ LEFT_ALT = 0xe2
+ LEFT_ARROW = 0x50
+ LEFT_BRACKET = 0x1c
+ LEFT_CONTROL = 0xe0
+ LEFT_GUI = 0xe3
+ LEFT_SHIFT = 0xe1
+ Latin_et_ponctuation = 0x0e
+ Lettres_grecques = 0x36
+ Macron = 0x34
+ OEM_102 = 0x64
+ OPTION = 0xe2
+ Ogonek = 0x38
+ PAGE_DOWN = 0x4e
+ PAGE_UP = 0x4b
+ PERIOD = 0x19
+ PRINT_SCREEN = 0x46
+ Point_souscrit = 0x37
+ RETURN = 0x28
+ RIGHT_ALT = 0xe6
+ RIGHT_ARROW = 0x4f
+ RIGHT_BRACKET = 0x1d
+ RIGHT_CONTROL = 0xe4
+ RIGHT_GUI = 0xe7
+ RIGHT_SHIFT = 0xe5
+ Rond_en_chef = 0x10
+ SCROLL_LOCK = 0x47
+ SEMICOLON = 0x1a
+ SPACEBAR = 0x2c
+ UP_ARROW = 0x52
+ WINDOWS = 0xe3
+ Symboles_scientifiques = 0x0c
+ Barre_oblique_couvrante = 0x12
+
+ @classmethod
+ def modifier_bit(cls, keycode):
+ """Return the modifer bit to be set in an HID keycode report if this is a
+ modifier key; otherwise return 0."""
+ return (
+ 1 << (keycode - 0xE0) if cls.LEFT_CONTROL <= keycode <= cls.RIGHT_GUI else 0
+ )
+ \ No newline at end of file
diff --git a/src/lib/adafruit_bitmap_font/__init__.py b/src/lib/adafruit_bitmap_font/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/adafruit_bitmap_font/__init__.py
diff --git a/src/lib/adafruit_display_shapes/__init__.py b/src/lib/adafruit_display_shapes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/adafruit_display_shapes/__init__.py
diff --git a/src/macros/1-ff.py b/src/macros/1-ff.py
new file mode 100644
index 0000000..f1d26ce
--- /dev/null
+++ b/src/macros/1-ff.py
@@ -0,0 +1,83 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+from adafruit_hid.mouse import Mouse
+
+import skeleton
+from actions import Action
+
+def reload(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.R])
+
+def tab(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.T])
+
+def close(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.W])
+
+def page_up(macropad, key, pressed):
+ macropad.mouse.move(
+ 0,
+ 0,
+ 1)
+
+def page_down(macropad, key, pressed):
+ macropad.mouse.move(
+ 0,
+ 0,
+ -1)
+
+class App(object):
+ def __init__(self):
+ self.group = skeleton.Group(0x050500,
+ [(9, "Page")
+ ,(10, "Tab")]
+ )
+
+ def rot_cw(self, macropad, key, pressed):
+ if self.group.selected == 10:
+ Action().key(pressed, [Keycode.CONTROL, Keycode.PAGE_DOWN])
+ else:
+ page_down(macropad, key, pressed)
+
+ def rot_ccw(self, macropad, key, pressed):
+ if self.group.selected == 10:
+ Action().key(pressed, [Keycode.CONTROL, Keycode.PAGE_UP])
+ else:
+ page_up(macropad, key, pressed)
+
+def key(code):
+ def action(macropad, key, pressed):
+ Action().key(pressed, code)
+ return action
+
+def build_application():
+ app = App()
+
+ configuration = skeleton.Configuration("Firefox")
+ configuration.registerKey(0, "^r", reload, 0x000500)
+ configuration.registerKey(1, "^t", tab, 0x000005)
+ configuration.registerKey(2, "x", close, 0x050000)
+ configuration.registerKey(3, "<", key([Keycode.CONTROL, Keycode.PAGE_UP]), 0x050505)
+ configuration.registerKey(5, ">", key([Keycode.CONTROL, Keycode.PAGE_DOWN]), 0x050505)
+ configuration.registerKey(12, "", app.rot_cw, None)
+ configuration.registerKey(13, "", app.rot_ccw,None)
+ configuration.registerGroup(app.group)
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/2-num.py b/src/macros/2-num.py
new file mode 100644
index 0000000..68b0911
--- /dev/null
+++ b/src/macros/2-num.py
@@ -0,0 +1,103 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
+import colorsys
+
+import skeleton
+from actions import Action
+
+
+class Color(object):
+ """ Represent the color of a key in the HSV model.
+
+ The class implements the int() function in order to being compatible with
+ the remaining of the application.
+ """
+ def __init__(self, h, s, v):
+ self.col = (h, s, v)
+
+ def __int__(self):
+ r, g, b = colorsys.hsv_to_rgb(self.col[0], self.col[1], self.col[2])
+ return (r<<16) + (g<<8) + b
+
+def build_application():
+ configuration = skeleton.Configuration("Num pad")
+ configuration.registerKey(
+ 0,
+ "7",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_SEVEN]),
+ Color(0.333, 1.0, 0.0196))
+ configuration.registerKey(
+ 1,
+ "8",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_EIGHT]),
+ Color(0.266, 1.0, 0.0196))
+ configuration.registerKey(
+ 2,
+ "9",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_NINE]),
+ Color(0.166, 1.0, 0.0196))
+ configuration.registerKey(
+ 3,
+ "4",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_FOUR]),
+ Color(0.4, 1.0, 0.0196))
+ configuration.registerKey(
+ 4,
+ "5",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_FIVE]),
+ Color(0.333, 0.6, 0.0196))
+ configuration.registerKey(
+ 5,
+ "6",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_SIX]),
+ Color(0.16, 0.6, 0.0196))
+ configuration.registerKey(
+ 6,
+ "1",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_ONE]),
+ # 0x000505
+ Color(0.5, 1.0, 0.0196))
+ configuration.registerKey(
+ 7,
+ "2",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_TWO]),
+ Color(0.5, 0.6, 0.0196))
+ configuration.registerKey(
+ 8,
+ "3",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_THREE]),
+ # 0x050505
+ Color(0.01, 0.01, 0.0196))
+ configuration.registerKey(
+ 9,
+ "0",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_ZERO]),
+ # 0x000507
+ Color(0.54, 1.0, 0.027))
+
+ # The dot key.
+ configuration.registerKey(
+ 11,
+ ".",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_PERIOD]),
+ # 0x050507
+ Color(0.66, 0.286, 0.027))
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/3-fkeys.py b/src/macros/3-fkeys.py
new file mode 100644
index 0000000..c35c500
--- /dev/null
+++ b/src/macros/3-fkeys.py
@@ -0,0 +1,87 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
+
+import skeleton
+from actions import Action
+
+def build_application():
+ configuration = skeleton.Configuration("F-Keys")
+ configuration.registerKey(
+ 0,
+ "F13",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F13]),
+ None)
+ configuration.registerKey(
+ 1,
+ "F14",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F14]),
+ None)
+ configuration.registerKey(
+ 2,
+ "F15",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F15]),
+ None)
+ configuration.registerKey(
+ 3,
+ "F16",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F16]),
+ None)
+ configuration.registerKey(
+ 4,
+ "F17",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F17]),
+ None)
+ configuration.registerKey(
+ 5,
+ "F18",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F18]),
+ None)
+ configuration.registerKey(
+ 6,
+ "F19",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F19]),
+ None)
+ configuration.registerKey(
+ 7,
+ "F20",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F20]),
+ None)
+ configuration.registerKey(
+ 8,
+ "F21",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F21]),
+ None)
+ configuration.registerKey(
+ 9,
+ "F22",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F22]),
+ None)
+ configuration.registerKey(
+ 10,
+ "F23",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F23]),
+ None)
+ configuration.registerKey(
+ 11,
+ "F24",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.F24]),
+ None)
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/4-move_mouse.py b/src/macros/4-move_mouse.py
new file mode 100644
index 0000000..ef6ede0
--- /dev/null
+++ b/src/macros/4-move_mouse.py
@@ -0,0 +1,54 @@
+import skeleton
+
+class Color(object):
+ """ Return a color which may change over time.
+ """
+ def __init__(self, code):
+ self.code = code
+
+ def __int__(self):
+ return self.code
+
+class NoIdle(object):
+
+ def __init__(self):
+ self.x = 10
+ self.color = Color(0x000500)
+ self.active = False
+
+ def update(self, macropad):
+ if not self.active:
+ return
+
+ self.x *= -1
+ macropad.mouse.move(
+ self.x,
+ 0,
+ 0)
+ # Update the color in red or blue in order to show that the heartbeat
+ # is active.
+ self.color.code = 0x010000 if self.x > 0 else 0x000001
+ macropad.pixels[4] = int(self.color)
+ macropad.pixels.show()
+
+ def pause(self, macropad, key, pressed):
+ if pressed:
+ self.active = not self.active
+ if self.active:
+ self.update(macropad)
+ else:
+ # Restore the default color (green)
+ self.color.code = 0x000500
+ macropad.pixels[4] = int(self.color)
+ macropad.pixels.show()
+
+
+def build_application():
+ configuration = skeleton.Configuration("NoIdle")
+ actor = NoIdle()
+ configuration.setTick(5000, actor.update)
+ configuration.registerKey(4, "switch", actor.pause, actor.color)
+ return configuration
+
+configuration = build_application()
+
diff --git a/src/macros/5-irssi.py b/src/macros/5-irssi.py
new file mode 100644
index 0000000..bcf5d81
--- /dev/null
+++ b/src/macros/5-irssi.py
@@ -0,0 +1,49 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+
+import skeleton
+from actions import Action
+
+def wc(macropad, key, pressed):
+ if not pressed:
+ return
+ Action().sequence(
+ [ [Keycode.KEYPAD_FORWARD_SLASH]
+ , [Keycode.W]
+ , [Keycode.C]
+ , [Keycode.ENTER]
+ ]
+ )
+
+def next_one(macropad, key, pressed):
+ Action().key(pressed, [Keycode.ALT, Keycode.A])
+
+def build_application():
+
+ configuration = skeleton.Configuration("Irssi")
+ configuration.visible = True
+ configuration.registerKey(0, "next.", next_one, 0x000200)
+ configuration.registerKey(2, "/wc", wc, 0x020000)
+
+
+ return configuration
+
+configuration = build_application()
+
+
diff --git a/src/macros/99-none.py b/src/macros/99-none.py
new file mode 100644
index 0000000..3c15de4
--- /dev/null
+++ b/src/macros/99-none.py
@@ -0,0 +1,3 @@
+import skeleton
+
+configuration = skeleton.Configuration("Nothing")
diff --git a/src/macros/99-wm.py b/src/macros/99-wm.py
new file mode 100644
index 0000000..225044b
--- /dev/null
+++ b/src/macros/99-wm.py
@@ -0,0 +1,62 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
+from adafruit_hid.mouse import Mouse
+
+import skeleton
+from actions import Action
+
+def desktop4(macropad, key, pressed):
+ Action().key(pressed, [Keycode.RIGHT_ALT, Keycode.F4])
+
+def desktop3(macropad, key, pressed):
+ Action().key(pressed, [Keycode.RIGHT_ALT, Keycode.F3])
+
+def desktop2(macropad, key, pressed):
+ Action().key(pressed, [Keycode.RIGHT_ALT, Keycode.F2])
+
+def desktop1(macropad, key, pressed):
+ Action().key(pressed, [Keycode.RIGHT_ALT, Keycode.F1])
+
+def rot_cw(macropad, key, pressed):
+ Action().key(pressed, [Keycode.GUI])
+ if pressed:
+ macropad.mouse.move(
+ 0,
+ 0,
+ -1)
+
+def rot_acw(macropad, key, pressed):
+ Action().key(pressed, [Keycode.GUI])
+ if pressed:
+ macropad.mouse.move(
+ 0,
+ 0,
+ 1)
+
+def build_application():
+ configuration = skeleton.Configuration("WM")
+ configuration.registerKey(2, "F4", desktop4, None)
+ configuration.registerKey(5, "F3", desktop3, None)
+ configuration.registerKey(8, "F2", desktop2, None)
+ configuration.registerKey(11, "F1", desktop1, None)
+ configuration.registerKey(12, "", rot_cw, None)
+ configuration.registerKey(13, "", rot_acw, None)
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/olympia.py b/src/macros/olympia.py
new file mode 100644
index 0000000..b124075
--- /dev/null
+++ b/src/macros/olympia.py
@@ -0,0 +1,91 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+import colorsys
+
+import skeleton
+from actions import Action
+
+
+class Color(object):
+ """ Represent the color of a key in the HSV model.
+
+ The class implements the int() function in order to being compatible with
+ the remaining of the application.
+ """
+ def __init__(self, h, s, v):
+ self.col = (h, s, v)
+
+ def __int__(self):
+ r, g, b = colorsys.hsv_to_rgb(self.col[0], self.col[1], self.col[2])
+ return (r<<16) + (g<<8) + b
+
+def key(code):
+ def action(macropad, key, pressed):
+ Action().key(pressed, code)
+ return action
+
+def build_application():
+ configuration = skeleton.Configuration("Olympia")
+ configuration.hidden = False
+ configuration.registerKey(0, "vue", key([Keycode.V]), 0x000500)
+ configuration.registerKey(1, "map", key([Keycode.M]), 0x000005)
+ configuration.registerKey(2, "x", key([Keycode.CONTROL, Keycode.W]), 0x050000)
+ configuration.registerKey(3, "\\", key([Keycode.KEYPAD_SEVEN]), Color(0.333, 1.0, 0.0196))
+ configuration.registerKey(
+ 4,
+ "|",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_EIGHT]),
+ Color(0.266, 1.0, 0.0196))
+ configuration.registerKey(
+ 5,
+ "/",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_NINE]),
+ Color(0.166, 1.0, 0.0196))
+ configuration.registerKey(
+ 6,
+ "<",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_FOUR]),
+ Color(0.4, 1.0, 0.0196))
+
+ configuration.registerKey(7, "Inventaire", key([Keycode.I]), Color(0.333, 0.6, 0.0196))
+
+ configuration.registerKey(
+ 8,
+ ">",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_SIX]),
+ # 0x050502
+ Color(0.16, 0.6, 0.0196))
+ configuration.registerKey(
+ 9,
+ "/",
+ key([Keycode.KEYPAD_ONE]),
+ 0x000505)
+ configuration.registerKey(
+ 10,
+ "|",
+ lambda macropad, key, pressed: Action().key(pressed, [Keycode.KEYPAD_TWO]),
+ Color(0.5, 0.6, 0.0196))
+ configuration.registerKey(
+ 11,
+ "\\",
+ key([Keycode.KEYPAD_THREE]),
+ 0x050505)
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/santana.py b/src/macros/santana.py
new file mode 100644
index 0000000..2fd88c3
--- /dev/null
+++ b/src/macros/santana.py
@@ -0,0 +1,47 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+from adafruit_hid.mouse import Mouse
+
+import skeleton
+from actions import Action
+
+def groovy(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.G])
+
+def log(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.L])
+
+def dql(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.SHIFT, Keycode.F])
+
+def package(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.P])
+
+def build_application():
+
+ configuration = skeleton.Configuration("Santana")
+ configuration.visible = False
+ configuration.registerKey(0, "groovy", groovy, 0x000500)
+ configuration.registerKey(1, "logs", log, 0x000005)
+
+ configuration.registerKey(3, "dql", dql, 0x050500)
+ configuration.registerKey(4, "package",package, 0x050000)
+ return configuration
+
+configuration = build_application()
diff --git a/src/macros/teams.py b/src/macros/teams.py
new file mode 100644
index 0000000..621f8b6
--- /dev/null
+++ b/src/macros/teams.py
@@ -0,0 +1,44 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+
+import skeleton
+from actions import Action
+
+def conv(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.TWO])
+
+def planning(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.FOUR])
+
+def link(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.K])
+
+def build_application():
+
+ configuration = skeleton.Configuration("Teams")
+ configuration.visible = False
+ configuration.registerKey(0, "Convers.", conv, 0x050000)
+ configuration.registerKey(3, "Planning", planning, 0x050200)
+ configuration.registerKey(2, "Link", link, 0x020002)
+
+
+ return configuration
+
+configuration = build_application()
+
diff --git a/src/macros/zim.py b/src/macros/zim.py
new file mode 100644
index 0000000..fa93c4a
--- /dev/null
+++ b/src/macros/zim.py
@@ -0,0 +1,53 @@
+# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# MACROPAD Hotkeys example: Mouse control
+
+# The syntax for Mouse macros is highly peculiar, in order to maintain
+# backward compatibility with the original keycode-only macro files.
+# The third item for each macro is a list in brackets, and each value within
+# is normally an integer (Keycode), float (delay) or string (typed literally).
+# Consumer Control codes were added as list-within-list, and then mouse
+# further complicates this by adding dicts-within-list. Each mouse-related
+# dict can have any mix of keys 'buttons' w/integer mask of button values
+# (positive to press, negative to release), 'x' w/horizontal motion,
+# 'y' w/vertical and 'wheel' with scrollwheel motion.
+
+# To reference Mouse constants, import Mouse like so...
+from keycode_win_frnb import Keycode # Use the french bepo layout
+
+import skeleton
+from actions import Action
+
+def t1(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.SHIFT, Keycode.ONE])
+
+def t2(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.SHIFT, Keycode.TWO])
+
+def t3(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.SHIFT, Keycode.THREE])
+
+def none(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.SHIFT, Keycode.NINE])
+
+def link(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.L])
+
+def build_application():
+
+ configuration = skeleton.Configuration("Zim")
+ configuration.visible = False
+ configuration.registerKey(0, "T1", t1, 0x000500)
+ configuration.registerKey(3, "T2", t2, 0x000200)
+ configuration.registerKey(6, "T3", t3, 0x000100)
+ configuration.registerKey(9, "None", none, 0x000000)
+
+ configuration.registerKey(2, "Link", link, 0x020002)
+
+
+ return configuration
+
+configuration = build_application()
+
diff --git a/src/menu.py b/src/menu.py
new file mode 100644
index 0000000..319cae1
--- /dev/null
+++ b/src/menu.py
@@ -0,0 +1,23 @@
+import skeleton
+
+def build_application(apps):
+
+ enabled_apps = [app for app in apps if app.visible]
+ max_l = min(12, len(enabled_apps))
+ configuration = skeleton.Configuration("Application menu")
+ for index in range(max_l):
+ if enabled_apps[index] is None:
+ break
+
+ # I use a lambda function here, the index is stored in a closure
+ # otherwise the value is erased and only keep the last iteration
+ # index
+ callback = lambda n:lambda macropad, key, pressed:enabled_apps[n]
+
+ configuration.registerKey(
+ index,
+ enabled_apps[index].title,
+ callback(index),
+ None
+ )
+ return configuration
diff --git a/src/readme.rst b/src/readme.rst
new file mode 100644
index 0000000..721bb53
--- /dev/null
+++ b/src/readme.rst
@@ -0,0 +1,73 @@
+.. -*- mode: rst -*-
+.. -*- coding: utf-8 -*-
+
+Application macropad
+
+Utilisation
+===========
+
+Activer le système de fichier
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Lors du démarrage, le système de fichier n’est pas monté. C’est à dire que
+le clavier n’apparait pas comme un lecteur auprès du système.
+
+Pour activer cette fonctionnalité, il faut appuyer sur la touche située en bas
+à droite du clavier pendant le démarrage du clavier.
+
+::
+
+ . . .
+ . . .
+ . . .
+ . . X
+
+Ce code est géré dans le fichier "boot.py"
+
+Pilotage par port série
+~~~~~~~~~~~~~~~~~~~~~~~
+
+L’application peut recevoir des commandes sur le port série : l’envoi d’un json
+tel que :
+
+.. code:: json
+
+ [{"layout": "valeur à charger"}]
+
+va changer le programme actif sur le macropad pour celui qui a été donné en
+paramètre.
+
+De la meme manière, le programme envoie sur le port-série les changements qui
+sont provoqués par l’utilisateur. Ainsi, il est possible d’avoir une
+application sur le PC qui se synchronise avec le Macropad sur la disposition à
+charger.
+
+
+Code
+====
+
+Configuration clavier
+~~~~~~~~~~~~~~~~~~~~~
+
+Le clavier ne connait pas les codes correspondants aux touches saisies.
+L’application n’est capable que d’emettre les codes des touches, mais cela
+n’aide pas la construction d’une application lisible.
+
+Les codes BÉPO sont disponibles dans le fichier `keycode_win_frnb.py` qui fait
+l’association des différentes touches :
+
+.. code:: python
+
+ from keycode_win_frnb import Keycode # Use the french bepo layout
+
+ def reload(macropad, key, pressed):
+ Action().key(pressed, [Keycode.CONTROL, Keycode.R])
+
+Compilation
+===========
+
+Les fichiers mpy sont des fichiers compilés correspondants aux fichier py mais
+occupants moins de place sur le disque. Ils doivent etre compilés à l’aide de
+l’outil mpy-cross qui est téléchargeable ici :
+
+https://adafruit-circuit-python.s3.amazonaws.com/index.html?prefix=bin/mpy-cross/
diff --git a/src/skeleton.py b/src/skeleton.py
new file mode 100644
index 0000000..8497d34
--- /dev/null
+++ b/src/skeleton.py
@@ -0,0 +1,199 @@
+import displayio
+import terminalio
+from adafruit_display_shapes.rect import Rect
+from adafruit_display_text import label
+
+from collections import namedtuple
+from supervisor import ticks_ms
+from actions import Action
+import ticks
+
+class Group(object):
+ def __init__(self, color, actions):
+
+ # The color of the LED for the selected element
+ self.color = color
+
+ self.actions = actions
+
+ self._selected = actions[0][0]
+
+ @property
+ def selected(self):
+ return self._selected
+
+ @selected.setter
+ def selected(self, value):
+ """Set the value, and switch the LED accordingly"""
+
+ keys = [action[0] for action in self.actions]
+
+ if value not in keys:
+ return
+
+ for keyId in keys:
+ Action().macropad.pixels[keyId] = 0
+ Action().macropad.pixels[value] = int(self.color)
+ self._selected = value
+
+class Configuration(object):
+
+ def __init__(self, title):
+
+ # Set the application available in the menu by default
+ self.visible = True
+ self.Action = namedtuple("Action", ("name", "callback", "color"))
+ self.title = title
+ self.keys = {
+ 0 : None, 1 : None, 2 : None,
+ 3 : None, 4 : None, 5 : None,
+ 6 : None, 7 : None, 8 : None,
+ 9 : None, 10 : None, 11 : None,
+ 12 : None,
+ 13 : None,
+ }
+ self.group = []
+ self.tick = None
+
+ def registerKey(self, keyId, name, callback, color):
+ if keyId > 13:
+ return
+ self.keys[keyId] = self.Action(name, callback, color)
+
+ def registerGroup(self, group):
+ self.group.append(group)
+
+ def setTick(self, duration, callback):
+ self.tick = (duration, callback)
+
+class Handler(object):
+
+ def __init__(self, macropad, configuration):
+ self.macropad = macropad
+ self.group = displayio.Group()
+
+ self.keys = {
+ 0 : None, 1 : None, 2 : None,
+ 3 : None, 4 : None, 5 : None,
+ 6 : None, 7 : None, 8 : None,
+ 9 : None, 10 : None, 11 : None,
+ 12 : None,
+ 13 : None,
+ }
+
+ self.colors = {
+ 0 : None, 1 : None, 2 : None,
+ 3 : None, 4 : None, 5 : None,
+ 6 : None, 7 : None, 8 : None,
+ 9 : None, 10 : None, 11 : None,
+ }
+
+ self.options = configuration.group
+
+ self.name = configuration.title
+
+ for index, value in configuration.keys.items():
+ if value is None:
+ continue
+ self.keys[index] = value.name, value.callback
+ self.colors[index] = value.color
+
+ for option in self.options:
+ for action in option.actions:
+ keyId = action[0]
+ self.keys[keyId] = action[1], lambda macropad, key, pressed: None
+ self.macropad.pixels[option.selected] = int(option.color)
+
+ self.periodic_callback = None
+ self.next_tick = None
+
+ if configuration.tick is not None:
+ self.periodic_callback = configuration.tick
+ self._update_tick()
+ self.macropad.pixels.show()
+
+ def _update_tick(self):
+ self.next_tick = ticks.add(
+ ticks_ms(),
+ self.periodic_callback[0]
+ )
+
+ def _set_color(self, key, pressed):
+ if pressed:
+ self.macropad.pixels[key] = int(0x010101)
+ else:
+ color = self.colors[key]
+ self.macropad.pixels[key] = int( self.colors[key] or 0 )
+ for option in self.options:
+ self.macropad.pixels[option.selected] = int(option.color)
+
+ def tick(self):
+ "Execute asyncronous events"
+
+ Action().collect_tasks()
+ if self.next_tick is None or ticks.less(self.next_tick):
+ return
+
+ self.periodic_callback[1](self.macropad)
+ self._update_tick()
+
+ def start(self):
+ self.position = self.macropad.encoder
+
+ for key_index in range(12):
+ x = key_index % 3
+ y = key_index // 3
+ self.group.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF,
+ anchored_position=((self.macropad.display.width - 1) * x / 2,
+ self.macropad.display.height - 1 -
+ (3 - y) * 12),
+ anchor_point=(x / 2, 1.0)))
+ self.group.append(Rect(0, 0, self.macropad.display.width, 12, fill=0xFFFFFF))
+ self.group.append(label.Label(terminalio.FONT, text='', color=0x000000,
+ anchored_position=(self.macropad.display.width//2, -2),
+ anchor_point=(0.5, 0.0)))
+ self.macropad.display.show(self.group)
+
+ self.group[13].text = self.name # Application name
+ for i in range(12):
+ if self.keys[i] is not None:
+ self.group[i].text = self.keys[i][0]
+ self.macropad.keyboard.release_all()
+ self.macropad.consumer_control.release()
+ self.macropad.mouse.release_all()
+ self.macropad.stop_tone()
+ for key in range(12):
+ self._set_color(key, False)
+ self.macropad.pixels.show()
+ self.macropad.display.refresh()
+
+ def execute_action(self, key, pressed):
+
+ for option in self.options:
+ option.selected = key
+
+ action = self.keys[key]
+
+ # Do nothing if there is no action for this key
+ if action is None:
+ return
+
+ # Update the leds
+ if key < 12:
+ if pressed: Action().flash(key, 0x010101)
+ else: self._set_color(key, pressed)
+ else:
+ for key_idx in range(12):
+ self._set_color(key_idx, pressed)
+ self.macropad.pixels.show()
+
+ result = action[1](self.macropad, key, pressed)
+
+ # If we use the rotary encoder, mark all the actions as resolved.
+ if key >= 12:
+ action[1](self.macropad, key, False)
+ for key_idx in range(12):
+ self._set_color(key_idx, False)
+ self.macropad.pixels.show()
+
+ return result
diff --git a/src/ticks.py b/src/ticks.py
new file mode 100644
index 0000000..ba6831a
--- /dev/null
+++ b/src/ticks.py
@@ -0,0 +1,21 @@
+from supervisor import ticks_ms
+
+# See the example at
+# https://docs.circuitpython.org/en/latest/shared-bindings/supervisor/index.html?#supervisor.ticks_ms
+_TICKS_PERIOD = const(1<<29)
+_TICKS_MAX = const(_TICKS_PERIOD-1)
+_TICKS_HALFPERIOD = const(_TICKS_PERIOD//2)
+
+def add(ticks, delta):
+ "Add a delta to a base number of ticks, performing wraparound at 2**29ms."
+ return (ticks + delta) % _TICKS_PERIOD
+
+def ticks_diff(ticks1, ticks2):
+ "Compute the signed difference between two ticks values, assuming that they are within 2**28 ticks"
+ diff = (ticks1 - ticks2) & _TICKS_MAX
+ diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
+ return diff
+
+def less(ticks):
+ "Return true iff ticks1 is less than ticks2, assuming that they are within 2**28 ticks"
+ return ticks_diff(ticks_ms(), ticks) < 0