Deploy `qmk_udev` during bootstrap, updated CLI bootstrap docs (#26147)

This commit is contained in:
Nick Brassel 2026-04-14 20:02:31 +10:00 committed by GitHub
parent c6475e0476
commit 15e8658e81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 202 additions and 251 deletions

View File

@ -4,47 +4,18 @@
The QMK CLI (command line interface) makes building and working with QMK keyboards easier. We have provided a number of commands to simplify and streamline tasks such as obtaining and compiling the QMK firmware, creating keymaps, and more. The QMK CLI (command line interface) makes building and working with QMK keyboards easier. We have provided a number of commands to simplify and streamline tasks such as obtaining and compiling the QMK firmware, creating keymaps, and more.
### Requirements {#requirements} ### Installation {#installation}
QMK requires Python 3.9 or greater. We try to keep the number of requirements small but you will also need to install the packages listed in [`requirements.txt`](https://github.com/qmk/qmk_firmware/blob/master/requirements.txt). These are installed automatically when you install the QMK CLI. The recommended way to install the QMK CLI and all necessary dependencies (toolchains, flashing utilities, udev rules on Linux) is to use the bootstrapper script:
### Install Using Homebrew (macOS, some Linux) {#install-using-homebrew}
If you have installed [Homebrew](https://brew.sh) you can tap and install QMK:
``` ```
brew install qmk/qmk/qmk curl -fsSL https://install.qmk.fm | sh
export QMK_HOME='~/qmk_firmware' # Optional, set the location for `qmk_firmware`
qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build environment
``` ```
### Install Using uv {#install-using-uv} For more options, run:
If you have installed [uv](https://docs.astral.sh/uv/), the QMK CLI can be installed and managed as a uv tool:
``` ```
uv tool install qmk curl -fsSL https://install.qmk.fm | sh -s -- --help
export QMK_HOME='~/qmk_firmware' # Optional, set the location for `qmk_firmware`
qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build environment
``` ```
This installation can be updated via `uv tool upgrade qmk`. See [Upgrading tools](https://docs.astral.sh/uv/guides/tools/#upgrading-tools) for more information. For detailed setup instructions, see [Setting Up Your QMK Environment](newbs_getting_started#set-up-your-environment).
### Install Using pip {#install-using-easy_install-or-pip}
If your system is not listed above you can install QMK manually. First ensure that you have Python 3.9 (or later) installed and have installed pip. Then install QMK with this command:
```
python3 -m pip install qmk
export QMK_HOME='~/qmk_firmware' # Optional, set the location for `qmk_firmware`
qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build environment
```
### Packaging For Other Operating Systems {#packaging-for-other-operating-systems}
We are looking for people to create and maintain a `qmk` package for more operating systems. If you would like to create a package for your OS please follow these guidelines:
* Follow best practices for your OS when they conflict with these guidelines
* Document why in a comment when you do deviate
* Install using a virtualenv
* Instruct the user to set the environment variable `QMK_HOME` to have the firmware source checked out somewhere other than `~/qmk_firmware`.

View File

@ -13,10 +13,10 @@ If you intend to maintain keyboards and/or contribute to QMK, you can enable the
`qmk config user.developer=True` `qmk config user.developer=True`
This will allow you to see all available subcommands. This will allow you to see all available subcommands.
**Note:** You will have to install additional requirements:
``` ::: tip
python3 -m pip install -r requirements-dev.txt If you installed QMK using the bootstrapper (`curl -fsSL https://install.qmk.fm | sh`), the development requirements are already installed.
``` :::
# Subcommands # Subcommands

View File

@ -19,13 +19,10 @@ Note that running `make` with `sudo` is generally ***not*** a good idea, and you
### Linux `udev` Rules {#linux-udev-rules} ### Linux `udev` Rules {#linux-udev-rules}
On Linux, you'll need proper privileges to communicate with the bootloader device. You can either use `sudo` when flashing firmware (not recommended), or place [this file](https://github.com/qmk/qmk_firmware/tree/master/util/udev/50-qmk.rules) into `/etc/udev/rules.d/`. On Linux, you'll need proper privileges to communicate with the bootloader device. You can either use `sudo` when flashing firmware (not recommended), or install the udev rules from the [qmk_udev](https://github.com/qmk/qmk_udev) repository by running:
Once added, run the following:
``` ```
sudo udevadm control --reload-rules util/install_udev.sh
sudo udevadm trigger
``` ```
**Note:** With older versions of ModemManager (< 1.12), filtering only works when not in strict mode. The following commands can update that setting: **Note:** With older versions of ModemManager (< 1.12), filtering only works when not in strict mode. The following commands can update that setting:

View File

@ -217,8 +217,8 @@ To generate this bootloader, use the `bootloader` target, eg. `make planck/rev4:
Compatible flashers: Compatible flashers:
* TBD * [QMK Toolbox](https://github.com/qmk/qmk_toolbox/releases) (recommended GUI)
* Currently, you need to either use the [Python script](https://github.com/qmk/lufa/tree/master/Bootloaders/HID/HostLoaderApp_python), or compile [`hid_bootloader_cli`](https://github.com/qmk/lufa/tree/master/Bootloaders/HID/HostLoaderApp), from the LUFA repo. Homebrew may (will) have support for this directly (via `brew install qmk/qmk/hid_bootloader_cli`). * [hid_bootloader_cli](https://github.com/qmk/lufa/tree/master/Bootloaders/HID/HostLoaderApp) / `:qmk-hid` target in QMK (recommended command line)
Flashing sequence: Flashing sequence:

View File

@ -1,62 +1,23 @@
"""OS-specific functions for: Linux """OS-specific functions for: Linux
""" """
import platform import platform
import shutil
from pathlib import Path from pathlib import Path
from milc import cli from milc import cli
from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS from qmk.constants import QMK_FIRMWARE
from .check import CheckStatus, release_info from .check import CheckStatus, release_info
QMK_UDEV_INSTALL_SCRIPT = 'util/install_udev.sh'
def _is_wsl(): def _is_wsl():
return 'microsoft' in platform.uname().release.lower() return 'microsoft' in platform.uname().release.lower()
def _udev_rule(vid, pid=None, *args):
""" Helper function that return udev rules
"""
rule = ""
if pid:
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % (
vid,
pid,
)
else:
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid
if args:
rule = ', '.join([rule, *args])
return rule
def _generate_desired_rules(bootloader_vids_pids):
rules = dict()
for bl in bootloader_vids_pids.keys():
rules[bl] = set()
for vid_pid in bootloader_vids_pids[bl]:
if bl == 'caterina' or bl == 'md-boot':
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"'))
else:
rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1]))
return rules
def _deprecated_udev_rule(vid, pid=None):
""" Helper function that return udev rules
Note: these are no longer the recommended rules, this is just used to check for them
"""
if pid:
return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", MODE:="0666"' % (vid, pid)
else:
return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", MODE:="0666"' % vid
def check_udev_rules(): def check_udev_rules():
"""Make sure the udev rules look good. """Make sure the udev rules look good.
""" """
rc = CheckStatus.OK
udev_dirs = [ udev_dirs = [
Path("/usr/lib/udev/rules.d/"), Path("/usr/lib/udev/rules.d/"),
Path("/usr/local/lib/udev/rules.d/"), Path("/usr/local/lib/udev/rules.d/"),
@ -64,24 +25,15 @@ def check_udev_rules():
Path("/etc/udev/rules.d/"), Path("/etc/udev/rules.d/"),
] ]
desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS) if not any(udev_dir.exists() for udev_dir in udev_dirs):
cli.log.warning("{fg_yellow}Can't find udev rules directories, skipping udev rule checking...")
cli.log.debug("Checked directories: %s", ', '.join(str(udev_dir) for udev_dir in udev_dirs))
return CheckStatus.WARNING
# These rules are no longer recommended, only use them to check for their presence. # Collect all non-comment lines from QMK-related rules files
deprecated_rules = {
'atmel-dfu': {_deprecated_udev_rule("03eb", "2ff4"), _deprecated_udev_rule("03eb", "2ffb"), _deprecated_udev_rule("03eb", "2ff0")},
'kiibohd': {_deprecated_udev_rule("1c11")},
'stm32': {_deprecated_udev_rule("1eaf", "0003"), _deprecated_udev_rule("0483", "df11")},
'bootloadhid': {_deprecated_udev_rule("16c0", "05df")},
'caterina': {'ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1"', 'ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1"'},
'tmk': {_deprecated_udev_rule("feed")}
}
if any(udev_dir.exists() for udev_dir in udev_dirs):
udev_rules = [rule_file for udev_dir in udev_dirs for rule_file in udev_dir.glob('*.rules')]
current_rules = set() current_rules = set()
for udev_dir in udev_dirs:
# Collect all rules from the config files for rule_file in udev_dir.glob('*qmk*'):
for rule_file in udev_rules:
try: try:
for line in rule_file.read_text(encoding='utf-8').split('\n'): for line in rule_file.read_text(encoding='utf-8').split('\n'):
line = line.strip() line = line.strip()
@ -90,45 +42,17 @@ def check_udev_rules():
except (PermissionError, FileNotFoundError): except (PermissionError, FileNotFoundError):
cli.log.debug("Failed to read: %s", rule_file) cli.log.debug("Failed to read: %s", rule_file)
# Check if the desired rules are among the currently present rules if not current_rules:
for bootloader, rules in desired_rules.items(): cli.log.warning("{fg_yellow}Missing udev rules for QMK boards. Please run '%s' to install the rules", QMK_UDEV_INSTALL_SCRIPT)
if not rules.issubset(current_rules): return CheckStatus.WARNING
deprecated_rule = deprecated_rules.get(bootloader)
if deprecated_rule and deprecated_rule.issubset(current_rules):
cli.log.warning("{fg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader)
else:
# For caterina, check if ModemManager is running
if bootloader == "caterina" and check_modem_manager():
cli.log.warning("{fg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.")
rc = CheckStatus.WARNING # Check for the qmk_udev ID_QMK marker
cli.log.warning("{fg_yellow}Missing or outdated udev rules for '%s' boards. Run 'sudo cp %s/util/udev/50-qmk.rules /etc/udev/rules.d/'.", bootloader, QMK_FIRMWARE) if any('ID_QMK' in rule for rule in current_rules):
return CheckStatus.OK
else: # Legacy rules found (TAG+="uaccess" without ID_QMK)
cli.log.warning("{fg_yellow}Can't find udev rules, skipping udev rule checking...") cli.log.warning("{fg_yellow}Found legacy udev rules. Please run '%s' to install the latest rules", QMK_UDEV_INSTALL_SCRIPT)
cli.log.debug("Checked directories: %s", ', '.join(str(udev_dir) for udev_dir in udev_dirs)) return CheckStatus.WARNING
return rc
def check_systemd():
"""Check if it's a systemd system
"""
return bool(shutil.which("systemctl"))
def check_modem_manager():
"""Returns True if ModemManager is running.
"""
if check_systemd():
mm_check = cli.run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
if mm_check.returncode == 0:
return True
else:
"""(TODO): Add check for non-systemd systems
"""
return False
def os_test_linux(): def os_test_linux():

View File

@ -357,7 +357,7 @@ __EOT__
install_uv() { install_uv() {
# Install `uv` (or update as necessary) # Install `uv` (or update as necessary)
download_url https://astral.sh/uv/install.sh - | TMPDIR="$(windows_ish_path "${TMPDIR:-}")" UV_INSTALL_DIR="$(windows_ish_path "${UV_INSTALL_DIR:-}")" sh download_url https://astral.sh/uv/install.sh - | TMPDIR="$(posix_ish_path "${TMPDIR:-}")" UV_INSTALL_DIR="$(windows_ish_path "${UV_INSTALL_DIR:-}")" sh
} }
setup_paths() { setup_paths() {
@ -464,27 +464,49 @@ __EOT__
} }
install_linux_udev_rules() { install_linux_udev_rules() {
# Download the udev rules to the toolchains location # Get the latest qmk_udev release
echo "Downloading QMK udev rules file..." >&2 local latest_udev_release=$(github_api_call repos/qmk/qmk_udev/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
local qmk_rules_target_file="$QMK_DISTRIB_DIR/50-qmk.rules" if [ -z "$latest_udev_release" ]; then
download_url "https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/util/udev/50-qmk.rules" "$qmk_rules_target_file" echo "Could not determine latest qmk_udev release." >&2
exit 1
fi
echo "Using qmk_udev release: $latest_udev_release" >&2
# Install the udev rules -- path list is aligned with qmk doctor's linux.py # Download the udev rules file
local udev_rules_paths=" local qmk_rules_file="$QMK_DISTRIB_DIR/50-qmk.rules"
/usr/lib/udev/rules.d local release_base="https://github.com/qmk/qmk_udev/releases/download/$latest_udev_release"
/usr/local/lib/udev/rules.d download_url "$release_base/50-qmk.rules" "$qmk_rules_file"
/run/udev/rules.d
/etc/udev/rules.d # Download the architecture-appropriate qmk_id binary
" local arch="$(fn_arch)"
for udev_rules_dir in $udev_rules_paths; do local qmk_id_file="$QMK_DISTRIB_DIR/qmk_id"
if [ -d "$udev_rules_dir" ]; then download_url "$release_base/qmk_id-linux${arch}" "$qmk_id_file"
echo "Installing udev rules to $udev_rules_dir/50-qmk.rules ..." >&2
$(nsudo) mv "$qmk_rules_target_file" "$udev_rules_dir" # Remove existing QMK udev rules and qmk_id helpers from all standard locations
$(nsudo) chown 0:0 "$udev_rules_dir/50-qmk.rules" echo "Removing existing QMK udev rules and helpers..." >&2
$(nsudo) chmod 644 "$udev_rules_dir/50-qmk.rules" for dir in /etc/udev/rules.d /run/udev/rules.d /usr/lib/udev/rules.d /usr/local/lib/udev/rules.d /lib/udev/rules.d; do
break if [ -d "$dir" ]; then
for f in "$dir"/*-qmk.rules; do
[ -e "$f" ] && echo "Removing $f" >&2 && $(nsudo) rm -f "$f"
done
fi fi
done done
for dir in /usr/lib/udev /usr/local/lib/udev /lib/udev; do
[ -e "$dir/qmk_id" ] && echo "Removing $dir/qmk_id" >&2 && $(nsudo) rm -f "$dir/qmk_id"
done
# Install qmk_id binary
echo "Installing /usr/lib/udev/qmk_id ..." >&2
$(nsudo) install -d -m 0755 /usr/lib/udev
$(nsudo) install -m 0755 "$qmk_id_file" /usr/lib/udev/qmk_id
# Install udev rules
echo "Installing /etc/udev/rules.d/50-qmk.rules ..." >&2
$(nsudo) install -d -m 0755 /etc/udev/rules.d
$(nsudo) install -m 0644 "$qmk_rules_file" /etc/udev/rules.d/50-qmk.rules
# Clean up downloaded files
rm -f "$qmk_rules_file" "$qmk_id_file" || true
# Reload udev rules # Reload udev rules
if command -v udevadm >/dev/null 2>&1; then if command -v udevadm >/dev/null 2>&1; then

126
util/install_udev.sh Executable file
View File

@ -0,0 +1,126 @@
#!/usr/bin/env sh
# Copyright 2025 Nick Brassel (@tzarc)
# SPDX-License-Identifier: GPL-2.0-or-later
################################################################################
# Installs the latest QMK udev rules and qmk_id helper from
# https://github.com/qmk/qmk_udev
################################################################################
set -e
nsudo() {
if [ "$(id -u)" -ne 0 ]; then
if [ -n "$(command -v sudo 2>/dev/null || true)" ]; then
echo "sudo"
elif [ -n "$(command -v doas 2>/dev/null || true)" ]; then
echo "doas"
else
echo "Please install 'sudo' or 'doas' to continue." >&2
exit 1
fi
fi
true
}
download_url() {
local url=$1
local filename=${2:-$(basename "$url")}
if [ -n "$(command -v curl 2>/dev/null || true)" ]; then
curl -LSf -o "$filename" "$url"
elif [ -n "$(command -v wget 2>/dev/null || true)" ]; then
wget "-O$filename" "$url"
else
echo "Please install 'curl' or 'wget' to continue." >&2
exit 1
fi
}
github_api_call() {
local url="$1"
local token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
if [ -n "${token:-}" ]; then
if [ -n "$(command -v curl 2>/dev/null || true)" ]; then
curl -fsSL -H "Authorization: token $token" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/$url"
elif [ -n "$(command -v wget 2>/dev/null || true)" ]; then
wget -q --header="Authorization: token $token" --header="Accept: application/vnd.github.v3+json" "https://api.github.com/$url" -O -
fi
else
download_url "https://api.github.com/$url" -
fi
}
fn_arch() {
local arch_name=$(uname -m | tr 'A-Z' 'a-z')
case "$arch_name" in
*arm64* | *aarch64*)
echo ARM64
;;
*riscv64*)
echo RV64
;;
*x86_64* | *x64*)
echo X64
;;
*)
echo "Unsupported architecture: $arch_name" >&2
exit 1
;;
esac
}
if [ "$(uname -s 2>/dev/null || true)" != "Linux" ]; then
echo "This script is only intended for Linux." >&2
exit 1
fi
# Create a temporary directory for downloads
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
# Get the latest qmk_udev release
echo "Fetching latest qmk_udev release..." >&2
latest_release=$(github_api_call repos/qmk/qmk_udev/releases/latest | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
if [ -z "$latest_release" ]; then
echo "Could not determine latest qmk_udev release." >&2
exit 1
fi
echo "Using qmk_udev release: $latest_release" >&2
release_base="https://github.com/qmk/qmk_udev/releases/download/$latest_release"
# Download the udev rules file and architecture-appropriate qmk_id binary
download_url "$release_base/50-qmk.rules" "$tmpdir/50-qmk.rules"
download_url "$release_base/qmk_id-linux$(fn_arch)" "$tmpdir/qmk_id"
# Remove existing QMK udev rules and qmk_id helpers from all standard locations
echo "Removing existing QMK udev rules and helpers..." >&2
for dir in /etc/udev/rules.d /run/udev/rules.d /usr/lib/udev/rules.d /usr/local/lib/udev/rules.d /lib/udev/rules.d; do
if [ -d "$dir" ]; then
for f in "$dir"/*-qmk.rules; do
[ -e "$f" ] && echo "Removing $f" >&2 && $(nsudo) rm -f "$f"
done
fi
done
for dir in /usr/lib/udev /usr/local/lib/udev /lib/udev; do
[ -e "$dir/qmk_id" ] && echo "Removing $dir/qmk_id" >&2 && $(nsudo) rm -f "$dir/qmk_id"
done
# Install qmk_id binary and udev rules
echo "Installing /usr/lib/udev/qmk_id ..." >&2
$(nsudo) install -d -m 0755 /usr/lib/udev
$(nsudo) install -m 0755 "$tmpdir/qmk_id" /usr/lib/udev/qmk_id
echo "Installing /etc/udev/rules.d/50-qmk.rules ..." >&2
$(nsudo) install -d -m 0755 /etc/udev/rules.d
$(nsudo) install -m 0644 "$tmpdir/50-qmk.rules" /etc/udev/rules.d/50-qmk.rules
# Reload udev rules
if command -v udevadm >/dev/null 2>&1; then
echo "Reloading udev rules..." >&2
$(nsudo) udevadm control --reload-rules || true
$(nsudo) udevadm trigger || true
else
echo "udevadm not found, skipping udev rules reload." >&2
fi
echo "Done." >&2

View File

@ -1,89 +0,0 @@
# Atmel DFU
### ATmega16U2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2fef", TAG+="uaccess"
### ATmega32U2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff0", TAG+="uaccess"
### ATmega16U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff3", TAG+="uaccess"
### ATmega32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff4", TAG+="uaccess"
### AT90USB64
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff9", TAG+="uaccess"
### AT90USB162
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ffa", TAG+="uaccess"
### AT90USB128
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ffb", TAG+="uaccess"
# Input Club
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c11", ATTRS{idProduct}=="b007", TAG+="uaccess"
# STM32duino
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0003", TAG+="uaccess"
# STM32 DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", TAG+="uaccess"
# BootloadHID
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", TAG+="uaccess"
# USBAspLoader
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", TAG+="uaccess"
# USBtinyISP
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1782", ATTRS{idProduct}=="0c9f", TAG+="uaccess"
# ModemManager should ignore the following devices
# Atmel SAM-BA (Massdrop)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
# Caterina (Pro Micro)
## pid.codes shared PID
### Keyboardio Atreus 2 Bootloader
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="2302", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Spark Fun Electronics
### Pro Micro 3V3/8MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9203", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Pro Micro 5V/16MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9205", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### LilyPad 3V3/8MHz (and some Pro Micro clones)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9207", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Pololu Electronics
### A-Star 32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="0101", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Arduino SA
### Leonardo
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Micro
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Adafruit Industries LLC
### Feather 32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000c", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### ItsyBitsy 32U4 3V3/8MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000d", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### ItsyBitsy 32U4 5V/16MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000e", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## dog hunter AG
### Leonardo
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Micro
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
# hid_listen
KERNEL=="hidraw*", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# hid bootloaders
## QMK HID
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2067", TAG+="uaccess"
## PJRC's HalfKay
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="0478", TAG+="uaccess"
# APM32 DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="314b", ATTRS{idProduct}=="0106", TAG+="uaccess"
# GD32V DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", TAG+="uaccess"
# WB32 DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="dfa0", TAG+="uaccess"
# AT32 DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e3c", ATTRS{idProduct}=="df11", TAG+="uaccess"