Hotkeys: add Consumer Control, mouse, audio feedback, clean up some macro formatting

This commit is contained in:
Phillip Burgess 2021-09-03 11:18:32 -07:00
parent f1636ad9c9
commit 224738889c
10 changed files with 208 additions and 54 deletions

View file

@ -1,9 +1,8 @@
""" """
A fairly straightforward macro/hotkey program for Adafruit MACROPAD. A macro/hotkey program for Adafruit MACROPAD. Macro setups are stored in the
Macro key setups are stored in the /macros folder (configurable below), /macros folder (configurable below), load up just the ones you're likely to
load up just the ones you're likely to use. Plug into computer's USB port, use. Plug into computer's USB port, use dial to select an application macro
use dial to select an application macro set, press MACROPAD keys to send set, press MACROPAD keys to send key sequences and other USB protocols.
key sequences.
""" """
# pylint: disable=import-error, unused-import, too-few-public-methods # pylint: disable=import-error, unused-import, too-few-public-methods
@ -44,6 +43,9 @@ class App:
macropad.pixels[i] = 0 macropad.pixels[i] = 0
group[i].text = '' group[i].text = ''
macropad.keyboard.release_all() macropad.keyboard.release_all()
macropad.consumer_control.release()
macropad.mouse.release_all()
macropad.stop_tone()
macropad.pixels.show() macropad.pixels.show()
macropad.display.refresh() macropad.display.refresh()
@ -129,15 +131,13 @@ while True:
sequence = apps[app_index].macros[key_number][2] sequence = apps[app_index].macros[key_number][2]
if pressed: if pressed:
# the sequence is arbitrary-length # 'sequence' is an arbitrary-length list, each item is one of:
# each item in the sequence is either # Positive integer (e.g. Keycode.KEYPAD_MINUS): key pressed
# an integer (e.g., Keycode.KEYPAD_MINUS), # Negative integer: (absolute value) key released
# a floating point value (e.g., 0.20) # Float (e.g. 0.25): delay in seconds
# or a string. # String (e.g. "Foo"): corresponding keys pressed & released
# Positive Integers ==> key pressed # List []: one or more Consumer Control codes (can also do float delay)
# Negative Integers ==> key released # Dict {}: mouse buttons/motion (might extend in future)
# Float ==> sleep in seconds
# String ==> each key in string pressed & released
if key_number < 12: # No pixel for encoder button if key_number < 12: # No pixel for encoder button
macropad.pixels[key_number] = 0xFFFFFF macropad.pixels[key_number] = 0xFFFFFF
macropad.pixels.show() macropad.pixels.show()
@ -149,13 +149,49 @@ while True:
macropad.keyboard.release(-item) macropad.keyboard.release(-item)
elif isinstance(item, float): elif isinstance(item, float):
time.sleep(item) time.sleep(item)
else: elif isinstance(item, str):
macropad.keyboard_layout.write(item) macropad.keyboard_layout.write(item)
elif isinstance(item, list):
for code in item:
if isinstance(code, int):
macropad.consumer_control.release()
macropad.consumer_control.press(code)
if isinstance(code, float):
time.sleep(code)
elif isinstance(item, dict):
if 'buttons' in item:
if item['buttons'] >= 0:
macropad.mouse.press(item['buttons'])
else:
macropad.mouse.release(-item['buttons'])
macropad.mouse.move(item['x'] if 'x' in item else 0,
item['y'] if 'y' in item else 0,
item['wheel'] if 'wheel' in item else 0)
if 'tone' in item:
if item['tone'] > 0:
macropad.stop_tone()
macropad.start_tone(item['tone'])
else:
macropad.stop_tone()
elif 'play' in item:
macropad.play_file(item['play'])
else: else:
# Release any still-pressed keys # Release any still-pressed keys, consumer codes, mouse buttons
# Keys and mouse buttons are individually released this way (rather
# than release_all()) because pad supports multi-key rollover, e.g.
# could have a meta key or right-mouse held down by one macro and
# press/release keys/buttons with others. Navigate popups, etc.
for item in sequence: for item in sequence:
if isinstance(item, int) and item >= 0: if isinstance(item, int):
macropad.keyboard.release(item) if item >= 0:
macropad.keyboard.release(item)
elif isinstance(item, dict):
if 'buttons' in item:
if item['buttons'] >= 0:
macropad.mouse.release(item['buttons'])
elif 'tone' in item:
macropad.stop_tone()
macropad.consumer_control.release()
if key_number < 12: # No pixel for encoder button if key_number < 12: # No pixel for encoder button
macropad.pixels[key_number] = apps[app_index].macros[key_number][0] macropad.pixels[key_number] = apps[app_index].macros[key_number][0]
macropad.pixels.show() macropad.pixels.show()

