# 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 from sleep_mode import Power # 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 power = Power(macropad) 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() power.tick() return # No key events key_number = event.key_number pressed = event.pressed power.execute_action(key_number, pressed or False) return app.execute_action(key_number, pressed or False) def switch_layout(configuration): global app app = skeleton.Handler(macropad, configuration) power.set_configuration(configuration) app.start() def search_and_switch(layout): for local_app in apps: if local_app.title == layout and app is not local_app: switch_layout(local_app) break def stack(layout): for configuration in apps: if configuration.title == layout and app is not configuration: app.stack(configuration) app.start() 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, "stack" : stack, "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: while usb_cdc.data.in_waiting != 0: line = usb_cdc.data.readline(usb_cdc.data.in_waiting).strip() try: commands = json.loads(line) for command in commands: for key, value in command.items(): actions[key](value) continue except ValueError: print(line) pass # 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 if configuration.is_layout: send_layout(None) run_application(app) continue