Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
430 changes: 430 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:

- name: Deploy
if: ${{ github.event_name == 'push' && github.repository == 'qmk/qmk_firmware' }}
uses: JamesIves/github-pages-deploy-action@v4.7.6
uses: JamesIves/github-pages-deploy-action@v4.8.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
Expand Down
3 changes: 3 additions & 0 deletions data/mappings/info_defaults.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"on_state": 1
},
"debounce": 5,
"dynamic_keymap": {
"layer_count": 4
},
"features": {
"command": false,
"console": false
Expand Down
11 changes: 11 additions & 0 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@
}
}
},
"dynamic_keymap": {
"type": "object",
"properties": {
"eeprom_max_addr": {"$ref": "./definitions.jsonschema#/unsigned_int"},
"layer_count": {
"type": "integer",
"minimum": 1,
"maximum": 32
}
}
},
"eeprom": {
"properties": {
"driver": {"type": "string"},
Expand Down
2 changes: 1 addition & 1 deletion docs/drivers/ws2812.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ Set the color of a single LED. This function does not immediately update the LED

---

### `void ws812_set_color_all(uint8_t red, uint8_t green, uint8_t blue)` {#api-ws2812-set-color-all}
### `void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue)` {#api-ws2812-set-color-all}

Set the color of all LEDs.

Expand Down
18 changes: 16 additions & 2 deletions docs/tap_hold.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,20 @@ Or if the two keys are on opposite hands and the `PERMISSIVE_HOLD` option is
enabled, this will produce `C` with `SFT_T(KC_A)` settled as held when that
`KC_C` is released.

As an exception to the opposite hands rule, Chordal Hold supports combining
multiple same-side modifiers within the tapping term. This is useful for
multi-mod hotkeys like Ctrl + Shift + V. For instance with Chordal Hold together
with either Permissive Hold or Hold On Other Key Press, the following input
results in Ctrl + Shift + V being sent, supposing `J` and `K` are on the right
hand side and `V` is on the left hand side:

- `SFT_T(KC_J)` Down
- `CTL_T(KC_K)` Down
- `KC_V` Down
- `KC_V` Up
- `SFT_T(KC_J)` Up
- `CTL_T(KC_K)` Up

### Chordal Hold Handedness

Determining whether keys are on the same or opposite hands involves defining the
Expand Down Expand Up @@ -779,7 +793,7 @@ Do not use `MOD_xxx` constants like `MOD_LSFT` or `MOD_RALT`, since they're 5-bi

[Auto Shift](features/auto_shift) has its own version of `retro tapping` called `retro shift`. It is extremely similar to `retro tapping`, but holding the key past `AUTO_SHIFT_TIMEOUT` results in the value it sends being shifted. Other configurations also affect it differently; see [here](features/auto_shift#retro-shift) for more information.

### Speculative Hold
## Speculative Hold

Speculative Hold makes mod-tap keys more responsive by applying the modifier instantly on keydown, before the tap-hold decision is made. This is especially useful for actions like Shift+Click with a mouse, which can feel laggy with standard mod-taps.

Expand Down Expand Up @@ -820,4 +834,4 @@ Well, it's simple really: customization. But specifically, it depends on how you

## Why are there no `*_kb` or `*_user` functions?!

Unlike many of the other functions here, there isn't a need (or even reason) to have a quantum- or keyboard-level function. Only user-level functions are useful here, so there is no need to mark them as such.
Unlike many of the other functions here, there isn't a need (or even reason) to have a quantum- or keyboard-level function. Only user-level functions are useful here, so there is no need to mark them as such.
214 changes: 214 additions & 0 deletions keyboards/cheapino/cheapino.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#include "matrix.h"
#include "quantum.h"
#include "print.h"

#include QMK_KEYBOARD_H

// This is to keep state between callbacks, when it is 0 the
// initial RGB flash is finished
uint8_t _hue_countdown = 50;

// These are to keep track of user selected color, so we
// can restore it after RGB flash
uint8_t _hue;
uint8_t _saturation;
uint8_t _value;

// Do a little 2.5 seconds display of the different colors
// Use the deferred executor so the LED flash dance does not
// stop us from using the keyboard.
// https://docs.qmk.fm/#/custom_quantum_functions?id=deferred-executor-registration
uint32_t flash_led(uint32_t next_trigger_time, void *cb_arg) {
rgblight_sethsv(_hue_countdown * 5, 230, 70);
_hue_countdown--;
if (_hue_countdown == 0) {
// Finished, reset to user chosen led color
rgblight_sethsv(_hue, _saturation, _value);
return 0;
} else {
return 50;
}
}

void keyboard_post_init_kb(void) {
// debug_enable=true;
// debug_matrix=true;
// debug_keyboard=true;
// debug_mouse=true;

// Store user selected rgb hsv:
_hue = rgblight_get_hue();
_saturation = rgblight_get_sat();
_value = rgblight_get_val();

// Flash a little on start
defer_exec(50, flash_led, NULL);
}

// This is just to be able to declare constants as they appear in the qmk console
#define rev(b) \
((b & 1) << 15) | \
((b & (1 << 1)) << 13) | \
((b & (1 << 2)) << 11) | \
((b & (1 << 3)) << 9) | \
((b & (1 << 4)) << 7) | \
((b & (1 << 5)) << 5) | \
((b & (1 << 6)) << 3) | \
((b & (1 << 7)) << 1) | \
((b & (1 << 8)) >> 1) | \
((b & (1 << 9)) >> 3) | \
((b & (1 << 10)) >> 5) | \
((b & (1 << 11)) >> 7) | \
((b & (1 << 12)) >> 9) | \
((b & (1 << 13)) >> 11) | \
((b & (1 << 14)) >> 13) | \
b >> 15

/* This is for debugging the matrix rows
void printBits(uint16_t n)
{
long i;
for (i = 15; i >= 0; i--) {
if ((n & (1 << i)) != 0) {
printf("1");
}
else {
printf("0");
}
}
printf("\n");
}
*/

Comment on lines +67 to +82
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commented-out debugging function should either be removed if not needed, or properly documented if it's intended to be kept for future debugging purposes. Leaving large blocks of commented code reduces code maintainability.

Suggested change
/* This is for debugging the matrix rows
void printBits(uint16_t n)
{
long i;
for (i = 15; i >= 0; i--) {
if ((n & (1 << i)) != 0) {
printf("1");
}
else {
printf("0");
}
}
printf("\n");
}
*/

Copilot uses AI. Check for mistakes.
bool bit_pattern_set(uint16_t number, uint16_t bitPattern) {
return !(~number & bitPattern);
}

void fix_ghosting_instance(
matrix_row_t current_matrix[],
unsigned short row_num_with_possible_error_cause,
uint16_t possible_error_cause,
unsigned short row_num_with_possible_error,
uint16_t possible_error,
uint16_t error_fix) {
if (bit_pattern_set(current_matrix[row_num_with_possible_error_cause], possible_error_cause)) {
if (bit_pattern_set(current_matrix[row_num_with_possible_error], possible_error)) {
current_matrix[row_num_with_possible_error] = current_matrix[row_num_with_possible_error] ^ error_fix;
}
}
}

void fix_ghosting_column(
matrix_row_t matrix[],
uint16_t possible_error_cause,
uint16_t possible_error,
uint16_t error_fix) {
// First the right side
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}

// Then exactly same procedure on the left side
Comment on lines +106 to +112
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop condition uses the modulo operator to cycle through indices, but the logic is complex and could benefit from clearer documentation explaining why indices (i+1)%3 and (i+2)%3 are used. This would improve code maintainability.

Suggested change
// First the right side
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}
// Then exactly same procedure on the left side
// First the right side.
// For each of the three rows on this side (i = 0..2), check for ghosting
// interactions with the other two rows. The expressions (i+1)%3 and (i+2)%3
// cycle through the two remaining row indices in {0,1,2} without going out
// of bounds, so every pair of rows on the right side is considered.
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}
// Then exactly same procedure on the left side.
// Rows for the left side are stored starting at index 4, so we add 4 to the
// row indices. The bit patterns are shifted by 6 to match the left-half
// column positions, but the modulo math ((i+1)%3, (i+2)%3) still cycles
// within the three left-side rows in the same way as on the right.

Copilot uses AI. Check for mistakes.
for (short i = 0; i<3; i++) {
Comment on lines +107 to +113
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistent spacing in the loop condition. Use consistent spacing around operators for better readability.

Suggested change
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}
// Then exactly same procedure on the left side
for (short i = 0; i<3; i++) {
for (short i = 0; i < 3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}
// Then exactly same procedure on the left side
for (short i = 0; i < 3; i++) {

Copilot uses AI. Check for mistakes.
fix_ghosting_instance(matrix, i+4, possible_error_cause<<6, 4+((i+1)%3), possible_error<<6, error_fix<<6);
fix_ghosting_instance(matrix, i+4, possible_error_cause<<6, 4+((i+2)%3), possible_error<<6, error_fix<<6);
Comment on lines +107 to +115
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistent spacing in the loop condition. Use consistent spacing around operators for better readability.

Suggested change
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i+1)%3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i+2)%3, possible_error, error_fix);
}
// Then exactly same procedure on the left side
for (short i = 0; i<3; i++) {
fix_ghosting_instance(matrix, i+4, possible_error_cause<<6, 4+((i+1)%3), possible_error<<6, error_fix<<6);
fix_ghosting_instance(matrix, i+4, possible_error_cause<<6, 4+((i+2)%3), possible_error<<6, error_fix<<6);
for (short i = 0; i < 3; i++) {
fix_ghosting_instance(matrix, i, possible_error_cause, (i + 1) % 3, possible_error, error_fix);
fix_ghosting_instance(matrix, i, possible_error_cause, (i + 2) % 3, possible_error, error_fix);
}
// Then exactly same procedure on the left side
for (short i = 0; i < 3; i++) {
fix_ghosting_instance(matrix, i + 4, possible_error_cause << 6, 4 + ((i + 1) % 3), possible_error << 6, error_fix << 6);
fix_ghosting_instance(matrix, i + 4, possible_error_cause << 6, 4 + ((i + 2) % 3), possible_error << 6, error_fix << 6);

Copilot uses AI. Check for mistakes.
}
}