View file

@ -2,9 +2,9 @@
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
app = { # REQUIRED dict, must be named 'app' app = { # REQUIRED dict, must be named 'app'
'name' : 'Linux Firefox', # Application name 'name' : 'Linux Firefox', # Application name
'macros' : [ # List of button macros... 'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE # COLOR LABEL KEY SEQUENCE
# 1st row ---------- # 1st row ----------
(0x004000, '< Back', [Keycode.CONTROL, '[']), (0x004000, '< Back', [Keycode.CONTROL, '[']),
@ -20,10 +20,10 @@ app = { # REQUIRED dict, must be named 'app'
(0x000040, 'Private', [Keycode.CONTROL, Keycode.SHIFT, 'p']), (0x000040, 'Private', [Keycode.CONTROL, Keycode.SHIFT, 'p']),
# 4th row ---------- # 4th row ----------
(0x101010, 'Ada', [Keycode.CONTROL, 't', -Keycode.CONTROL, (0x101010, 'Ada', [Keycode.CONTROL, 't', -Keycode.CONTROL,
'www.adafruit.com\n']), # adafruit.com in a new tab 'www.adafruit.com\n']), # adafruit.com in a new tab
(0x000040, 'Dev Mode', [Keycode.F12]), # dev mode (0x000040, 'Dev Mode', [Keycode.F12]), # dev mode
(0x101010, 'Digi', [Keycode.CONTROL, 't', -Keycode.CONTROL, (0x101010, 'Digi', [Keycode.CONTROL, 't', -Keycode.CONTROL,
'digikey.com\n']), # digikey in a new tab 'digikey.com\n']), # digikey in a new tab
# Encoder button --- # Encoder button ---
(0x000000, '', [Keycode.CONTROL, 'w']) # Close window/tab (0x000000, '', [Keycode.CONTROL, 'w']) # Close window/tab
] ]

View file

@ -20,8 +20,8 @@ app = { # REQUIRED dict, must be named 'app'
(0x004000, 'Nums', [Keycode.SHIFT, Keycode.COMMAND, 'o']), (0x004000, 'Nums', [Keycode.SHIFT, Keycode.COMMAND, 'o']),
(0x004000, 'Check', [Keycode.SHIFT, Keycode.COMMAND, 't']), (0x004000, 'Check', [Keycode.SHIFT, Keycode.COMMAND, 't']),
# 4th row ---------- # 4th row ----------
(0x004000, 'Date', [Keycode.SHIFT, Keycode.COMMAND, 'D' ]), (0x004000, 'Date', [Keycode.SHIFT, Keycode.COMMAND, 'D']),
(0x004000, 'Time', [Keycode.OPTION, Keycode.SHIFT, Keycode.COMMAND, 'D' ]), (0x004000, 'Time', [Keycode.OPTION, Keycode.SHIFT, Keycode.COMMAND, 'D']),
(0x004000, 'Divider', [Keycode.SHIFT, Keycode.COMMAND, 'H']), (0x004000, 'Divider', [Keycode.SHIFT, Keycode.COMMAND, 'H']),
# Encoder button --- # Encoder button ---
(0x000000, '', [Keycode.COMMAND, 'w']) # Close window/tab (0x000000, '', [Keycode.COMMAND, 'w']) # Close window/tab

View file

@ -0,0 +1,40 @@
# MACROPAD Hotkeys example: Consumer Control codes (media keys)
# The syntax for Consumer Control macros is a little 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 are distinguished by enclosing them in a list within
# the list, which is why you'll see double brackets [[ ]] below.
# Like Keycodes, Consumer Control codes can be positive (press) or negative
# (release), and float values can be inserted for pauses.
# To reference Consumer Control codes, import ConsumerControlCode like so...
from adafruit_hid.consumer_control_code import ConsumerControlCode
# You can still import Keycode as well if a macro file mixes types!
# See other macro files for typical Keycode examples.
app = { # REQUIRED dict, must be named 'app'
'name' : 'Media', # Application name
'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE
# 1st row ----------
(0x000000, '', []),
(0x000020, 'Vol+', [[ConsumerControlCode.VOLUME_INCREMENT]]),
(0x202020, 'Bright+', [[ConsumerControlCode.BRIGHTNESS_INCREMENT]]),
# 2nd row ----------
(0x000000, '', []),
(0x000020, 'Vol-', [[ConsumerControlCode.VOLUME_DECREMENT]]),
(0x202020, 'Bright-', [[ConsumerControlCode.BRIGHTNESS_DECREMENT]]),
# 3rd row ----------
(0x000000, '', []),
(0x200000, 'Mute', [[ConsumerControlCode.MUTE]]),
(0x000000, '', []),
# 4th row ----------
(0x202000, '<<', [[ConsumerControlCode.SCAN_PREVIOUS_TRACK]]),
(0x002000, 'Play/Pause', [[ConsumerControlCode.PLAY_PAUSE]]),
(0x202000, '>>', [[ConsumerControlCode.SCAN_NEXT_TRACK]]),
# Encoder button ---
(0x000000, '', [])
]
}

View file

@ -8,16 +8,16 @@ from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
# NOTE: There appears to be some delay when bringing up the command screen. # NOTE: There appears to be some delay when bringing up the command screen.
DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen
DELAY_BEFORE_RETURN = 0.10 DELAY_BEFORE_RETURN = 0.10
# NOTE: On PC, characters are sometimes lost due to lag. No simple fix for # NOTE: On PC, characters are sometimes lost due to lag. No simple fix for
# lost keystrokes is known. However, the commands do work most of the time. # lost keystrokes is known. However, the commands do work most of the time.
app = { # REQUIRED dict, must be named 'app' app = { # REQUIRED dict, must be named 'app'
'name' : 'Minecraft (/msg)', # Application name 'name' : 'Minecraft (/msg)', # Application name
'macros' : [ # List of button macros... 'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE # COLOR LABEL KEY SEQUENCE
# 1st row ---------- # 1st row ----------
(0x000020, 'list', [ (0x000020, 'list', [
@ -33,13 +33,13 @@ app = { # REQUIRED dict, must be named 'app'
'list', 'list',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
# 2nd row ---------- # 2nd row ----------
(0x000000, '', []), (0x000000, '', []),
(0x000000, '', []), (0x000000, '', []),
(0x000000, '', []), (0x000000, '', []),
# 3rd row ---------- # 3rd row ----------
(0x000000, '', []), (0x000000, '', []),
(0x000000, '', []), (0x000000, '', []),
(0x000000, '', []), (0x000000, '', []),
# 4th row ---------- # 4th row ----------
(0x101010, 'bed', [ (0x101010, 'bed', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,

View file

@ -8,32 +8,32 @@ from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
# See https://minecraft.fandom.com/wiki/Effect # See https://minecraft.fandom.com/wiki/Effect
DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen
DELAY_BEFORE_RETURN = 0.10 # give minecraft time to show all the keys pressed... DELAY_BEFORE_RETURN = 0.10 # give minecraft time to show all the keys pressed...
app = { # REQUIRED dict, must be named 'app' app = { # REQUIRED dict, must be named 'app'
'name' : 'Minecraft PE (effect)', # Application name 'name' : 'Minecraft PE (effect)', # Application name
# #
# /effect <player: target> <effect: Effect> # /effect <player: target> <effect: Effect>
# [seconds: int] [amplifier: int] [hideParticles: Boolean] # [seconds: int] [amplifier: int] [hideParticles: Boolean]
# #
'macros' : [ # List of button macros... 'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE # COLOR LABEL KEY SEQUENCE
# 1st row ---------- # 1st row ----------
(0x002000, 'speed', [ (0x002000, 'speed', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s speed 999999999 1 true', 'effect @s speed 999999999 1 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
(0x002000, 'str', [ (0x002000, 'str', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s strength 999999999 1 true', 'effect @s strength 999999999 1 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
(0x002000, 'haste', [ (0x002000, 'haste', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s haste 999999999 1 true', 'effect @s haste 999999999 1 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
# 2nd row ---------- # 2nd row ----------
(0x002000, 'jump', [ (0x002000, 'jump', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s jump_boost 999999999 1 true', 'effect @s jump_boost 999999999 1 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
@ -41,7 +41,7 @@ app = { # REQUIRED dict, must be named 'app'
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s water_breathing 999999999 0 true', 'effect @s water_breathing 999999999 0 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
(0x202020, 'darkv', [ (0x202020, 'darkv', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s night_vision 999999999 0 true', 'effect @s night_vision 999999999 0 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
@ -50,7 +50,7 @@ app = { # REQUIRED dict, must be named 'app'
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s health_boost 999999999 4 true', 'effect @s health_boost 999999999 4 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
(0x300000, 'regen', [ (0x300000, 'regen', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s regeneration 999999999 4 true', 'effect @s regeneration 999999999 4 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
@ -63,7 +63,7 @@ app = { # REQUIRED dict, must be named 'app'
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s resistance 999999999 3 true', 'effect @s resistance 999999999 3 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),
(0x101010, 'invis', [ (0x101010, 'invis', [
'/', DELAY_AFTER_SLASH, '/', DELAY_AFTER_SLASH,
'effect @s invisibility 999999999 0 true', 'effect @s invisibility 999999999 0 true',
DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]), DELAY_BEFORE_RETURN, Keycode.RETURN, -Keycode.RETURN]),

View file

@ -3,11 +3,9 @@
# Note: Must enable "full keyboad gameplay" to equip armor automatically. # Note: Must enable "full keyboad gameplay" to equip armor automatically.
# This is found under "settings", then "keyboard and mouse". # This is found under "settings", then "keyboard and mouse".
# NOTE: There is a line length limit (? ~100 char ?). Exceeding that limit appears # NOTE: There is a line length limit (? ~100 char ?). Exceeding that limit
# to result in silent failure. Therefore, the key sequences are split # appears to result in silent failure. Therefore, the key sequences are
# across multiple lines. # split across multiple lines.
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
@ -21,7 +19,7 @@ from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
# macro files before attempting to adjust settings in this one. # macro files before attempting to adjust settings in this one.
DELAY_AFTER_COMMAND = 0.75 DELAY_AFTER_COMMAND = 0.75
DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen DELAY_AFTER_SLASH = 0.80 # required so minecraft has time to bring up command screen
DELAY_BEFORE_RETURN = 0.10 # give minecraft time to show all the keys pressed... DELAY_BEFORE_RETURN = 0.10 # give minecraft time to show all the keys pressed...

View file

@ -0,0 +1,41 @@
# 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.mouse import Mouse
# You can still import Keycode and/or ConsumerControl as well if a macro file
# mixes types! See other macro files for typical Keycode examples.
app = { # REQUIRED dict, must be named 'app'
'name' : 'Mouse', # Application name
'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE
# 1st row ----------
(0x200000, 'L', [{'buttons':Mouse.LEFT_BUTTON}]),
(0x202000, 'M', [{'buttons':Mouse.MIDDLE_BUTTON}]),
(0x002000, 'R', [{'buttons':Mouse.RIGHT_BUTTON}]),
# 2nd row ----------
(0x000000, '', []),
(0x202020, 'Up', [{'y':-10}]),
(0x000000, '', []),
# 3rd row ----------
(0x202020, 'Left', [{'x':-10}]),
(0x000000, '', []),
(0x202020, 'Right', [{'x':10}]),
# 4th row ----------
(0x000000, '', []),
(0x202020, 'Down', [{'y':10}]),
(0x000000, '', []),
# Encoder button ---
(0x000000, '', [])
]
}

View file

@ -2,9 +2,9 @@
from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values
app = { # REQUIRED dict, must be named 'app' app = { # REQUIRED dict, must be named 'app'
'name' : 'Numpad', # Application name 'name' : 'Numpad', # Application name
'macros' : [ # List of button macros... 'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE # COLOR LABEL KEY SEQUENCE
# 1st row ---------- # 1st row ----------
(0x202000, '7', ['7']), (0x202000, '7', ['7']),

View file

@ -0,0 +1,39 @@
# MACROPAD Hotkeys example: Tones
# The syntax for Tones in 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 and
# tone further complicate this by adding dicts-within-list. Each tone-related
# item is the key 'tone' with either an integer frequency value, or 0 to stop
# the tone mid-macro (tone is also stopped when key is released).
# Helpful: https://en.wikipedia.org/wiki/Piano_key_frequencies
# This example ONLY shows tones (and delays), but really they can be mixed
# with other elements (keys, codes, mouse) to provide auditory feedback.
app = { # REQUIRED dict, must be named 'app'
'name' : 'Tones', # Application name
'macros' : [ # List of button macros...
# COLOR LABEL KEY SEQUENCE
# 1st row ----------
(0x200000, 'C3', [{'tone':131}]),
(0x202000, 'C4', [{'tone':262}]),
(0x002000, 'C5', [{'tone':523}]),
# 2nd row ----------
(0x000020, 'Rising', [{'tone':131}, 0.2, {'tone':262}, 0.2, {'tone':523}]),
(0x000000, '', []),
(0x000020, 'Falling', [{'tone':523}, 0.2, {'tone':262}, 0.2, {'tone':131}]),
# 3rd row ----------
(0x000000, '', []),
(0x000000, '', []),
(0x000000, '', []),
# 4th row ----------
(0x000000, '', []),
(0x000000, '', []),
(0x000000, '', []),
# Encoder button ---
(0x000000, '', [])
]
}