summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rwxr-xr-xutil/atmega32a_program.py105
-rw-r--r--util/bootloader_at90usb128_1.0.1.hex2
-rw-r--r--util/bootloader_at90usb64_1.0.0.hex2
-rwxr-xr-xutil/chibios_conf_updater.sh184
-rwxr-xr-xutil/docker_build.sh44
-rw-r--r--util/drivers.txt3
-rwxr-xr-xutil/generate_internal_docs.sh2
-rwxr-xr-xutil/install/arch.sh12
-rwxr-xr-xutil/install/debian.sh11
-rwxr-xr-xutil/install/fedora.sh11
-rwxr-xr-xutil/install/freebsd.sh4
-rwxr-xr-xutil/install/gentoo.sh9
-rwxr-xr-xutil/install/linux_shared.sh2
-rwxr-xr-xutil/install/macos.sh4
-rwxr-xr-xutil/install/msys2.sh13
-rwxr-xr-xutil/install/slackware.sh2
-rwxr-xr-xutil/install/solus.sh2
-rwxr-xr-xutil/install/void.sh4
-rwxr-xr-xutil/linux_install.sh245
-rwxr-xr-xutil/list_keyboards.sh12
-rwxr-xr-xutil/macos_install.sh30
-rwxr-xr-xutil/new_keyboard.sh25
-rwxr-xr-xutil/new_project.sh70
-rw-r--r--util/nix/poetry.lock468
-rw-r--r--util/nix/pyproject.toml36
-rw-r--r--util/nix/sources.json38
-rw-r--r--util/nix/sources.nix174
-rwxr-xr-xutil/qmk_install.sh19
-rw-r--r--util/qmk_tab_complete.sh2
-rw-r--r--util/reset.eep9
-rwxr-xr-xutil/rules_cleaner.sh40
-rwxr-xr-xutil/stm32eeprom_parser.py316
-rw-r--r--util/udev/50-qmk.rules11
-rwxr-xr-xutil/uf2conv.py319
-rwxr-xr-xutil/update_chibios_mirror.sh92
-rw-r--r--util/vagrant/Dockerfile2
-rw-r--r--util/vagrant/readme.md2
37 files changed, 1806 insertions, 520 deletions
diff --git a/util/atmega32a_program.py b/util/atmega32a_program.py
deleted file mode 100755
index b777b91106..0000000000
--- a/util/atmega32a_program.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 Luiz Ribeiro <luizribeiro@gmail.com>, Sebastian Kaim <sebb@sebb767.de>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import print_function
-
-import os
-import sys
-import time
-import usb
-
-
-def checkForKeyboardInNormalMode():
- """Returns a device if a ps2avrGB device in normal made (that is in keyboard mode) or None if it is not found."""
- return usb.core.find(idVendor=0x20A0, idProduct=0x422D)
-
-def checkForKeyboardInBootloaderMode():
- """Returns True if a ps2avrGB device in bootloader (flashable) mode is found and False otherwise."""
- return (usb.core.find(idVendor=0x16c0, idProduct=0x05df) is not None)
-
-def flashKeyboard(firmware_file):
- """Calls bootloadHID to flash the given file to the device."""
- print('Flashing firmware to device ...')
- if os.system('bootloadHID -r "%s"' % firmware_file) == 0:
- print('\nDone!')
- else:
- print('\nbootloadHID returned an error.')
-
-def printDeviceInfo(dev):
- """Prints all infos for a given USB device"""
- print('Device Information:')
- print(' idVendor: %d (0x%04x)' % (dev.idVendor, dev.idVendor))
- print(' idProduct: %d (0x%04x)' % (dev.idProduct, dev.idProduct))
- print('Manufacturer: %s' % (dev.iManufacturer))
- print('Serial: %s' % (dev.iSerialNumber))
- print('Product: %s' % (dev.iProduct), end='\n\n')
-
-def sendDeviceToBootloaderMode(dev):
- """Tries to send a given ps2avrGB keyboard to bootloader mode to allow flashing."""
- try:
- dev.set_configuration()
-
- request_type = usb.util.build_request_type(
- usb.util.CTRL_OUT,
- usb.util.CTRL_TYPE_CLASS,
- usb.util.CTRL_RECIPIENT_DEVICE)
-
- USBRQ_HID_SET_REPORT = 0x09
- HID_REPORT_OPTION = 0x0301
-
- dev.ctrl_transfer(request_type, USBRQ_HID_SET_REPORT, HID_REPORT_OPTION, 0, [0, 0, 0xFF] + [0] * 5)
- except usb.core.USBError:
- # for some reason I keep getting USBError, but it works!
- pass
-
-
-if len(sys.argv) < 2:
- print('Usage: %s <firmware.hex>' % sys.argv[0])
- sys.exit(1)
-
-kb = checkForKeyboardInNormalMode()
-
-if kb is not None:
- print('Found a keyboard in normal mode. Attempting to send it to bootloader mode ...', end='')
- sendDeviceToBootloaderMode(kb)
- print(' done.')
- print("Hint: If your keyboard can't be set to bootloader mode automatically, plug it in while pressing the bootloader key to do so manually.")
- print(" You can find more infos about this here: https://github.com/qmk/qmk_firmware/tree/master/keyboards/ps2avrGB#setting-the-board-to-bootloader-mode")
-
-attempts = 12 # 60 seconds
-found = False
-for attempt in range(1, attempts + 1):
- print("Searching for keyboard in bootloader mode (%i/%i) ... " % (attempt, attempts), end='')
-
- if checkForKeyboardInBootloaderMode():
- print('Found', end='\n\n')
- flashKeyboard(sys.argv[1])
- found = True
- break
- else:
- print('Nothing.', end='')
-
- if attempt != attempts: # no need to wait on the last attempt
- print(' Sleeping 5 seconds.', end='')
- time.sleep(5)
-
- # print a newline
- print()
-
-if not found:
- print("Couldn't find a flashable keyboard. Aborting.")
- sys.exit(2)
-
diff --git a/util/bootloader_at90usb128_1.0.1.hex b/util/bootloader_at90usb128_1.0.1.hex
index 90491a82a6..5ecbed2880 100644
--- a/util/bootloader_at90usb128_1.0.1.hex
+++ b/util/bootloader_at90usb128_1.0.1.hex
@@ -269,7 +269,7 @@
:10F11A002A2F0C946CF00E9451F780E090E0A0E056
:10F12A00B0E08C019D010E94BEF780509F4FAF4F07
:10F13A00BF4F8F3F0FED900701E0A007B80788F394
-:10F14A00E4E00C9462F712010020FE010020EB03B8
+:10F14A00E4E00C9462F712010002FF010020EB03D5
:10F15A00FB2F0000010203010902120001010080D5
:10F16A00320904000000000000000C0341005400B2
:10F17A004D0045004C001E03410054003900300088
diff --git a/util/bootloader_at90usb64_1.0.0.hex b/util/bootloader_at90usb64_1.0.0.hex
index 69dd45bbd6..5b443d7195 100644
--- a/util/bootloader_at90usb64_1.0.0.hex
+++ b/util/bootloader_at90usb64_1.0.0.hex
@@ -1,7 +1,7 @@
:020000020000FC
:04F000000C94B97E35
:04F028000C94C77D00
-:10F08B0012010020FE010020EB03F92F000000000D
+:10F08B0012010002FF010020EB03F92F000000002A
:10F09B000001090212000101008032090400000086
:04F0AB000000000061
:10F0B0000000827E0800717E747E767E787E7A7E85
diff --git a/util/chibios_conf_updater.sh b/util/chibios_conf_updater.sh
new file mode 100755
index 0000000000..5ba8aa677b
--- /dev/null
+++ b/util/chibios_conf_updater.sh
@@ -0,0 +1,184 @@
+#!/usr/bin/env bash
+
+set -eEuo pipefail
+umask 022
+
+sinfo() { echo "$@" >&2 ; }
+shead() { sinfo "" ; sinfo "---------------------------------" ; sinfo "-- $@" ; sinfo "---------------------------------" ; }
+havecmd() { command command type "${1}" >/dev/null 2>&1 || return 1 ; }
+
+this_script="$(realpath "${BASH_SOURCE[0]}")"
+script_dir="$(realpath "$(dirname "$this_script")")"
+qmk_firmware_dir="$(realpath "$script_dir/../")"
+
+declare -A file_hashes
+
+export PATH="$PATH:$script_dir/fmpp/bin"
+
+build_fmpp() {
+ [ -f "$script_dir/fmpp.tar.gz" ] \
+ || wget -O"$script_dir/fmpp.tar.gz" https://github.com/freemarker/fmpp/archive/v0.9.16.tar.gz
+ [ -d "$script_dir/fmpp" ] \
+ || { mkdir "$script_dir/fmpp" && tar xf "$script_dir/fmpp.tar.gz" -C "$script_dir/fmpp" --strip-components=1 ; }
+ pushd "$script_dir/fmpp" >/dev/null 2>&1
+ sed -e "s#bootclasspath.path=.*#bootclasspath.path=$(find /usr/lib/jvm -name 'rt.jar' | sort | tail -n1)#g" \
+ -e "s#ant.jar.path=.*#ant.jar.path=$(find /usr/share/java -name 'ant-1*.jar' | sort | tail -n1)#g" \
+ build.properties.sample > build.properties
+ sed -e 's#source="1.5"#source="1.8"#g' \
+ -e 's#target="1.5"#target="1.8"#g' \
+ build.xml > build.xml.new
+ mv build.xml.new build.xml
+ ant clean
+ ant
+ chmod +x "$script_dir/fmpp/bin/fmpp"
+ popd >/dev/null 2>&1
+}
+
+find_chibi_files() {
+ local search_path="$1"
+ shift
+ local conditions=( "$@" )
+ for file in $(find -L "$search_path" -not -path '*/lib/chibios*' -and -not -path '*/lib/ugfx*' -and -not -path '*/util/*' -and \( "${conditions[@]}" \) | sort) ; do
+ if [ -z "$(grep 'include_next' "$file")" ] ; then
+ echo $file
+ fi
+ done
+}
+
+revert_chibi_files() {
+ local search_path="$1"
+ shead "Reverting ChibiOS config/board files..."
+ for file in $(find_chibi_files "$search_path" -name chconf.h -or -name halconf.h -or -name mcuconf.h -or -name board.c -or -name board.h -or -name board.mk -or -name board.chcfg) ; do
+ pushd "$search_path" >/dev/null 2>&1
+ local relpath=$(realpath --relative-to="$search_path" "$file")
+ git checkout upstream/develop -- "$relpath" || git checkout origin/develop -- "$relpath" || true
+ popd >/dev/null 2>&1
+ done
+}
+
+populate_file_hashes() {
+ local search_path="$1"
+ shead "Determining duplicate config/board files..."
+ for file in $(find_chibi_files "$search_path" -name chconf.h -or -name halconf.h -or -name mcuconf.h -or -name board.c -or -name board.h) ; do
+ local key="file_$(clang-format "$file" | sha1sum | cut -d' ' -f1)"
+ local relpath=$(realpath --relative-to="$search_path" "$file")
+ file_hashes[$key]="${file_hashes[$key]:-} $relpath"
+ done
+ for file in $(find_chibi_files "$search_path" -name board.mk -or -name board.chcfg) ; do
+ local key="file_$(cat "$file" | sha1sum | cut -d' ' -f1)"
+ local relpath=$(realpath --relative-to="$search_path" "$file")
+ file_hashes[$key]="${file_hashes[$key]:-} $relpath"
+ done
+}
+
+determine_equivalent_files() {
+ local search_file="$1"
+ for K in "${!file_hashes[@]}"; do
+ for V in ${file_hashes[$K]}; do
+ if [[ "$V" == "$search_file" ]] ; then
+ for V in ${file_hashes[$K]}; do
+ echo "$V"
+ done
+ return 0
+ fi
+ done
+ done
+ return 1
+}
+
+deploy_staged_files() {
+ shead "Deploying staged files..."
+ for file in $(find "$qmk_firmware_dir/util/chibios-upgrade-staging" -type f) ; do
+ local relpath=$(realpath --relative-to="$qmk_firmware_dir/util/chibios-upgrade-staging" "$file")
+ sinfo "Deploying staged file: $relpath"
+ for other in $(determine_equivalent_files "$relpath") ; do
+ sinfo " => $other"
+ cp "$qmk_firmware_dir/util/chibios-upgrade-staging/$relpath" "$qmk_firmware_dir/$other"
+ done
+ done
+}
+
+swap_mcuconf_f3xx_f303() {
+ shead "Swapping STM32F3xx_MCUCONF -> STM32F303_MCUCONF..."
+ for file in $(find_chibi_files "$qmk_firmware_dir" -name mcuconf.h) ; do
+ sed -i 's#STM32F3xx_MCUCONF#STM32F303_MCUCONF#g' "$file"
+ dos2unix "$file" >/dev/null 2>&1
+ done
+}
+
+upgrade_conf_files_generic() {
+ local search_filename="$1"
+ local update_script="$2"
+ shead "Updating $search_filename files ($update_script)..."
+ pushd "$qmk_firmware_dir/lib/chibios/tools/updater" >/dev/null 2>&1
+ for file in $(find_chibi_files "$qmk_firmware_dir" -name "$search_filename") ; do
+ cp -f "$file" "$file.orig"
+ clang-format --style='{IndentPPDirectives: None}' -i "$file"
+ cp -f "$file" "$file.formatted"
+ bash "$update_script" "$file"
+ if ! diff "$file" "$file.formatted" >/dev/null 2>&1 ; then
+ dos2unix "$file" >/dev/null 2>&1
+ else
+ cp -f "$file.orig" "$file"
+ fi
+ rm -f "$file.orig" "$file.formatted"
+ done
+ popd >/dev/null 2>&1
+}
+
+upgrade_chconf_files() {
+ upgrade_conf_files_generic chconf.h update_chconf_rt.sh
+}
+
+upgrade_halconf_files() {
+ upgrade_conf_files_generic halconf.h update_halconf.sh
+
+ OIFS=$IFS
+ IFS=$'\n'
+ for file in $(find_chibi_files "$qmk_firmware_dir" -name halconf.h) ; do
+ echo $file
+ sed -i 's@#include "mcuconf.h"@#include <mcuconf.h>@g' "$file"
+ done
+ IFS=$OIFS
+}
+
+upgrade_mcuconf_files() {
+ pushd "$qmk_firmware_dir/lib/chibios/tools/updater" >/dev/null 2>&1
+ for f in $(find . -name 'update_mcuconf*') ; do
+ upgrade_conf_files_generic mcuconf.h $f
+ done
+ popd >/dev/null 2>&1
+}
+
+update_staged_files() {
+ shead "Updating staged files with ChibiOS upgraded versions..."
+ for file in $(find "$qmk_firmware_dir/util/chibios-upgrade-staging" -type f) ; do
+ local relpath=$(realpath --relative-to="$qmk_firmware_dir/util/chibios-upgrade-staging" "$file")
+ sinfo "Updating staged file: $relpath"
+ cp "$qmk_firmware_dir/$relpath" "$qmk_firmware_dir/util/chibios-upgrade-staging/$relpath"
+ done
+}
+
+havecmd fmpp || build_fmpp
+revert_chibi_files "$qmk_firmware_dir"
+populate_file_hashes "$qmk_firmware_dir"
+
+shead "Showing duplicate ChibiOS files..."
+for K in "${!file_hashes[@]}"; do
+ sinfo ${K#file_}:
+ for V in ${file_hashes[$K]}; do
+ sinfo " $V"
+ done
+done
+
+if [ "${1:-}" == "-r" ] ; then
+ exit 0
+fi
+
+swap_mcuconf_f3xx_f303
+
+deploy_staged_files
+upgrade_mcuconf_files
+upgrade_chconf_files
+upgrade_halconf_files
+update_staged_files
diff --git a/util/docker_build.sh b/util/docker_build.sh
index 7106344690..368b0f13fc 100755
--- a/util/docker_build.sh
+++ b/util/docker_build.sh
@@ -17,12 +17,27 @@ done
if [ $# -gt 1 ]; then
errcho "$USAGE"
exit 1
-elif ! command -v docker >/dev/null 2>&1; then
- errcho "Error: docker not found"
- errcho "See https://docs.docker.com/install/#supported-platforms for installation instructions"
- exit 2
fi
+# Allow $RUNTIME to be overriden by the user as an environment variable
+# Else check if either docker or podman exit and set them as runtime
+# if none are found error out
+if [ -z "$RUNTIME" ]; then
+ if command -v docker >/dev/null 2>&1; then
+ RUNTIME="docker"
+ elif command -v podman >/dev/null 2>&1; then
+ RUNTIME="podman"
+ else
+ errcho "Error: no compatible container runtime found."
+ errcho "Either podman or docker are required."
+ errcho "See https://podman.io/getting-started/installation"
+ errcho "or https://docs.docker.com/install/#supported-platforms"
+ errcho "for installation instructions."
+ exit 2
+ fi
+fi
+
+
# Determine arguments
if [ $# -eq 0 ]; then
printf "keyboard=" && read -r keyboard
@@ -37,25 +52,34 @@ else
exit 1
fi
fi
+if [ -z "$keyboard" ]; then
+ keyboard=all
+fi
if [ -n "$target" ]; then
- if [ "$(uname)" = "Linux" ] || docker-machine active >/dev/null 2>&1; then
- usb_args="--privileged -v /dev/bus/usb:/dev/bus/usb"
- else
+ # IF we are using docker on non Linux and docker-machine isn't working print an error
+ # ELSE set usb_args
+ if [ ! "$(uname)" = "Linux" ] && [ "$RUNTIME" = "docker" ] && ! docker-machine active >/dev/null 2>&1; then
errcho "Error: target requires docker-machine to work on your platform"
errcho "See http://gw.tnode.com/docker/docker-machine-with-usb-support-on-windows-macos"
errcho "Consider flashing with QMK Toolbox (https://github.com/qmk/qmk_toolbox) instead"
exit 3
+ else
+ usb_args="--privileged -v /dev:/dev"
fi
fi
dir=$(pwd -W 2>/dev/null) || dir=$PWD # Use Windows path if on Windows
+if [ "$RUNTIME" = "docker" ]; then
+ uid_arg="--user $(id -u):$(id -g)"
+fi
+
# Run container and build firmware
-docker run --rm -it $usb_args \
- --user $(id -u):$(id -g) \
+"$RUNTIME" run --rm -it $usb_args \
+ $uid_arg \
-w /qmk_firmware \
-v "$dir":/qmk_firmware \
-e ALT_GET_KEYBOARDS=true \
-e SKIP_GIT="$SKIP_GIT" \
-e MAKEFLAGS="$MAKEFLAGS" \
- qmkfm/base_container \
+ qmkfm/qmk_cli \
make "$keyboard${keymap:+:$keymap}${target:+:$target}"
diff --git a/util/drivers.txt b/util/drivers.txt
index c3c5e286b1..1f6c67c4c5 100644
--- a/util/drivers.txt
+++ b/util/drivers.txt
@@ -4,6 +4,8 @@
# Driver can be one of winusb,libusb,libusbk
# Use Windows Powershell and type [guid]::NewGuid() to generate guids
winusb,STM32 Bootloader,0483,DF11,6d98a87f-4ecf-464d-89ed-8c684d857a75
+winusb,APM32 Bootloader,314B,0106,9ff3cc31-6772-4a3f-a492-a80d91f7a853
+winusb,STM32duino Bootloader,1EAF,0003,746915ec-99d8-4a90-a722-3c85ba31e4fe
libusbk,USBaspLoader,16C0,05DC,e69affdc-0ef0-427c-aefb-4e593c9d2724
winusb,Kiibohd DFU Bootloader,1C11,B007,aa5a3f86-b81e-4416-89ad-0c1ea1ed63af
libusb,ATmega16U2,03EB,2FEF,007274da-b75f-492e-a288-8fc0aff8339f
@@ -11,4 +13,5 @@ libusb,ATmega32U2,03EB,2FF0,ddc2c572-cb6e-4f61-a6cc-1a5de941f063
libusb,ATmega16U4,03EB,2FF3,3180d426-bf93-4578-a693-2efbc337da8e
libusb,ATmega32U4,03EB,2FF4,5f9726fd-f9de-487a-9fbd-8b3524a7a56a
libusb,AT90USB64,03EB,2FF9,c6a708ad-e97d-43cd-b04a-3180d737a71b
+libusb,AT90USB162,03EB,2FFA,ef8546f0-ef09-4e7c-8fc2-ffbae1dcd84a
libusb,AT90USB128,03EB,2FFB,fd217df3-59d0-440a-a8f3-4c0c8c84daa3
diff --git a/util/generate_internal_docs.sh b/util/generate_internal_docs.sh
index bfee797d3a..b107a37ad6 100755
--- a/util/generate_internal_docs.sh
+++ b/util/generate_internal_docs.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -x
diff --git a/util/install/arch.sh b/util/install/arch.sh
index 7442e2f136..bef98ac37f 100755
--- a/util/install/arch.sh
+++ b/util/install/arch.sh
@@ -1,16 +1,16 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install() {
echo "Installing dependencies"
sudo pacman --needed --noconfirm -S \
- base-devel clang diffutils gcc git unzip wget zip \
- python-pip \
- avr-binutils \
- arm-none-eabi-binutils arm-none-eabi-gcc arm-none-eabi-newlib \
- avrdude dfu-programmer dfu-util
+ base-devel clang diffutils gcc git unzip wget zip python-pip \
+ avr-binutils arm-none-eabi-binutils arm-none-eabi-gcc \
+ arm-none-eabi-newlib avrdude dfu-programmer dfu-util
sudo pacman --needed --noconfirm -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz
sudo pacman --needed --noconfirm -S avr-libc # Must be installed after the above, or it will bring in the latest avr-gcc instead
+ sudo pacman --needed --noconfirm -S hidapi # This will fail if the community repo isn't enabled
+
python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt
}
diff --git a/util/install/debian.sh b/util/install/debian.sh
index 0ae9764a33..57588e371a 100755
--- a/util/install/debian.sh
+++ b/util/install/debian.sh
@@ -1,11 +1,11 @@
-#!/bin/bash
+#!/usr/bin/env bash
DEBIAN_FRONTEND=noninteractive
DEBCONF_NONINTERACTIVE_SEEN=true
export DEBIAN_FRONTEND DEBCONF_NONINTERACTIVE_SEEN
_qmk_install_prepare() {
- sudo apt-get update
+ sudo apt-get update $SKIP_PROMPT
}
_qmk_install() {
@@ -13,10 +13,9 @@ _qmk_install() {
sudo apt-get -yq install \
build-essential clang-format diffutils gcc git unzip wget zip \
- python3-pip \
- binutils-avr gcc-avr avr-libc \
- binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi \
- avrdude dfu-programmer dfu-util teensy-loader-cli libusb-dev
+ python3-pip binutils-avr gcc-avr avr-libc binutils-arm-none-eabi \
+ gcc-arm-none-eabi libnewlib-arm-none-eabi avrdude dfu-programmer \
+ dfu-util teensy-loader-cli libhidapi-hidraw0 libusb-dev
python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt
}
diff --git a/util/install/fedora.sh b/util/install/fedora.sh
index 44b71b98bf..e123447d3f 100755
--- a/util/install/fedora.sh
+++ b/util/install/fedora.sh
@@ -1,15 +1,14 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install() {
echo "Installing dependencies"
# TODO: Check whether devel/headers packages are really needed
- sudo dnf -y install \
- clang diffutils git gcc glibc-headers kernel-devel kernel-headers make unzip wget zip \
- python3 \
- avr-binutils avr-gcc avr-libc \
+ sudo dnf $SKIP_PROMPT install \
+ clang diffutils git gcc glibc-headers kernel-devel kernel-headers \
+ make unzip wget zip python3 avr-binutils avr-gcc avr-libc \
arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib \
- avrdude dfu-programmer dfu-util libusb-devel
+ avrdude dfu-programmer dfu-util hidapi
python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt
}
diff --git a/util/install/freebsd.sh b/util/install/freebsd.sh
index 353c52d647..595911969d 100755
--- a/util/install/freebsd.sh
+++ b/util/install/freebsd.sh
@@ -1,7 +1,7 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
- sudo pkg update
+ sudo pkg update $SKIP_PROMPT
}
_qmk_install() {
diff --git a/util/install/gentoo.sh b/util/install/gentoo.sh
index d4284e9a93..b031fc7629 100755
--- a/util/install/gentoo.sh
+++ b/util/install/gentoo.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
echo "This script will make a USE change in order to ensure that that QMK works on your system."
@@ -22,9 +22,10 @@ _qmk_install() {
echo "sys-devel/gcc multilib" | sudo tee --append /etc/portage/package.use/qmkfirmware >/dev/null
sudo emerge -auN sys-devel/gcc
sudo emerge -au --noreplace \
- app-arch/unzip app-arch/zip net-misc/wget sys-devel/clang sys-devel/crossdev \
- \>=dev-lang/python-3.6 \
- dev-embedded/avrdude dev-embedded/dfu-programmer app-mobilephone/dfu-util
+ app-arch/unzip app-arch/zip net-misc/wget sys-devel/clang \
+ sys-devel/crossdev \>=dev-lang/python-3.7 dev-embedded/avrdude \
+ dev-embedded/dfu-programmer app-mobilephone/dfu-util sys-apps/hwloc \
+ dev-libs/hidapi
sudo crossdev -s4 --stable --g \<9 --portage --verbose --target avr
sudo crossdev -s4 --stable --g \<9 --portage --verbose --target arm-none-eabi
diff --git a/util/install/linux_shared.sh b/util/install/linux_shared.sh
index cb81e8611f..e060f425f5 100755
--- a/util/install/linux_shared.sh
+++ b/util/install/linux_shared.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# For those distros that do not package bootloadHID
_qmk_install_bootloadhid() {
diff --git a/util/install/macos.sh b/util/install/macos.sh
index 4b87217ba0..870b4bec94 100755
--- a/util/install/macos.sh
+++ b/util/install/macos.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
echo "Checking Homebrew installation"
@@ -9,7 +9,7 @@ _qmk_install_prepare() {
return 1
fi
- brew update && brew upgrade --ignore-pinned
+ brew update && brew upgrade --formulae --ignore-pinned
}
_qmk_install() {
diff --git a/util/install/msys2.sh b/util/install/msys2.sh
index c8598a60fa..203f8eff0c 100755
--- a/util/install/msys2.sh
+++ b/util/install/msys2.sh
@@ -1,7 +1,7 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
- pacman -Syu
+ pacman -Syu $MSYS2_CONFIRM
}
_qmk_install() {
@@ -9,11 +9,10 @@ _qmk_install() {
pacman --needed --noconfirm --disable-download-timeout -S pactoys-git
pacboy sync --needed --noconfirm --disable-download-timeout \
- base-devel: toolchain:x clang:x git: unzip: \
- python3-pip:x \
- avr-binutils:x avr-gcc:x avr-libc:x \
- arm-none-eabi-binutils:x arm-none-eabi-gcc:x arm-none-eabi-newlib:x \
- avrdude:x bootloadhid:x dfu-programmer:x dfu-util:x teensy-loader-cli:x
+ base-devel: toolchain:x clang:x git: unzip: python3-pip:x \
+ avr-binutils:x avr-gcc:x avr-libc:x arm-none-eabi-binutils:x \
+ arm-none-eabi-gcc:x arm-none-eabi-newlib:x avrdude:x bootloadhid:x \
+ dfu-programmer:x dfu-util:x teensy-loader-cli:x hidapi:x
_qmk_install_drivers
diff --git a/util/install/slackware.sh b/util/install/slackware.sh
index d73303159d..df4d073e7b 100755
--- a/util/install/slackware.sh
+++ b/util/install/slackware.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
echo "Before you continue, please ensure that your user is added to sudoers and that sboinstall is configured."
diff --git a/util/install/solus.sh b/util/install/solus.sh
index 5633f7039c..fad0605cf6 100755
--- a/util/install/solus.sh
+++ b/util/install/solus.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install_prepare() {
sudo eopkg -y update-repo
diff --git a/util/install/void.sh b/util/install/void.sh
index 9ec7019e5c..6aeb8e00ae 100755
--- a/util/install/void.sh
+++ b/util/install/void.sh
@@ -1,9 +1,9 @@
-#!/bin/bash
+#!/usr/bin/env bash
_qmk_install() {
echo "Installing dependencies"
- sudo xbps-install \
+ sudo xbps-install $SKIP_PROMPT \
gcc git make wget unzip zip \
python3-pip \
avr-binutils avr-gcc avr-libc \
diff --git a/util/linux_install.sh b/util/linux_install.sh
deleted file mode 100755
index 01518a29d7..0000000000
--- a/util/linux_install.sh
+++ /dev/null
@@ -1,245 +0,0 @@
-#!/bin/sh
-
-# Note: This file uses tabs to indent. Please don't mix tabs and spaces.
-
-GENTOO_WARNING="This script will make a USE change in order to ensure that that QMK works on your system. All changes will be sent to the the file /etc/portage/package.use/qmk_firmware -- please review it, and read Portage's output carefully before installing any packages on your system. You will also need to ensure that your kernel is compiled with support for the keyboard chip that you are using (e.g. enable Arduino for the Pro Micro). Further information can be found on the Gentoo wiki."
-
-SLACKWARE_WARNING="You will need the following packages from slackbuilds.org:\n\tarm-binutils\n\tarm-gcc\n\tavr-binutils\n\tavr-gcc\n\tavr-libc\n\tavrdude\n\tdfu-programmer\n\tdfu-util\n\tnewlib\nThese packages will be installed with sudo and sboinstall, so ensure that your user is added to sudoers and that sboinstall is configured."
-
-SOLUS_INFO="Your tools are now installed. To start using them, open new terminal or source these scripts:\n\t/usr/share/defaults/etc/profile.d/50-arm-toolchain-path.sh\n\t/usr/share/defaults/etc/profile.d/50-avr-toolchain-path.sh"
-
-util_dir=$(dirname "$0")
-
-# For those distros that do not package bootloadHID
-install_bootloadhid() {
- if ! command -v bootloadHID >/dev/null; then
- wget https://www.obdev.at/downloads/vusb/bootloadHID.2012-12-08.tar.gz -O - | tar -xz -C /tmp
- cd /tmp/bootloadHID.2012-12-08/commandline/
- if make; then
- sudo cp bootloadHID /usr/local/bin
- fi
- cd -
- fi
-}
-
-if grep ID /etc/os-release | grep -qE "fedora"; then
- sudo dnf install \
- arm-none-eabi-binutils-cs \
- arm-none-eabi-gcc-cs \
- arm-none-eabi-newlib \
- avr-binutils \
- avr-gcc \
- avr-libc \
- binutils-avr32-linux-gnu \
- clang \
- dfu-util \
- dfu-programmer \
- diffutils \
- git \
- gcc \
- glibc-headers \
- kernel-devel \
- kernel-headers \
- libusb-devel \
- make \
- perl \
- python3 \
- unzip \
- wget \
- zip
-
-elif grep ID /etc/os-release | grep -qE 'debian|ubuntu'; then
- DEBIAN_FRONTEND=noninteractive
- DEBCONF_NONINTERACTIVE_SEEN=true
- export DEBIAN_FRONTEND DEBCONF_NONINTERACTIVE_SEEN
- sudo apt-get update
- sudo apt-get install \
- build-essential \
- avr-libc \
- binutils-arm-none-eabi \
- binutils-avr \
- clang-format \
- dfu-programmer \
- dfu-util \
- diffutils \
- gcc \
- gcc-arm-none-eabi \
- gcc-avr \
- git \
- libnewlib-arm-none-eabi \
- libusb-dev \
- python3 \
- python3-pip \
- unzip \
- wget \
- zip
-
-elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then
- sudo pacman --needed -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz
- sudo pacman -S --needed \
- arm-none-eabi-binutils \
- arm-none-eabi-gcc \
- arm-none-eabi-newlib \
- avr-binutils \
- avr-libc \
- base-devel \
- clang \
- dfu-programmer \
- dfu-util \
- diffutils \
- gcc \
- git \
- libusb-compat \
- python \
- python-pip \
- unzip \
- wget \
- zip
-
-elif grep ID /etc/os-release | grep -q gentoo; then
- echo "$GENTOO_WARNING" | fmt
- printf "\nProceed (y/N)? "
- read -r answer
- if echo "$answer" | grep -iq "^y"; then
- sudo touch /etc/portage/package.use/qmkfirmware
- # tee is used here since sudo doesn't apply to >>
- echo "sys-devel/gcc multilib" | sudo tee --append /etc/portage/package.use/qmkfirmware >/dev/null
- sudo emerge -auN sys-devel/gcc
- sudo emerge -au --noreplace \
- app-arch/unzip \
- app-arch/zip \
- app-mobilephone/dfu-util \
- dev-embedded/avrdude \
- net-misc/wget \
- sys-devel/clang \
- sys-devel/crossdev
- sudo crossdev -s4 --stable --g \<9 --portage --verbose --target avr
- sudo crossdev -s4 --stable --g \<9 --portage --verbose --target arm-none-eabi
- echo "Done!"
- else
- echo "Quitting..."
- fi
-
-elif grep ID /etc/os-release | grep -q sabayon; then
- sudo equo install \
- app-arch/unzip \
- app-arch/zip \
- app-mobilephone/dfu-util \
- dev-embedded/avrdude \
- dev-lang/python \
- net-misc/wget \
- sys-devel/clang \
- sys-devel/gcc \
- sys-devel/crossdev
- sudo crossdev -s4 --stable --g \<9 --portage --verbose --target avr
- sudo crossdev -s4 --stable --g \<9 --portage --verbose --target arm-none-eabi
- echo "Done!"
-
-elif grep ID /etc/os-release | grep -qE "opensuse|tumbleweed"; then
- CROSS_AVR_GCC=cross-avr-gcc8
- CROSS_ARM_GCC=cross-arm-none-gcc8
- if grep ID /etc/os-release | grep -q "15."; then
- CROSS_AVR_GCC=cross-avr-gcc7
- CROSS_ARM_GCC=cross-arm-none-gcc7
- fi
- sudo zypper install \
- avr-libc \
- clang \
- $CROSS_AVR_GCC \
- $CROSS_ARM_GCC \
- cross-avr-binutils \
- cross-arm-none-newlib-devel \
- cross-arm-binutils cross-arm-none-newlib-devel \
- dfu-tool \
- dfu-programmer \
- gcc \
- libusb-devel \
- python3 \
- unzip \
- wget \
- zip
-
-elif grep ID /etc/os-release | grep -q slackware; then
- printf "$SLACKWARE_WARNING\n"
- printf "\nProceed (y/N)? "
- read -r answer
- if echo "$answer" | grep -iq "^y" ;then
- sudo sboinstall \
- avr-binutils \
- avr-gcc \
- avr-libc \
- avrdude \
- dfu-programmer \
- dfu-util \
- arm-binutils \
- arm-gcc \
- newlib \
- python3
- echo "Done!"
- else
- echo "Quitting..."
- fi
-
-elif grep ID /etc/os-release | grep -q solus; then
- sudo eopkg ur
- sudo eopkg it \
- -c system.devel \
- arm-none-eabi-gcc \
- arm-none-eabi-binutils \
- arm-none-eabi-newlib \
- avr-libc \
- avr-binutils \
- avr-gcc \
- avrdude \
- dfu-util \
- dfu-programmer \
- libusb-devel \
- python3 \
- git \
- wget \
- zip \
- unzip
- printf "\n$SOLUS_INFO\n"
-
-elif grep ID /etc/os-release | grep -q void; then
- sudo xbps-install \
- avr-binutils \
- avr-gcc \
- avr-libc \
- cross-arm-none-eabi-binutils \
- cross-arm-none-eabi-gcc \
- cross-arm-none-eabi-newlib \
- avrdude \
- dfu-programmer \
- dfu-util \
- gcc \
- git \
- libusb-compat-devel \
- make \
- wget \
- unzip \
- zip
-
-else
- echo "Sorry, we don't recognize your OS. Help us by contributing support!"
- echo
- echo "https://docs.qmk.fm/#/contributing"
-fi
-
-# Global install tasks
-install_bootloadhid
-pip3 install --user -r ${util_dir}/../requirements.txt
-
-if uname -a | grep -qi microsoft; then
- echo "********************************************************************************"
- echo "* Detected Windows Subsystem for Linux. *"
- echo "* Currently, WSL has no access to USB devices and so flashing from within the *"
- echo "* WSL terminal will not work. *"
- echo "* *"
- echo "* Please install the QMK Toolbox instead: *"
- echo "* https://github.com/qmk/qmk_toolbox/releases *"
- echo "* Then, map your WSL filesystem as a network drive: *"
- echo "* \\\\\\\\wsl$\\<distro> *"
- echo "********************************************************************************"
- echo
-fi
diff --git a/util/list_keyboards.sh b/util/list_keyboards.sh
index 1c103e0f11..aa6ed1c6af 100755
--- a/util/list_keyboards.sh
+++ b/util/list_keyboards.sh
@@ -4,5 +4,15 @@
# This allows us to exclude keyboards by including a .noci file.
find -L keyboards -type f -name rules.mk | grep -v keymaps | sed 's!keyboards/\(.*\)/rules.mk!\1!' | while read keyboard; do
- [ "$1" = "noci" -a -e "keyboards/${keyboard}/.noci" ] || echo "$keyboard"
+ if [ "$1" = "noci" ]; then
+ case "$keyboard" in
+ handwired/*)
+ ;;
+ *)
+ test -e "keyboards/${keyboard}/.noci" || echo "$keyboard"
+ ;;
+ esac
+ else
+ echo "$keyboard"
+ fi
done
diff --git a/util/macos_install.sh b/util/macos_install.sh
deleted file mode 100755
index 810f234ab3..0000000000
--- a/util/macos_install.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-util_dir=$(dirname "$0")
-
-if ! brew --version 2>&1 > /dev/null; then
- echo "Error! Homebrew not installed or broken!"
- echo -n "Would you like to install homebrew now? [y/n] "
- while read ANSWER; do
- case $ANSWER in
- y|Y)
- /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- break
- ;;
- n|N)
- exit 1
- ;;
- *)
- echo -n "Would you like to install homebrew now? [y/n] "
- ;;
- esac
- done
-fi
-
-# All macOS dependencies are managed in the homebrew package:
-# https://github.com/qmk/homebrew-qmk
-brew update
-brew install qmk/qmk/qmk
-brew link --force avr-gcc@8
-
-pip3 install -r "${util_dir}/../requirements.txt"
diff --git a/util/new_keyboard.sh b/util/new_keyboard.sh
index 01458b4f19..62e8cb9e13 100755
--- a/util/new_keyboard.sh
+++ b/util/new_keyboard.sh
@@ -1,7 +1,7 @@
-#!/bin/bash
+#!/usr/bin/env bash
# This script generates a new keyboard directory under keyboards/,
-# and copies the template files from quantum/template/ into it.
+# and copies the template files from data/templates/ into it.
# Print an error message with the word "ERROR" in red.
echo_error() {
@@ -32,17 +32,19 @@ set_git_username() {
# Copy the template files to the new keyboard directory.
copy_templates() {
+ mkdir -p "$keyboard_dir"
+
echo -n "Copying base template files..."
- cp -r "quantum/template/base" "${keyboard_dir}"
+ cp -r "data/templates/base/." "${keyboard_dir}"
echo " done"
echo -n "Copying $keyboard_type template files..."
- cp -r "quantum/template/${keyboard_type}/." "${keyboard_dir}"
+ cp -r "data/templates/${keyboard_type}/." "${keyboard_dir}"
echo " done"
echo -n "Renaming keyboard files..."
- mv "${keyboard_dir}/template.c" "${keyboard_dir}/${keyboard_name}.c"
- mv "${keyboard_dir}/template.h" "${keyboard_dir}/${keyboard_name}.h"
+ mv "${keyboard_dir}/keyboard.c" "${keyboard_dir}/${keyboard_base_name}.c"
+ mv "${keyboard_dir}/keyboard.h" "${keyboard_dir}/${keyboard_base_name}.h"
echo " done"
}
@@ -87,10 +89,10 @@ replace_keyboard_placeholders() {
"${keyboard_dir}/config.h"
"${keyboard_dir}/info.json"
"${keyboard_dir}/readme.md"
- "${keyboard_dir}/${keyboard_name}.c"
+ "${keyboard_dir}/${keyboard_base_name}.c"
"${keyboard_dir}/keymaps/default/readme.md"
)
- replace_placeholders "%KEYBOARD%" "$keyboard_name" "${replace_keyboard_filenames[@]}"
+ replace_placeholders "%KEYBOARD%" "$keyboard_base_name" "${replace_keyboard_filenames[@]}"
}
# Replace %YOUR_NAME% with the username.
@@ -127,6 +129,12 @@ if [ ! -d "quantum" ]; then
exit 1
fi
+echo_bold "########################################"
+echo_bold "# NOTICE #"
+echo_bold "# This script has been deprecated. #"
+echo_bold "# Please use qmk new-keyboard instead. #"
+echo_bold "########################################"
+echo
echo_bold "Generating a new QMK keyboard directory"
echo
@@ -134,6 +142,7 @@ echo
while [ -z "$keyboard_name" ]; do
prompt "Keyboard Name" ""
keyboard_name=$prompt_return
+ keyboard_base_name=$(basename $keyboard_name)
done
keyboard_dir="keyboards/$keyboard_name"
diff --git a/util/new_project.sh b/util/new_project.sh
deleted file mode 100755
index 9dec714b02..0000000000
--- a/util/new_project.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/sh
-# Script to make a new quantum project
-# Jack Humbert 2015
-
-KEYBOARD=$1
-KEYBOARD_TYPE=$2
-
-if [ -z "$KEYBOARD" ]; then
- echo "Usage: $0 <keyboard_name> <keyboard_type>"
- echo "Example: $0 gh60 avr"
- echo "Example: $0 bfake ps2avrgb"
- exit 1
-elif [ -z "$KEYBOARD_TYPE" ]; then
- KEYBOARD_TYPE=avr
-fi
-
-if [ "$KEYBOARD_TYPE" != "avr" ] && [ "$KEYBOARD_TYPE" != "ps2avrgb" ]; then
- echo "Invalid keyboard type target"
- exit 1
-fi
-
-if [ -e "keyboards/$1" ]; then
- echo "Error! keyboards/$1 already exists!"
- exit 1
-fi
-
-cd "$(dirname "$0")/.." || exit
-
-KEYBOARD_NAME=$(basename "$1")
-KEYBOARD_NAME_UPPERCASE=$(echo "$KEYBOARD_NAME" | awk '{print toupper($0)}')
-NEW_KBD=keyboards/${KEYBOARD}
-
-
-cp -r quantum/template/base "$NEW_KBD"
-cp -r "quantum/template/$KEYBOARD_TYPE/." "$NEW_KBD"
-
-mv "${NEW_KBD}/template.c" "${NEW_KBD}/${KEYBOARD_NAME}.c"
-mv "${NEW_KBD}/template.h" "${NEW_KBD}/${KEYBOARD_NAME}.h"
-find "${NEW_KBD}" -type f -exec sed -i '' -e "s;%KEYBOARD%;${KEYBOARD_NAME};g" {} \;
-find "${NEW_KBD}" -type f -exec sed -i '' -e "s;%KEYBOARD_UPPERCASE%;${KEYBOARD_NAME_UPPERCASE};g" {} \;
-
-GIT=$(whereis git)
-if [ "$GIT" != "" ]; then
- IS_GIT_REPO=$($GIT log >>/dev/null 2>&1; echo $?)
- if [ "$IS_GIT_REPO" -eq 0 ]; then
- ID="$($GIT config --get user.name)"
- read -rp "What is your name? [$ID] " YOUR_NAME
- if [ -n "$YOUR_NAME" ]; then
- ID=$YOUR_NAME
- fi
- echo "Using $ID as user name"
-
- for i in "$NEW_KBD/config.h" \
- "$NEW_KBD/$KEYBOARD_NAME.c" \
- "$NEW_KBD/$KEYBOARD_NAME.h" \
- "$NEW_KBD/keymaps/default/config.h" \
- "$NEW_KBD/keymaps/default/keymap.c"
- do
- awk -v id="$ID" '{sub(/%YOUR_NAME%/,id); print}' < "$i" > "$i.$$"
- mv "$i.$$" "$i"
- done
- fi
-fi
-
-cat <<-EOF
-######################################################
-# $NEW_KBD project created. To start
-# working on things, cd into $NEW_KBD
-######################################################
-EOF
diff --git a/util/nix/poetry.lock b/util/nix/poetry.lock
new file mode 100644
index 0000000000..9bb0f64deb
--- /dev/null
+++ b/util/nix/poetry.lock
@@ -0,0 +1,468 @@
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "argcomplete"
+version = "1.12.3"
+description = "Bash tab completion for argparse"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["coverage", "flake8", "pexpect", "wheel"]
+
+[[package]]
+name = "attrs"
+version = "21.2.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coverage"
+version = "5.5"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+toml = ["toml"]
+
+[[package]]
+name = "flake8"
+version = "3.9.2"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[package.dependencies]
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.7.0,<2.8.0"
+pyflakes = ">=2.3.0,<2.4.0"
+
+[[package]]
+name = "flake8-polyfill"
+version = "1.0.2"
+description = "Polyfill package for Flake8 plugins"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+flake8 = "*"
+
+[[package]]
+name = "halo"
+version = "0.0.31"
+description = "Beautiful terminal spinners in Python"
+category = "main"
+optional = false
+python-versions = ">=3.4"
+
+[package.dependencies]
+colorama = ">=0.3.9"
+log-symbols = ">=0.0.14"
+six = ">=1.12.0"
+spinners = ">=0.0.24"
+termcolor = ">=1.1.0"
+
+[package.extras]
+ipython = ["IPython (==5.7.0)", "ipywidgets (==7.1.0)"]
+
+[[package]]
+name = "hid"
+version = "1.0.4"
+description = "ctypes bindings for hidapi"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "hjson"
+version = "3.0.2"
+description = "Hjson, a user interface for JSON."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "jsonschema"
+version = "3.2.0"
+description = "An implementation of JSON Schema validation for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+pyrsistent = ">=0.14.0"
+six = ">=1.11.0"
+
+[package.extras]
+format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
+format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
+
+[[package]]
+name = "log-symbols"
+version = "0.0.14"
+description = "Colored symbols for various log levels for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+colorama = ">=0.3.9"
+
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "milc"
+version = "1.6.2"
+description = "Opinionated Batteries-Included Python 3 CLI Framework."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+appdirs = "*"
+argcomplete = "*"
+colorama = "*"
+halo = "*"
+spinners = "*"
+
+[[package]]
+name = "nose2"
+version = "0.10.0"
+description = "unittest2 with plugins, the succesor to nose"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+coverage = ">=4.4.1"
+six = ">=1.7"
+
+[package.extras]
+coverage_plugin = ["coverage (>=4.4.1)"]
+dev = ["Sphinx (>=1.6.5)", "sphinx-rtd-theme", "mock", "coverage"]
+
+[[package]]
+name = "pep8-naming"
+version = "0.12.1"
+description = "Check PEP-8 naming conventions, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+flake8 = ">=3.9.1"
+flake8-polyfill = ">=1.0.2,<2"
+
+[[package]]
+name = "pycodestyle"
+version = "2.7.0"
+description = "Python style guide checker"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pyflakes"
+version = "2.3.1"
+description = "passive checker of Python programs"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pygments"
+version = "2.10.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "pyrsistent"
+version = "0.18.0"
+description = "Persistent/Functional/Immutable data structures"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyusb"
+version = "1.2.1"
+description = "Python USB access module"
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[[package]]
+name = "qmk"
+version = "1.0.0"
+description = "A program to help users work with QMK Firmware."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+hid = "*"
+hjson = "*"
+jsonschema = ">=3"
+milc = ">=1.4.2"
+pygments = "*"
+pyusb = "*"
+qmk-dotty-dict = "*"
+
+[[package]]
+name = "qmk-dotty-dict"
+version = "1.3.0.post1"
+description = "Dictionary wrapper for quick access to deeply nested keys."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "spinners"
+version = "0.0.24"
+description = "Spinners for terminals"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+description = "ANSII Color formatting for output in terminal."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "yapf"
+version = "0.31.0"
+description = "A formatter for Python code."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.8"
+content-hash = "468ae51aaddfe2ce62938f131c688bbc447e41608d4dd7d30db36391c38bda20"
+
+[metadata.files]
+appdirs = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+argcomplete = [
+ {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
+ {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
+]
+attrs = [
+ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+ {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+]
+colorama = [
+ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coverage = [
+ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
+ {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
+ {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
+ {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
+ {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
+ {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
+ {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
+ {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
+ {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
+ {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
+ {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
+ {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
+ {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
+ {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
+ {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
+ {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
+ {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
+ {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
+ {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
+ {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
+ {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
+ {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
+ {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
+ {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
+ {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
+ {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
+ {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
+ {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
+ {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
+ {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
+ {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
+ {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
+ {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
+ {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
+ {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
+ {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
+ {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
+ {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
+ {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
+ {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
+ {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
+ {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
+ {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
+ {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
+ {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
+ {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
+ {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
+ {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
+ {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
+ {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
+ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
+ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
+]
+flake8 = [
+ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
+ {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
+]
+flake8-polyfill = [
+ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
+ {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
+]
+halo = [
+ {file = "halo-0.0.31-py2-none-any.whl", hash = "sha256:5350488fb7d2aa7c31a1344120cee67a872901ce8858f60da7946cef96c208ab"},
+ {file = "halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6"},
+]
+hid = [
+ {file = "hid-1.0.4-py2-none-any.whl", hash = "sha256:fba9913f07030b01059b822b24c83b370ca3f444e9e6443bd662f9f1aa3f0780"},
+ {file = "hid-1.0.4.tar.gz", hash = "sha256:f61b0382f37a334bc8ba8604bc84b94875ee4f594fbbaf82b2c3b3e827883fc1"},
+]
+hjson = [
+ {file = "hjson-3.0.2-py3-none-any.whl", hash = "sha256:5546438bf4e1b52bc964c6a47c4ed10fa5fba8a1b264e22efa893e333baad2db"},
+ {file = "hjson-3.0.2.tar.gz", hash = "sha256:2838fd7200e5839ea4516ece953f3a19892c41089f0d933ba3f68e596aacfcd5"},
+]
+jsonschema = [
+ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
+ {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
+]
+log-symbols = [
+ {file = "log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca"},
+ {file = "log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556"},
+]
+mccabe = [
+ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+milc = [
+ {file = "milc-1.6.2-py2.py3-none-any.whl", hash = "sha256:cb26404c7f3d6797c9c42005de732161e45e21294cde85845e914b279321bdad"},
+ {file = "milc-1.6.2.tar.gz", hash = "sha256:779710a0b9300bef3c5748158887e6c734659e147d55548d9e4701d7a7d5dddf"},
+]
+nose2 = [
+ {file = "nose2-0.10.0-py2.py3-none-any.whl", hash = "sha256:aa620e759f2c5018d9ba041340391913e282ecebd3c392027f1575847b093ec6"},
+ {file = "nose2-0.10.0.tar.gz", hash = "sha256:886ba617a96de0130c54b24479bd5c2d74d5c940d40f3809c3a275511a0c4a60"},
+]
+pep8-naming = [
+ {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"},
+ {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
+]
+pycodestyle = [
+ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
+ {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+]
+pyflakes = [
+ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
+ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
+]
+pygments = [
+ {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
+ {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
+]
+pyrsistent = [
+ {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"},
+ {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"},
+ {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"},
+ {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"},
+ {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"},
+ {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"},
+ {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"},
+ {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"},
+ {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"},
+ {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"},
+ {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"},
+ {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"},
+ {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"},
+ {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"},
+ {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"},
+ {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"},
+ {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"},
+ {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"},
+ {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"},
+ {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"},
+ {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"},
+]
+pyusb = [
+ {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"},
+ {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"},
+]
+qmk = [
+ {file = "qmk-1.0.0-py2.py3-none-any.whl", hash = "sha256:63d69b97a533d91b0cfa7887e68cac7df52c6f7bddf4bf44d17ef1241d85ffff"},
+ {file = "qmk-1.0.0.tar.gz", hash = "sha256:da62eec73c4548cc37b0b9be3937202dc3a301dc2f2663610ecca751a610f9ca"},
+]
+qmk-dotty-dict = [
+ {file = "qmk_dotty_dict-1.3.0.post1-py3-none-any.whl", hash = "sha256:a9cb7fc3ff9631190fee0ecac14986a0ac7b4b6892347dc9d7486a4c4ea24492"},
+ {file = "qmk_dotty_dict-1.3.0.post1.tar.gz", hash = "sha256:3b611e393660bfaa6835c68e94784bae80fe07b8490978b5ecab03a0d2fc7ea2"},
+]
+six = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+spinners = [
+ {file = "spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98"},
+ {file = "spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f"},
+]
+termcolor = [
+ {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
+]
+yapf = [
+ {file = "yapf-0.31.0-py2.py3-none-any.whl", hash = "sha256:e3a234ba8455fe201eaa649cdac872d590089a18b661e39bbac7020978dd9c2e"},
+ {file = "yapf-0.31.0.tar.gz", hash = "sha256:408fb9a2b254c302f49db83c59f9aa0b4b0fd0ec25be3a5c51181327922ff63d"},
+]
diff --git a/util/nix/pyproject.toml b/util/nix/pyproject.toml
new file mode 100644
index 0000000000..1ec8aacd4a
--- /dev/null
+++ b/util/nix/pyproject.toml
@@ -0,0 +1,36 @@
+# This file should be kept in sync with requirements.txt and requirements-dev.txt
+# It is particularly required by the Nix environment (see shell.nix). To update versions,
+# normally one would run "poetry update --lock"
+[tool.poetry]
+name = "qmk_firmware"
+version = "0.1.0"
+description = ""
+authors = []
+
+[tool.poetry.dependencies]
+python = "^3.8"
+appdirs = "*"
+argcomplete = "*"
+colorama = "*"
+hid = "*"
+hjson = "*"
+jsonschema = ">=3"
+milc = ">=1.4.2"
+Pygments = "*"
+pyusb = "*"
+qmk-dotty-dict = "*"
+
+# This dependency is not mentioned in requirements.txt (QMK CLI is not a
+# library package that is required by the Python code in qmk_firmware), but is
+# required to build a proper nix-shell environment.
+qmk = "*"
+
+[tool.poetry.dev-dependencies]
+nose2 = "*"
+flake8 = "*"
+pep8-naming = "*"
+yapf = "*"
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/util/nix/sources.json b/util/nix/sources.json
new file mode 100644
index 0000000000..caf5cb7d29
--- /dev/null
+++ b/util/nix/sources.json
@@ -0,0 +1,38 @@
+{
+ "niv": {
+ "branch": "master",
+ "description": "Easy dependency management for Nix projects",
+ "homepage": "https://github.com/nmattia/niv",
+ "owner": "nmattia",
+ "repo": "niv",
+ "rev": "af958e8057f345ee1aca714c1247ef3ba1c15f5e",
+ "sha256": "1qjavxabbrsh73yck5dcq8jggvh3r2jkbr6b5nlz5d9yrqm9255n",
+ "type": "tarball",
+ "url": "https://github.com/nmattia/niv/archive/af958e8057f345ee1aca714c1247ef3ba1c15f5e.tar.gz",
+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+ },
+ "nixpkgs": {
+ "branch": "nixpkgs-unstable",
+ "description": "Nix Packages collection",
+ "homepage": "",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "c0e881852006b132236cbf0301bd1939bb50867e",
+ "sha256": "0fy7z7yxk5n7yslsvx5cyc6h21qwi4bhxf3awhirniszlbvaazy2",
+ "type": "tarball",
+ "url": "https://github.com/NixOS/nixpkgs/archive/c0e881852006b132236cbf0301bd1939bb50867e.tar.gz",
+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+ },
+ "poetry2nix": {
+ "branch": "master",
+ "description": "Convert poetry projects to nix automagically [maintainer=@adisbladis] ",
+ "homepage": "",
+ "owner": "nix-community",
+ "repo": "poetry2nix",
+ "rev": "2d27d44397242b28c3f0081e0432e4f6c951f3a1",
+ "sha256": "06syfg150r59m4kksj5547b5kwxjxjaif5hiljcq966kb9hxsvmv",
+ "type": "tarball",
+ "url": "https://github.com/nix-community/poetry2nix/archive/2d27d44397242b28c3f0081e0432e4f6c951f3a1.tar.gz",
+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+ }
+}
diff --git a/util/nix/sources.nix b/util/nix/sources.nix
new file mode 100644
index 0000000000..1938409ddd
--- /dev/null
+++ b/util/nix/sources.nix
@@ -0,0 +1,174 @@
+# This file has been generated by Niv.
+
+let
+
+ #
+ # The fetchers. fetch_<type> fetches specs of type <type>.
+ #
+
+ fetch_file = pkgs: name: spec:
+ let
+ name' = sanitizeName name + "-src";
+ in
+ if spec.builtin or true then
+ builtins_fetchurl { inherit (spec) url sha256; name = name'; }
+ else
+ pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
+
+ fetch_tarball = pkgs: name: spec:
+ let
+ name' = sanitizeName name + "-src";
+ in
+ if spec.builtin or true then
+ builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
+ else
+ pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
+
+ fetch_git = name: spec:
+ let
+ ref =
+ if spec ? ref then spec.ref else
+ if spec ? branch then "refs/heads/${spec.branch}" else
+ if spec ? tag then "refs/tags/${spec.tag}" else
+ abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
+ in
+ builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
+
+ fetch_local = spec: spec.path;
+
+ fetch_builtin-tarball = name: throw
+ ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
+ $ niv modify ${name} -a type=tarball -a builtin=true'';
+
+ fetch_builtin-url = name: throw
+ ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
+ $ niv modify ${name} -a type=file -a builtin=true'';
+
+ #
+ # Various helpers
+ #
+
+ # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
+ sanitizeName = name:
+ (
+ concatMapStrings (s: if builtins.isList s then "-" else s)
+ (
+ builtins.split "[^[:alnum:]+._?=-]+"
+ ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
+ )
+ );
+
+ # The set of packages used when specs are fetched using non-builtins.
+ mkPkgs = sources: system:
+ let
+ sourcesNixpkgs =
+ import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
+ hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
+ hasThisAsNixpkgsPath = <nixpkgs> == ./.;
+ in
+ if builtins.hasAttr "nixpkgs" sources
+ then sourcesNixpkgs
+ else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
+ import <nixpkgs> {}
+ else
+ abort
+ ''
+ Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
+ add a package called "nixpkgs" to your sources.json.
+ '';
+
+ # The actual fetching function.
+ fetch = pkgs: name: spec:
+
+ if ! builtins.hasAttr "type" spec then
+ abort "ERROR: niv spec ${name} does not have a 'type' attribute"
+ else if spec.type == "file" then fetch_file pkgs name spec
+ else if spec.type == "tarball" then fetch_tarball pkgs name spec
+ else if spec.type == "git" then fetch_git name spec
+ else if spec.type == "local" then fetch_local spec
+ else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
+ else if spec.type == "builtin-url" then fetch_builtin-url name
+ else
+ abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
+
+ # If the environment variable NIV_OVERRIDE_${name} is set, then use
+ # the path directly as opposed to the fetched source.
+ replace = name: drv:
+ let
+ saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
+ ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
+ in
+ if ersatz == "" then drv else
+ # this turns the string into an actual Nix path (for both absolute and
+ # relative paths)
+ if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
+
+ # Ports of functions for older nix versions
+
+ # a Nix version of mapAttrs if the built-in doesn't exist
+ mapAttrs = builtins.mapAttrs or (
+ f: set: with builtins;
+ listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
+ );
+
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
+ range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
+
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
+
+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
+ concatMapStrings = f: list: concatStrings (map f list);
+ concatStrings = builtins.concatStringsSep "";
+
+ # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
+ optionalAttrs = cond: as: if cond then as else {};
+
+ # fetchTarball version that is compatible between all the versions of Nix
+ builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
+ let
+ inherit (builtins) lessThan nixVersion fetchTarball;
+ in
+ if lessThan nixVersion "1.12" then
+ fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
+ else
+ fetchTarball attrs;
+
+ # fetchurl version that is compatible between all the versions of Nix
+ builtins_fetchurl = { url, name ? null, sha256 }@attrs:
+ let
+ inherit (builtins) lessThan nixVersion fetchurl;
+ in
+ if lessThan nixVersion "1.12" then
+ fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
+ else
+ fetchurl attrs;
+
+ # Create the final "sources" from the config
+ mkSources = config:
+ mapAttrs (
+ name: spec:
+ if builtins.hasAttr "outPath" spec
+ then abort
+ "The values in sources.json should not have an 'outPath' attribute"
+ else
+ spec // { outPath = replace name (fetch config.pkgs name spec); }
+ ) config.sources;
+
+ # The "config" used by the fetchers
+ mkConfig =
+ { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
+ , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
+ , system ? builtins.currentSystem
+ , pkgs ? mkPkgs sources system
+ }: rec {
+ # The sources, i.e. the attribute set of spec name to spec
+ inherit sources;
+
+ # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
+ inherit pkgs;
+ };
+
+in
+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
diff --git a/util/qmk_install.sh b/util/qmk_install.sh
index 5076e980a2..5f22ba0ad5 100755
--- a/util/qmk_install.sh
+++ b/util/qmk_install.sh
@@ -1,7 +1,14 @@
-#!/bin/bash
+#!/usr/bin/env bash
-QMK_FIRMWARE_DIR=$(cd -P -- "$(dirname -- "$0")/.." && pwd -P)
+QMK_FIRMWARE_DIR=$(cd -P -- "$(dirname -- "$0")/.." >/dev/null && pwd -P)
QMK_FIRMWARE_UTIL_DIR=$QMK_FIRMWARE_DIR/util
+if [ "$1" = "-y" ]; then
+ SKIP_PROMPT='-y'
+ MSYS2_CONFIRM='--noconfirm'
+else
+ SKIP_PROMPT=''
+ MSYS2_CONFIRM=''
+fi
case $(uname -a) in
*Darwin*)
@@ -25,10 +32,6 @@ case $(uname -a) in
. "$QMK_FIRMWARE_UTIL_DIR/install/fedora.sh";;
*gentoo*)
. "$QMK_FIRMWARE_UTIL_DIR/install/gentoo.sh";;
- *opensuse*|*tumbleweed*)
- . "$QMK_FIRMWARE_UTIL_DIR/install/opensuse.sh";;
- *sabayon*)
- . "$QMK_FIRMWARE_UTIL_DIR/install/sabayon.sh";;
*slackware*)
. "$QMK_FIRMWARE_UTIL_DIR/install/slackware.sh";;
*solus*)
@@ -36,9 +39,9 @@ case $(uname -a) in
*void*)
. "$QMK_FIRMWARE_UTIL_DIR/install/void.sh";;
*)
- echo "Sorry, we don't recognize your distribution. Help us by contributing support!"
+ echo "Sorry, we don't recognize your distribution. Try using the docker image instead:"
echo
- echo "https://docs.qmk.fm/#/contributing"
+ echo "https://docs.qmk.fm/#/getting_started_docker"
exit 1;;
esac
diff --git a/util/qmk_tab_complete.sh b/util/qmk_tab_complete.sh
new file mode 100644
index 0000000000..ebcb5536ac
--- /dev/null
+++ b/util/qmk_tab_complete.sh
@@ -0,0 +1,2 @@
+# Register qmk with tab completion
+eval "$(register-python-argcomplete --no-defaults qmk)"
diff --git a/util/reset.eep b/util/reset.eep
new file mode 100644
index 0000000000..a8a75389fe
--- /dev/null
+++ b/util/reset.eep
@@ -0,0 +1,9 @@
+:10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00
+:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
+:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
+:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
+:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
+:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
+:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
+:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
+:00000001FF
diff --git a/util/rules_cleaner.sh b/util/rules_cleaner.sh
new file mode 100755
index 0000000000..0367ae849f
--- /dev/null
+++ b/util/rules_cleaner.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+# This script finds all rules.mk files in keyboards/ subdirectories,
+# and deletes the build option filesize impacts from them.
+
+# Print an error message with the word "ERROR" in red.
+echo_error() {
+ echo -e "[\033[0;91mERROR\033[m]: $1"
+}
+
+# If we've been started from util/, we want to be in qmk_firmware/
+[[ "$PWD" == *util ]] && cd ..
+
+# The root qmk_firmware/ directory should have a subdirectory called quantum/
+if [ ! -d "quantum" ]; then
+ echo_error "Could not detect the QMK firmware directory!"
+ echo_error "Are you sure you're in the right place?"
+ exit 1
+fi
+
+# Set the inplace editing parameter for sed.
+# macOS/BSD sed expects a file extension immediately following -i.
+set_sed_i() {
+ sed_i=(-i)
+
+ case $(uname -a) in
+ *Darwin*) sed_i=(-i "")
+ esac
+}
+set_sed_i
+
+# Exclude keyamps/ directories
+files=$(find keyboards -type f -name 'rules.mk' -not \( -path '*/keymaps*' -prune \))
+
+# Edit rules.mk files
+for file in $files; do
+ sed "${sed_i[@]}" -e "s/(+[0-9].*)$//g" "$file"
+done
+
+echo "Cleaned up rules.mk files."
diff --git a/util/stm32eeprom_parser.py b/util/stm32eeprom_parser.py
new file mode 100755
index 0000000000..e08b67064b
--- /dev/null
+++ b/util/stm32eeprom_parser.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+#
+# Copyright 2021 Don Kjer
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import print_function
+
+import argparse
+from struct import pack, unpack
+import os, sys
+
+MAGIC_FEEA = '\xea\xff\xfe\xff'
+
+MAGIC_FEE9 = '\x16\x01'
+EMPTY_WORD = '\xff\xff'
+WORD_ENCODING = 0x8000
+VALUE_NEXT = 0x6000
+VALUE_RESERVED = 0x4000
+VALUE_ENCODED = 0x2000
+BYTE_RANGE = 0x80
+
+CHUNK_SIZE = 1024
+
+STRUCT_FMTS = {
+ 1: 'B',
+ 2: 'H',
+ 4: 'I'
+}
+PRINTABLE='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '
+
+EECONFIG_V1 = [
+ ("MAGIC", 0, 2),
+ ("DEBUG", 2, 1),
+ ("DEFAULT_LAYER", 3, 1),
+ ("KEYMAP", 4, 1),
+ ("MOUSEKEY_ACCEL", 5, 1),
+ ("BACKLIGHT", 6, 1),
+ ("AUDIO", 7, 1),
+ ("RGBLIGHT", 8, 4),
+ ("UNICODEMODE", 12, 1),
+ ("STENOMODE", 13, 1),
+ ("HANDEDNESS", 14, 1),
+ ("KEYBOARD", 15, 4),
+ ("USER", 19, 4),
+ ("VELOCIKEY", 23, 1),
+ ("HAPTIC", 24, 4),
+ ("MATRIX", 28, 4),
+ ("MATRIX_EXTENDED", 32, 2),
+ ("KEYMAP_UPPER_BYTE", 34, 1),
+]
+VIABASE_V1 = 35
+
+VERBOSE = False
+
+def parseArgs():
+ parser = argparse.ArgumentParser(description='Decode an STM32 emulated eeprom dump')
+ parser.add_argument('-s', '--size', type=int,
+ help='Size of the emulated eeprom (default: input_size / 2)')
+ parser.add_argument('-o', '--output', help='File to write decoded eeprom to')
+ parser.add_argument('-y', '--layout-options-size', type=int,
+ help='VIA layout options size (default: 1)', default=1)
+ parser.add_argument('-t', '--custom-config-size', type=int,
+ help='VIA custom config size (default: 0)', default=0)
+ parser.add_argument('-l', '--layers', type=int,
+ help='VIA keyboard layers (default: 4)', default=4)
+ parser.add_argument('-r', '--rows', type=int, help='VIA matrix rows')
+ parser.add_argument('-c', '--cols', type=int, help='VIA matrix columns')
+ parser.add_argument('-m', '--macros', type=int,
+ help='VIA macro count (default: 16)', default=16)
+ parser.add_argument('-C', '--canonical', action='store_true',
+ help='Canonical hex+ASCII display.')
+ parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
+ parser.add_argument('input', help='Raw contents of the STM32 flash area used to emulate eeprom')
+ return parser.parse_args()
+
+
+def decodeEepromFEEA(in_file, size):
+ decoded=size*[None]
+ pos = 0
+ while True:
+ chunk = in_file.read(CHUNK_SIZE)
+ for i in range(0, len(chunk), 2):
+ decoded[pos] = unpack('B', chunk[i])[0]
+ pos += 1
+ if pos >= size:
+ break
+
+ if len(chunk) < CHUNK_SIZE or pos >= size:
+ break
+ return decoded
+
+def decodeEepromFEE9(in_file, size):
+ decoded=size*[None]
+ pos = 0
+ # Read compacted flash
+ while True:
+ read_size = min(size - pos, CHUNK_SIZE)
+ chunk = in_file.read(read_size)
+ for i in range(len(chunk)):
+ decoded[pos] = unpack('B', chunk[i])[0] ^ 0xFF
+ pos += 1
+ if pos >= size:
+ break
+
+ if len(chunk) < read_size or pos >= size:
+ break
+ if VERBOSE:
+ print("COMPACTED EEPROM:")
+ dumpBinary(decoded, True)
+ print("WRITE LOG:")
+ # Read write log
+ while True:
+ entry = in_file.read(2)
+ if len(entry) < 2:
+ print("Partial log address at position 0x%04x" % pos, file=sys.stderr)
+ break
+ pos += 2
+
+ if entry == EMPTY_WORD:
+ break
+
+ be_entry = unpack('>H', entry)[0]
+ entry = unpack('H', entry)[0]
+ if not (entry & WORD_ENCODING):
+ address = entry >> 8
+ decoded[address] = entry & 0xFF
+ if VERBOSE:
+ print("[0x%04x]: BYTE 0x%02x = 0x%02x" % (be_entry, address, decoded[address]))
+ else:
+ if (entry & VALUE_NEXT) == VALUE_NEXT:
+ # Read next word as value
+ value = in_file.read(2)
+ if len(value) < 2:
+ print("Partial log value at position 0x%04x" % pos, file=sys.stderr)
+ break
+ pos += 2
+ address = entry & 0x1FFF
+ address <<= 1
+ address += BYTE_RANGE
+ decoded[address] = unpack('B', value[0])[0] ^ 0xFF
+ decoded[address+1] = unpack('B', value[1])[0] ^ 0xFF
+ be_value = unpack('>H', value)[0]
+ if VERBOSE:
+ print("[0x%04x 0x%04x]: WORD 0x%04x = 0x%02x%02x" % (be_entry, be_value, address, decoded[address+1], decoded[address]))
+ else:
+ # Reserved for future use
+ if entry & VALUE_RESERVED:
+ if VERBOSE:
+ print("[0x%04x]: RESERVED 0x%04x" % (be_entry, address))
+ continue
+ address = entry & 0x1FFF
+ address <<= 1
+ decoded[address] = (entry & VALUE_ENCODED) >> 13
+ decoded[address+1] = 0
+ if VERBOSE:
+ print("[0x%04x]: ENCODED 0x%04x = 0x%02x%02x" % (be_entry, address, decoded[address+1], decoded[address]))
+
+ return decoded
+
+def dumpBinary(data, canonical):
+ def display(pos, row):
+ print("%04x" % pos, end='')
+ for i in range(len(row)):
+ if i % 8 == 0:
+ print(" ", end='')
+ char = row[i]
+ if char is None:
+ print(" ", end='')
+ else:
+ print(" %02x" % row[i], end='')
+ if canonical:
+ print(" |", end='')
+ for i in range(len(row)):
+ char = row[i]
+ if char is None:
+ char = " "
+ else:
+ char = chr(char)
+ if char not in PRINTABLE:
+ char = "."
+ print(char, end='')
+ print("|", end='')
+
+ print("")
+
+ size = len(data)
+ prev_row = ''
+ first_repeat = True
+ for pos in range(0, size, 16):
+ row=data[pos:pos+16]
+ row[len(row):16] = (16-len(row))*[None]
+ if row == prev_row:
+ if first_repeat:
+ print("*")
+ first_repeat = False
+ else:
+ first_repeat = True
+ display(pos, row)
+ prev_row = row
+ print("%04x" % (pos+16))
+
+def dumpEeconfig(data, eeconfig):
+ print("EECONFIG:")
+ for (name, pos, length) in eeconfig:
+ fmt = STRUCT_FMTS[length]
+ value = unpack(fmt, ''.join([chr(x) for x in data[pos:pos+length]]))[0]
+ print(("%%04x %%s = 0x%%0%dx" % (length * 2)) % (pos, name, value))
+
+def dumpVia(data, base, layers, cols, rows, macros,
+ layout_options_size, custom_config_size):
+ magicYear = data[base + 0]
+ magicMonth = data[base + 1]
+ magicDay = data[base + 2]
+ # Sanity check
+ if not 10 <= magicYear <= 0x99 or \
+ not 0 <= magicMonth <= 0x12 or \
+ not 0 <= magicDay <= 0x31:
+ print("ERROR: VIA Signature is not valid; Year:%x, Month:%x, Day:%x" % (magicYear, magicMonth, magicDay))
+ return
+ if cols is None or rows is None:
+ print("ERROR: VIA dump requires specifying --rows and --cols", file=sys.stderr)
+ return 2
+ print("VIA:")
+ # Decode magic
+ print("%04x MAGIC = 20%02x-%02x-%02x" % (base, magicYear, magicMonth, magicDay))
+ # Decode layout options
+ options = 0
+ pos = base + 3
+ for i in range(base+3, base+3+layout_options_size):
+ options = options << 8
+ options |= data[i]
+ print(("%%04x LAYOUT_OPTIONS = 0x%%0%dx" % (layout_options_size * 2)) % (pos, options))
+ pos += layout_options_size + custom_config_size
+ # Decode keycodes
+ keymap_size = layers * rows * cols * 2
+ if (pos + keymap_size) >= (len(data) - 1):
+ print("ERROR: VIA keymap requires %d bytes, but only %d available" % (keymap_size, len(data) - pos))
+ return 3
+ for layer in range(layers):
+ print("%s LAYER %d %s" % ('-'*int(cols*2.5), layer, '-'*int(cols*2.5)))
+ for row in range(rows):
+ print("%04x | " % pos, end='')
+ for col in range(cols):
+ keycode = (data[pos] << 8) | (data[pos+1])
+ print(" %04x" % keycode, end='')
+ pos += 2
+ print("")
+ # Decode macros
+ for macro_num in range(macros):
+ macro = ""
+ macro_pos = pos
+ while pos < len(data):
+ char = chr(data[pos])
+ pos += 1
+ if char == '\x00':
+ print("%04x MACRO[%d] = '%s'" % (macro_pos, macro_num, macro))
+ break
+ else:
+ macro += char
+ return 0
+
+
+def decodeSTM32Eeprom(input, canonical, size=None, output=None, **kwargs):
+ input_size = os.path.getsize(input)
+ if size is None:
+ size = input_size >> 1
+
+ # Read the first few bytes to check magic signature
+ with open(input, 'rb') as in_file:
+ magic=in_file.read(4)
+ in_file.seek(0)
+
+ if magic == MAGIC_FEEA:
+ decoded = decodeEepromFEEA(in_file, size)
+ eeconfig = EECONFIG_V1
+ via_base = VIABASE_V1
+ elif magic[:2] == MAGIC_FEE9:
+ decoded = decodeEepromFEE9(in_file, size)
+ eeconfig = EECONFIG_V1
+ via_base = VIABASE_V1
+ else:
+ print("Unknown magic signature: %s" % " ".join(["0x%02x" % ord(x) for x in magic]), file=sys.stderr)
+ return 1
+
+ if output is not None:
+ with open(output, 'wb') as out_file:
+ out_file.write(pack('%dB' % len(decoded), *decoded))
+ print("DECODED EEPROM:")
+ dumpBinary(decoded, canonical)
+ dumpEeconfig(decoded, eeconfig)
+ if kwargs['rows'] is not None and kwargs['cols'] is not None:
+ return dumpVia(decoded, via_base, **kwargs)
+
+ return 0
+
+def main():
+ global VERBOSE
+ kwargs = vars(parseArgs())
+ VERBOSE = kwargs.pop('verbose')
+ return decodeSTM32Eeprom(**kwargs)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/util/udev/50-qmk.rules b/util/udev/50-qmk.rules
index 70bd7e6e3e..db27d4dc81 100644
--- a/util/udev/50-qmk.rules
+++ b/util/udev/50-qmk.rules
@@ -9,6 +9,8 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff3", TAG+="uacc
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"
@@ -58,3 +60,12 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000e", TAG+="uacc
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"
diff --git a/util/uf2conv.py b/util/uf2conv.py
new file mode 100755
index 0000000000..8677a828c9
--- /dev/null
+++ b/util/uf2conv.py
@@ -0,0 +1,319 @@
+#!/usr/bin/env python3
+import sys
+import struct
+import subprocess
+import re
+import os
+import os.path
+import argparse
+
+
+UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
+UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
+UF2_MAGIC_END = 0x0AB16F30 # Ditto
+
+families = {
+ 'SAMD21': 0x68ed2b88,
+ 'SAML21': 0x1851780a,
+ 'SAMD51': 0x55114460,
+ 'NRF52': 0x1b57745f,
+ 'STM32F0': 0x647824b6,
+ 'STM32F1': 0x5ee21072,
+ 'STM32F2': 0x5d1a0a2e,
+ 'STM32F3': 0x6b846188,
+ 'STM32F4': 0x57755a57,
+ 'STM32F7': 0x53b80f00,
+ 'STM32G0': 0x300f5633,
+ 'STM32G4': 0x4c71240a,
+ 'STM32H7': 0x6db66082,
+ 'STM32L0': 0x202e3a91,
+ 'STM32L1': 0x1e1f432d,
+ 'STM32L4': 0x00ff6919,
+ 'STM32L5': 0x04240bdf,
+ 'STM32WB': 0x70d16653,
+ 'STM32WL': 0x21460ff0,
+ 'ATMEGA32': 0x16573617,
+ 'MIMXRT10XX': 0x4FB2D5BD,
+ 'LPC55': 0x2abc77ec,
+ 'GD32F350': 0x31D228C6,
+ 'ESP32S2': 0xbfdd4eee,
+ 'RP2040': 0xe48bff56
+}
+
+INFO_FILE = "/INFO_UF2.TXT"
+
+appstartaddr = 0x2000
+familyid = 0x0
+
+
+def is_uf2(buf):
+ w = struct.unpack("<II", buf[0:8])
+ return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
+
+def is_hex(buf):
+ try:
+ w = buf[0:30].decode("utf-8")
+ except UnicodeDecodeError:
+ return False
+ if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
+ return True
+ return False
+
+def convert_from_uf2(buf):
+ global appstartaddr
+ numblocks = len(buf) // 512
+ curraddr = None
+ outp = []
+ for blockno in range(numblocks):
+ ptr = blockno * 512
+ block = buf[ptr:ptr + 512]
+ hd = struct.unpack(b"<IIIIIIII", block[0:32])
+ if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
+ print("Skipping block at " + ptr + "; bad magic")
+ continue
+ if hd[2] & 1:
+ # NO-flash flag set; skip block
+ continue
+ datalen = hd[4]
+ if datalen > 476:
+ assert False, "Invalid UF2 data size at " + ptr
+ newaddr = hd[3]
+ if curraddr is None:
+ appstartaddr = newaddr
+ curraddr = newaddr
+ padding = newaddr - curraddr
+ if padding < 0:
+ assert False, "Block out of order at " + ptr
+ if padding > 10*1024*1024:
+ assert False, "More than 10M of padding needed at " + ptr
+ if padding % 4 != 0:
+ assert False, "Non-word padding size at " + ptr
+ while padding > 0:
+ padding -= 4
+ outp += b"\x00\x00\x00\x00"
+ outp.append(block[32 : 32 + datalen])
+ curraddr = newaddr + datalen
+ return b"".join(outp)
+
+def convert_to_carray(file_content):
+ outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
+ outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
+ for i in range(len(file_content)):
+ if i % 16 == 0:
+ outp += "\n"
+ outp += "0x%02x, " % file_content[i]
+ outp += "\n};\n"
+ return bytes(outp, "utf-8")
+
+def convert_to_uf2(file_content):
+ global familyid
+ datapadding = b""
+ while len(datapadding) < 512 - 256 - 32 - 4:
+ datapadding += b"\x00\x00\x00\x00"
+ numblocks = (len(file_content) + 255) // 256
+ outp = []
+ for blockno in range(numblocks):
+ ptr = 256 * blockno
+ chunk = file_content[ptr:ptr + 256]
+ flags = 0x0
+ if familyid:
+ flags |= 0x2000
+ hd = struct.pack(b"<IIIIIIII",
+ UF2_MAGIC_START0, UF2_MAGIC_START1,
+ flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
+ while len(chunk) < 256:
+ chunk += b"\x00"
+ block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
+ assert len(block) == 512
+ outp.append(block)
+ return b"".join(outp)
+
+class Block:
+ def __init__(self, addr):
+ self.addr = addr
+ self.bytes = bytearray(256)
+
+ def encode(self, blockno, numblocks):
+ global familyid
+ flags = 0x0
+ if familyid:
+ flags |= 0x2000
+ hd = struct.pack("<IIIIIIII",
+ UF2_MAGIC_START0, UF2_MAGIC_START1,
+ flags, self.addr, 256, blockno, numblocks, familyid)
+ hd += self.bytes[0:256]
+ while len(hd) < 512 - 4:
+ hd += b"\x00"
+ hd += struct.pack("<I", UF2_MAGIC_END)
+ return hd
+
+def convert_from_hex_to_uf2(buf):
+ global appstartaddr
+ appstartaddr = None
+ upper = 0
+ currblock = None
+ blocks = []
+ for line in buf.split('\n'):
+ if line[0] != ":":
+ continue
+ i = 1
+ rec = []
+ while i < len(line) - 1:
+ rec.append(int(line[i:i+2], 16))
+ i += 2
+ tp = rec[3]
+ if tp == 4:
+ upper = ((rec[4] << 8) | rec[5]) << 16
+ elif tp == 2:
+ upper = ((rec[4] << 8) | rec[5]) << 4
+ assert (upper & 0xffff) == 0
+ elif tp == 1:
+ break
+ elif tp == 0:
+ addr = upper | (rec[1] << 8) | rec[2]
+ if appstartaddr is None:
+ appstartaddr = addr
+ i = 4
+ while i < len(rec) - 1:
+ if not currblock or currblock.addr & ~0xff != addr & ~0xff:
+ currblock = Block(addr & ~0xff)
+ blocks.append(currblock)
+ currblock.bytes[addr & 0xff] = rec[i]
+ addr += 1
+ i += 1
+ numblocks = len(blocks)
+ resfile = b""
+ for i in range(0, numblocks):
+ resfile += blocks[i].encode(i, numblocks)
+ return resfile
+
+def to_str(b):
+ return b.decode("utf-8")
+
+def get_drives():
+ drives = []
+ if sys.platform == "win32":
+ r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
+ "get", "DeviceID,", "VolumeName,",
+ "FileSystem,", "DriveType"])
+ for line in to_str(r).split('\n'):
+ words = re.split('\s+', line)
+ if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
+ drives.append(words[0])
+ else:
+ rootpath = "/media"
+ if sys.platform == "darwin":
+ rootpath = "/Volumes"
+ elif sys.platform == "linux":
+ tmp = rootpath + "/" + os.environ["USER"]
+ if os.path.isdir(tmp):
+ rootpath = tmp
+ for d in os.listdir(rootpath):
+ drives.append(os.path.join(rootpath, d))
+
+
+ def has_info(d):
+ try:
+ return os.path.isfile(d + INFO_FILE)
+ except Exception:
+ return False
+
+ return list(filter(has_info, drives))
+
+
+def board_id(path):
+ with open(path + INFO_FILE, mode='r') as file:
+ file_content = file.read()
+ return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
+
+
+def list_drives():
+ for d in get_drives():
+ print(d, board_id(d))
+
+
+def write_file(name, buf):
+ with open(name, "wb") as f:
+ f.write(buf)
+ print("Wrote %d bytes to %s" % (len(buf), name))
+
+
+def main():
+ global appstartaddr, familyid
+ def error(msg):
+ print(msg)
+ sys.exit(1)
+ parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
+ parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
+ help='input file (HEX, BIN or UF2)')
+ parser.add_argument('-b' , '--base', dest='base', type=str,
+ default="0x2000",
+ help='set base address of application for BIN format (default: 0x2000)')
+ parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
+ help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
+ parser.add_argument('-d' , '--device', dest="device_path",
+ help='select a device path to flash')
+ parser.add_argument('-l' , '--list', action='store_true',
+ help='list connected devices')
+ parser.add_argument('-c' , '--convert', action='store_true',
+ help='do not flash, just convert')
+ parser.add_argument('-D' , '--deploy', action='store_true',
+ help='just flash, do not convert')
+ parser.add_argument('-f' , '--family', dest='family', type=str,
+ default="0x0",
+ help='specify familyID - number or name (default: 0x0)')
+ parser.add_argument('-C' , '--carray', action='store_true',
+ help='convert binary file to a C array, not UF2')
+ args = parser.parse_args()
+ appstartaddr = int(args.base, 0)
+
+ if args.family.upper() in families:
+ familyid = families[args.family.upper()]
+ else:
+ try:
+ familyid = int(args.family, 0)
+ except ValueError:
+ error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
+
+ if args.list:
+ list_drives()
+ else:
+ if not args.input:
+ error("Need input file")
+ with open(args.input, mode='rb') as f:
+ inpbuf = f.read()
+ from_uf2 = is_uf2(inpbuf)
+ ext = "uf2"
+ if args.deploy:
+ outbuf = inpbuf
+ elif from_uf2:
+ outbuf = convert_from_uf2(inpbuf)
+ ext = "bin"
+ elif is_hex(inpbuf):
+ outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
+ elif args.carray:
+ outbuf = convert_to_carray(inpbuf)
+ ext = "h"
+ else:
+ outbuf = convert_to_uf2(inpbuf)
+ print("Converting to %s, output size: %d, start address: 0x%x" %
+ (ext, len(outbuf), appstartaddr))
+ if args.convert or ext != "uf2":
+ drives = []
+ if args.output is None:
+ args.output = "flash." + ext
+ else:
+ drives = get_drives()
+
+ if args.output:
+ write_file(args.output, outbuf)
+ else:
+ if len(drives) == 0:
+ error("No drive to deploy.")
+ for d in drives:
+ print("Flashing %s (%s)" % (d, board_id(d)))
+ write_file(d + "/NEW.UF2", outbuf)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/update_chibios_mirror.sh b/util/update_chibios_mirror.sh
new file mode 100755
index 0000000000..83aee22817
--- /dev/null
+++ b/util/update_chibios_mirror.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+################################
+# Configuration
+
+# The ChibiOS branches to mirror
+chibios_branches="trunk stable_20.3.x stable_21.6.x"
+
+# The ChibiOS tags to mirror
+chibios_tags="ver20.3.1 ver20.3.2 ver20.3.3 ver21.6.0"
+
+# The ChibiOS-Contrib branches to mirror
+contrib_branches="chibios-20.3.x"
+
+################################
+# Actions
+
+set -eEuo pipefail
+umask 022
+
+this_script="$(realpath "${BASH_SOURCE[0]}")"
+script_dir="$(realpath "$(dirname "$this_script")")"
+qmk_firmware_dir="$(realpath "$script_dir/../")"
+chibios_dir="$qmk_firmware_dir/lib/chibios"
+contrib_dir="$qmk_firmware_dir/lib/chibios-contrib"
+
+chibios_git_location=$(realpath "$chibios_dir/$(cat "$chibios_dir/.git" | awk '/gitdir:/ {print $2}')")
+chibios_git_config=$(realpath "$chibios_git_location/config")
+contrib_git_location=$(realpath "$contrib_dir/$(cat "$contrib_dir/.git" | awk '/gitdir:/ {print $2}')")
+contrib_git_config=$(realpath "$contrib_git_location/config")
+
+cd "$chibios_dir"
+
+if [[ -z "$(cat "$chibios_git_config" | grep '\[svn-remote "svn"\]')" ]] ; then
+ git svn init --stdlayout --prefix='svn/' http://svn.osdn.net/svnroot/chibios/
+fi
+
+if [[ -z "$(cat "$chibios_git_config" | grep '\[remote "qmk"\]')" ]] ; then
+ git remote add qmk git@github.com:qmk/ChibiOS.git
+ git remote set-url qmk git@github.com:qmk/ChibiOS.git --push
+else
+ git remote set-url qmk git@github.com:qmk/ChibiOS.git
+ git remote set-url qmk git@github.com:qmk/ChibiOS.git --push
+fi
+
+echo "Updating remotes..."
+git fetch --all --tags --prune
+
+echo "Fetching latest from subversion..."
+git svn fetch
+
+echo "Updating ChibiOS branches..."
+for branch in $chibios_branches ; do
+ echo "Creating branch 'svn-mirror/$branch' from 'svn/$branch'..."
+ git branch -f svn-mirror/$branch svn/$branch \
+ && git push qmk svn-mirror/$branch
+done
+
+echo "Updating ChibiOS tags..."
+for tagname in $chibios_tags ; do
+ echo "Creating tag 'svn-mirror/$tagname' from 'svn/tags/$tagname'..."
+ GIT_COMMITTER_DATE="$(git log -n1 --pretty=format:'%ad' svn/tags/$tagname)" git tag -f -a -m "Tagging $tagname" svn-mirror/$tagname svn/tags/$tagname
+ git push qmk svn-mirror/$tagname
+done
+
+cd "$contrib_dir"
+
+if [[ -z "$(cat "$contrib_git_config" | grep '\[remote "qmk"\]')" ]] ; then
+ git remote add qmk git@github.com:qmk/ChibiOS-Contrib.git
+ git remote set-url qmk git@github.com:qmk/ChibiOS-Contrib.git --push
+else
+ git remote set-url qmk git@github.com:qmk/ChibiOS-Contrib.git
+ git remote set-url qmk git@github.com:qmk/ChibiOS-Contrib.git --push
+fi
+
+if [[ -z "$(cat "$contrib_git_config" | grep '\[remote "upstream"\]')" ]] ; then
+ git remote add upstream git@github.com:ChibiOS/ChibiOS-Contrib.git
+ git remote set-url upstream git@github.com:ChibiOS/ChibiOS-Contrib.git --push
+else
+ git remote set-url upstream git@github.com:ChibiOS/ChibiOS-Contrib.git
+ git remote set-url upstream git@github.com:ChibiOS/ChibiOS-Contrib.git --push
+fi
+
+echo "Updating remotes..."
+git fetch --all --tags --prune
+
+echo "Updating ChibiOS-Contrib branches..."
+for branch in $contrib_branches ; do
+ echo "Creating branch 'mirror/$branch' from 'upstream/$branch'..."
+ git branch -f mirror/$branch upstream/$branch \
+ && git push qmk mirror/$branch
+done
diff --git a/util/vagrant/Dockerfile b/util/vagrant/Dockerfile
index 1936ee023a..951d4fc40d 100644
--- a/util/vagrant/Dockerfile
+++ b/util/vagrant/Dockerfile
@@ -1,4 +1,4 @@
-FROM qmkfm/base_container
+FROM qmkfm/qmk_cli
# Basic upgrades; install sudo and SSH.
RUN apt-get update && apt-get install --no-install-recommends -y \
diff --git a/util/vagrant/readme.md b/util/vagrant/readme.md
index e4b870a642..a8396007ee 100644
--- a/util/vagrant/readme.md
+++ b/util/vagrant/readme.md
@@ -1,7 +1,7 @@
# QMK Vagrant Utilities
## Dockerfile
-Vagrant-friendly `qmkfm/base_container`.
+Vagrant-friendly `qmkfm/qmk_cli`.
In order for the Docker provider and `vagrant ssh` to function the container has a few extra requirements.