It started simply enough with a few key presses, but folks had bigger ideas for things to automate. Could we work in media controls like play/pause and volume, or mouse control?

The adafruit_hid library certainly provides access to such things, but a bigger question was: could these be added without breaking compatibility with all the existing macro files that users have created and shared? The first problem is simple and the code was extended to handle it, but to handle this second problem, the approach is a little obtuse. I apologize, but this seemed the best way to keep those existing files working and not require a do-over.

It relies on the fact that these macro files aren’t simply structured text like XML or JSON, they’re snippets of actual Python code. Python can distinguish between different data types — integers, lists, dictionaries and so forth — so we leverage that to distinguish key codes from media control, mouse and so forth.

Consider that the last item for each entry in a macro file is normally either a quoted string (which gets typed verbatim), or a list of Keycodes and/or strings in a set of square brackets [ … ], like so:

(0x000040, 'Brush', 'B'),
(0x004000, 'Undo', [Keycode.CONTROL, 'z']),

Along the way, it was decided that timed pauses were sometimes needed, and (as shown on the prior page) these could be inserted in that bracketed list as floating-point numbers. Keycode values are really just integers, and CircuitPython can distinguish the two data types:

[Keycode.A, 0.5, Keycode.B, 1.0, Keycode.C]

So…to handle different controls, we just extended this types-in-list distinction to more types.

Media Control (“Consumer Control” codes)

For media functions like play/pause, volume or brightness your macro file should first import the ConsumerControlCode values. The example macro file media.py demonstrates this.

from adafruit_hid.consumer_control_code import ConsumerControlCode

Like HID Keycodes, ConsumerControl codes are also just integers. But there are a few ranges where they may overlap. So, to distinguish between the two, Consumer Control codes go in a [] list nested inside the Keycode list, like this:

[[ConsumerControlCode.SCAN_NEXT_TRACK]]

(Notice the double brackets: [[ … ]] )

If you need to get fancy, it’s possible to mix Keycodes, strings and/or Consumer Control codes in the same list (delays, too).

[Keycode.CONTROL, [ConsumerControlCode.SCAN_NEXT_TRACK], -Keycode.CONTROL, 1.0, 'foo']

(Press Control key, activate “next track,” release Control key, pause one second, type the string “foo”.)

For a complete list of Consumer Control codes, you can type in the CircuitPython REPL:

>>> from adafruit_hid.consumer_control_code import ConsumerControlCode
>>> print(dir(ConsumerControlCode))
['__class__', '__module__', '__name__', '__qualname__', '__bases__', '__dict__', 'RECORD', 'FAST_FORWARD', 'REWIND', 'SCAN_NEXT_TRACK', 'SCAN_PREVIOUS_TRACK', 'STOP', 'EJECT', 'PLAY_PAUSE', 'MUTE', 'VOLUME_DECREMENT', 'VOLUME_INCREMENT', 'BRIGHTNESS_DECREMENT', 'BRIGHTNESS_INCREMENT']

Mouse Control

For mouse-related actions like movement and button clicks, your macro file should first import the HID Mouse module. The example macro file mouse.py demonstrates this.

from adafruit_hid.mouse import Mouse

A mouse is a more complex thing, with spatial axes in addition to buttons. A bracketed list won’t do…we have to step this up to a full Python dictionary, in curly braces {}. But this appears inside the original outermost Keycode [] list.

[{'buttons':Mouse.LEFT_BUTTON}]

(Notice the bracket-brace combo: [{ … }] )

The dictionary key (the quoted bit of text before the colon) can be one of four values: 'x', 'y', 'wheel' and 'buttons', controlling cursor movement, scroll wheel position, and button states, respectively.

The dictionary value (after the colon) depends on what’s being controlled. For 'x', 'y', 'wheel', it’s a signed integer for relative motion. For example, 10 to move the cursor right or down by 10 pixels, or -10 to move up or left. For 'buttons', it can be Mouse.LEFT_BUTTON, Mouse.MIDDLE_BUTTON or Mouse.RIGHT_BUTTON (you can combine multiple buttons with +) and, like Keycode, can be negative if you want to indicate a button release in the middle of a longer sequence.

And again, like with Consumer Control codes, this can be mixed with Keycodes and delays as part of a complex sequence.

Experimental Stuff

The use of Python dictionaries for mouse control really opened this up for just about anything. As an experiment, the ability to play tones through MACROPAD’s onboard speaker was added. The example macro file tones.py demonstrates this. Not very useful on its own, but when used in combination with keyboard and mouse actions this could provide some audio feedback to each macro.

This plays a 262 Hz tone (roughly middle-C-ish) for as long as the corresponding key is held:

[{'tone':262}]

Only one tone can play at a time. These can be mixed in the [ ] macro list with other types. A value of 0 (or releasing the key) stops any currently-playing tone. For example, this will play middle-C for one half second:

[{'tone':262}, 0.5, {'tone':0}]

This guide was first published on Jul 07, 2021. It was last updated on 2021-10-19 14:29:49 -0400.

This page (Mouse and Media Controls) was last updated on Nov 27, 2021.

Text editor powered by tinymce.