mirror of
https://github.com/qmk/qmk_firmware.git
synced 2025-09-10 17:15:43 +00:00
Compare commits
26 Commits
73330d9a17
...
065232eab7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065232eab7 | ||
|
|
0351b598f9 | ||
|
|
fc55fcff3d | ||
|
|
c113250c4e | ||
|
|
b4bdf3f1d5 | ||
|
|
633479ced5 | ||
|
|
7b03ff4128 | ||
|
|
a0cf215605 | ||
|
|
63c607db1c | ||
|
|
89f855dfab | ||
|
|
89fda0145c | ||
|
|
959fbb4949 | ||
|
|
70612b9a63 | ||
|
|
e11c07ef80 | ||
|
|
dce571ecb0 | ||
|
|
04c16add83 | ||
|
|
d4e7d4942e | ||
|
|
17f3bf4e6b | ||
|
|
ddca9e86cb | ||
|
|
8d76f7a5e6 | ||
|
|
1f69fe97a0 | ||
|
|
7802fde5a0 | ||
|
|
2dc201be98 | ||
|
|
04e8ccfe42 | ||
|
|
77790a3d22 | ||
|
|
952f532592 |
@@ -6,7 +6,7 @@ This page describes how QMK's data driven JSON configuration system works. It is
|
||||
|
||||
Historically QMK has been configured through a combination of two mechanisms- `rules.mk` and `config.h`. While this worked well when QMK was only a handful of keyboards we've grown to encompass nearly 4000 supported keyboards. That extrapolates out to 6000 configuration files under `keyboards/` alone! The freeform nature of these files and the unique patterns people have used to avoid duplication have made ongoing maintenance a challenge, and a large number of our keyboards follow patterns that are outdated and sometimes harder to understand.
|
||||
|
||||
We have also been working on bringing the power of QMK to people who aren't comformable with a CLI, and other projects such as VIA are working to make using QMK as easy as installing a program. These tools need information about how a keyboard is laid out or what pins and features are available so that users can take full advantage of QMK. We introduced `info.json` as a first step towards this. The QMK API is an effort to combine these 3 sources of information- `config.h`, `rules.mk`, and `info.json`- into a single source of truth that end-user tools can use.
|
||||
We have also been working on bringing the power of QMK to people who aren't comfortable with a CLI, and other projects such as VIA are working to make using QMK as easy as installing a program. These tools need information about how a keyboard is laid out or what pins and features are available so that users can take full advantage of QMK. We introduced `info.json` as a first step towards this. The QMK API is an effort to combine these 3 sources of information- `config.h`, `rules.mk`, and `info.json`- into a single source of truth that end-user tools can use.
|
||||
|
||||
Now we have support for generating `rules.mk` and `config.h` values from `info.json`, allowing us to have a single source of truth. This will allow us to use automated tooling to maintain keyboards saving a lot of time and maintenance work.
|
||||
|
||||
|
||||
@@ -17,6 +17,24 @@ combo_t key_combos[] = {
|
||||
|
||||
This will send "Escape" if you hit the A and B keys, and Ctrl+Z when you hit the C and D keys.
|
||||
|
||||
## Combo timing and triggering
|
||||
|
||||
A combo will trigger if and only if
|
||||
- all of its keys are pressed within its combo term (by default defined as `COMBO_TERM` milliseconds), and
|
||||
- none of the following occurs
|
||||
- One of the combo's keys was released before the final key was pressed
|
||||
- Two events (press and/or release) occur for any one keycode (whether or not it belongs to the combo) between the first and last triggering key presses
|
||||
- The key buffer overflows between the first and last key presses of the combo
|
||||
- A hold, tap, ordering, contiguity, or generic `combo_should_trigger` condition prevents it from firing (see below). By default, this constraints all combos to be *contiguous*. That means that if a key that doesn't belong to the combo is pressed between the first and last triggers, then the combo will not fire. (However, irrelevant key *releases* will not interrupt combos.)
|
||||
- An overlapping combo with higher priority triggers instead
|
||||
|
||||
Combos, and key presses/releases not consumed by combos, will always be released in their event order. The event time of a combo is that of its FIRST trigger key press.
|
||||
|
||||
Each key press will be consumed by at most one combo. That means that when overlapping combos have all trigger keys pressed, we need to decide which one will fire. By default, we prioritize combos as follows:
|
||||
- first, we prioritize combos whose first trigger key was pressed earliest
|
||||
- next, we prioritize longer combos
|
||||
- finally, we prioritize combos with a larger index (i.e., appearing later in the list of combos)
|
||||
|
||||
## Advanced Keycodes Support
|
||||
Advanced keycodes, such as [Mod-Tap](../mod_tap) and [Tap Dance](tap_dance) are also supported together with combos. If you use these advanced keycodes in your keymap, you will need to place the full keycode in the combo definition, e.g.:
|
||||
|
||||
@@ -114,37 +132,42 @@ You can enable, disable and toggle the Combo feature on the fly. This is useful
|
||||
These configuration settings can be set in your `config.h` file.
|
||||
|
||||
### Combo Term
|
||||
By default, the timeout for the Combos to be recognized is set to 50ms. This can be changed if accidental combo misfires are happening or if you're having difficulties pressing keys at the same time. For instance, `#define COMBO_TERM 40` would set the timeout period for combos to 40ms.
|
||||
By default, the timeout for the Combos to be recognized is set to 150ms. This can be changed if accidental combo misfires are happening or if you're having difficulties pressing keys at the same time. For instance, `#define COMBO_TERM 40` would set the timeout period for combos to 40ms. See below for how to define this differently for each combo.
|
||||
|
||||
### Buffer and state sizes
|
||||
If you're using long combos, or you have a lot of overlapping combos, you may run into issues with this, as the buffers may not be large enough to accommodate what you're doing. In this case, you can configure the sizes of the buffers used. Be aware, larger combo sizes and larger buffers will increase memory usage!
|
||||
|
||||
To configure the amount of keys a combo can be composed of, change the following:
|
||||
To configure the maximum amount of keys a combo can be composed of, change the following:
|
||||
|
||||
| Keys | Define to be set |
|
||||
| Keys (`MAX_COMBO_LENGTH`) | Define to be set |
|
||||
|------|-----------------------------------|
|
||||
| 6 | `#define EXTRA_SHORT_COMBOS` |
|
||||
| 4 | `#define EXTRA_SMALL_COMBOS` |
|
||||
| 8 | QMK Default |
|
||||
| 16 | `#define EXTRA_LONG_COMBOS` |
|
||||
| 32 | `#define EXTRA_EXTRA_LONG_COMBOS` |
|
||||
|
||||
Defining `EXTRA_SHORT_COMBOS` combines a combo's internal state into just one byte. This can, in some cases, save some memory. If it doesn't, no point using it. If you do, you also have to make sure you don't define combos with more than 6 keys.
|
||||
|
||||
Processing combos has two buffers, one for the key presses, another for the combos being activated. Use the following options to configure the sizes of these buffers:
|
||||
A larger maximum combo length will cause a (pretty negligible) increase in memory usage. Another cost of longer combos is limiting the maximum number of combos that can be defined. The maximum combo count is `(65536 / MAX_COMBO_LENGTH) - 1`.
|
||||
|
||||
If you have a modest number of combos that aren't too large, you can save additional memory by defining `COMBO_COMPRESSED`. This compresses the internal state of each combo to a single byte. However, this should ONLY be set if you have fewer than `(256/MAX_COMBO_LENGTH) -1` combos, where `MAX_COMBO_LENGTH` is inferred from the flags above.
|
||||
|
||||
|
||||
Processing combos requires two buffers, one for the key presses, another for currently active combos. Use the following options to configure the sizes of these buffers:
|
||||
|
||||
| Define | Default |
|
||||
|-------------------------------------|------------------------------------------------------|
|
||||
| `#define COMBO_KEY_BUFFER_LENGTH 8` | 8 (the key amount `(EXTRA_)EXTRA_LONG_COMBOS` gives) |
|
||||
| `#define COMBO_KEY_BUFFER_LENGTH 8` | `MAX_COMBO_LENGTH` + 4 |
|
||||
| `#define COMBO_BUFFER_LENGTH 4` | 4 |
|
||||
|
||||
If the key buffer overflows, then completed combos might get activated before their hold term expires, and incomplete combos might get inactivated. If the combo buffer overflows, then active combos might be deactivated before all their keys are released. Longer buffers increase memory usage.
|
||||
|
||||
### Modifier Combos
|
||||
If a combo resolves to a Modifier, the window for processing the combo can be extended independently from normal combos. By default, this is disabled but can be enabled with `#define COMBO_MUST_HOLD_MODS`, and the time window can be configured with `#define COMBO_HOLD_TERM 150` (default: `TAPPING_TERM`). With `COMBO_MUST_HOLD_MODS`, you cannot tap the combo any more which makes the combo less prone to misfires.
|
||||
|
||||
### Strict key press order
|
||||
By defining `COMBO_MUST_PRESS_IN_ORDER` combos only activate when the keys are pressed in the same order as they are defined in the key array.
|
||||
|
||||
### Per Combo Timing, Holding, Tapping and Key Press Order
|
||||
For each combo, it is possible to configure the time window it has to pressed in, if it needs to be held down, if it needs to be tapped, or if its keys need to be pressed in order.
|
||||
### Per Combo Timing, Holding, Tapping, Key Press Order, and Contiguity
|
||||
For each combo, it is possible to configure the time window it has to pressed in, if it needs to be held down, if it needs to be tapped, if its keys need to be pressed in order, or if its key presses need to be contiguous.
|
||||
|
||||
For example, tap-only combos are useful if any (or all) of the underlying keys are mod-tap or layer-tap keys. When you tap the combo, you get the combo result. When you press the combo and hold it down, the combo doesn't activate. Instead the keys are processed separately as if the combo wasn't even there.
|
||||
|
||||
@@ -156,6 +179,8 @@ In order to use these features, the following configuration options and function
|
||||
| `COMBO_MUST_HOLD_PER_COMBO` | `bool get_combo_must_hold(uint16_t combo_index, combo_t *combo)` | Controls if a given combo should fire immediately on tap or if it needs to be held. (default: `false`) |
|
||||
| `COMBO_MUST_TAP_PER_COMBO` | `bool get_combo_must_tap(uint16_t combo_index, combo_t *combo)` | Controls if a given combo should fire only if tapped within `COMBO_HOLD_TERM`. (default: `false`) |
|
||||
| `COMBO_MUST_PRESS_IN_ORDER_PER_COMBO` | `bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo)` | Controls if a given combo should fire only if its keys are pressed in order. (default: `true`) |
|
||||
| `COMBO_CONTIGUOUS_PER_COMBO` | `bool is_combo_contiguous(uint16_t index, combo_t *combo, uint16_t keycode, keyrecord_t *record, uint8_t n_unpressed_keys)` | Controls if a partially activated combo should be de-activated by a keypress not included in the combo |
|
||||
| `COMBO_SHOULD_TRIGGER` | `combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record)` |
|
||||
|
||||
Examples:
|
||||
```c
|
||||
@@ -177,7 +202,7 @@ uint16_t get_combo_term(uint16_t combo_index, combo_t *combo) {
|
||||
// i.e. the exact array of keys you defined for the combo.
|
||||
// This can be useful if your combos have a common key and you want to apply the
|
||||
// same combo term for all of them.
|
||||
if (combo->keys[0] == KC_ENT) { // if first key in the array is Enter
|
||||
if (pgm_read_word(&combo->keys[0]) == KC_ENT) { // if first key in the array is Enter
|
||||
return 150;
|
||||
}
|
||||
|
||||
@@ -239,6 +264,32 @@ bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef COMBO_CONTIGUOUS_PER_COMBO
|
||||
bool is_combo_contiguous(uint16_t index, combo_t *combo, uint16_t keycode, keyrecord_t *record, uint8_t n_unpressed_keys) {
|
||||
/* Decide if a key *press* for a key not involved in a combo should interrupt that combo.
|
||||
* A "contiguous" combo requires that all the keys of the combo are pressed together, without any other key presses
|
||||
* occurring in between. A "non-contiguous" combo will still fire even if irrelevant keys are pressed between its triggers.
|
||||
* This function lets us define that behavior on a per-combo basis, and even based on which non-combo key has been pressed
|
||||
*
|
||||
* `index` and `combo` as above
|
||||
* `keycode` and `record` describe a key that has been pressed that DOES NOT belong to this combo
|
||||
* `n_unpressed_keys` is the number of keys of combo we are still waiting to be pressed for the combo to complete
|
||||
*/
|
||||
if (keycode == KC_LCTL) {
|
||||
return false; // left control doesn't interrupt any combo
|
||||
}
|
||||
switch (combo_index) {
|
||||
case MY_INDEPENDENT_COMBO_1:
|
||||
case MY_INDEPENDENT_COMBO_2:
|
||||
// I like to mash these together, so they shouldn't be contiguous
|
||||
return false;
|
||||
default:
|
||||
// Default to requiring that no unrelated key presses interrupt the combo
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
### Generic hook to (dis)allow a combo activation
|
||||
@@ -246,6 +297,9 @@ bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) {
|
||||
By defining `COMBO_SHOULD_TRIGGER` and its companying function `bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record)` you can block or allow combos to activate on the conditions of your choice.
|
||||
For example, you could disallow some combos on the base layer and allow them on another. Or disable combos on the home row when a timer is running.
|
||||
|
||||
This function is called for every keypress for keys included in the combo. It must return true for each of these keypresses in
|
||||
order for the combo to trigger.
|
||||
|
||||
Examples:
|
||||
```c
|
||||
bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
|
||||
@@ -261,24 +315,24 @@ bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode
|
||||
}
|
||||
```
|
||||
|
||||
### Combo timer
|
||||
### Customizable combo prioritization
|
||||
|
||||
Normally, the timer is started on the first key press and then reset on every subsequent key press within the `COMBO_TERM`.
|
||||
Inputting combos is relaxed like this, but also slightly more prone to accidental misfires.
|
||||
If the default prioritization of combos described above doesn't work for you, you can override it by defining the following function:
|
||||
|
||||
The next two options alter the behaviour of the timer.
|
||||
```c
|
||||
/* Return true if combo1 is preferred to combo2 if they could both activate.
|
||||
* Default behavior: prefer longer combos, and break ties by preferring combos with higher indices */
|
||||
bool is_combo_preferred(uint16_t combo_index1, uint16_t combo_index2, uint8_t combo_length1) {
|
||||
uint8_t combo_length2 = _get_combo_length(combo_get(combo_index2));
|
||||
if (combo_length1 > combo_length2) {
|
||||
return true;
|
||||
}
|
||||
return combo_index1 > combo_index2;
|
||||
}
|
||||
```
|
||||
|
||||
#### `#define COMBO_STRICT_TIMER`
|
||||
However, we always prefer combos whose first triggering key is earlier, even if they are shorter. E.g., if I press `A,B,C,D` in order, then combo `A+B` will be preferred to `B+C+D` regardless of how `is_combo_preferred` is implemented.
|
||||
|
||||
With `COMBO_STRICT_TIMER`, the timer is started only on the first key press.
|
||||
Inputting combos is now less relaxed; you need to make sure the full chord is pressed within the `COMBO_TERM`.
|
||||
Misfires are less common but if you type multiple combos fast, there is a
|
||||
chance that the latter ones might not activate properly.
|
||||
|
||||
#### `#define COMBO_NO_TIMER`
|
||||
|
||||
By defining `COMBO_NO_TIMER`, the timer is disabled completely and combos are activated on the first key release.
|
||||
This also disables the "must hold" functionalities as they just wouldn't work at all.
|
||||
|
||||
### Customizable key releases
|
||||
|
||||
@@ -316,6 +370,7 @@ bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Customizable key repress
|
||||
By defining `COMBO_PROCESS_KEY_REPRESS` and implementing `bool process_combo_key_repress(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode)` you can run your custom code when you repress just released key of a combo. By combining it with custom `process_combo_event` we can for example make special handling for Alt+Tab to switch windows, which, on combo F+G activation, registers Alt and presses Tab - then we can switch windows forward by releasing G and pressing it again, or backwards with F key. Here's the full example:
|
||||
|
||||
|
||||
33
keyboards/haverworks/theseus75/config.h
Normal file
33
keyboards/haverworks/theseus75/config.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Defines for configuring the serial driver for split comms (see https://docs.qmk.fm/drivers/serial) */
|
||||
#define SERIAL_USART_DRIVER SD3 // USART 3
|
||||
|
||||
/* Defines for required pins */
|
||||
#define ID_PIN A13
|
||||
#define USBSW_PIN A14 // Switches the hub input/output with the MUXes
|
||||
#define PSW_PIN A15 // Turns the power routing to the USB-C port on/off
|
||||
#define BUS_B_PIN C13 // Tells the hub to be bus-powered or self-powered (which in turn tells the clients to use 100 mA or 500 mA)
|
||||
#define USB_VBUS_PIN C15 // Only the master side will have VBUS present at power-up (used to determine master/slave)
|
||||
#define USBPD_1_PIN F0
|
||||
#define USBPD_2_PIN F1
|
||||
|
||||
/* Additional defines for managing power and state scross split */
|
||||
#define DISABLE_BUS_POWER_MODE TRUE
|
||||
#define KB_STATE_SYNC_INTERVAL 500
|
||||
#define USBPD_ALLOWANCE_CHECK_INTERVAL 100
|
||||
#define SPLIT_TRANSACTION_IDS_KB RPC_ID_KB_STATE
|
||||
|
||||
/* Defines for the RGB matrix */
|
||||
#define INDICATOR_MAX_BRIGHTNESS 255
|
||||
#define CAPS_LOCK_LED_INDEX 0 // LED1 on PCB
|
||||
#define WS2812_EXTERNAL_PULLUP
|
||||
#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
|
||||
10
keyboards/haverworks/theseus75/halconf.h
Normal file
10
keyboards/haverworks/theseus75/halconf.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#define HAL_USE_SERIAL TRUE
|
||||
|
||||
#define HAL_USE_PWM TRUE
|
||||
|
||||
#include_next <halconf.h>
|
||||
456
keyboards/haverworks/theseus75/keyboard.json
Normal file
456
keyboards/haverworks/theseus75/keyboard.json
Normal file
@@ -0,0 +1,456 @@
|
||||
{
|
||||
"manufacturer": "Haverworks",
|
||||
"keyboard_name": "Theseus75",
|
||||
"maintainer": "ebastler",
|
||||
"bootloader": "stm32-dfu",
|
||||
"bootmagic": {
|
||||
"matrix": [0, 1]
|
||||
},
|
||||
"diode_direction": "COL2ROW",
|
||||
"encoder": {
|
||||
"rotary": [
|
||||
{"pin_a": "A8", "pin_b": "C6", "resolution": 2}
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"bootmagic": true,
|
||||
"encoder": true,
|
||||
"extrakey": true,
|
||||
"mousekey": true,
|
||||
"nkro": true,
|
||||
"rgb_matrix": true
|
||||
},
|
||||
"matrix_pins": {
|
||||
"cols": ["B11", "C14", "B6", "B5", "B4", "B3", "C11", "C10", null],
|
||||
"rows": ["A9", "A10", "B15", "B12", "B13", "B14"]
|
||||
},
|
||||
"processor": "STM32G431",
|
||||
"rgb_matrix": {
|
||||
"animations": {
|
||||
"alphas_mods": true,
|
||||
"band_pinwheel_sat": true,
|
||||
"band_pinwheel_val": true,
|
||||
"band_sat": true,
|
||||
"band_spiral_sat": true,
|
||||
"band_spiral_val": true,
|
||||
"band_val": true,
|
||||
"breathing": true,
|
||||
"cycle_all": true,
|
||||
"cycle_left_right": true,
|
||||
"cycle_out_in": true,
|
||||
"cycle_out_in_dual": true,
|
||||
"cycle_pinwheel": true,
|
||||
"cycle_spiral": true,
|
||||
"cycle_up_down": true,
|
||||
"dual_beacon": true,
|
||||
"gradient_left_right": true,
|
||||
"gradient_up_down": true,
|
||||
"hue_breathing": true,
|
||||
"hue_pendulum": true,
|
||||
"hue_wave": true,
|
||||
"jellybean_raindrops": true,
|
||||
"pixel_flow": true,
|
||||
"pixel_fractal": true,
|
||||
"pixel_rain": true,
|
||||
"rainbow_beacon": true,
|
||||
"rainbow_moving_chevron": true,
|
||||
"rainbow_pinwheels": true,
|
||||
"raindrops": true
|
||||
},
|
||||
"driver": "ws2812",
|
||||
"layout": [
|
||||
{"matrix": [3, 1], "x": 32, "y": 34, "flags": 8},
|
||||
{"x": 28, "y": 1, "flags": 2},
|
||||
{"x": 43, "y": 1, "flags": 2},
|
||||
{"x": 56, "y": 1, "flags": 2},
|
||||
{"x": 69, "y": 1, "flags": 2},
|
||||
{"x": 84, "y": 1, "flags": 2},
|
||||
{"x": 99, "y": 1, "flags": 2},
|
||||
{"x": 97, "y": 8, "flags": 2},
|
||||
{"x": 91, "y": 19, "flags": 2},
|
||||
{"x": 91, "y": 31, "flags": 2},
|
||||
{"x": 92, "y": 42, "flags": 2},
|
||||
{"x": 96, "y": 53, "flags": 2},
|
||||
{"x": 97, "y": 64, "flags": 2},
|
||||
{"x": 81, "y": 64, "flags": 2},
|
||||
{"x": 66, "y": 64, "flags": 2},
|
||||
{"x": 53, "y": 64, "flags": 2},
|
||||
{"x": 40, "y": 64, "flags": 2},
|
||||
{"x": 26, "y": 64, "flags": 2},
|
||||
{"x": 13, "y": 64, "flags": 2},
|
||||
{"x": 0, "y": 64, "flags": 2},
|
||||
{"x": 0, "y": 53, "flags": 2},
|
||||
{"x": 0, "y": 42, "flags": 2},
|
||||
{"x": 0, "y": 31, "flags": 2},
|
||||
{"x": 0, "y": 19, "flags": 2},
|
||||
{"x": 0, "y": 9, "flags": 2},
|
||||
{"x": 0, "y": 1, "flags": 2},
|
||||
{"x": 13, "y": 1, "flags": 2},
|
||||
{"x": 224, "y": 31, "flags": 2},
|
||||
{"x": 224, "y": 42, "flags": 2},
|
||||
{"x": 224, "y": 53, "flags": 2},
|
||||
{"x": 224, "y": 64, "flags": 2},
|
||||
{"x": 209, "y": 64, "flags": 2},
|
||||
{"x": 194, "y": 64, "flags": 2},
|
||||
{"x": 180, "y": 64, "flags": 2},
|
||||
{"x": 165, "y": 64, "flags": 2},
|
||||
{"x": 152, "y": 64, "flags": 2},
|
||||
{"x": 138, "y": 64, "flags": 2},
|
||||
{"x": 124, "y": 64, "flags": 2},
|
||||
{"x": 124, "y": 53, "flags": 2},
|
||||
{"x": 119, "y": 42, "flags": 2},
|
||||
{"x": 114, "y": 31, "flags": 2},
|
||||
{"x": 117, "y": 19, "flags": 2},
|
||||
{"x": 122, "y": 8, "flags": 2},
|
||||
{"x": 126, "y": 1, "flags": 2},
|
||||
{"x": 140, "y": 1, "flags": 2},
|
||||
{"x": 155, "y": 1, "flags": 2},
|
||||
{"x": 168, "y": 1, "flags": 2},
|
||||
{"x": 181, "y": 1, "flags": 2},
|
||||
{"x": 196, "y": 1, "flags": 2},
|
||||
{"x": 211, "y": 1, "flags": 2},
|
||||
{"x": 224, "y": 1, "flags": 2},
|
||||
{"x": 224, "y": 8, "flags": 2},
|
||||
{"x": 224, "y": 19, "flags": 2}
|
||||
],
|
||||
"max_brightness": 128,
|
||||
"sleep": true,
|
||||
"split_count": [27, 26]
|
||||
},
|
||||
"split": {
|
||||
"bootmagic": {
|
||||
"matrix": [6, 6]
|
||||
},
|
||||
"enabled": true,
|
||||
"encoder": {
|
||||
"right": {
|
||||
"rotary": [
|
||||
{"pin_a": "B5", "pin_b": "B4", "resolution": 2}
|
||||
]
|
||||
}
|
||||
},
|
||||
"handedness": {
|
||||
"pin": "B9"
|
||||
},
|
||||
"matrix_pins": {
|
||||
"right": {
|
||||
"cols": ["A10", "A9", "A8", "C6", "B15", "B14", "B13", "B12", "C14"],
|
||||
"rows": ["B3", "C10", "C11", "A1", "A0", "B6"]
|
||||
}
|
||||
},
|
||||
"serial": {
|
||||
"driver": "usart",
|
||||
"pin": "B10"
|
||||
},
|
||||
"soft_serial_speed": 0,
|
||||
"transport": {
|
||||
"sync": {
|
||||
"indicators": true,
|
||||
"layer_state": true,
|
||||
"matrix_state": true
|
||||
},
|
||||
"watchdog": true
|
||||
}
|
||||
},
|
||||
"url": "https://haver.works/theseus75",
|
||||
"usb": {
|
||||
"device_version": "1.0.0",
|
||||
"pid": "0x0001",
|
||||
"vid": "0x6877"
|
||||
},
|
||||
"ws2812": {
|
||||
"driver": "pwm",
|
||||
"pin": "B7"
|
||||
},
|
||||
"layouts": {
|
||||
"LAYOUT_all": {
|
||||
"layout": [
|
||||
{"label": "LENC", "matrix": [0, 0], "x": 0, "y": 0},
|
||||
{"label": "ESC", "matrix": [0, 1], "x": 1.5, "y": 0},
|
||||
{"label": "F1", "matrix": [0, 2], "x": 2.75, "y": 0},
|
||||
{"label": "F2", "matrix": [0, 3], "x": 3.75, "y": 0},
|
||||
{"label": "F3", "matrix": [0, 4], "x": 4.75, "y": 0},
|
||||
{"label": "F4", "matrix": [0, 5], "x": 5.75, "y": 0},
|
||||
{"label": "F5", "matrix": [0, 6], "x": 7, "y": 0},
|
||||
{"label": "F6", "matrix": [0, 7], "x": 8, "y": 0},
|
||||
{"label": "F7", "matrix": [6, 0], "x": 10, "y": 0},
|
||||
{"label": "F8", "matrix": [6, 1], "x": 11, "y": 0},
|
||||
{"label": "F9", "matrix": [6, 2], "x": 12.25, "y": 0},
|
||||
{"label": "F10", "matrix": [6, 3], "x": 13.25, "y": 0},
|
||||
{"label": "F11", "matrix": [6, 4], "x": 14.25, "y": 0},
|
||||
{"label": "F12", "matrix": [6, 5], "x": 15.25, "y": 0},
|
||||
{"label": "F13", "matrix": [6, 6], "x": 16.5, "y": 0},
|
||||
{"label": "RENC", "matrix": [6, 8], "x": 18, "y": 0},
|
||||
{"label": "M1", "matrix": [1, 0], "x": 0, "y": 1},
|
||||
{"label": "GRAV", "matrix": [1, 1], "x": 1.5, "y": 1},
|
||||
{"label": "1", "matrix": [1, 2], "x": 2.5, "y": 1},
|
||||
{"label": "2", "matrix": [1, 3], "x": 3.5, "y": 1},
|
||||
{"label": "3", "matrix": [1, 4], "x": 4.5, "y": 1},
|
||||
{"label": "4", "matrix": [1, 5], "x": 5.5, "y": 1},
|
||||
{"label": "5", "matrix": [1, 6], "x": 6.5, "y": 1},
|
||||
{"label": "6", "matrix": [1, 7], "x": 7.5, "y": 1},
|
||||
{"label": "7", "matrix": [7, 0], "x": 9.5, "y": 1},
|
||||
{"label": "8", "matrix": [7, 1], "x": 10.5, "y": 1},
|
||||
{"label": "9", "matrix": [7, 2], "x": 11.5, "y": 1},
|
||||
{"label": "0", "matrix": [7, 3], "x": 12.5, "y": 1},
|
||||
{"label": "-", "matrix": [7, 4], "x": 13.5, "y": 1},
|
||||
{"label": "=", "matrix": [7, 5], "x": 14.5, "y": 1},
|
||||
{"label": "Back", "matrix": [7, 6], "x": 15.5, "y": 1},
|
||||
{"label": "Del", "matrix": [7, 7], "x": 16.5, "y": 1},
|
||||
{"label": "Home", "matrix": [7, 8], "x": 18, "y": 1},
|
||||
{"label": "M2", "matrix": [2, 0], "x": 0, "y": 2},
|
||||
{"label": "Tab", "matrix": [2, 1], "x": 1.5, "y": 2, "w": 1.5},
|
||||
{"label": "Q", "matrix": [2, 3], "x": 3, "y": 2},
|
||||
{"label": "W", "matrix": [2, 4], "x": 4, "y": 2},
|
||||
{"label": "E", "matrix": [2, 5], "x": 5, "y": 2},
|
||||
{"label": "R", "matrix": [2, 6], "x": 6, "y": 2},
|
||||
{"label": "T", "matrix": [2, 7], "x": 7, "y": 2},
|
||||
{"label": "Y", "matrix": [8, 0], "x": 9, "y": 2},
|
||||
{"label": "U", "matrix": [8, 1], "x": 10, "y": 2},
|
||||
{"label": "I", "matrix": [8, 2], "x": 11, "y": 2},
|
||||
{"label": "O", "matrix": [8, 3], "x": 12, "y": 2},
|
||||
{"label": "P", "matrix": [8, 4], "x": 13, "y": 2},
|
||||
{"label": "[", "matrix": [8, 5], "x": 14, "y": 2},
|
||||
{"label": "]", "matrix": [8, 6], "x": 15, "y": 2},
|
||||
{"label": "BSLS", "matrix": [8, 7], "x": 16, "y": 2, "w": 1.5},
|
||||
{"label": "PgUp", "matrix": [8, 8], "x": 18, "y": 2},
|
||||
{"label": "M3", "matrix": [3, 0], "x": 0, "y": 3},
|
||||
{"label": "Caps", "matrix": [3, 1], "x": 1.5, "y": 3, "w": 1.75},
|
||||
{"label": "A", "matrix": [3, 3], "x": 3.25, "y": 3},
|
||||
{"label": "S", "matrix": [3, 4], "x": 4.25, "y": 3},
|
||||
{"label": "D", "matrix": [3, 5], "x": 5.25, "y": 3},
|
||||
{"label": "F", "matrix": [3, 6], "x": 6.25, "y": 3},
|
||||
{"label": "G", "matrix": [3, 7], "x": 7.25, "y": 3},
|
||||
{"label": "H", "matrix": [9, 0], "x": 9.25, "y": 3},
|
||||
{"label": "J", "matrix": [9, 1], "x": 10.25, "y": 3},
|
||||
{"label": "K", "matrix": [9, 2], "x": 11.25, "y": 3},
|
||||
{"label": "L", "matrix": [9, 3], "x": 12.25, "y": 3},
|
||||
{"label": ";", "matrix": [9, 4], "x": 13.25, "y": 3},
|
||||
{"label": "'", "matrix": [9, 5], "x": 14.25, "y": 3},
|
||||
{"label": "NUHS", "matrix": [9, 6], "x": 15.25, "y": 3},
|
||||
{"label": "Enter", "matrix": [9, 7], "x": 16.25, "y": 3, "w": 1.25},
|
||||
{"label": "PgDn", "matrix": [9, 8], "x": 18, "y": 3},
|
||||
{"label": "M4", "matrix": [4, 0], "x": 0, "y": 4},
|
||||
{"label": "Shift", "matrix": [4, 1], "x": 1.5, "y": 4, "w": 1.25},
|
||||
{"label": "NUBS", "matrix": [4, 2], "x": 2.75, "y": 4},
|
||||
{"label": "Z", "matrix": [4, 3], "x": 3.75, "y": 4},
|
||||
{"label": "X", "matrix": [4, 4], "x": 4.75, "y": 4},
|
||||
{"label": "C", "matrix": [4, 5], "x": 5.75, "y": 4},
|
||||
{"label": "V", "matrix": [4, 6], "x": 6.75, "y": 4},
|
||||
{"label": "B", "matrix": [4, 7], "x": 7.75, "y": 4},
|
||||
{"label": "N", "matrix": [10, 0], "x": 9.75, "y": 4},
|
||||
{"label": "M", "matrix": [10, 1], "x": 10.75, "y": 4},
|
||||
{"label": ",", "matrix": [10, 2], "x": 11.75, "y": 4},
|
||||
{"label": ".", "matrix": [10, 3], "x": 12.75, "y": 4},
|
||||
{"label": "/", "matrix": [10, 4], "x": 13.75, "y": 4},
|
||||
{"label": "Shift", "matrix": [10, 6], "x": 14.75, "y": 4, "w": 1.75},
|
||||
{"label": "Up", "matrix": [10, 7], "x": 16.75, "y": 4},
|
||||
{"label": "End", "matrix": [10, 8], "x": 18, "y": 4},
|
||||
{"label": "M5", "matrix": [5, 0], "x": 0, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [5, 1], "x": 1.5, "y": 5, "w": 1.25},
|
||||
{"label": "Gui", "matrix": [5, 2], "x": 2.75, "y": 5, "w": 1.25},
|
||||
{"label": "Alt", "matrix": [5, 3], "x": 4, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [5, 4], "x": 5.25, "y": 5, "w": 2.25},
|
||||
{"label": "FN", "matrix": [5, 7], "x": 7.5, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [11, 1], "x": 9.75, "y": 5, "w": 2.75},
|
||||
{"label": "Alt", "matrix": [11, 3], "x": 12.5, "y": 5},
|
||||
{"label": "Gui", "matrix": [11, 4], "x": 13.5, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [11, 5], "x": 14.5, "y": 5},
|
||||
{"label": "Left", "matrix": [11, 6], "x": 15.75, "y": 5},
|
||||
{"label": "Down", "matrix": [11, 7], "x": 16.75, "y": 5},
|
||||
{"label": "Rght", "matrix": [11, 8], "x": 17.75, "y": 5}
|
||||
]
|
||||
},
|
||||
"LAYOUT_ansi": {
|
||||
"layout": [
|
||||
{"label": "LENC", "matrix": [0, 0], "x": 0, "y": 0},
|
||||
{"label": "ESC", "matrix": [0, 1], "x": 1.5, "y": 0},
|
||||
{"label": "F1", "matrix": [0, 2], "x": 2.75, "y": 0},
|
||||
{"label": "F2", "matrix": [0, 3], "x": 3.75, "y": 0},
|
||||
{"label": "F3", "matrix": [0, 4], "x": 4.75, "y": 0},
|
||||
{"label": "F4", "matrix": [0, 5], "x": 5.75, "y": 0},
|
||||
{"label": "F5", "matrix": [0, 6], "x": 7, "y": 0},
|
||||
{"label": "F6", "matrix": [0, 7], "x": 8, "y": 0},
|
||||
{"label": "F7", "matrix": [6, 0], "x": 10, "y": 0},
|
||||
{"label": "F8", "matrix": [6, 1], "x": 11, "y": 0},
|
||||
{"label": "F9", "matrix": [6, 2], "x": 12.25, "y": 0},
|
||||
{"label": "F10", "matrix": [6, 3], "x": 13.25, "y": 0},
|
||||
{"label": "F11", "matrix": [6, 4], "x": 14.25, "y": 0},
|
||||
{"label": "F12", "matrix": [6, 5], "x": 15.25, "y": 0},
|
||||
{"label": "F13", "matrix": [6, 6], "x": 16.5, "y": 0},
|
||||
{"label": "RENC", "matrix": [6, 8], "x": 18, "y": 0},
|
||||
{"label": "M1", "matrix": [1, 0], "x": 0, "y": 1},
|
||||
{"label": "GRAV", "matrix": [1, 1], "x": 1.5, "y": 1},
|
||||
{"label": "1", "matrix": [1, 2], "x": 2.5, "y": 1},
|
||||
{"label": "2", "matrix": [1, 3], "x": 3.5, "y": 1},
|
||||
{"label": "3", "matrix": [1, 4], "x": 4.5, "y": 1},
|
||||
{"label": "4", "matrix": [1, 5], "x": 5.5, "y": 1},
|
||||
{"label": "5", "matrix": [1, 6], "x": 6.5, "y": 1},
|
||||
{"label": "6", "matrix": [1, 7], "x": 7.5, "y": 1},
|
||||
{"label": "7", "matrix": [7, 0], "x": 9.5, "y": 1},
|
||||
{"label": "8", "matrix": [7, 1], "x": 10.5, "y": 1},
|
||||
{"label": "9", "matrix": [7, 2], "x": 11.5, "y": 1},
|
||||
{"label": "0", "matrix": [7, 3], "x": 12.5, "y": 1},
|
||||
{"label": "-", "matrix": [7, 4], "x": 13.5, "y": 1},
|
||||
{"label": "=", "matrix": [7, 5], "x": 14.5, "y": 1},
|
||||
{"label": "Back", "matrix": [7, 6], "x": 15.5, "y": 1, "w": 2},
|
||||
{"label": "Home", "matrix": [7, 8], "x": 18, "y": 1},
|
||||
{"label": "M2", "matrix": [2, 0], "x": 0, "y": 2},
|
||||
{"label": "Tab", "matrix": [2, 1], "x": 1.5, "y": 2, "w": 1.5},
|
||||
{"label": "Q", "matrix": [2, 3], "x": 3, "y": 2},
|
||||
{"label": "W", "matrix": [2, 4], "x": 4, "y": 2},
|
||||
{"label": "E", "matrix": [2, 5], "x": 5, "y": 2},
|
||||
{"label": "R", "matrix": [2, 6], "x": 6, "y": 2},
|
||||
{"label": "T", "matrix": [2, 7], "x": 7, "y": 2},
|
||||
{"label": "Y", "matrix": [8, 0], "x": 9, "y": 2},
|
||||
{"label": "U", "matrix": [8, 1], "x": 10, "y": 2},
|
||||
{"label": "I", "matrix": [8, 2], "x": 11, "y": 2},
|
||||
{"label": "O", "matrix": [8, 3], "x": 12, "y": 2},
|
||||
{"label": "P", "matrix": [8, 4], "x": 13, "y": 2},
|
||||
{"label": "[", "matrix": [8, 5], "x": 14, "y": 2},
|
||||
{"label": "]", "matrix": [8, 6], "x": 15, "y": 2},
|
||||
{"label": "BSLS", "matrix": [8, 7], "x": 16, "y": 2, "w": 1.5},
|
||||
{"label": "PgUp", "matrix": [8, 8], "x": 18, "y": 2},
|
||||
{"label": "M3", "matrix": [3, 0], "x": 0, "y": 3},
|
||||
{"label": "Caps", "matrix": [3, 1], "x": 1.5, "y": 3, "w": 1.75},
|
||||
{"label": "A", "matrix": [3, 3], "x": 3.25, "y": 3},
|
||||
{"label": "S", "matrix": [3, 4], "x": 4.25, "y": 3},
|
||||
{"label": "D", "matrix": [3, 5], "x": 5.25, "y": 3},
|
||||
{"label": "F", "matrix": [3, 6], "x": 6.25, "y": 3},
|
||||
{"label": "G", "matrix": [3, 7], "x": 7.25, "y": 3},
|
||||
{"label": "H", "matrix": [9, 0], "x": 9.25, "y": 3},
|
||||
{"label": "J", "matrix": [9, 1], "x": 10.25, "y": 3},
|
||||
{"label": "K", "matrix": [9, 2], "x": 11.25, "y": 3},
|
||||
{"label": "L", "matrix": [9, 3], "x": 12.25, "y": 3},
|
||||
{"label": ";", "matrix": [9, 4], "x": 13.25, "y": 3},
|
||||
{"label": "'", "matrix": [9, 5], "x": 14.25, "y": 3},
|
||||
{"label": "Enter", "matrix": [9, 7], "x": 15.25, "y": 3, "w": 2.25},
|
||||
{"label": "PgDn", "matrix": [9, 8], "x": 18, "y": 3},
|
||||
{"label": "M4", "matrix": [4, 0], "x": 0, "y": 4},
|
||||
{"label": "Shift", "matrix": [4, 1], "x": 1.5, "y": 4, "w": 2.25},
|
||||
{"label": "Z", "matrix": [4, 3], "x": 3.75, "y": 4},
|
||||
{"label": "X", "matrix": [4, 4], "x": 4.75, "y": 4},
|
||||
{"label": "C", "matrix": [4, 5], "x": 5.75, "y": 4},
|
||||
{"label": "V", "matrix": [4, 6], "x": 6.75, "y": 4},
|
||||
{"label": "B", "matrix": [4, 7], "x": 7.75, "y": 4},
|
||||
{"label": "N", "matrix": [10, 0], "x": 9.75, "y": 4},
|
||||
{"label": "M", "matrix": [10, 1], "x": 10.75, "y": 4},
|
||||
{"label": ",", "matrix": [10, 2], "x": 11.75, "y": 4},
|
||||
{"label": ".", "matrix": [10, 3], "x": 12.75, "y": 4},
|
||||
{"label": "/", "matrix": [10, 4], "x": 13.75, "y": 4},
|
||||
{"label": "Shift", "matrix": [10, 6], "x": 14.75, "y": 4, "w": 1.75},
|
||||
{"label": "Up", "matrix": [10, 7], "x": 16.75, "y": 4},
|
||||
{"label": "End", "matrix": [10, 8], "x": 18, "y": 4},
|
||||
{"label": "M5", "matrix": [5, 0], "x": 0, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [5, 1], "x": 1.5, "y": 5, "w": 1.25},
|
||||
{"label": "Gui", "matrix": [5, 2], "x": 2.75, "y": 5, "w": 1.25},
|
||||
{"label": "Alt", "matrix": [5, 3], "x": 4, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [5, 4], "x": 5.25, "y": 5, "w": 2.25},
|
||||
{"label": "FN", "matrix": [5, 7], "x": 7.5, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [11, 1], "x": 9.75, "y": 5, "w": 2.75},
|
||||
{"label": "Alt", "matrix": [11, 3], "x": 12.5, "y": 5},
|
||||
{"label": "Gui", "matrix": [11, 4], "x": 13.5, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [11, 5], "x": 14.5, "y": 5},
|
||||
{"label": "Left", "matrix": [11, 6], "x": 15.75, "y": 5},
|
||||
{"label": "Down", "matrix": [11, 7], "x": 16.75, "y": 5},
|
||||
{"label": "Rght", "matrix": [11, 8], "x": 17.75, "y": 5}
|
||||
]
|
||||
},
|
||||
"LAYOUT_iso": {
|
||||
"layout": [
|
||||
{"label": "LENC", "matrix": [0, 0], "x": 0, "y": 0},
|
||||
{"label": "ESC", "matrix": [0, 1], "x": 1.5, "y": 0},
|
||||
{"label": "F1", "matrix": [0, 2], "x": 2.75, "y": 0},
|
||||
{"label": "F2", "matrix": [0, 3], "x": 3.75, "y": 0},
|
||||
{"label": "F3", "matrix": [0, 4], "x": 4.75, "y": 0},
|
||||
{"label": "F4", "matrix": [0, 5], "x": 5.75, "y": 0},
|
||||
{"label": "F5", "matrix": [0, 6], "x": 7, "y": 0},
|
||||
{"label": "F6", "matrix": [0, 7], "x": 8, "y": 0},
|
||||
{"label": "F7", "matrix": [6, 0], "x": 10, "y": 0},
|
||||
{"label": "F8", "matrix": [6, 1], "x": 11, "y": 0},
|
||||
{"label": "F9", "matrix": [6, 2], "x": 12.25, "y": 0},
|
||||
{"label": "F10", "matrix": [6, 3], "x": 13.25, "y": 0},
|
||||
{"label": "F11", "matrix": [6, 4], "x": 14.25, "y": 0},
|
||||
{"label": "F12", "matrix": [6, 5], "x": 15.25, "y": 0},
|
||||
{"label": "F13", "matrix": [6, 6], "x": 16.5, "y": 0},
|
||||
{"label": "RENC", "matrix": [6, 8], "x": 18, "y": 0},
|
||||
{"label": "M1", "matrix": [1, 0], "x": 0, "y": 1},
|
||||
{"label": "GRAV", "matrix": [1, 1], "x": 1.5, "y": 1},
|
||||
{"label": "1", "matrix": [1, 2], "x": 2.5, "y": 1},
|
||||
{"label": "2", "matrix": [1, 3], "x": 3.5, "y": 1},
|
||||
{"label": "3", "matrix": [1, 4], "x": 4.5, "y": 1},
|
||||
{"label": "4", "matrix": [1, 5], "x": 5.5, "y": 1},
|
||||
{"label": "5", "matrix": [1, 6], "x": 6.5, "y": 1},
|
||||
{"label": "6", "matrix": [1, 7], "x": 7.5, "y": 1},
|
||||
{"label": "7", "matrix": [7, 0], "x": 9.5, "y": 1},
|
||||
{"label": "8", "matrix": [7, 1], "x": 10.5, "y": 1},
|
||||
{"label": "9", "matrix": [7, 2], "x": 11.5, "y": 1},
|
||||
{"label": "0", "matrix": [7, 3], "x": 12.5, "y": 1},
|
||||
{"label": "-", "matrix": [7, 4], "x": 13.5, "y": 1},
|
||||
{"label": "=", "matrix": [7, 5], "x": 14.5, "y": 1},
|
||||
{"label": "Back", "matrix": [7, 6], "x": 15.5, "y": 1, "w": 2},
|
||||
{"label": "Home", "matrix": [7, 8], "x": 18, "y": 1},
|
||||
{"label": "M2", "matrix": [2, 0], "x": 0, "y": 2},
|
||||
{"label": "Tab", "matrix": [2, 1], "x": 1.5, "y": 2, "w": 1.5},
|
||||
{"label": "Q", "matrix": [2, 3], "x": 3, "y": 2},
|
||||
{"label": "W", "matrix": [2, 4], "x": 4, "y": 2},
|
||||
{"label": "E", "matrix": [2, 5], "x": 5, "y": 2},
|
||||
{"label": "R", "matrix": [2, 6], "x": 6, "y": 2},
|
||||
{"label": "T", "matrix": [2, 7], "x": 7, "y": 2},
|
||||
{"label": "Y", "matrix": [8, 0], "x": 9, "y": 2},
|
||||
{"label": "U", "matrix": [8, 1], "x": 10, "y": 2},
|
||||
{"label": "I", "matrix": [8, 2], "x": 11, "y": 2},
|
||||
{"label": "O", "matrix": [8, 3], "x": 12, "y": 2},
|
||||
{"label": "P", "matrix": [8, 4], "x": 13, "y": 2},
|
||||
{"label": "[", "matrix": [8, 5], "x": 14, "y": 2},
|
||||
{"label": "]", "matrix": [8, 6], "x": 15, "y": 2},
|
||||
{"label": "PgUp", "matrix": [8, 8], "x": 18, "y": 2},
|
||||
{"label": "M3", "matrix": [3, 0], "x": 0, "y": 3},
|
||||
{"label": "Caps", "matrix": [3, 1], "x": 1.5, "y": 3, "w": 1.75},
|
||||
{"label": "A", "matrix": [3, 3], "x": 3.25, "y": 3},
|
||||
{"label": "S", "matrix": [3, 4], "x": 4.25, "y": 3},
|
||||
{"label": "D", "matrix": [3, 5], "x": 5.25, "y": 3},
|
||||
{"label": "F", "matrix": [3, 6], "x": 6.25, "y": 3},
|
||||
{"label": "G", "matrix": [3, 7], "x": 7.25, "y": 3},
|
||||
{"label": "H", "matrix": [9, 0], "x": 9.25, "y": 3},
|
||||
{"label": "J", "matrix": [9, 1], "x": 10.25, "y": 3},
|
||||
{"label": "K", "matrix": [9, 2], "x": 11.25, "y": 3},
|
||||
{"label": "L", "matrix": [9, 3], "x": 12.25, "y": 3},
|
||||
{"label": ";", "matrix": [9, 4], "x": 13.25, "y": 3},
|
||||
{"label": "'", "matrix": [9, 5], "x": 14.25, "y": 3},
|
||||
{"label": "NUHS", "matrix": [9, 6], "x": 15.25, "y": 3},
|
||||
{"label": "Enter", "matrix": [9, 7], "x": 16.25, "y": 2, "w": 1.25, "h": 2},
|
||||
{"label": "PgDn", "matrix": [9, 8], "x": 18, "y": 3},
|
||||
{"label": "M4", "matrix": [4, 0], "x": 0, "y": 4},
|
||||
{"label": "Shift", "matrix": [4, 1], "x": 1.5, "y": 4, "w": 1.25},
|
||||
{"label": "NUBS", "matrix": [4, 2], "x": 2.75, "y": 4},
|
||||
{"label": "Z", "matrix": [4, 3], "x": 3.75, "y": 4},
|
||||
{"label": "X", "matrix": [4, 4], "x": 4.75, "y": 4},
|
||||
{"label": "C", "matrix": [4, 5], "x": 5.75, "y": 4},
|
||||
{"label": "V", "matrix": [4, 6], "x": 6.75, "y": 4},
|
||||
{"label": "B", "matrix": [4, 7], "x": 7.75, "y": 4},
|
||||
{"label": "N", "matrix": [10, 0], "x": 9.75, "y": 4},
|
||||
{"label": "M", "matrix": [10, 1], "x": 10.75, "y": 4},
|
||||
{"label": ",", "matrix": [10, 2], "x": 11.75, "y": 4},
|
||||
{"label": ".", "matrix": [10, 3], "x": 12.75, "y": 4},
|
||||
{"label": "/", "matrix": [10, 4], "x": 13.75, "y": 4},
|
||||
{"label": "Shift", "matrix": [10, 6], "x": 14.75, "y": 4, "w": 1.75},
|
||||
{"label": "Up", "matrix": [10, 7], "x": 16.75, "y": 4},
|
||||
{"label": "End", "matrix": [10, 8], "x": 18, "y": 4},
|
||||
{"label": "M5", "matrix": [5, 0], "x": 0, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [5, 1], "x": 1.5, "y": 5, "w": 1.25},
|
||||
{"label": "Gui", "matrix": [5, 2], "x": 2.75, "y": 5, "w": 1.25},
|
||||
{"label": "Alt", "matrix": [5, 3], "x": 4, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [5, 4], "x": 5.25, "y": 5, "w": 2.25},
|
||||
{"label": "FN", "matrix": [5, 7], "x": 7.5, "y": 5, "w": 1.25},
|
||||
{"label": "Space", "matrix": [11, 1], "x": 9.75, "y": 5, "w": 2.75},
|
||||
{"label": "Alt", "matrix": [11, 3], "x": 12.5, "y": 5},
|
||||
{"label": "Gui", "matrix": [11, 4], "x": 13.5, "y": 5},
|
||||
{"label": "Ctrl", "matrix": [11, 5], "x": 14.5, "y": 5},
|
||||
{"label": "Left", "matrix": [11, 6], "x": 15.75, "y": 5},
|
||||
{"label": "Down", "matrix": [11, 7], "x": 16.75, "y": 5},
|
||||
{"label": "Rght", "matrix": [11, 8], "x": 17.75, "y": 5}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
31
keyboards/haverworks/theseus75/keymaps/ansi/keymap.c
Normal file
31
keyboards/haverworks/theseus75/keymaps/ansi/keymap.c
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include QMK_KEYBOARD_H
|
||||
|
||||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||
|
||||
[0] = LAYOUT_ansi(
|
||||
RM_TOGG, 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_PSCR, KC_MUTE,
|
||||
KC_F13, 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_HOME,
|
||||
KC_F14, 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_PGUP,
|
||||
KC_F15, 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_PGDN,
|
||||
KC_F16, 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_END,
|
||||
KC_F17, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, MO(1), KC_SPC, KC_RALT, KC_RGUI, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
|
||||
),
|
||||
[1] = LAYOUT_ansi(
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_MPLY,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_DEL, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS
|
||||
)
|
||||
};
|
||||
|
||||
#ifdef ENCODER_MAP_ENABLE
|
||||
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
|
||||
[0] = { ENCODER_CCW_CW(RM_VALD, RM_VALU), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
|
||||
[1] = { ENCODER_CCW_CW(RM_PREV, RM_NEXT), ENCODER_CCW_CW(KC_MPRV, KC_MNXT) },
|
||||
};
|
||||
#endif
|
||||
1
keyboards/haverworks/theseus75/keymaps/ansi/rules.mk
Normal file
1
keyboards/haverworks/theseus75/keymaps/ansi/rules.mk
Normal file
@@ -0,0 +1 @@
|
||||
ENCODER_MAP_ENABLE = yes
|
||||
31
keyboards/haverworks/theseus75/keymaps/default/keymap.c
Normal file
31
keyboards/haverworks/theseus75/keymaps/default/keymap.c
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include QMK_KEYBOARD_H
|
||||
|
||||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||
|
||||
[0] = LAYOUT_all(
|
||||
RM_TOGG, 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_PSCR, KC_MUTE,
|
||||
KC_F13, 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_HOME,
|
||||
KC_F14, 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_PGUP,
|
||||
KC_F15, 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_NUHS, KC_ENT, KC_PGDN,
|
||||
KC_F16, KC_LSFT, KC_NUBS, 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_END,
|
||||
KC_F17, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, MO(1), KC_SPC, KC_RALT, KC_RGUI, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
|
||||
),
|
||||
[1] = LAYOUT_all(
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_MPLY,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS
|
||||
)
|
||||
};
|
||||
|
||||
#ifdef ENCODER_MAP_ENABLE
|
||||
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
|
||||
[0] = { ENCODER_CCW_CW(RM_VALD, RM_VALU), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
|
||||
[1] = { ENCODER_CCW_CW(RM_PREV, RM_NEXT), ENCODER_CCW_CW(KC_MPRV, KC_MNXT) },
|
||||
};
|
||||
#endif
|
||||
1
keyboards/haverworks/theseus75/keymaps/default/rules.mk
Normal file
1
keyboards/haverworks/theseus75/keymaps/default/rules.mk
Normal file
@@ -0,0 +1 @@
|
||||
ENCODER_MAP_ENABLE = yes
|
||||
31
keyboards/haverworks/theseus75/keymaps/iso/keymap.c
Normal file
31
keyboards/haverworks/theseus75/keymaps/iso/keymap.c
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include QMK_KEYBOARD_H
|
||||
|
||||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||
|
||||
[0] = LAYOUT_iso(
|
||||
RM_TOGG, 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_PSCR, KC_MUTE,
|
||||
KC_F13, 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_HOME,
|
||||
KC_F14, 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_PGUP,
|
||||
KC_F15, 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_NUHS, KC_ENT, KC_PGDN,
|
||||
KC_F16, KC_LSFT, KC_NUBS, 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_END,
|
||||
KC_F17, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, MO(1), KC_SPC, KC_RALT, KC_RGUI, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
|
||||
),
|
||||
[1] = LAYOUT_iso(
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_MPLY,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_DEL, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
|
||||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS
|
||||
)
|
||||
};
|
||||
|
||||
#ifdef ENCODER_MAP_ENABLE
|
||||
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
|
||||
[0] = { ENCODER_CCW_CW(RM_VALD, RM_VALU), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
|
||||
[1] = { ENCODER_CCW_CW(RM_PREV, RM_NEXT), ENCODER_CCW_CW(KC_MPRV, KC_MNXT) },
|
||||
};
|
||||
#endif
|
||||
1
keyboards/haverworks/theseus75/keymaps/iso/rules.mk
Normal file
1
keyboards/haverworks/theseus75/keymaps/iso/rules.mk
Normal file
@@ -0,0 +1 @@
|
||||
ENCODER_MAP_ENABLE = yes
|
||||
14
keyboards/haverworks/theseus75/mcuconf.h
Normal file
14
keyboards/haverworks/theseus75/mcuconf.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler)
|
||||
// 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 Underglow PWM driver */
|
||||
#undef STM32_PWM_USE_TIM3
|
||||
#define STM32_PWM_USE_TIM3 TRUE
|
||||
31
keyboards/haverworks/theseus75/readme.md
Normal file
31
keyboards/haverworks/theseus75/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Haverworks Theseus75
|
||||
|
||||

|
||||
|
||||
The Theseus75 is a 75% row-staggered split keyboard with a macro column and rotary encoders on both sides. Each half features a dual-role USB-C port and integrated USB hub, allowing the unused port to act as a USB-C 2.0 High-Speed host for connecting a numpad, mouse, flash drive, security key, mobile phone, or similar device.
|
||||
|
||||
> [!NOTE]
|
||||
> Up to 5V at 1.5A can be supplied, depending on negotiations with the host.
|
||||
|
||||
* Keyboard Maintainers: [Moritz Plattner](https://github.com/ebastler), [Alex Havermale](https://github.com/haversnail)
|
||||
* Hardware Supported: Haverworks Theseus75 v1 PCBs (hot-swap and solder)
|
||||
* Hardware Availability: [Group buy](https://haver.works/theseus75)
|
||||
|
||||
Make example for this keyboard (after setting up your build environment):
|
||||
|
||||
make haverworks/theseus75:default
|
||||
|
||||
Flashing example for this keyboard:
|
||||
|
||||
make haverworks/theseus75: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 two ways:
|
||||
|
||||
* **Physical reset button**: Hold the <kbd>Reset</kbd>/<kbd>Flash</kbd> button on the back of the PCB for approximately one second (instructions are also included on the PCB)
|
||||
* **Bootmagic reset**:
|
||||
* **Left half**: Hold down the first key to the right of the encoder (<kbd>Esc</kbd> by default) and plug in the keyboard
|
||||
* **Right half**: Hold down the first key to the left of the encoder (<kbd>Print Screen</kbd> by default) and plug in the keyboard
|
||||
214
keyboards/haverworks/theseus75/theseus75.c
Normal file
214
keyboards/haverworks/theseus75/theseus75.c
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "quantum.h"
|
||||
#include "transactions.h"
|
||||
#include <stdio.h>
|
||||
#include "print.h"
|
||||
#include "split_util.h"
|
||||
#include "usbpd.h"
|
||||
|
||||
typedef struct _kb_state_t {
|
||||
usbpd_allowance_t allowance;
|
||||
} kb_state_t;
|
||||
|
||||
kb_state_t kb_state;
|
||||
|
||||
const char* usbpd_str(usbpd_allowance_t allowance) {
|
||||
switch (allowance) {
|
||||
case USBPD_500MA:
|
||||
return "500mA";
|
||||
case USBPD_1500MA:
|
||||
return "1500mA";
|
||||
case USBPD_3000MA:
|
||||
return "3000mA";
|
||||
default:
|
||||
dprintf("Encountered unknown allowance enum value: %d\n", allowance);
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void kb_state_slave_handler(uint8_t m2s_size, const void* m2s_buffer, uint8_t s2m_size, void* s2m_buffer) {
|
||||
if (m2s_size == sizeof(kb_state_t)) {
|
||||
memcpy(&kb_state, m2s_buffer, sizeof(kb_state_t));
|
||||
} else {
|
||||
dprintf("Unexpected response in slave handler\n"); // TODO: add split debug logging
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void keyboard_post_init_kb(void) {
|
||||
// Register keyboard state transaction:
|
||||
transaction_register_rpc(RPC_ID_KB_STATE, kb_state_slave_handler);
|
||||
|
||||
// Set default state values:
|
||||
kb_state.allowance = USBPD_500MA;
|
||||
|
||||
// If the keyboard is master,
|
||||
if (is_keyboard_master()) {
|
||||
// Turn on power to the split half and to underglow LEDs:
|
||||
gpio_set_pin_output(PSW_PIN);
|
||||
gpio_write_pin_high(PSW_PIN);
|
||||
|
||||
// Enable inputs used for current negotiation:
|
||||
gpio_set_pin_input_high(USBPD_1_PIN);
|
||||
gpio_set_pin_input_high(USBPD_2_PIN);
|
||||
|
||||
// Not needed in this mode (always high-Z with pull-up on PCB if port controller is sink)
|
||||
gpio_set_pin_input_high(ID_PIN);
|
||||
} else {
|
||||
// Prepare output to enable power for USB output after negotiation:
|
||||
gpio_set_pin_output(PSW_PIN);
|
||||
|
||||
// Switch the USB MUXes between hub and ports:
|
||||
gpio_set_pin_output(USBSW_PIN);
|
||||
gpio_write_pin_high(USBSW_PIN);
|
||||
|
||||
// Enable outputs used for current negotiation and default to 500mA:
|
||||
gpio_set_pin_output(USBPD_1_PIN);
|
||||
gpio_write_pin_high(USBPD_1_PIN);
|
||||
gpio_set_pin_output(USBPD_2_PIN);
|
||||
gpio_write_pin_high(USBPD_2_PIN);
|
||||
|
||||
// Use ID pin to check if client is detected (if low: USB source port powered):
|
||||
gpio_set_pin_input_high(ID_PIN);
|
||||
|
||||
// Indicate that the hub is either self-powered or bus-powered based on whether the bus-power mode flag is enabled
|
||||
// (configurable for users who would rather always have their device connect, regardless of whether they meet the specs):
|
||||
gpio_set_pin_output(BUS_B_PIN);
|
||||
if (DISABLE_BUS_POWER_MODE == TRUE) {
|
||||
gpio_write_pin_high(BUS_B_PIN);
|
||||
} else {
|
||||
gpio_write_pin_low(BUS_B_PIN);
|
||||
}
|
||||
}
|
||||
// Call the corresponding _user() function (see https://docs.qmk.fm/#/custom_quantum_functions)
|
||||
keyboard_post_init_user();
|
||||
}
|
||||
|
||||
void housekeeping_task_kb(void) {
|
||||
// Update any shared kb state to send to slave:
|
||||
static uint32_t last_usbpd_allowance_check_time = 0;
|
||||
if (timer_elapsed32(last_usbpd_allowance_check_time) > USBPD_ALLOWANCE_CHECK_INTERVAL) {
|
||||
// On master side: check USBPD_1_PIN and USBPD_2_PIN to determine current negotiated with host
|
||||
// (Can't use the usbpd_get_allowance() function, as this uses this uses the native CC PD interface
|
||||
// of the G series MCU, while we're using dedicated port controllers instead):
|
||||
if (is_keyboard_master()) {
|
||||
usbpd_allowance_t allowance;
|
||||
|
||||
if (gpio_read_pin(USBPD_1_PIN)) {
|
||||
allowance = USBPD_500MA;
|
||||
} else if (gpio_read_pin(USBPD_2_PIN)) {
|
||||
allowance = USBPD_1500MA;
|
||||
} else {
|
||||
allowance = USBPD_3000MA;
|
||||
}
|
||||
|
||||
if (kb_state.allowance != allowance) {
|
||||
printf("Host negotiated current: %s -> %s\n", usbpd_str(kb_state.allowance), usbpd_str(allowance));
|
||||
kb_state.allowance = allowance;
|
||||
}
|
||||
} else {
|
||||
// On peripheral side - If ID_PIN is low: USB client negotiated 5V successfully -> enable power routing
|
||||
// Check if PSW_PIN is not already high to avoid wasting time
|
||||
if (!gpio_read_pin(ID_PIN) && !gpio_read_pin(PSW_PIN)) {
|
||||
gpio_write_pin_high(PSW_PIN);
|
||||
dprintf("USB downstream device connected\n"); // TODO: add split debug logging
|
||||
} else if (gpio_read_pin(ID_PIN) && gpio_read_pin(PSW_PIN)) {
|
||||
gpio_write_pin_low(PSW_PIN);
|
||||
dprintf("USB downstream device disconnected\n"); // TODO: add split debug logging
|
||||
}
|
||||
};
|
||||
last_usbpd_allowance_check_time = timer_read32();
|
||||
};
|
||||
|
||||
// Sync state from master to slave:
|
||||
if (is_keyboard_master() && is_transport_connected()) {
|
||||
bool needs_sync = false;
|
||||
static uint32_t last_kb_state_sync_time;
|
||||
static kb_state_t last_kb_state;
|
||||
|
||||
// Check if the state values are different:
|
||||
if (memcmp(&kb_state, &last_kb_state, sizeof(kb_state_t))) {
|
||||
needs_sync = true;
|
||||
memcpy(&last_kb_state, &kb_state, sizeof(kb_state_t));
|
||||
}
|
||||
|
||||
// Sync state every so often regardless:
|
||||
if (timer_elapsed32(last_kb_state_sync_time) > KB_STATE_SYNC_INTERVAL) {
|
||||
needs_sync = true;
|
||||
}
|
||||
|
||||
if (needs_sync) {
|
||||
bool did_sync = transaction_rpc_send(RPC_ID_KB_STATE, sizeof(kb_state_t), &kb_state);
|
||||
if (did_sync) {
|
||||
dprintf("Synced to slave\n");
|
||||
last_kb_state_sync_time = timer_read32();
|
||||
} else {
|
||||
dprintf("Failed to sync state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the USBPD output pins on slave half whenever allowance has changed:
|
||||
if (!is_keyboard_master()) {
|
||||
static usbpd_allowance_t last_allowance;
|
||||
|
||||
if (last_allowance != kb_state.allowance) {
|
||||
last_allowance = kb_state.allowance;
|
||||
|
||||
printf("Setting USB-PD output to %s (%s-powered)\n", usbpd_str(kb_state.allowance), kb_state.allowance == USBPD_500MA ? "bus" : "self"); // TODO: add split debug logging
|
||||
|
||||
switch (kb_state.allowance) {
|
||||
default:
|
||||
case USBPD_500MA:
|
||||
// Set USBPD output to 500 mA:
|
||||
gpio_write_pin_high(USBPD_1_PIN);
|
||||
gpio_write_pin_high(USBPD_2_PIN);
|
||||
if (DISABLE_BUS_POWER_MODE == TRUE) {
|
||||
// Indicate hub is self-powered and devices can try to connect or fast charge:
|
||||
gpio_write_pin_high(BUS_B_PIN);
|
||||
} else {
|
||||
// Indicate hub is bus-powered and devices should not try to connect or fast charge:
|
||||
gpio_write_pin_low(BUS_B_PIN);
|
||||
}
|
||||
break;
|
||||
case USBPD_1500MA:
|
||||
// Set USBPD output to 500 mA:
|
||||
gpio_write_pin_high(USBPD_1_PIN);
|
||||
gpio_write_pin_high(USBPD_2_PIN);
|
||||
// Indicate hub is self-powered and devices can try to connect or fast charge:
|
||||
gpio_write_pin_high(BUS_B_PIN);
|
||||
break;
|
||||
case USBPD_3000MA:
|
||||
// Set USBPD output to 1500 mA:
|
||||
gpio_write_pin_low(USBPD_1_PIN);
|
||||
gpio_write_pin_high(USBPD_2_PIN);
|
||||
// Indicate hub is self-powered and devices can try to connect or fast charge:
|
||||
gpio_write_pin_high(BUS_B_PIN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
bool rgb_matrix_indicators_kb(void) {
|
||||
if (!rgb_matrix_indicators_user()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host_keyboard_led_state().caps_lock) {
|
||||
rgb_matrix_set_color(CAPS_LOCK_LED_INDEX, INDICATOR_MAX_BRIGHTNESS, INDICATOR_MAX_BRIGHTNESS, INDICATOR_MAX_BRIGHTNESS);
|
||||
} else {
|
||||
rgb_matrix_set_color(CAPS_LOCK_LED_INDEX, 0, 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -541,6 +541,9 @@ void keyboard_init(void) {
|
||||
#ifdef HAPTIC_ENABLE
|
||||
haptic_init();
|
||||
#endif
|
||||
#ifdef COMBO_ENABLE
|
||||
combo_enable();
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG_MATRIX_SCAN_RATE) && defined(CONSOLE_ENABLE)
|
||||
debug_enable = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,39 +22,44 @@
|
||||
#include "keycodes.h"
|
||||
#include "quantum_keycodes.h"
|
||||
|
||||
#ifdef EXTRA_SHORT_COMBOS
|
||||
# define MAX_COMBO_LENGTH 6
|
||||
#elif defined(EXTRA_EXTRA_LONG_COMBOS)
|
||||
/* COMBO_BUFFER_LENGTH defines the maximum number of simulatenously active combos. */
|
||||
#ifndef COMBO_BUFFER_LENGTH
|
||||
# define COMBO_BUFFER_LENGTH 4
|
||||
#endif
|
||||
|
||||
#if defined(EXTRA_EXTRA_LONG_COMBOS)
|
||||
# define MAX_COMBO_LENGTH 32
|
||||
# define COMBO_STATE_BITS 5
|
||||
typedef uint32_t combo_active_state_t;
|
||||
#elif defined(EXTRA_LONG_COMBOS)
|
||||
# define MAX_COMBO_LENGTH 16
|
||||
# define COMBO_STATE_BITS 4
|
||||
typedef uint16_t combo_active_state_t;
|
||||
#elif defined(EXTRA_SMALL_COMBOS)
|
||||
# define MAX_COMBO_LENGTH 4
|
||||
# define COMBO_STATE_BITS 2
|
||||
typedef uint8_t combo_active_state_t;
|
||||
#else
|
||||
# define MAX_COMBO_LENGTH 8
|
||||
# define COMBO_STATE_BITS 3
|
||||
typedef uint8_t combo_active_state_t;
|
||||
#endif
|
||||
|
||||
#ifdef COMBO_COMPRESSED
|
||||
/* If combo_count() < (256/MAX_COMBO_LENGTH) - 1, this can be defined to save some space */
|
||||
typedef uint8_t combo_state_t;
|
||||
#else
|
||||
typedef uint16_t combo_state_t;
|
||||
#endif
|
||||
|
||||
#ifndef COMBO_KEY_BUFFER_LENGTH
|
||||
# define COMBO_KEY_BUFFER_LENGTH MAX_COMBO_LENGTH
|
||||
#endif
|
||||
#ifndef COMBO_BUFFER_LENGTH
|
||||
# define COMBO_BUFFER_LENGTH 4
|
||||
# define COMBO_KEY_BUFFER_LENGTH (MAX_COMBO_LENGTH + 4)
|
||||
#endif
|
||||
|
||||
typedef struct combo_t {
|
||||
const uint16_t *keys;
|
||||
uint16_t keycode;
|
||||
#ifdef EXTRA_SHORT_COMBOS
|
||||
uint8_t state;
|
||||
#else
|
||||
bool disabled;
|
||||
bool active;
|
||||
# if defined(EXTRA_EXTRA_LONG_COMBOS)
|
||||
uint32_t state;
|
||||
# elif defined(EXTRA_LONG_COMBOS)
|
||||
uint16_t state;
|
||||
# else
|
||||
uint8_t state;
|
||||
# endif
|
||||
#endif
|
||||
combo_state_t state;
|
||||
} combo_t;
|
||||
|
||||
#define COMBO(ck, ca) \
|
||||
@@ -64,14 +69,14 @@ typedef struct combo_t {
|
||||
|
||||
#define COMBO_END 0
|
||||
#ifndef COMBO_TERM
|
||||
# define COMBO_TERM 50
|
||||
# define COMBO_TERM 150
|
||||
#endif
|
||||
#ifndef COMBO_HOLD_TERM
|
||||
# define COMBO_HOLD_TERM TAPPING_TERM
|
||||
#endif
|
||||
|
||||
/* check if keycode is only modifiers */
|
||||
#define KEYCODE_IS_MOD(code) (IS_MODIFIER_KEYCODE(code) || (IS_QK_MODS(code) && !QK_MODS_GET_BASIC_KEYCODE(code)))
|
||||
#define KEYCODE_IS_MOD(code) (IS_MOD(code) || (code >= QK_MODS && code <= QK_MODS_MAX && !(code & QK_BASIC_MAX)))
|
||||
|
||||
bool process_combo(uint16_t keycode, keyrecord_t *record);
|
||||
void combo_task(void);
|
||||
|
||||
@@ -6,20 +6,27 @@ RGB_MATRIX_EFFECT(PIXEL_RAIN)
|
||||
# ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS
|
||||
|
||||
static bool PIXEL_RAIN(effect_params_t* params) {
|
||||
static fast_timer_t timer = 0;
|
||||
static uint16_t index = RGB_MATRIX_LED_COUNT + 1;
|
||||
static uint8_t index = 0;
|
||||
static uint32_t timer = 0;
|
||||
|
||||
if ((params->iter == 0) && (timer_elapsed_fast(timer) > (320 - rgb_matrix_config.speed))) {
|
||||
if (params->iter == 0 && params->init) {
|
||||
index = random8_max(RGB_MATRIX_LED_COUNT);
|
||||
timer = timer_read_fast();
|
||||
}
|
||||
|
||||
RGB_MATRIX_USE_LIMITS(led_min, led_max);
|
||||
if (led_min <= index && index < led_max && HAS_ANY_FLAGS(g_led_config.flags[index], params->flags)) {
|
||||
hsv_t hsv = (random8() & 2) ? (hsv_t){0, 0, 0} : (hsv_t){random8(), random8_min_max(127, 255), rgb_matrix_config.hsv.v};
|
||||
rgb_t rgb = rgb_matrix_hsv_to_rgb(hsv);
|
||||
rgb_matrix_set_color(index, rgb.r, rgb.g, rgb.b);
|
||||
index = RGB_MATRIX_LED_COUNT + 1;
|
||||
if (timer < g_rgb_timer) { // Execute when the delay period has elapsed
|
||||
if (led_min <= index && index < led_max && HAS_ANY_FLAGS(g_led_config.flags[index], params->flags)) {
|
||||
// Assign a random HSV color to hsv with 50% probability, otherwise assign zeroed hsv
|
||||
hsv_t hsv = (random8() & 2) ? (hsv_t){0, 0, 0} : (hsv_t){random8(), random8_min_max(127, 255), rgb_matrix_config.hsv.v};
|
||||
rgb_t rgb = rgb_matrix_hsv_to_rgb(hsv);
|
||||
rgb_matrix_set_color(index, rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
if (!rgb_matrix_check_finished_leds(led_max)) {
|
||||
// In the final LED range, update the LED index and advance the timer for
|
||||
// the next cycle, scaling the delay between 256–2048 ms based on speed.
|
||||
index = random8_max(RGB_MATRIX_LED_COUNT);
|
||||
timer = g_rgb_timer + (2048 - scale16by8(1792, rgb_matrix_config.speed));
|
||||
}
|
||||
}
|
||||
return rgb_matrix_check_finished_leds(led_max);
|
||||
}
|
||||
|
||||
8
tests/combo/combo_active_buffer/config.h
Normal file
8
tests/combo/combo_active_buffer/config.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_BUFFER_LENGTH 2
|
||||
6
tests/combo/combo_active_buffer/test.mk
Normal file
6
tests/combo/combo_active_buffer/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_buffer.c
|
||||
37
tests/combo/combo_active_buffer/test_combo.cpp
Normal file
37
tests/combo/combo_active_buffer/test_combo.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
extern bool combo_override;
|
||||
|
||||
class ComboBuffer : public TestFixture {};
|
||||
|
||||
TEST_F(ComboBuffer, combo_active_buffer_overflow) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_c(0, 2, 0, KC_C);
|
||||
KeymapKey key_d(0, 3, 0, KC_D);
|
||||
KeymapKey key_e(0, 4, 0, KC_E);
|
||||
KeymapKey key_f(0, 5, 0, KC_F);
|
||||
set_keymap({key_a, key_b, key_c, key_d, key_e, key_f});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_2));
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_REPORT(driver, (KC_2, KC_3));
|
||||
EXPECT_REPORT(driver, (KC_3));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b, key_c, key_d, key_e, key_f});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
18
tests/combo/combo_active_buffer/test_combo_buffer.c
Normal file
18
tests/combo/combo_active_buffer/test_combo_buffer.c
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include "stdio.h"
|
||||
|
||||
enum combos { ab_1, cd_2, ef_3 };
|
||||
|
||||
uint16_t const ab_1_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const cd_2_combo[] = {KC_C, KC_D, COMBO_END};
|
||||
uint16_t const ef_3_combo[] = {KC_E, KC_F, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[ab_1] = COMBO(ab_1_combo, KC_1),
|
||||
[cd_2] = COMBO(cd_2_combo, KC_2),
|
||||
[ef_3] = COMBO(ef_3_combo, KC_3),
|
||||
};
|
||||
// clang-format on
|
||||
8
tests/combo/combo_conflicts/config.h
Normal file
8
tests/combo/combo_conflicts/config.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_CONTIGUOUS_PER_COMBO
|
||||
6
tests/combo/combo_conflicts/test.mk
Normal file
6
tests/combo/combo_conflicts/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_conflicts.c
|
||||
132
tests/combo/combo_conflicts/test_combo.cpp
Normal file
132
tests/combo/combo_conflicts/test_combo.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
class ComboConflicts : public TestFixture {};
|
||||
|
||||
TEST_F(ComboConflicts, combo_irrelevant_press) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_x(0, 2, 0, KC_X);
|
||||
KeymapKey key_y(0, 3, 0, KC_Y);
|
||||
KeymapKey key_z(0, 4, 0, KC_Z);
|
||||
set_keymap({key_a, key_b, key_x, key_y, key_z});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_1, KC_Z));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, Z, B in that order
|
||||
// Combo for A+B should be triggered since it does not require contiguity
|
||||
tap_combo({key_a, key_z, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A)).Times(3);
|
||||
EXPECT_REPORT(driver, (KC_A, KC_Z));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, press and release Z, press B in that order; release B then A
|
||||
// Combo for A+B should not be triggered since there was a press+release of Z in between
|
||||
run_one_scan_loop();
|
||||
key_a.press();
|
||||
run_one_scan_loop();
|
||||
key_z.press();
|
||||
run_one_scan_loop();
|
||||
key_z.release();
|
||||
run_one_scan_loop();
|
||||
key_b.press();
|
||||
run_one_scan_loop();
|
||||
key_b.release();
|
||||
run_one_scan_loop();
|
||||
key_a.release();
|
||||
run_one_scan_loop();
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_X));
|
||||
EXPECT_REPORT(driver, (KC_X, KC_Z));
|
||||
EXPECT_REPORT(driver, (KC_X, KC_Z, KC_Y));
|
||||
EXPECT_REPORT(driver, (KC_Z, KC_Y));
|
||||
EXPECT_REPORT(driver, (KC_Y));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, Z, Y in that order; release X, Z, Y
|
||||
// Combo for X+Y should not be triggered since it requires contiguity
|
||||
tap_combo({key_x, key_z, key_y});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboConflicts, combo_priority) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_x(0, 2, 0, KC_X);
|
||||
KeymapKey key_y(0, 3, 0, KC_Y);
|
||||
set_keymap({key_a, key_b, key_x, key_y});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_4)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_4, KC_A));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, A, B, Y in that order
|
||||
// Combo for X+B+Y should be triggered since it has higher priority (index)
|
||||
tap_combo({key_x, key_a, key_b, key_y});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_4)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_4, KC_A));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, Y, A, B in that order
|
||||
// Combo for X+B+Y should be triggered since it has higher priority (index)
|
||||
tap_combo({key_x, key_y, key_a, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboConflicts, combo_priority_trigger) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_x(0, 2, 0, KC_X);
|
||||
KeymapKey key_y(0, 3, 0, KC_Y);
|
||||
set_keymap({key_a, key_b, key_x, key_y});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_3)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_3, KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, X, B, Y in that order
|
||||
// Combo for X+A+Y should be triggered since it has earlier trigger
|
||||
tap_combo({key_a, key_x, key_b, key_y});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboConflicts, combo_wait_for_preferred) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_x(0, 2, 0, KC_X);
|
||||
KeymapKey key_y(0, 3, 0, KC_Y);
|
||||
KeymapKey key_c(0, 4, 0, KC_C);
|
||||
set_keymap({key_a, key_b, key_x, key_y, key_c});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_4)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_4, KC_A));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, A, Y, B in that order
|
||||
// Combo for X+B+Y should be triggered since it has higher priority (index), even though X+A+Y completed before it
|
||||
tap_combo({key_x, key_a, key_y, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_6));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, Y, A, B, C in that order
|
||||
// Combo for X+A+B+C+Y should be triggered sice it has higher priority than the others
|
||||
tap_combo({key_x, key_y, key_a, key_b, key_c});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
33
tests/combo/combo_conflicts/test_combo_conflicts.c
Normal file
33
tests/combo/combo_conflicts/test_combo_conflicts.c
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
|
||||
enum combos { ab_1, xy_2, axy_3, bxy_4, cxy_5, abcxy_6 };
|
||||
|
||||
uint16_t const ab_1_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const xy_2_combo[] = {KC_X, KC_Y, COMBO_END};
|
||||
uint16_t const axy_3_combo[] = {KC_A, KC_X, KC_Y, COMBO_END};
|
||||
uint16_t const bxy_4_combo[] = {KC_B, KC_X, KC_Y, COMBO_END};
|
||||
uint16_t const cxy_5_combo[] = {KC_C, KC_X, KC_Y, COMBO_END};
|
||||
uint16_t const abcxy_6_combo[] = {KC_A, KC_B, KC_C, KC_X, KC_Y, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[ab_1] = COMBO(ab_1_combo, KC_1),
|
||||
[xy_2] = COMBO(xy_2_combo, KC_2),
|
||||
[axy_3] = COMBO(axy_3_combo, KC_3),
|
||||
[bxy_4] = COMBO(bxy_4_combo, KC_4),
|
||||
[cxy_5] = COMBO(cxy_5_combo, KC_5),
|
||||
[abcxy_6] = COMBO(abcxy_6_combo, KC_6)
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
bool is_combo_contiguous(uint16_t index, combo_t *combo, keyrecord_t *record, uint8_t n_unpressed_keys) {
|
||||
switch (index) {
|
||||
case xy_2:
|
||||
case cxy_5:
|
||||
return true; // xy_2 and cxy_5 are contiguous combos
|
||||
default:
|
||||
return false; // no other combos are contiguous
|
||||
}
|
||||
}
|
||||
9
tests/combo/combo_events/config.h
Normal file
9
tests/combo/combo_events/config.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_PROCESS_KEY_RELEASE
|
||||
#define COMBO_SHOULD_TRIGGER
|
||||
6
tests/combo/combo_events/test.mk
Normal file
6
tests/combo/combo_events/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_events.c
|
||||
67
tests/combo/combo_events/test_combo.cpp
Normal file
67
tests/combo/combo_events/test_combo.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
extern bool combo_override;
|
||||
|
||||
class ComboEvents : public TestFixture {};
|
||||
|
||||
TEST_F(ComboEvents, combo_action) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 1, KC_A);
|
||||
KeymapKey key_b(0, 0, 2, KC_B);
|
||||
set_keymap({key_a, key_b});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver).Times(2);
|
||||
tap_combo({key_a, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboEvents, combo_release) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_x(0, 0, 1, KC_X);
|
||||
KeymapKey key_y(0, 0, 2, KC_Y);
|
||||
set_keymap({key_x, key_y});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1)).Times(3);
|
||||
EXPECT_REPORT(driver, (KC_1, KC_2));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_3));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_x, key_y});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
// Combo deactivates early when y is released
|
||||
EXPECT_REPORT(driver, (KC_1)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_1, KC_3));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_y, key_x});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboEvents, combo_should_trigger) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 1, KC_A);
|
||||
KeymapKey key_b(0, 0, 2, KC_B);
|
||||
set_keymap({key_a, key_b});
|
||||
|
||||
combo_override = true;
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
combo_override = false;
|
||||
}
|
||||
50
tests/combo/combo_events/test_combo_events.c
Normal file
50
tests/combo/combo_events/test_combo_events.c
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include "stdio.h"
|
||||
|
||||
enum combos { ab_action, xy_123 };
|
||||
|
||||
uint16_t const ab_action_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const xy_123_combo[] = {KC_X, KC_Y, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[ab_action] = COMBO_ACTION(ab_action_combo),
|
||||
[xy_123] = COMBO(xy_123_combo, KC_1),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
void process_combo_event(uint16_t combo_index, bool pressed) {
|
||||
switch (combo_index) {
|
||||
case ab_action:
|
||||
if (pressed) {
|
||||
tap_code(KC_1);
|
||||
} else {
|
||||
tap_code(KC_2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) {
|
||||
switch (combo_index) {
|
||||
case xy_123:
|
||||
switch (keycode) {
|
||||
case KC_X:
|
||||
tap_code(KC_2);
|
||||
break;
|
||||
case KC_Y:
|
||||
tap_code(KC_3);
|
||||
return true; // deactivate combo
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool combo_override = false;
|
||||
|
||||
bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) {
|
||||
return !combo_override;
|
||||
}
|
||||
10
tests/combo/combo_hold_tap/config.h
Normal file
10
tests/combo/combo_hold_tap/config.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_MUST_HOLD_PER_COMBO
|
||||
#define COMBO_MUST_TAP_PER_COMBO
|
||||
#define COMBO_HOLD_TERM 100
|
||||
6
tests/combo/combo_hold_tap/test.mk
Normal file
6
tests/combo/combo_hold_tap/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_hold_tap.c
|
||||
53
tests/combo/combo_hold_tap/test_combo.cpp
Normal file
53
tests/combo/combo_hold_tap/test_combo.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
class ComboHoldTap : public TestFixture {};
|
||||
|
||||
TEST_F(ComboHoldTap, combo_hold) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 1, KC_A);
|
||||
KeymapKey key_b(0, 0, 2, KC_B);
|
||||
set_keymap({key_a, key_b});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b}, COMBO_HOLD_TERM + 1);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b}, COMBO_HOLD_TERM - 1);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboHoldTap, combo_tap) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_x(0, 0, 1, KC_X);
|
||||
KeymapKey key_y(0, 0, 2, KC_Y);
|
||||
set_keymap({key_x, key_y});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_x, key_y}, COMBO_HOLD_TERM - 1);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_X));
|
||||
EXPECT_REPORT(driver, (KC_X, KC_Y));
|
||||
EXPECT_REPORT(driver, (KC_Y));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_x, key_y}, COMBO_HOLD_TERM + 1);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
34
tests/combo/combo_hold_tap/test_combo_hold_tap.c
Normal file
34
tests/combo/combo_hold_tap/test_combo_hold_tap.c
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include "stdio.h"
|
||||
|
||||
enum combos { ab_1, xy_2 };
|
||||
|
||||
uint16_t const ab_1_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const xy_2_combo[] = {KC_X, KC_Y, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[ab_1] = COMBO(ab_1_combo, KC_1),
|
||||
[xy_2] = COMBO(xy_2_combo, KC_2),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
bool get_combo_must_hold(uint16_t combo_index, combo_t *combo) {
|
||||
switch (combo_index) {
|
||||
case ab_1:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get_combo_must_tap(uint16_t combo_index, combo_t *combo) {
|
||||
switch (combo_index) {
|
||||
case xy_2:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
9
tests/combo/combo_key_buffer/config.h
Normal file
9
tests/combo/combo_key_buffer/config.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_KEY_BUFFER_LENGTH 4
|
||||
#define COMBO_CONTIGUOUS_PER_COMBO
|
||||
6
tests/combo/combo_key_buffer/test.mk
Normal file
6
tests/combo/combo_key_buffer/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_key_buffer.c
|
||||
103
tests/combo/combo_key_buffer/test_combo.cpp
Normal file
103
tests/combo/combo_key_buffer/test_combo.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
extern bool combo_override;
|
||||
|
||||
class ComboKeyBuffer : public TestFixture {};
|
||||
|
||||
TEST_F(ComboKeyBuffer, combo_key_buffer) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_i(0, 2, 0, KC_I);
|
||||
KeymapKey key_j(0, 3, 0, KC_J);
|
||||
KeymapKey key_k(0, 4, 0, KC_K);
|
||||
KeymapKey key_a2(0, 5, 0, KC_A);
|
||||
set_keymap({key_a, key_b, key_i, key_j, key_k, key_a2});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I, KC_J));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_J));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_i, key_j, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
// buffer overflow prevents combo from firing
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J, KC_K));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J, KC_K, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_I, KC_J, KC_K, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_J, KC_K, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_K, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_i, key_j, key_k, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
// buffer overflow prevents combo from firing initially, fires with a second key press
|
||||
EXPECT_REPORT(driver, (KC_A)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J, KC_K));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_I, KC_J, KC_K, KC_1));
|
||||
// the first KC_A release is consumed by the active combo even though it is a different position
|
||||
EXPECT_REPORT(driver, (KC_A, KC_J, KC_K, KC_1));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_K, KC_1));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_1));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
for (KeymapKey key : {key_a, key_i, key_j, key_k, key_b, key_a2}) {
|
||||
key.press();
|
||||
run_one_scan_loop();
|
||||
}
|
||||
for (KeymapKey key : {key_a, key_i, key_j, key_k, key_b, key_a2}) {
|
||||
key.release();
|
||||
run_one_scan_loop();
|
||||
}
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboKeyBuffer, combo_key_buffer_blocked) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_i(0, 2, 0, KC_I);
|
||||
KeymapKey key_j(0, 3, 0, KC_J);
|
||||
KeymapKey key_k(0, 4, 0, KC_K);
|
||||
KeymapKey key_x(0, 5, 0, KC_X);
|
||||
set_keymap({key_a, key_b, key_i, key_j, key_k, key_x});
|
||||
|
||||
// If the key buffer doesn't overflow, we wait for ABX to fire
|
||||
EXPECT_REPORT(driver, (KC_2)).Times(2);
|
||||
EXPECT_REPORT(driver, (KC_I, KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b, key_i, key_x});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
// If the key buffer overflows, we just use AB
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I, KC_J));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I, KC_J, KC_K));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_I, KC_J, KC_K, KC_X));
|
||||
EXPECT_REPORT(driver, (KC_I, KC_J, KC_K, KC_X));
|
||||
EXPECT_REPORT(driver, (KC_J, KC_K, KC_X));
|
||||
EXPECT_REPORT(driver, (KC_K, KC_X));
|
||||
EXPECT_REPORT(driver, (KC_X));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
tap_combo({key_a, key_b, key_i, key_j, key_k, key_x});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
20
tests/combo/combo_key_buffer/test_combo_key_buffer.c
Normal file
20
tests/combo/combo_key_buffer/test_combo_key_buffer.c
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include "stdio.h"
|
||||
|
||||
enum combos { ab_1, abx_2 };
|
||||
|
||||
uint16_t const ab_1_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const abx_2_combo[] = {KC_A, KC_B, KC_X, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[ab_1] = COMBO(ab_1_combo, KC_1),
|
||||
[abx_2] = COMBO(abx_2_combo, KC_2),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
bool is_combo_contiguous(uint16_t index, combo_t *combo, keyrecord_t *record, uint8_t n_unpressed_keys) {
|
||||
return false; // No combos are contiguous in this test
|
||||
}
|
||||
8
tests/combo/combo_order/config.h
Normal file
8
tests/combo/combo_order/config.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#define COMBO_MUST_PRESS_IN_ORDER_PER_COMBO
|
||||
6
tests/combo/combo_order/test.mk
Normal file
6
tests/combo/combo_order/test.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2025 @johnwilmes
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
COMBO_ENABLE = yes
|
||||
|
||||
INTROSPECTION_KEYMAP_C = test_combo_order.c
|
||||
70
tests/combo/combo_order/test_combo.cpp
Normal file
70
tests/combo/combo_order/test_combo.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_common.hpp"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
using testing::_;
|
||||
using testing::InSequence;
|
||||
|
||||
class ComboOrder : public TestFixture {};
|
||||
|
||||
TEST_F(ComboOrder, combo_requires_order) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_c(0, 2, 0, KC_C);
|
||||
set_keymap({key_a, key_b, key_c});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, B, C in that order
|
||||
// triggers ABC combo
|
||||
tap_combo({key_a, key_b, key_c});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press C, A, B in that order; release B, A, C
|
||||
// triggers CAB combo in order
|
||||
tap_combo({key_c, key_a, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_C));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_C, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_C, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, C, B
|
||||
// does not trigger any combo
|
||||
tap_combo({key_a, key_c, key_b});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(ComboOrder, combo_doesnt_require_order) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_x(0, 0, 0, KC_X);
|
||||
KeymapKey key_y(0, 1, 0, KC_Y);
|
||||
KeymapKey key_z(0, 2, 0, KC_Z);
|
||||
set_keymap({key_x, key_y, key_z});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_3));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press X, Y, Z in that order
|
||||
// triggers XYZ combo
|
||||
tap_combo({key_x, key_y, key_z});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
|
||||
EXPECT_REPORT(driver, (KC_3));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press Y, X, Z in that order
|
||||
// triggers XYZ combo
|
||||
tap_combo({key_y, key_x, key_z});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
30
tests/combo/combo_order/test_combo_order.c
Normal file
30
tests/combo/combo_order/test_combo_order.c
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 @johnwilmes
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include "stdio.h"
|
||||
|
||||
enum combos { abc_1, cab_2, xyz_3 };
|
||||
|
||||
uint16_t const abc_1_combo[] = {KC_A, KC_B, KC_C, COMBO_END};
|
||||
uint16_t const cab_2_combo[] = {KC_C, KC_A, KC_B, COMBO_END};
|
||||
uint16_t const xyz_3_combo[] = {KC_X, KC_Y, KC_Z, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[abc_1] = COMBO(abc_1_combo, KC_1),
|
||||
[cab_2] = COMBO(cab_2_combo, KC_2),
|
||||
[xyz_3] = COMBO(xyz_3_combo, KC_3)
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) {
|
||||
switch (combo_index) {
|
||||
// these two must be pressed in order
|
||||
case abc_1:
|
||||
case cab_2:
|
||||
return true;
|
||||
default:
|
||||
// xyz does not require pressing in order
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6,3 +6,5 @@
|
||||
#include "test_common.h"
|
||||
|
||||
#define TAPPING_TERM 200
|
||||
#define COMBO_TERM 40
|
||||
#define COMBO_COMPRESSED
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "keyboard_report_util.hpp"
|
||||
#include "keycode.h"
|
||||
#include "test_common.h"
|
||||
#include "test_driver.hpp"
|
||||
#include "test_common.hpp"
|
||||
#include "test_fixture.hpp"
|
||||
#include "test_keymap_key.hpp"
|
||||
|
||||
@@ -14,6 +13,117 @@ using testing::InSequence;
|
||||
|
||||
class Combo : public TestFixture {};
|
||||
|
||||
TEST_F(Combo, combo_basic) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
set_keymap({key_a, key_b});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press key A, wait for less than COMBO_TERM, then press key B
|
||||
run_one_scan_loop(); // Ensure that combo timer is > 0
|
||||
key_a.press();
|
||||
idle_for(COMBO_TERM - 1);
|
||||
key_b.press();
|
||||
run_one_scan_loop();
|
||||
key_a.release();
|
||||
run_one_scan_loop();
|
||||
key_b.release();
|
||||
run_one_scan_loop();
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_too_slow) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
set_keymap({key_a, key_b});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_B));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press key A, wait for more than COMBO_TERM, then press key B
|
||||
run_one_scan_loop(); // Ensure that combo timer is > 0
|
||||
key_a.press();
|
||||
idle_for(COMBO_TERM + 1);
|
||||
key_b.press();
|
||||
run_one_scan_loop();
|
||||
key_a.release();
|
||||
run_one_scan_loop();
|
||||
key_b.release();
|
||||
run_one_scan_loop();
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_release_interrupt) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_c(0, 2, 0, KC_C);
|
||||
KeymapKey key_d(0, 3, 0, KC_D);
|
||||
set_keymap({key_a, key_c, key_d});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_2));
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, C, D in that order; release A after C
|
||||
// Should still trigger combo for C+D
|
||||
run_one_scan_loop();
|
||||
key_a.press();
|
||||
run_one_scan_loop();
|
||||
key_c.press();
|
||||
run_one_scan_loop();
|
||||
key_a.release();
|
||||
run_one_scan_loop();
|
||||
key_d.press();
|
||||
run_one_scan_loop();
|
||||
key_c.release();
|
||||
run_one_scan_loop();
|
||||
key_d.release();
|
||||
run_one_scan_loop();
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_disjoint) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_c(0, 2, 0, KC_C);
|
||||
KeymapKey key_d(0, 3, 0, KC_D);
|
||||
set_keymap({key_a, key_b, key_c, key_d});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_1));
|
||||
EXPECT_REPORT(driver, (KC_1, KC_2));
|
||||
EXPECT_REPORT(driver, (KC_2));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, B, C, D in that order; trigger combos for A+B and C+D
|
||||
tap_combo({key_a, key_b, key_c, key_d});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_noncontiguous) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_a(0, 0, 0, KC_A);
|
||||
KeymapKey key_b(0, 1, 0, KC_B);
|
||||
KeymapKey key_c(0, 2, 0, KC_C);
|
||||
KeymapKey key_d(0, 3, 0, KC_D);
|
||||
set_keymap({key_a, key_b, key_c, key_d});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_A));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_C));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_C, KC_B));
|
||||
EXPECT_REPORT(driver, (KC_A, KC_C, KC_B, KC_D));
|
||||
EXPECT_REPORT(driver, (KC_C, KC_B, KC_D));
|
||||
EXPECT_REPORT(driver, (KC_B, KC_D));
|
||||
EXPECT_REPORT(driver, (KC_D));
|
||||
EXPECT_EMPTY_REPORT(driver);
|
||||
// Press A, C, B, D in that order; don't trigger any combos
|
||||
tap_combo({key_a, key_c, key_b, key_d});
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_modtest_tapped) {
|
||||
TestDriver driver;
|
||||
KeymapKey key_y(0, 0, 1, KC_Y);
|
||||
@@ -54,3 +164,17 @@ TEST_F(Combo, combo_osmshift_tapped) {
|
||||
tap_key(key_i);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
TEST_F(Combo, combo_single_key) {
|
||||
// https://github.com/qmk/qmk_firmware/issues/25197
|
||||
TestDriver driver;
|
||||
KeymapKey key_t(0, 0, 0, KC_T);
|
||||
set_keymap({key_t});
|
||||
|
||||
EXPECT_REPORT(driver, (KC_3)).Times(3);
|
||||
EXPECT_EMPTY_REPORT(driver).Times(3);
|
||||
tap_combo({key_t}, 1);
|
||||
tap_combo({key_t}, 1);
|
||||
tap_combo({key_t}, 1);
|
||||
VERIFY_AND_CLEAR(driver);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
// Copyright 2023 @filterpaper
|
||||
// Copyright 2023 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "quantum.h"
|
||||
#include <quantum.h>
|
||||
|
||||
enum combos { modtest, osmshift };
|
||||
enum combos { modtest, osmshift, ab_1, cd_2, t_3 };
|
||||
|
||||
uint16_t const modtest_combo[] = {KC_Y, KC_U, COMBO_END};
|
||||
uint16_t const osmshift_combo[] = {KC_Z, KC_X, COMBO_END};
|
||||
uint16_t const ab_1_combo[] = {KC_A, KC_B, COMBO_END};
|
||||
uint16_t const cd_2_combo[] = {KC_C, KC_D, COMBO_END};
|
||||
uint16_t const t_3_combo[] = {KC_T, COMBO_END};
|
||||
|
||||
// clang-format off
|
||||
combo_t key_combos[] = {
|
||||
[modtest] = COMBO(modtest_combo, RSFT_T(KC_SPACE)),
|
||||
[osmshift] = COMBO(osmshift_combo, OSM(MOD_LSFT))
|
||||
[osmshift] = COMBO(osmshift_combo, OSM(MOD_LSFT)),
|
||||
[ab_1] = COMBO(ab_1_combo, KC_1),
|
||||
[cd_2] = COMBO(cd_2_combo, KC_2),
|
||||
[t_3] = COMBO(t_3_combo, KC_3),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
Reference in New Issue
Block a user