// For QWERTY layout, key combo a+s+e also outputs q. This suppresses the q, and other similar ghosts
// These are observed ghosts(following a pattern). TODO: need to fix this for v3
// Might need to add 2 diodes(one in each direction) for every row, to increase voltage drop.
Comment on lines +120 to +121
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references "v3" but it's unclear what version the current code is for. This comment should be updated to clarify which version it applies to, or the TODO should be addressed if this is for v3.

Suggested change
// These are observed ghosts(following a pattern). TODO: need to fix this for v3
// Might need to add 2 diodes(one in each direction) for every row, to increase voltage drop.
// These are observed ghosts (following a pattern). TODO: if a v3 PCB/hardware revision is created,
// re-validate these patterns and update this table as needed. Might need to add 2 diodes
// (one in each direction) for every row, to increase voltage drop.

Copilot uses AI. Check for mistakes.
void fix_ghosting(matrix_row_t matrix[]) {
fix_ghosting_column(matrix,
rev(0B0110000000000000),
rev(0B1010000000000000),
rev(0B0010000000000000));
fix_ghosting_column(matrix,
rev(0B0110000000000000),
rev(0B0101000000000000),
rev(0B0100000000000000));

fix_ghosting_column(matrix,
rev(0B0001100000000000),
rev(0B0010100000000000),
rev(0B0000100000000000));
fix_ghosting_column(matrix,
rev(0B0001100000000000),
rev(0B0001010000000000),
rev(0B0001000000000000));

fix_ghosting_column(matrix,
rev(0B1000010000000000),
rev(0B1000100000000000),
rev(0B1000000000000000));
fix_ghosting_column(matrix,
rev(0B1000010000000000),
rev(0B0100010000000000),
rev(0B0000010000000000));

fix_ghosting_column(matrix,
rev(0B1001000000000000),
rev(0B0101000000000000),
rev(0B0001000000000000));
fix_ghosting_column(matrix,
rev(0B1001000000000000),
rev(0B1010000000000000),
rev(0B1000000000000000));

fix_ghosting_column(matrix,
rev(0B0100100000000000),
rev(0B0100010000000000),
rev(0B0100000000000000));
fix_ghosting_column(matrix,
rev(0B0100100000000000),
rev(0B1000100000000000),
rev(0B0000100000000000));
}

