Skip to content

Instantly share code, notes, and snippets.

@BrookeDot
Last active July 28, 2021 06:17
Show Gist options
  • Save BrookeDot/32ff270b0316e15bc2d5f23848bf02f9 to your computer and use it in GitHub Desktop.
Save BrookeDot/32ff270b0316e15bc2d5f23848bf02f9 to your computer and use it in GitHub Desktop.
MACROPAD Hotkeys with Consumer Key Support

MACROPAD Hotkeys with Consumer Key Support

This is a fork of the code written by Adafruit and published at: https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Macropad_Hotkeys/code.py

Full Tutorial here: https://learn.adafruit.com/macropad-hotkeys

This fork adds support for Consumer Keys, such as play/pause and volumn buttons. This is done by adding a fourth boolen parameter to the macro array to determine if the command is a consumer key. The reason for that is that for consumer keys one must use consumer_control.send instead of keyboard.press to send the key.

The changes are on lines 130 - 139. I'm also including an example macro file which uses the fouth parameter.

"""
A fairly straightforward macro/hotkey program for Adafruit MACROPAD.
Macro key 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.
"""
# pylint: disable=import-error, unused-import, too-few-public-methods
import os
import displayio
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_macropad import MacroPad
# CONFIGURABLES ------------------------
MACRO_FOLDER = '/macros'
# CLASSES AND FUNCTIONS ----------------
class App:
""" Class representing a host-side application, for which we have a set
of macro sequences. Project code was originally more complex and
this was helpful, but maybe it's excessive now?"""
def __init__(self, appdata):
self.name = appdata['name']
self.macros = appdata['macros']
def switch(self):
""" Activate application settings; update OLED labels and LED
colors. """
GROUP[13].text = self.name # Application name
for i in range(12):
if i < len(self.macros): # Key in use, set label + LED color
MACROPAD.pixels[i] = self.macros[i][0]
GROUP[i].text = self.macros[i][1]
else: # Key not in use, no label or LED
MACROPAD.pixels[i] = 0
GROUP[i].text = ''
MACROPAD.pixels.show()
MACROPAD.display.refresh()
# INITIALIZATION -----------------------
MACROPAD = MacroPad()
MACROPAD.display.auto_refresh = False
MACROPAD.pixels.auto_write = False
MACROPAD.pixels.brightness = 0.1
# Set up displayio group with all the labels
GROUP = displayio.Group(max_size=14) # 12 keys + rect + app name
for KEY_INDEX in range(12):
x = KEY_INDEX % 3
y = KEY_INDEX // 3
GROUP.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF,
anchored_position=((MACROPAD.display.width - 1) * x / 2,
MACROPAD.display.height - 1 -
(3 - y) * 12),
anchor_point=(x / 2, 1.0)))
GROUP.append(Rect(0, 0, MACROPAD.display.width, 12, fill=0xFFFFFF))
GROUP.append(label.Label(terminalio.FONT, text='', color=0x000000,
anchored_position=(MACROPAD.display.width//2, -2),
anchor_point=(0.5, 0.0)))
MACROPAD.display.show(GROUP)
# 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.endswith('.py'):
try:
module = __import__(MACRO_FOLDER + '/' + FILENAME[:-3])
APPS.append(App(module.app))
except (SyntaxError, ImportError, AttributeError, KeyError, NameError,
IndexError, TypeError) as err:
pass
if not APPS:
GROUP[13].text = 'NO MACRO FILES FOUND'
MACROPAD.display.refresh()
while True:
pass
LAST_POSITION = None
LAST_ENCODER_SWITCH = MACROPAD.encoder_switch_debounced.pressed
APP_INDEX = 0
APPS[APP_INDEX].switch()
# MAIN LOOP ----------------------------
while True:
# Read encoder position. If it's changed, switch apps.
POSITION = MACROPAD.encoder
if POSITION != LAST_POSITION:
APP_INDEX = POSITION % len(APPS)
APPS[APP_INDEX].switch()
LAST_POSITION = POSITION
# 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.
MACROPAD.encoder_switch_debounced.update()
ENCODER_SWITCH = MACROPAD.encoder_switch_debounced.pressed
if ENCODER_SWITCH != LAST_ENCODER_SWITCH:
LAST_ENCODER_SWITCH = ENCODER_SWITCH
if len(APPS[APP_INDEX].macros) < 13:
continue # No 13th macro, just resume main loop
KEY_NUMBER = 12 # else process below as 13th macro
PRESSED = ENCODER_SWITCH
else:
EVENT = MACROPAD.keys.events.get()
if not EVENT or EVENT.key_number >= len(APPS[APP_INDEX].macros):
continue # No key events, or no corresponding macro, resume loop
KEY_NUMBER = EVENT.key_number
PRESSED = EVENT.pressed
# If code reaches here, a key or the encoder button WAS pressed/released
# and there IS a corresponding macro available for it...other situations
# are avoided by 'continue' statements above which resume the loop.
SEQUENCE = APPS[APP_INDEX].macros[KEY_NUMBER][2]
CONSUMER_KEY = APPS[APP_INDEX].macros[KEY_NUMBER][3]
if PRESSED:
if KEY_NUMBER < 12: # No pixel for encoder button
MACROPAD.pixels[KEY_NUMBER] = 0xECEBE6
MACROPAD.pixels.show()
for item in SEQUENCE:
# If the fouth array value is set to true, 1 or True then send composer key.
if CONSUMER_KEY:
MACROPAD.consumer_control.send(item)
if isinstance(item, int):
if item >= 0:
MACROPAD.keyboard.press(item)
else:
MACROPAD.keyboard.release(item)
else:
MACROPAD.keyboard_layout.write(item)
else:
# Release any still-pressed modifier keys
for item in SEQUENCE:
if isinstance(item, int) and item >= 0:
MACROPAD.keyboard.release(item)
if KEY_NUMBER < 12: # No pixel for encoder button
MACROPAD.pixels[KEY_NUMBER] = APPS[APP_INDEX].macros[KEY_NUMBER][0]
MACROPAD.pixels.show()
# MACROPAD Hotkeys: Media Keys
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
from adafruit_hid.consumer_control import ConsumerControl # REQUIRED if using ConsumerControlCode.* values
from adafruit_hid.consumer_control_code import ConsumerControlCode # REQUIRED if using ConsumerControlCode.* values
app = { # REQUIRED dict, must be named 'app'
'name' : 'Media Keys', # Application name
'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE CONSUMER CONTROL
# 1st row ----------
(0xA50000, 'Play', [ConsumerControlCode.PLAY_PAUSE], True), # Play/Pause
(0xA50000, 'Prev', [ConsumerControlCode.SCAN_PREVIOUS_TRACK], True), # Previous Track
(0xA50000, 'Next', [ConsumerControlCode.SCAN_NEXT_TRACK], True), # Next Track
# 2nd row ----------
(0x000000, '', '', False), # Not in use
(0x000000, '', '', False), # Not in use
(0x000000, '', '', False), # Not in use
# 3rd row ----------
(0x000000, '', '', False), # Not in use
(0x000000, '', '', False), # Not in use
(0x000000, '', '', False), # Not in use
# 4th row ----------
(0xA50000, 'Vol -', [ConsumerControlCode.VOLUME_DECREMENT], True), # Volume Down
(0xA50000, 'Vol +', [ConsumerControlCode.VOLUME_INCREMENT], True), # Volume Up
(0xA50000, 'Mute', [ConsumerControlCode.MUTE], True), # Mute
# Encoder# button ---
(0x000000, '', '', False), # Not in use
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment