Zoom with The Key v2

Yet another Zoom mute indicator

The Key v2 with rainbow swirl LEDs Originally an April Fool’s joke, Drop and Stack Overflow released the second version of their compact macropad, The Key v2. I missed the original run of The Key, but I preordered the v2 and recently received it.

The hardware feels great and the v2 adds some fun LEDs. The actual functionality, keys for ctrl/command, C and V, wasn’t actually that useful. Luckily, the keyboard is incredibly customizable. Using QMK, and a customizable keyboard firmware, I was able to change what the keys did and even added additional functionality for key combos.

Windows Notifications

Another The Key v2 owner took the customization to another level. Using Python, they were able to listen for Windows notifications and flash the LEDs on The Key. I loved the idea of using The Key’s LEDs as an indicator and combining it with my Zoom Mute scripts.

Setting up Vial

The first thing I needed to do was install Vial compatible firmware. The sample Python code only worked with Vial firmware, so the first thing I had to do was install Vial firmware. Vial is a QMK fork with a better GUI. Shout out to JetSerge for sharing a custom Vial firmware for The Key V2. I used QMK Toolbox to flash The Key with the custom firmware and then used the Vial web UI to create a custom key mapping:

  • C sends Command + Shift + A (mute/unmute)
  • V sends Command + Shift + V (enable/disable video)
  • The Stack Overflow button + V sends Command + W (to quit the Zoom session)
  • The Stack Overflow button + C sends return (to confirm quitting the Zoom session)

Getting it to work on a Mac

The next thing I needed to do was to figure out if I could even change the LEDs with Python on a Mac. I barely know what I’m doing in Python, so it was a lot of trial and error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import hid
import struct

# constants
CMD_VIA_LIGHTING_SET_VALUE = 0x07
QMK_RGBLIGHT_EFFECT = 0x81
MSG_LEN = 32

# hid device path
# use hid.enumerate() to figure out
PATH = b'\\\\?\\HID#VID_FEED&PID_6070&MI_01#7&36c4d81a&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}'

# example: change RGB mode to 14 (Rainbow Swirl)
mode = 14
msg = struct.pack(">BBB", CMD_VIA_LIGHTING_SET_VALUE, QMK_RGBLIGHT_EFFECT, mode)
msg += b"\x00" * (MSG_LEN - len(msg))
dev = hid.Device(path=PATH)
dev.write(b"\x00" + msg)
dev.close()

I fired up my terminal and tried running Python (Python 3.9.13 at the time) and going through the script. I tried import hid and got an error that there was no module named hid. I found the pypi page for hid and installed it with pip3 install hid and its dependency hidapi with brew install hidapi.

Using the comments in the script, I ran hid.enumerate() and got back a long list of all of the input devices attached to my MacBook (keyboards, mice, trackpads and The Key). Using hid.enumberate(vendor_id,product_id) with The Key’s vendor_id and product_id, I filtered the list down to the four listings for The Key v2.

I created a test.py file with the script above and tried ran it with the various paths. The third path worked and it successfully changed the LEDs to the rainbow swirl.

Other Modes and Colors

This GitHub repository has the Python code for all the patterns and color options. Using that as the base, I stripped it down to three different Python scripts.

  1. Change the mode to breathing and the color to green (see below)
  2. Change the mode to breathing and the color to red
  3. Change the mode to rainbow swirl (this is my default not in a meeting color)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import hid
import struct

# hid device path
# use hid.enumerate() to figure out
PATH = b'your_devices_path'

# constants
CMD_VIA_LIGHTING_SET_VALUE = 0x07
VIALRGB_SET_MODE = 0x41
QMK_RGBLIGHT_BRIGHTNESS = 0x80
QMK_RGBLIGHT_EFFECT = 0x81
QMK_RGBLIGHT_COLOR = 0x83
MSG_LEN = 32

# helper functions
def change_rgb_mode(mode):
    msg = struct.pack(">BBB", CMD_VIA_LIGHTING_SET_VALUE, QMK_RGBLIGHT_EFFECT, mode)
    return(msg)

def change_rgb_brightness(v):
    msg = struct.pack(">BBB", CMD_VIA_LIGHTING_SET_VALUE, QMK_RGBLIGHT_BRIGHTNESS, v)
    return(msg)

def change_rgb_color(h, s):
    msg = struct.pack(">BBBB", CMD_VIA_LIGHTING_SET_VALUE, QMK_RGBLIGHT_COLOR, h, s)
    return(msg)

def format_msg(msg):
    msg += b"\x00" * (MSG_LEN - len(msg))
    return(msg)

def send_msg(dev, msg):
    dev.write(b"\x00" + msg)

mode = 3
dev = hid.Device(path=PATH)
msg = format_msg(change_rgb_mode(mode))
send_msg(dev, msg)
dev.close()

dev = hid.Device(path=PATH)
msg = format_msg(change_rgb_color(80, 255))
send_msg(dev, msg)
dev.close()

Adapting my old SwiftBar plugin

With Python scripts for unmuted (green), muted (red), and not in a meeting (rainbow swirl) I could now integrate the scripts with my old SwiftBar plugin. I replaced the Blink(1) commands with the Python scripts, for instance:

1
2
3
4
5
6
if [ "$zm_status" = "true" ]; then
  echo "🟢"
  echo ---
  python3 ~/the-key/green.py
  exit
fi

I fired up SwiftBar, started a Zoom meeting and it sort of worked. SwiftBar was set to run the script every second, so instead of gentle green breathing, it was more of a strobe light. I updated my shell script to write the current status to a file. When the script runs, it checks the log file and compares the status from the file to the current status. The Python script only runs when they don’t match.

1
2
3
4
5
6
7
8
if [ "$zm_status" = "true" ]; then
  echo "🟢"
  echo ---
  if [ "$zm_status" != "$zm_prev" ]; then
    python3 ~/the-key/green.py
  fi
  exit
fi

Once that was in place, it totally worked.

Improvements

While it worked, I wanted to make some improvements to make it a smoother experience. After some Googling I learned how to pass a command line argument to a Python script. I combined my three Python scripts into a single change_rgb.py script.

1
2
3
4
5
6
7
8
if [ "$zm_status" = "true" ]; then
  echo "🟢"
  echo ---
  if [ "$zm_status" != "$zm_prev" ]; then
    python3 ~/the-key/change_rgb.py green
  fi
  exit
fi

The green argument gets passed to the script and updates The Key appropriately.

Getting the new path

Everytime I plugged The Key into my Mac or restarted my computer, there would be a new set of paths. After running hid.enumerate() a few times, I decided I’d script it.

Originally, I wrote a script to output the list of four paths, but I happened to notice that the correct path corresponded to 'interface_number': 1.

I updated the script to just pull just the path corresponding to interface 1, and output to a file. I then imported that file into change_rgb.py. There’s some weirdness with writing bytes and encoding, but I just experimented until it worked. There’s probably a better way to do this, but, again, I don’t really know Python,

Future improvements

I’d made enough improvements for my needs, but there are a few things that would be nice to fix.

  1. I’d like to use relative paths. I’m using absolute paths because I don’t know any better.
  2. It would be nice to rewrite the whole thing into a single Python script.

Get it on GitHub

I’ve created a repo on GitHub with all the finished scripts. While this is setup for The Key v2, any Vial compatible keyboard might work as well. GitHub Repo

Is This My Thing?

I don’t know why I keep doing projects to make a Zoom mute indicator. Zoom is great and all, but you’d think there are more fun ways I could spend my free time. If you’re interested, here are my past Zoom mute status projects:


See also