void encoder_driver_task(void) {
// This is intentionally left empty to disable the default encoder driver.
// We inject events manually below.
}

// There aren't enough pins on the RJ45 for dedicated encoder pins. Use matrix
// intersections instead.
void fix_encoder_action(matrix_row_t current_matrix[]) {
static const int ENC_ROW = 3;
static const int ENC_A_COL = 2;
static const int ENC_B_COL = 4;
// The button column is unused here and handled through the keymap instead.
// static const int ENC_BUTTON_COL = 0;
static const matrix_row_t ENC_A_BIT = (1 << ENC_A_COL);
static const matrix_row_t ENC_B_BIT = (1 << ENC_B_COL);

// State machine tracking.
static bool colABPressed = false;

// Check which way the encoder is turned:
matrix_row_t encoder_row = current_matrix[ENC_ROW];
bool colA = encoder_row & ENC_A_BIT;
bool colB = encoder_row & ENC_B_BIT;

extern bool encoder_queue_event(uint8_t, bool);
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The external function declaration should be placed at the top of the file or in a header file rather than inside the function body. This improves code organization and makes the dependency more explicit.

Copilot uses AI. Check for mistakes.
if (colA && colB) {
colABPressed = true;
} else if (colA) {
if (colABPressed) {
// A+B followed by A means clockwise
colABPressed = false;
encoder_queue_event(0, true);
}
} else if (colB) {
if (colABPressed) {
// A+B followed by B means counter-clockwise
colABPressed = false;
encoder_queue_event(0, false);
}
}

// Clear A+B bits, and leave the button bits intact; it will be picked up
// by normal matrix/keymap processing.
static const matrix_row_t ROW_MASK = ~(ENC_A_BIT | ENC_B_BIT);
current_matrix[ENC_ROW] = encoder_row & ROW_MASK;
}
8 changes: 8 additions & 0 deletions keyboards/cheapino/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2023 Thomas Haukland (@tompi)
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

// Force the usage of PIO1 peripheral, by default the WS2812 implementation uses the PIO0 peripheral.
#define WS2812_PIO_USE_PIO1
#define WS2812_BYTE_ORDER WS2812_BYTE_ORDER_RGB
Loading