1
0

Compare commits

..

23 Commits

Author SHA1 Message Date
Nick Brassel
15e8658e81 Deploy qmk_udev during bootstrap, updated CLI bootstrap docs (#26147) 2026-04-14 11:02:31 +01:00
Toast
c6475e0476 Implement num lock light for Keychron V5 (#26019) 2026-04-13 22:49:27 +01:00
Danny
3001d81e3d Add Sinc LM Rev. 1 (#26134) 2026-04-13 16:28:59 -04:00
dependabot[bot]
407e6e242e Bump actions/github-script from 8 to 9 (#26142)
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 10:15:09 +01:00
QMK Bot
18ed7c6caf [CI] Format code according to conventions (#26138)
Format code according to conventions
2026-04-09 13:26:08 +01:00
Joel Challis
92c0e2cee1 Align C formatting extensions (#26137) 2026-04-09 12:03:53 +01:00
Joel Challis
6a11370434 Avoid BrokenPipeError errors in qmk clean 2026-04-09 11:30:33 +01:00
Joel Challis
2bd8e43256 Enhance checks for invalid keyboard build targets (#26122)
Co-authored-by: フィルターペーパー <76888457+filterpaper@users.noreply.github.com>
2026-04-05 15:38:40 +10:00
Joel Challis
933cb8cc35 Update CI workflow to dynamically set keymaps (#26120) 2026-04-05 15:37:41 +10:00
Joel Challis
1426eedfc1 Fix cache list command in CI workflow 2026-04-02 08:23:41 +01:00
Joel Challis
e4b998ccb0 Only attempt cache deletion if exists (#26124) 2026-04-02 16:00:00 +11:00
Joel Challis
b5af7a3390 Enable ccache within CI (#26121) 2026-04-01 19:41:19 +11:00
Joel Challis
ed80e21858 Resolve 'using serial compilation of 2 LTRANS jobs' warning (#26113) 2026-03-30 15:25:29 +11:00
Joel Challis
9d24bc8a33 Fix userspace detection in Makefile (#26117) 2026-03-30 11:24:32 +08:00
QMK Bot
c7fde3d8cc [CI] Format code according to conventions (#26108)
Format code according to conventions
2026-03-27 03:56:53 +00:00
Sebastian Morgenstern
0fdb5df94d Added new pttbutton keyboard (#25952) 2026-03-27 03:15:28 +00:00
Joel Challis
bd500ae092 Detect PRs bypassing DD changes (#26094) 2026-03-20 06:27:49 +11:00
Peter Cock
fec01edaa4 Explain how to use STORE_SETUPS and PRINT_SETUPS (#26070)
* Explain how to use STORE_SETUPS and PRINT_SETUPS

I found the instructions a little terse, but managed.

This also adds a note about PRINT_SETUPS reporting random numbers if STORE_SETUPS hasn't been used.

* Remove line breaks

Existing docs seem to use a mix of hard line breaks at sentence
ends, or 80 chars-ish, versus no line breaks in paragraphs.

* Suggestion during review

Co-authored-by: フィルターペーパー <76888457+filterpaper@users.noreply.github.com>

---------

Co-authored-by: フィルターペーパー <76888457+filterpaper@users.noreply.github.com>
2026-03-19 11:40:49 +08:00
Peter Cock
7619e991cf QMK avoids raw line breaks within paragraphs in Markdown (#26080)
* QMK avoids raw line breaks within paragraphs in Markdown

Mentioned in passing during a few of my recent documentation pull requests, eg #26070.

* Suggestion during review

Co-authored-by: フィルターペーパー <76888457+filterpaper@users.noreply.github.com>

---------

Co-authored-by: フィルターペーパー <76888457+filterpaper@users.noreply.github.com>
2026-03-19 10:16:35 +08:00
Dasky
627ad33233 Fix pointing device driver typo (#26091)
fix typo
2026-03-18 18:50:33 -07:00
フィルターペーパー
2cbcd76ef0 License violations updates. (#26076) 2026-03-16 03:12:18 +00:00
Joel Challis
322e673bcb Remove use of andstor/file-existence-action (#26078) 2026-03-16 03:12:04 +00:00
dependabot[bot]
4be8880177 Bump geekyeggo/delete-artifact from 5 to 6 (#26077)
Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 5 to 6.
- [Release notes](https://github.com/geekyeggo/delete-artifact/releases)
- [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md)
- [Commits](https://github.com/geekyeggo/delete-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: geekyeggo/delete-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 02:23:53 +00:00
42 changed files with 985 additions and 357 deletions

View File

@@ -32,6 +32,7 @@ jobs:
container: ghcr.io/qmk/qmk_cli
outputs:
keymaps: ${{ steps.generate_slice_length.outputs.keymaps }}
slice_length: ${{ steps.generate_slice_length.outputs.slice_length }}
steps:
@@ -47,12 +48,20 @@ jobs:
- name: Determine concurrency
id: generate_slice_length
shell: 'bash {0}'
run: |
target_count=$( {
qmk find -km default 2>/dev/null
qmk find -km xap 2>/dev/null
} | sort | uniq | wc -l)
targets=()
target_count=0
for target in "default" "xap"; do
count=$(qmk find -km $target 2>/dev/null | wc -l)
if [ $count -gt 0 ]; then
target_count=$(($target_count + $count))
targets+=($target)
fi
done
keymaps=$(jq -c -n '$ARGS.positional' --args "${targets[@]}")
slice_length=$((target_count / ($CONCURRENT_JOBS - 1))) # Err on the side of caution
echo "keymaps=$keymaps" >> $GITHUB_OUTPUT
echo "slice_length=$slice_length" >> $GITHUB_OUTPUT
build_targets:
@@ -61,7 +70,7 @@ jobs:
strategy:
fail-fast: false
matrix:
keymap: [default, xap]
keymap: ${{ fromJson(needs.determine_concurrency.outputs.keymaps) }}
uses: ./.github/workflows/ci_build_major_branch_keymap.yml
with:
branch: ${{ inputs.branch || github.ref_name }}
@@ -123,14 +132,7 @@ jobs:
SOURCE_DIR: .
DEST_DIR: ${{ inputs.branch || github.ref_name }}/latest
- name: Check if failure marker file exists
id: check_failure_marker
uses: andstor/file-existence-action@v3
with:
files: ./.failed
- name: Fail build if needed
if: steps.check_failure_marker.outputs.files_exists == 'true'
run: |
# Exit with failure if the compilation stage failed
exit 1
[ ! -e .failed ] || exit 1

View File

@@ -38,17 +38,18 @@ jobs:
run: pip3 install -r requirements-dev.txt
- name: Generate build targets
shell: 'bash {0}'
id: generate_targets
run: |
{ # Intentionally use `shuf` here so that we share manufacturers across all build groups -- some have a lot of ARM-based boards which inherently take longer
counter=0
echo -n '{'
qmk find -km ${{ inputs.keymap }} 2>/dev/null | sort | uniq | shuf | xargs -L${{ inputs.slice_length }} | while IFS=$'\n' read target ; do
qmk find -km ${{ inputs.keymap }} 2>/dev/null | sort | uniq | shuf --random-source=<(openssl enc -aes-256-ctr -pass pass:qmk -nosalt </dev/zero 2>/dev/null) | xargs -L${{ inputs.slice_length }} | while IFS=$'\n' read target ; do
if [ $counter -gt 0 ]; then
echo -n ','
fi
counter=$((counter+1))
printf "\"group %02d\":{" $counter
printf "\"group-%02d\":{" $counter
echo -n '"targets":"'
echo $target | tr ' ' '\n' | sort | uniq | xargs echo -n
echo -n '"}'
@@ -72,6 +73,9 @@ jobs:
container: ghcr.io/qmk/qmk_cli
continue-on-error: true
env:
CCACHE_CONFIGPATH: ~/.cache
strategy:
matrix:
target: ${{ fromJson(needs.generate_targets.outputs.targets) }}
@@ -83,6 +87,8 @@ jobs:
- name: Checkout QMK Firmware
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies
run: pip3 install -r requirements-dev.txt
@@ -93,24 +99,47 @@ jobs:
name: targets-${{ inputs.keymap }}
path: .
- name: Deploy submodules
run: |
qmk git-submodule -f
- name: Dump targets
run: |
jq -r '.["${{ matrix.target }}"].targets' targets.json | tr ' ' '\n' | sort
- name: Restore Cache
id: cache
uses: actions/cache/restore@v5
with:
path: ${{ env.CCACHE_CONFIGPATH }}
key: compile-${{ inputs.keymap }}-${{ matrix.target }}
- name: Build targets
continue-on-error: true
run: |
export NCPUS=$(( $(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null) -1 ))
targets=$(jq -r '.["${{ matrix.target }}"].targets' targets.json | tr ' ' '\n' | sort)
if [ -z "${targets}" ]; then
echo "Zero build targets detected"
exit 0
fi
qmk mass-compile -t -j $NCPUS -e DUMP_CI_METADATA=yes $targets || touch .failed
qmk mass-compile -t -j $(nproc) -e DUMP_CI_METADATA=yes -e USE_CCACHE=yes $targets || touch .failed
- name: Dump ccache stats
run: |
ccache -s
# Delete the old cache on hit to emulate a cache update. See https://github.com/actions/cache/issues/342.
- name: Delete old cache
env:
GH_TOKEN: ${{ github.token }}
if: steps.cache.outputs.cache-hit
run: |
count=$(gh cache list --ref ${{ github.ref }} --key ${{ steps.cache.outputs.cache-primary-key }} --json id | jq length)
if [ $count -gt 0 ]; then
gh cache delete --ref ${{ github.ref }} ${{ steps.cache.outputs.cache-primary-key }}
fi
- name: Save Cache
uses: actions/cache/save@v5
with:
path: ${{ env.CCACHE_CONFIGPATH }}
key: compile-${{ inputs.keymap }}-${{ matrix.target }}
- name: Upload binaries
uses: actions/upload-artifact@v7
@@ -166,7 +195,7 @@ jobs:
truncate --size='<960K' $GITHUB_STEP_SUMMARY || true
- name: Delete temporary build artifacts
uses: geekyeggo/delete-artifact@v5
uses: geekyeggo/delete-artifact@v6
with:
name: |
firmware-${{ inputs.keymap }}-*

View File

@@ -22,7 +22,7 @@ jobs:
steps:
- name: Deploy Develop
if: ${{ github.repository == 'qmk/qmk_firmware' }}
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.QMK_BOT_TOKEN }}
script: |

View File

@@ -89,23 +89,7 @@ jobs:
if: always()
shell: 'bash {0}'
run: |
exit_code=0
for file in $(find keyboards/ -name rules.mk | grep -v /keymaps/ | grep -v /common/ | grep -v /lib/); do
dir=$(dirname $file)
$(find $dir -name keyboard.json -exec false {} +)
if [[ $? == 0 ]]; then
echo "$dir::Legacy target detected"
((++exit_code))
fi
done
if [[ $exit_code -gt 255 ]]; then
exit 255
fi
exit $exit_code
qmk ci-validate-keyboard-targets
- name: Verify keyboard aliases
if: always()

View File

@@ -8,6 +8,9 @@ on:
paths:
- 'data/constants/**'
- 'lib/python/**'
- 'quantum/rgblight/rgblight_breathe_table.h'
- 'quantum/keycodes.h'
- 'quantum/keymap_extras/**'
jobs:
regen:

View File

@@ -38,14 +38,17 @@ $(info QMK Firmware $(QMK_VERSION))
endif
endif
# Try to determine userspace from qmk config, if set.
ifeq ($(QMK_USERSPACE),)
QMK_USERSPACE = $(shell qmk config -ro user.overlay_dir | cut -d= -f2 | sed -e 's@^None$$@@g')
endif
# Determine which qmk cli to use
QMK_BIN := qmk
# Try to determine userspace from qmk config, if set. Handle direct query on qmk_cli>=1.1.7
# falling back to legacy method of only supporting user.overlay_dir config
# sort is used to buffer 'qmk env' output and avoid BrokenPipeError errors
export override QMK_USERSPACE := $(shell \
$(QMK_BIN) env | sort | grep -q QMK_USERSPACE \
&& $(QMK_BIN) env QMK_USERSPACE \
|| $(QMK_BIN) config -ro user.overlay_dir | cut -d= -f2 | sed -e 's@^None$$@@g')
# avoid 'Entering|Leaving directory' messages
MAKEFLAGS += --no-print-directory

View File

@@ -58,7 +58,7 @@ endif
#---------------- C Compiler Options ----------------
ifeq ($(strip $(LTO_ENABLE)), yes)
CDEFS += -flto
CDEFS += -flto=auto
CDEFS += -DLTO_ENABLE
endif

View File

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

View File

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

View File

@@ -14,6 +14,10 @@ Example:
This page covers my super cool feature. You can use this feature to make coffee, squeeze fresh oj, and have an egg mcmuffin and hashbrowns delivered from your local macca's by drone.
```
# Paragraphs
Do not use hard line breaks within the raw Markdown for each paragraph. These are optional in Markdown, and have no effect on the rendered output. This means each raw paragraph will be a single long line in your editor (best viewed with line wrapping enabled).
# Headings
Your page should generally have multiple "H1" headings. Only H1 and H2 headings will included in the Table of Contents, so plan them out appropriately. Excess width should be avoided in H1 and H2 headings to prevent the Table of Contents from getting too wide.

View File

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

View File

@@ -142,8 +142,13 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
}
```
Then please open an issue on Github with this information and tell what OS was not detected correctly and if you have any intermediate devices between keyboard and your computer.
Add both `STORE_SETUPS` and `PRINT_SETUPS` to your keyboard's keymap. Connect the keyboard to the device where the OS was not recognised, and press the `STORE_SETUPS` key to capture and store the fingerprint. On your development computer, run one of the suggested [console debugging tools](/faq_debug#debugging-tools), connect the keyboard, and press the `PRINT_SETUPS` key. The console should display multiple lines of data from the most recent `STORE_SETUPS` run.
Open an issue on GitHub and paste the console output into the issue. Also tell us which OS (including the version, if possible) was not detected correctly and whether any intermediate devices, such as a USB hub, were used between the keyboard and the target device.
::: tip
If `STORE_SETUPS` has not been used previously, `PRINT_SETUPS` will report whatever values are already present in the controller's EEPROM. These may appear as random numbers.
:::
## Credits

View File

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

View File

@@ -33,11 +33,13 @@ If you own a board from one of the following vendors already, consider asking th
| iLovBee | Official 30-day copyright source code request issued Sep 11 2024 due to deception on PR, no response received. Ambiguity on PRs -- marketing says wireless, PR author said wired-only, then included wireless code anyway. Seemingly intentionally deceptive. |
| KiiBOOM | Seems to use the same OEM as Epomaker, same problems. |
| kprepublic | Makes no attempt to release source code, all boards in QMK are reverse-engineered, created, and supported by the community. New board variants magically appear without telling customers they're incompatible with existing QMK versions, in some cases bricking boards or requiring ISP flashing. |
| Lofree | Selling tri-mode boards based on QMK without sources, just `via.json` provided. |
| Luminkey | Selling tri-mode boards based on QMK without sources, just `via.json` provided. |
| Meletrix | Selling tri-mode boards based on QMK without sources, just `via.json` provided. |
| mmd / Smartmmd / i-game.tech | Ambiguity on PRs -- marketing says wireless, PR author said wired-only, then included wireless code anyway. Seemingly intentionally deceptive. |
| MyKeyClub | Community-supported JRIS75, vendor was contacted by community members and refused to cooperate. |
| owlab | Selling wired based on QMK without sources, just `via.json` provided. Ambiguous as to whether or not wireless firmware is based on QMK, given that their configuration tool looks very similar to VIA. |
| PMO Lab | Selling tri-mode boards based on QMK without sources, just `via.json` provided. |
| pressplayid | Selling wired and tri-mode boards based on QMK without sources, just `via.json` provided |
| qwertykeys | Selling wired and tri-mode boards based on QMK without sources, just `via.json` provided. |
| Redragon | Selling tri-mode boards based on QMK without sources, attempted upstreaming crippled firmware without wireless. |

View File

@@ -2,65 +2,68 @@
// A simple ringbuffer holding Size elements of type T
template <typename T, uint8_t Size>
class RingBuffer {
protected:
T buf_[Size];
uint8_t head_{0}, tail_{0};
public:
inline uint8_t nextPosition(uint8_t position) {
return (position + 1) % Size;
}
protected:
T buf_[Size];
uint8_t head_{0}, tail_{0};
inline uint8_t prevPosition(uint8_t position) {
if (position == 0) {
return Size - 1;
}
return position - 1;
}
inline bool enqueue(const T &item) {
static_assert(Size > 1, "RingBuffer size must be > 1");
uint8_t next = nextPosition(head_);
if (next == tail_) {
// Full
return false;
public:
inline uint8_t nextPosition(uint8_t position) {
return (position + 1) % Size;
}
buf_[head_] = item;
head_ = next;
return true;
}
inline bool get(T &dest, bool commit = true) {
auto tail = tail_;
if (tail == head_) {
// No more data
return false;
inline uint8_t prevPosition(uint8_t position) {
if (position == 0) {
return Size - 1;
}
return position - 1;
}
dest = buf_[tail];
tail = nextPosition(tail);
inline bool enqueue(const T &item) {
static_assert(Size > 1, "RingBuffer size must be > 1");
uint8_t next = nextPosition(head_);
if (next == tail_) {
// Full
return false;
}
if (commit) {
tail_ = tail;
buf_[head_] = item;
head_ = next;
return true;
}
return true;
}
inline bool empty() const { return head_ == tail_; }
inline bool get(T &dest, bool commit = true) {
auto tail = tail_;
if (tail == head_) {
// No more data
return false;
}
inline uint8_t size() const {
int diff = head_ - tail_;
if (diff >= 0) {
return diff;
dest = buf_[tail];
tail = nextPosition(tail);
if (commit) {
tail_ = tail;
}
return true;
}
return Size + diff;
}
inline T& front() {
return buf_[tail_];
}
inline bool empty() const {
return head_ == tail_;
}
inline bool peek(T &item) {
return get(item, false);
}
inline uint8_t size() const {
int diff = head_ - tail_;
if (diff >= 0) {
return diff;
}
return Size + diff;
}
inline T &front() {
return buf_[tail_];
}
inline bool peek(T &item) {
return get(item, false);
}
};

View File

@@ -23,7 +23,7 @@
#include "gpio.h"
#include "pointing_device_internal.h"
const pointing_device_driver_t pmw3320_pointing_device_drivera = {
const pointing_device_driver_t pmw3320_pointing_device_driver = {
.init = pmw3320_init,
.get_report = pmw3320_get_report,
.set_cpi = pmw3320_set_cpi,

View File

@@ -0,0 +1,21 @@
{
"manufacturer": "Keebio",
"url": "https://keeb.io",
"maintainer": "nooges",
"usb": {
"vid": "0xCB10"
},
"features": {
"bootmagic": true,
"console": true,
"extrakey": true,
"mousekey": true,
"nkro": false
},
"split": {
"enabled": true
},
"build": {
"lto": true
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2026 Keebio (@keebio)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT(
KC_MUTE, RM_NEXT, KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_VOLD, KC_VOLU,
KC_F1, KC_F2, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_DEL,
KC_F3, KC_F4, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_HOME,
KC_F5, KC_F6, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_PGUP,
KC_F7, KC_F8, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_PGDN,
KC_F9, KC_F10, MO(1), KC_LCTL, KC_LALT, KC_LGUI, KC_SPC, KC_SPC, MO(1), KC_RALT, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
),
[1] = LAYOUT(
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
RM_HUEU, RM_HUED, KC_GRV, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, _______,
RM_SATU, RM_SATD, RM_TOGG, RM_NEXT, KC_UP, _______, QK_BOOT, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_END,
RM_VALU, RM_VALD, _______, KC_LEFT, KC_DOWN, KC_RGHT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
RM_SPDU, RM_SPDD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
)
};

View File

@@ -0,0 +1,27 @@
# Sinc LM
A split 75%/TKL staggered low-profile keyboard made and sold by Keebio. [More info at Keebio](https://keeb.io).
* Keyboard Maintainer: [Bakingpy/nooges](https://github.com/nooges)
* Hardware Availability: [Keebio](https://keeb.io/)
Make example for this keyboard (after setting up your build environment):
make keebio/sinc_lm/rev1:default
Example of flashing this keyboard:
make keebio/sinc_lm/rev1:default:flash
Handedness detection is already hardwired onto the PCB, so no need to deal with `EE_HANDS` or flashing .eep files.
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).
## Bootloader
Enter the bootloader in one of these ways:
* **Physical reset button**: Press and hold the button on the back of the PCB for at least 2 seconds and let go, or double-press the button
* **Keycode in layout**: Press the key mapped to `QK_BOOT` if it is available
A build guide for this keyboard can be found here: [Keebio Build Guides](https://docs.keeb.io)

View File

@@ -0,0 +1,23 @@
// Copyright 2026 Keebio (@keebio)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/* Defines for the split keyboard setup */
#define SERIAL_USART_DRIVER SD3 // USART 3
#define SERIAL_USART_TX_PIN B10
#define SERIAL_USART_RX_PIN B11
#define SERIAL_USART_TX_PAL_MODE 7
#define SERIAL_USART_RX_PAL_MODE 7
#define SERIAL_USART_FULL_DUPLEX
#define SERIAL_USART_PIN_SWAP
#define USB_VBUS_PIN C6
/* Defines for the RGB matrix */
#define WS2812_PWM_DRIVER PWMD3
#define WS2812_PWM_CHANNEL 4
#define WS2812_PWM_PAL_MODE 10
#define WS2812_DMA_STREAM STM32_DMA1_STREAM2
#define WS2812_DMA_CHANNEL 2
#define WS2812_DMAMUX_ID STM32_DMAMUX1_TIM3_UP

View File

@@ -0,0 +1,10 @@
// Copyright 2026 Keebio (@keebio)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#define HAL_USE_SERIAL TRUE
#define HAL_USE_PWM TRUE
#include_next <halconf.h>

View File

@@ -0,0 +1,358 @@
{
"manufacturer": "Keebio",
"keyboard_name": "Sinc LM Rev. 1",
"maintainer": "nooges",
"bootloader": "stm32-dfu",
"bootmagic": {
"matrix": [0, 2]
},
"diode_direction": "COL2ROW",
"features": {
"bootmagic": true,
"extrakey": true,
"mousekey": true,
"rgb_matrix": true
},
"matrix_pins": {
"cols": ["B13", "B14", "B2", "F0", "C10", "B6", "C13", "C14", "C15"],
"rows": ["B4", "C11", "B12", "B5", "F1", "B1"]
},
"processor": "STM32G431",
"rgb_matrix": {
"animations": {
"alphas_mods": true,
"gradient_up_down": true,
"gradient_left_right": true,
"breathing": true,
"band_sat": true,
"band_val": true,
"band_pinwheel_sat": true,
"band_pinwheel_val": true,
"band_spiral_sat": true,
"band_spiral_val": true,
"cycle_all": true,
"cycle_left_right": true,
"cycle_up_down": true,
"cycle_out_in": true,
"cycle_out_in_dual": true,
"rainbow_moving_chevron": true,
"cycle_pinwheel": true,
"cycle_spiral": true,
"dual_beacon": true,
"rainbow_beacon": true,
"rainbow_pinwheels": true,
"flower_blooming": true,
"raindrops": true,
"jellybean_raindrops": true,
"hue_breathing": true,
"hue_pendulum": true,
"hue_wave": true,
"pixel_fractal": true,
"pixel_flow": true,
"pixel_rain": true,
"typing_heatmap": true,
"digital_rain": true,
"solid_reactive_simple": true,
"solid_reactive": true,
"solid_reactive_wide": true,
"solid_reactive_multiwide": true,
"solid_reactive_cross": true,
"solid_reactive_multicross": true,
"solid_reactive_nexus": true,
"solid_reactive_multinexus": true,
"splash": true,
"multisplash": true,
"solid_splash": true,
"solid_multisplash": true,
"starlight": true,
"starlight_smooth": true,
"starlight_dual_hue": true,
"starlight_dual_sat": true,
"riverflow": true
},
"driver": "ws2812",
"layout": [
{ "matrix": [5, 2], "x": 30, "y": 4, "flags": 4 },
{ "x": 32, "y": 10, "flags": 2 },
{ "matrix": [5, 3], "x": 45, "y": 4, "flags": 4 },
{ "matrix": [5, 4], "x": 58, "y": 4, "flags": 4 },
{ "matrix": [5, 5], "x": 70, "y": 4, "flags": 4 },
{ "x": 76, "y": 2, "flags": 2 },
{ "matrix": [5, 6], "x": 82, "y": 4, "flags": 4 },
{ "matrix": [5, 7], "x": 97, "y": 4, "flags": 4 },
{ "matrix": [5, 8], "x": 109, "y": 4, "flags": 4 },
{ "x": 103, "y": 10, "flags": 2 },
{ "matrix": [0, 8], "x": 103, "y": 18, "flags": 4 },
{ "matrix": [0, 7], "x": 91, "y": 18, "flags": 4 },
{ "matrix": [0, 6], "x": 79, "y": 18, "flags": 4 },
{ "matrix": [0, 5], "x": 67, "y": 18, "flags": 4 },
{ "matrix": [0, 4], "x": 54, "y": 18, "flags": 4 },
{ "matrix": [0, 3], "x": 42, "y": 18, "flags": 4 },
{ "matrix": [0, 2], "x": 30, "y": 18, "flags": 4 },
{ "matrix": [1, 2], "x": 33, "y": 30, "flags": 4 },
{ "matrix": [1, 3], "x": 48, "y": 30, "flags": 4 },
{ "matrix": [1, 4], "x": 61, "y": 30, "flags": 4 },
{ "matrix": [1, 5], "x": 73, "y": 30, "flags": 4 },
{ "matrix": [1, 6], "x": 85, "y": 30, "flags": 4 },
{ "matrix": [1, 7], "x": 97, "y": 30, "flags": 4 },
{ "matrix": [2, 7], "x": 100, "y": 41, "flags": 4 },
{ "matrix": [2, 6], "x": 88, "y": 41, "flags": 4 },
{ "matrix": [2, 5], "x": 76, "y": 41, "flags": 4 },
{ "matrix": [2, 4], "x": 64, "y": 41, "flags": 4 },
{ "matrix": [2, 3], "x": 51, "y": 41, "flags": 4 },
{ "matrix": [2, 2], "x": 35, "y": 41, "flags": 4 },
{ "matrix": [3, 2], "x": 38, "y": 53, "flags": 4 },
{ "matrix": [3, 4], "x": 58, "y": 53, "flags": 4 },
{ "matrix": [3, 5], "x": 70, "y": 53, "flags": 4 },
{ "matrix": [3, 6], "x": 82, "y": 53, "flags": 4 },
{ "matrix": [3, 7], "x": 94, "y": 53, "flags": 4 },
{ "x": 100, "y": 46, "flags": 2 },
{ "matrix": [3, 8], "x": 106, "y": 53, "flags": 4 },
{ "matrix": [4, 7], "x": 98, "y": 64, "flags": 4 },
{ "x": 85, "y": 64, "flags": 2 },
{ "matrix": [4, 5], "x": 77, "y": 64, "flags": 4 },
{ "matrix": [4, 4], "x": 62, "y": 64, "flags": 4 },
{ "matrix": [4, 3], "x": 47, "y": 64, "flags": 4 },
{ "x": 39, "y": 60, "flags": 4 },
{ "matrix": [4, 2], "x": 32, "y": 64, "flags": 4 },
{ "matrix": [4, 1], "x": 12, "y": 64, "flags": 4 },
{ "x": 6, "y": 60, "flags": 2 },
{ "matrix": [4, 0], "x": 0, "y": 64, "flags": 4 },
{ "matrix": [3, 0], "x": 0, "y": 53, "flags": 4 },
{ "matrix": [3, 1], "x": 12, "y": 53, "flags": 4 },
{ "matrix": [2, 1], "x": 12, "y": 41, "flags": 4 },
{ "matrix": [2, 0], "x": 0, "y": 41, "flags": 4 },
{ "matrix": [1, 0], "x": 0, "y": 30, "flags": 4 },
{ "matrix": [1, 1], "x": 12, "y": 30, "flags": 4 },
{ "matrix": [0, 1], "x": 12, "y": 18, "flags": 4 },
{ "matrix": [0, 0], "x": 0, "y": 18, "flags": 4 },
{ "matrix": [5, 0], "x": 0, "y": 4, "flags": 4 },
{ "x": 6, "y": 2, "flags": 2 },
{ "matrix": [5, 1], "x": 12, "y": 4, "flags": 4 },
{ "x": 224, "y": 10, "flags": 2 },
{ "matrix": [11, 8], "x": 224, "y": 4, "flags": 4 },
{ "matrix": [11, 7], "x": 212, "y": 4, "flags": 4 },
{ "matrix": [11, 6], "x": 197, "y": 4, "flags": 4 },
{ "x": 191, "y": 10, "flags": 2 },
{ "matrix": [11, 5], "x": 185, "y": 4, "flags": 4 },
{ "matrix": [11, 4], "x": 173, "y": 4, "flags": 4 },
{ "x": 166, "y": 10, "flags": 2 },
{ "matrix": [11, 3], "x": 160, "y": 4, "flags": 4 },
{ "matrix": [11, 2], "x": 145, "y": 4, "flags": 4 },
{ "x": 139, "y": 10, "flags": 2 },
{ "matrix": [11, 1], "x": 133, "y": 4, "flags": 4 },
{ "matrix": [6, 0], "x": 127, "y": 18, "flags": 4 },
{ "matrix": [6, 1], "x": 139, "y": 18, "flags": 4 },
{ "matrix": [6, 2], "x": 151, "y": 18, "flags": 4 },
{ "matrix": [6, 3], "x": 163, "y": 18, "flags": 4 },
{ "matrix": [6, 4], "x": 176, "y": 18, "flags": 4 },
{ "matrix": [6, 5], "x": 188, "y": 18, "flags": 4 },
{ "matrix": [6, 7], "x": 206, "y": 18, "flags": 4 },
{ "matrix": [6, 8], "x": 224, "y": 18, "flags": 4 },
{ "matrix": [7, 8], "x": 224, "y": 30, "flags": 4 },
{ "matrix": [7, 7], "x": 209, "y": 30, "flags": 4 },
{ "matrix": [7, 6], "x": 194, "y": 30, "flags": 4 },
{ "matrix": [7, 5], "x": 182, "y": 30, "flags": 4 },
{ "matrix": [7, 4], "x": 170, "y": 30, "flags": 4 },
{ "matrix": [7, 3], "x": 157, "y": 30, "flags": 4 },
{ "matrix": [7, 2], "x": 145, "y": 30, "flags": 4 },
{ "matrix": [7, 1], "x": 133, "y": 30, "flags": 4 },
{ "matrix": [7, 0], "x": 121, "y": 30, "flags": 4 },
{ "matrix": [8, 0], "x": 124, "y": 41, "flags": 4 },
{ "matrix": [8, 1], "x": 136, "y": 41, "flags": 4 },
{ "matrix": [8, 2], "x": 148, "y": 41, "flags": 4 },
{ "matrix": [8, 3], "x": 160, "y": 41, "flags": 4 },
{ "matrix": [8, 4], "x": 173, "y": 41, "flags": 4 },
{ "matrix": [8, 5], "x": 185, "y": 41, "flags": 4 },
{ "matrix": [8, 7], "x": 204, "y": 41, "flags": 4 },
{ "matrix": [8, 8], "x": 224, "y": 41, "flags": 4 },
{ "matrix": [9, 8], "x": 224, "y": 53, "flags": 4 },
{ "matrix": [9, 7], "x": 212, "y": 53, "flags": 4 },
{ "matrix": [9, 6], "x": 195, "y": 53, "flags": 4 },
{ "matrix": [9, 4], "x": 179, "y": 53, "flags": 4 },
{ "matrix": [9, 3], "x": 166, "y": 53, "flags": 4 },
{ "matrix": [9, 2], "x": 154, "y": 53, "flags": 4 },
{ "matrix": [9, 1], "x": 142, "y": 53, "flags": 4 },
{ "matrix": [9, 0], "x": 130, "y": 53, "flags": 4 },
{ "x": 127, "y": 64, "flags": 2 },
{ "matrix": [10, 1], "x": 141, "y": 64, "flags": 4 },
{ "x": 157, "y": 64, "flags": 2 },
{ "matrix": [10, 2], "x": 163, "y": 64, "flags": 4 },
{ "matrix": [10, 3], "x": 176, "y": 64, "flags": 4 },
{ "matrix": [10, 4], "x": 188, "y": 64, "flags": 4 },
{ "x": 194, "y": 64, "flags": 2 },
{ "matrix": [10, 6], "x": 200, "y": 64, "flags": 4 },
{ "matrix": [10, 7], "x": 212, "y": 64, "flags": 4 },
{ "x": 218, "y": 64, "flags": 2 },
{ "matrix": [10, 8], "x": 224, "y": 64, "flags": 4 }
],
"max_brightness": 120,
"sleep": true,
"split_count": [57, 56]
},
"split": {
"bootmagic": {
"matrix": [11, 8]
},
"enabled": true,
"handedness": {
"pin": "A0"
},
"matrix_pins": {
"right": {
"cols": ["B2", "A15", "A2", "A1", "A4", "B15", "B6", "C13", "C14"],
"rows": ["B12", "B13", "B14", "B5", "F1", "B1"]
}
},
"serial": {
"driver": "usart"
},
"transport": {
"sync": {
"matrix_state": true
}
}
},
"url": "https://keeb.io",
"usb": {
"device_version": "1.0.0",
"pid": "0x1667",
"vid": "0xCB10"
},
"ws2812": {
"driver": "pwm",
"pin": "B7"
},
"layouts": {
"LAYOUT": {
"layout": [
{"label": "Mute", "matrix": [5, 0], "x": 0, "y": 0},
{"label": "Play", "matrix": [5, 1], "x": 1, "y": 0},
{"label": "Esc", "matrix": [5, 2], "x": 2.25, "y": 0},
{"label": "F1", "matrix": [5, 3], "x": 3.5, "y": 0},
{"label": "F2", "matrix": [5, 4], "x": 4.5, "y": 0},
{"label": "F3", "matrix": [5, 5], "x": 5.5, "y": 0},
{"label": "F4", "matrix": [5, 6], "x": 6.5, "y": 0},
{"label": "F5", "matrix": [5, 7], "x": 7.75, "y": 0},
{"label": "F6", "matrix": [5, 8], "x": 8.75, "y": 0},
{"label": "F7", "matrix": [11, 1], "x": 10.75, "y": 0},
{"label": "F8", "matrix": [11, 2], "x": 11.75, "y": 0},
{"label": "F9", "matrix": [11, 3], "x": 13, "y": 0},
{"label": "F10", "matrix": [11, 4], "x": 14, "y": 0},
{"label": "F11", "matrix": [11, 5], "x": 15, "y": 0},
{"label": "F12", "matrix": [11, 6], "x": 16, "y": 0},
{"label": "Volume Up", "matrix": [11, 7], "x": 17.25, "y": 0},
{"label": "Volume Down", "matrix": [11, 8], "x": 18.25, "y": 0},
{"label": "F1", "matrix": [0, 0], "x": 0, "y": 1.25},
{"label": "F2", "matrix": [0, 1], "x": 1, "y": 1.25},
{"label": "Grave", "matrix": [0, 2], "x": 2.25, "y": 1.25},
{"label": "1", "matrix": [0, 3], "x": 3.25, "y": 1.25},
{"label": "2", "matrix": [0, 4], "x": 4.25, "y": 1.25},
{"label": "3", "matrix": [0, 5], "x": 5.25, "y": 1.25},
{"label": "4", "matrix": [0, 6], "x": 6.25, "y": 1.25},
{"label": "5", "matrix": [0, 7], "x": 7.25, "y": 1.25},
{"label": "6", "matrix": [0, 8], "x": 8.25, "y": 1.25},
{"label": "7", "matrix": [6, 0], "x": 10.25, "y": 1.25},
{"label": "8", "matrix": [6, 1], "x": 11.25, "y": 1.25},
{"label": "9", "matrix": [6, 2], "x": 12.25, "y": 1.25},
{"label": "0", "matrix": [6, 3], "x": 13.25, "y": 1.25},
{"label": "-", "matrix": [6, 4], "x": 14.25, "y": 1.25},
{"label": "=", "matrix": [6, 5], "x": 15.25, "y": 1.25},
{"label": "Bksp", "matrix": [6, 7], "x": 16.25, "y": 1.25, "w": 2},
{"label": "Del", "matrix": [6, 8], "x": 18.25, "y": 1.25},
{"label": "F3", "matrix": [1, 0], "x": 0, "y": 2.25},
{"label": "F4", "matrix": [1, 1], "x": 1, "y": 2.25},
{"label": "Tab", "matrix": [1, 2], "x": 2.25, "y": 2.25, "w": 1.5},
{"label": "Q", "matrix": [1, 3], "x": 3.75, "y": 2.25},
{"label": "W", "matrix": [1, 4], "x": 4.75, "y": 2.25},
{"label": "E", "matrix": [1, 5], "x": 5.75, "y": 2.25},
{"label": "R", "matrix": [1, 6], "x": 6.75, "y": 2.25},
{"label": "T", "matrix": [1, 7], "x": 7.75, "y": 2.25},
{"label": "Y", "matrix": [7, 0], "x": 9.75, "y": 2.25},
{"label": "U", "matrix": [7, 1], "x": 10.75, "y": 2.25},
{"label": "I", "matrix": [7, 2], "x": 11.75, "y": 2.25},
{"label": "O", "matrix": [7, 3], "x": 12.75, "y": 2.25},
{"label": "P", "matrix": [7, 4], "x": 13.75, "y": 2.25},
{"label": "{", "matrix": [7, 5], "x": 14.75, "y": 2.25},
{"label": "}", "matrix": [7, 6], "x": 15.75, "y": 2.25},
{"label": "|", "matrix": [7, 7], "x": 16.75, "y": 2.25, "w": 1.5},
{"label": "Home", "matrix": [7, 8], "x": 18.25, "y": 2.25},
{"label": "F5", "matrix": [2, 0], "x": 0, "y": 3.25},
{"label": "F6", "matrix": [2, 1], "x": 1, "y": 3.25},
{"label": "Caps Lock", "matrix": [2, 2], "x": 2.25, "y": 3.25, "w": 1.75},
{"label": "A", "matrix": [2, 3], "x": 4, "y": 3.25},
{"label": "S", "matrix": [2, 4], "x": 5, "y": 3.25},
{"label": "D", "matrix": [2, 5], "x": 6, "y": 3.25},
{"label": "F", "matrix": [2, 6], "x": 7, "y": 3.25},
{"label": "G", "matrix": [2, 7], "x": 8, "y": 3.25},
{"label": "H", "matrix": [8, 0], "x": 10, "y": 3.25},
{"label": "J", "matrix": [8, 1], "x": 11, "y": 3.25},
{"label": "K", "matrix": [8, 2], "x": 12, "y": 3.25},
{"label": "L", "matrix": [8, 3], "x": 13, "y": 3.25},
{"label": ":", "matrix": [8, 4], "x": 14, "y": 3.25},
{"label": "\"", "matrix": [8, 5], "x": 15, "y": 3.25},
{"label": "Enter", "matrix": [8, 7], "x": 16, "y": 3.25, "w": 2.25},
{"label": "PgUp", "matrix": [8, 8], "x": 18.25, "y": 3.25},
{"label": "F7", "matrix": [3, 0], "x": 0, "y": 4.25},
{"label": "F8", "matrix": [3, 1], "x": 1, "y": 4.25},
{"label": "Shift", "matrix": [3, 2], "x": 2.25, "y": 4.25, "w": 2.25},
{"label": "Z", "matrix": [3, 4], "x": 4.5, "y": 4.25},
{"label": "X", "matrix": [3, 5], "x": 5.5, "y": 4.25},
{"label": "C", "matrix": [3, 6], "x": 6.5, "y": 4.25},
{"label": "V", "matrix": [3, 7], "x": 7.5, "y": 4.25},
{"label": "B", "matrix": [3, 8], "x": 8.5, "y": 4.25},
{"label": "N", "matrix": [9, 0], "x": 10.5, "y": 4.25},
{"label": "M", "matrix": [9, 1], "x": 11.5, "y": 4.25},
{"label": ",", "matrix": [9, 2], "x": 12.5, "y": 4.25},
{"label": ".", "matrix": [9, 3], "x": 13.5, "y": 4.25},
{"label": "/", "matrix": [9, 4], "x": 14.5, "y": 4.25},
{"label": "Shift", "matrix": [9, 6], "x": 15.5, "y": 4.25, "w": 1.75},
{"label": "Up", "matrix": [9, 7], "x": 17.25, "y": 4.25},
{"label": "PgDn", "matrix": [9, 8], "x": 18.25, "y": 4.25},
{"label": "F9", "matrix": [4, 0], "x": 0, "y": 5.25},
{"label": "F10", "matrix": [4, 1], "x": 1, "y": 5.25},
{"label": "Fn", "matrix": [4, 2], "x": 2.25, "y": 5.25, "w": 1.25},
{"label": "Ctrl", "matrix": [4, 3], "x": 3.5, "y": 5.25, "w": 1.25},
{"label": "Alt", "matrix": [4, 4], "x": 4.75, "y": 5.25, "w": 1.25},
{"label": "Win", "matrix": [4, 5], "x": 6, "y": 5.25, "w": 1.25},
{"label": "Space", "matrix": [4, 7], "x": 7.25, "y": 5.25, "w": 2.25},
{"label": "Space", "matrix": [10, 1], "x": 10.5, "y": 5.25, "w": 2.75},
{"label": "Fn", "matrix": [10, 2], "x": 13.25, "y": 5.25},
{"label": "Alt", "matrix": [10, 3], "x": 14.25, "y": 5.25},
{"label": "Ctrl", "matrix": [10, 4], "x": 15.25, "y": 5.25},
{"label": "Left", "matrix": [10, 6], "x": 16.25, "y": 5.25},
{"label": "Down", "matrix": [10, 7], "x": 17.25, "y": 5.25},
{"label": "Right", "matrix": [10, 8], "x": 18.25, "y": 5.25}
]
}
}
}

View File

@@ -0,0 +1,14 @@
// Copyright 2026 Keebio (@keebio)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include_next <mcuconf.h>
/* enable USART3, used for split comms */
#undef STM32_SERIAL_USE_USART3
#define STM32_SERIAL_USE_USART3 TRUE
/* enable TIM3, used for RGB LED PWM driver */
#undef STM32_PWM_USE_TIM3
#define STM32_PWM_USE_TIM3 TRUE

View File

@@ -0,0 +1,11 @@
// Copyright 2026 Keebio (@keebio)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "quantum.h"
void keyboard_pre_init_kb(void) {
// Disable the PD peripheral in pre-init because its pins are being used in the matrix:
PWR->CR3 |= PWR_CR3_UCPD_DBDIS;
// Call the corresponding _user() function (see https://docs.qmk.fm/#/custom_quantum_functions)
keyboard_pre_init_user();
}

View File

@@ -18,3 +18,6 @@
/* Enable caps-lock LED */
#define CAPS_LOCK_LED_INDEX 54
/* Enable num-lock LED */
#define NUM_LOCK_LED_INDEX 33

View File

@@ -21,3 +21,6 @@
/* Enable caps-lock LED */
#define CAPS_LOCK_LED_INDEX 52
/* Enable num-lock LED */
#define NUM_LOCK_LED_INDEX 31

View File

@@ -18,3 +18,6 @@
/* Enable caps-lock LED */
#define CAPS_LOCK_LED_INDEX 53
/* Enable num-lock LED */
#define NUM_LOCK_LED_INDEX 33

View File

@@ -21,3 +21,6 @@
/* Enable caps-lock LED */
#define CAPS_LOCK_LED_INDEX 51
/* Enable num-lock LED */
#define NUM_LOCK_LED_INDEX 31

View File

@@ -30,7 +30,7 @@ bool dip_switch_update_kb(uint8_t index, bool active) {
#endif // DIP_SWITCH_ENABLE
#if defined(RGB_MATRIX_ENABLE) && defined(CAPS_LOCK_LED_INDEX)
#if defined(RGB_MATRIX_ENABLE) && (defined(CAPS_LOCK_LED_INDEX) || defined(NUM_LOCK_LED_INDEX))
bool process_record_kb(uint16_t keycode, keyrecord_t *record) {
if (!process_record_user(keycode, record)) { return false; }
@@ -69,6 +69,15 @@ bool rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max) {
RGB_MATRIX_INDICATOR_SET_COLOR(CAPS_LOCK_LED_INDEX, 0, 0, 0);
}
}
#ifdef NUM_LOCK_LED_INDEX
if (host_keyboard_led_state().num_lock) {
RGB_MATRIX_INDICATOR_SET_COLOR(NUM_LOCK_LED_INDEX, 255, 255, 255);
} else {
if (!rgb_matrix_get_flags()) {
RGB_MATRIX_INDICATOR_SET_COLOR(NUM_LOCK_LED_INDEX, 0, 0, 0);
}
}
#endif
return true;
}

View File

@@ -0,0 +1,28 @@
{
"manufacturer": "Sebastian Morgenstern",
"keyboard_name": "pttbutton",
"maintainer": "morningstar1",
"bootloader": "stm32-dfu",
"diode_direction": "COL2ROW",
"features": {
"extrakey": true
},
"matrix_pins": {
"cols": ["B0"],
"rows": ["A0"]
},
"processor": "STM32F042",
"url": "https://github.com/morningstar1/pttbutton",
"usb": {
"device_version": "1.0.0",
"vid": "0x736D",
"pid": "0xAFFE"
},
"layouts": {
"LAYOUT": {
"layout": [
{"matrix": [0, 0], "x": 0, "y": 0}
]
}
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2026 SEbastian Morgenstern
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT(
KC_F14
)
};

View File

@@ -0,0 +1,42 @@
/* Copyright 2026 Sebastian Morgenstern
*
* 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/>.
*/
#include "quantum.h"
#include "hal.h"
void board_init(void) {
// Remap PA11->PA9 and PA12->PA10 for USB
SYSCFG->CFGR1 |= SYSCFG_CFGR1_PA11_PA12_RMP;
}
void keyboard_pre_init_kb(void) {
// Immediately set the LED pin as an output and set it ON
gpio_set_pin_output(A15);
gpio_write_pin_high(A15);
keyboard_pre_init_user();
}
void keyboard_post_init_kb(void) {
// Blink the LED so we know everything is running OK
// Finish with LED OFF
gpio_write_pin_low(A15);
wait_ms(100);
gpio_write_pin_high(A15);
wait_ms(100);
gpio_write_pin_low(A15);
keyboard_post_init_user();
}

View File

@@ -0,0 +1,25 @@
# pttbutton
![pttbutton](https://github.com/morningstar1/PTTButton/blob/main/image/PTTButton.png?raw=true)
A simple one button keyboard with a USB Hub. Primary intention is to use it as a Push To Talk (PTT) button.
* Keyboard Maintainer: [Sebastian Morgenstern](https://github.com/morningstar1)
* Hardware Supported: [PTTButton V1.0](https://github.com/morningstar1/PTTButton)
* Hardware Availability: please contact me <sebastian.morgenstern@gmail.com>
Make example for this keyboard (after setting up your build environment):
make morningstar1/pttbutton:default
Flashing example for this keyboard:
make morningstar1/pttbutton:default:flash
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).
## Bootloader
Enter the bootloader in one way:
* **Physical reset button**: Briefly press the small button on the PCB

View File

@@ -45,7 +45,7 @@ def strip_multiline_comment(string):
def c_source_files(dir_names):
"""Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
"""Returns a list of all *.c, *.h, *.cpp, and *.hpp files for a given list of directories
Args:
@@ -54,7 +54,7 @@ def c_source_files(dir_names):
"""
files = []
for dir in dir_names:
files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp'])
files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp', '.hpp'])
return files

View File

@@ -56,6 +56,7 @@ safe_commands = [
subcommands = [
'qmk.cli.ci.validate_aliases',
'qmk.cli.ci.validate_keyboard_targets',
'qmk.cli.bux',
'qmk.cli.c2json',
'qmk.cli.cd',

View File

@@ -0,0 +1,28 @@
"""Validates the list of keyboard targets.
"""
from milc import cli
from pathlib import Path
@cli.subcommand('Validates the list of keyboard targets.', hidden=True)
def ci_validate_keyboard_targets(cli):
errors = set()
for rules_mk in Path('keyboards').glob('**/rules.mk'):
if any({'keymaps', 'common', 'lib'} & set(rules_mk.parts)):
continue
folder = rules_mk.parent
if not any(folder.glob('**/keyboard.json')):
errors.add(folder)
for keymap in Path('keyboards').glob('**/keymaps/'):
folder = keymap.parent
if not any(folder.glob('**/keyboard.json')):
errors.add(folder)
for error in errors:
print(f"{error}::Legacy target detected")
exit(min(len(errors), 255))

View File

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

View File

@@ -19,21 +19,21 @@
#include <ostream>
#include "gmock/gmock.h"
bool operator==(const report_keyboard_t& lhs, const report_keyboard_t& rhs);
bool operator==(const report_keyboard_t& lhs, const report_keyboard_t& rhs);
std::ostream& operator<<(std::ostream& stream, const report_keyboard_t& value);
class KeyboardReportMatcher : public testing::MatcherInterface<report_keyboard_t&> {
public:
public:
KeyboardReportMatcher(const std::vector<uint8_t>& keys);
virtual bool MatchAndExplain(report_keyboard_t& report, testing::MatchResultListener* listener) const override;
virtual void DescribeTo(::std::ostream* os) const override;
virtual void DescribeNegationTo(::std::ostream* os) const override;
private:
private:
report_keyboard_t m_report;
};
template<typename... Ts>
template <typename... Ts>
inline testing::Matcher<report_keyboard_t&> KeyboardReport(Ts... keys) {
return testing::MakeMatcher(new KeyboardReportMatcher(std::vector<uint8_t>({keys...})));
}

View File

@@ -21,7 +21,7 @@
class TestLogger : public std::ostream {
public:
TestLogger() : std::ostream(&m_log){};
TestLogger() : std::ostream(&m_log) {};
TestLogger& info();
TestLogger& trace();
TestLogger& error();

View File

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

126
util/install_udev.sh Executable file
View File

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

View File

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