diff options
author | Ryan <[email protected]> | 2024-06-02 12:42:24 +1000 |
---|---|---|
committer | GitHub <[email protected]> | 2024-06-02 12:42:24 +1000 |
commit | 78a0adfbb4d2c4e12f93f2a62ded0020d406243e (patch) | |
tree | e6e123b36569559233025add654580e090dc0154 /docs/features | |
parent | fa6d23235bf429446250cd5212e209d5fbfdbac2 (diff) | |
download | qmk_firmware-78a0adfbb4d2c4e12f93f2a62ded0020d406243e.tar.gz qmk_firmware-78a0adfbb4d2c4e12f93f2a62ded0020d406243e.zip |
[docs] Organize driver & feature docs into subfolders (#23848)
Co-authored-by: Nick Brassel <[email protected]>
Diffstat (limited to 'docs/features')
45 files changed, 12085 insertions, 0 deletions
diff --git a/docs/features/audio.md b/docs/features/audio.md new file mode 100644 index 0000000000..2f5e5b2d98 --- /dev/null +++ b/docs/features/audio.md @@ -0,0 +1,377 @@ +# Audio + +Your keyboard can make sounds! If you've got a spare pin you can hook up a simple speaker and make it beep. You can use those beeps to indicate layer transitions, modifiers, special keys, or just to play some funky 8bit tunes. + +To activate this feature, add `AUDIO_ENABLE = yes` to your `rules.mk`. + +## AVR based boards +On Atmega32U4 based boards, up to two simultaneous tones can be rendered. +With one speaker connected to a PWM capable pin on PORTC driven by timer 3 and the other on one of the PWM pins on PORTB driven by timer 1. + +The following pins can be configured as audio outputs in `config.h` - for one speaker set either one out of: + +* `#define AUDIO_PIN C4` +* `#define AUDIO_PIN C5` +* `#define AUDIO_PIN C6` +* `#define AUDIO_PIN B5` +* `#define AUDIO_PIN B6` +* `#define AUDIO_PIN B7` + +and *optionally*, for a second speaker, one of: +* `#define AUDIO_PIN_ALT B5` +* `#define AUDIO_PIN_ALT B6` +* `#define AUDIO_PIN_ALT B7` + +### Wiring +per speaker is - for example with a piezo buzzer - the black lead to Ground, and the red lead connected to the selected AUDIO_PIN for the primary; and similarly with AUDIO_PIN_ALT for the secondary. + + +## ARM based boards +for more technical details, see the notes on [Audio driver](../drivers/audio). + +<!-- because I'm not sure where to fit this in: https://waveeditonline.com/ --> +### DAC (basic) +Most STM32 MCUs have DAC peripherals, with a notable exception of the STM32F1xx series. Generally, the DAC peripheral drives pins A4 or A5. To enable DAC-based audio output on STM32 devices, add `AUDIO_DRIVER = dac_basic` to `rules.mk` and set in `config.h` either: + +`#define AUDIO_PIN A4` or `#define AUDIO_PIN A5` + +the other DAC channel can optionally be used with a secondary speaker, just set: + +`#define AUDIO_PIN_ALT A4` or `#define AUDIO_PIN_ALT A5` + +Do note though that the dac_basic driver is only capable of reproducing one tone per speaker/channel at a time, for more tones simultaneously, try the dac_additive driver. + +#### Wiring: +for two piezos, for example configured as `AUDIO_PIN A4` and `AUDIO_PIN_ALT A5` would be: red lead to A4 and black to Ground, and similarly with the second one: A5 = red, and Ground = black + +another alternative is to drive *one* piezo with both DAC pins - for an extra "push". +wiring red to A4 and black to A5 (or the other way round) and add `#define AUDIO_PIN_ALT_AS_NEGATIVE` to `config.h` + +##### Proton-C Example: +The Proton-C comes (optionally) with one 'builtin' piezo, which is wired to A4+A5. +For this board `config.h` would include these defines: + +```c +#define AUDIO_PIN A5 +#define AUDIO_PIN_ALT A4 +#define AUDIO_PIN_ALT_AS_NEGATIVE +``` + +### DAC (additive) +Another option, besides dac_basic (which produces sound through a square-wave), is to use the DAC to do additive wave synthesis. +With a number of predefined wave-forms or by providing your own implementation to generate samples on the fly. +To use this feature set `AUDIO_DRIVER = dac_additive` in your `rules.mk`, and select in `config.h` EITHER `#define AUDIO_PIN A4` or `#define AUDIO_PIN A5`. + +The used waveform *defaults* to sine, but others can be selected by adding one of the following defines to `config.h`: + +* `#define AUDIO_DAC_SAMPLE_WAVEFORM_SINE` +* `#define AUDIO_DAC_SAMPLE_WAVEFORM_TRIANGLE` +* `#define AUDIO_DAC_SAMPLE_WAVEFORM_TRAPEZOID` +* `#define AUDIO_DAC_SAMPLE_WAVEFORM_SQUARE` + +Should you rather choose to generate and use your own sample-table with the DAC unit, implement `uint16_t dac_value_generate(void)` with your keyboard - for an example implementation see keyboards/planck/keymaps/synth_sample or keyboards/planck/keymaps/synth_wavetable + + +### PWM (software) +if the DAC pins are unavailable (or the MCU has no usable DAC at all, like STM32F1xx); PWM can be an alternative. +Note that there is currently only one speaker/pin supported. + +set in `rules.mk`: + +`AUDIO_DRIVER = pwm_software` and in `config.h`: +`#define AUDIO_PIN C13` (can be any pin) to have the selected pin output a pwm signal, generated from a timer callback which toggles the pin in software. + +#### Wiring +the usual piezo wiring: red goes to the selected AUDIO_PIN, black goes to ground. + +OR if you can chose to drive one piezo with two pins, for example `#define AUDIO_PIN B1`, `#define AUDIO_PIN_ALT B2` in `config.h`, with `#define AUDIO_PIN_ALT_AS_NEGATIVE` - then the red lead could go to B1, the black to B2. + +### PWM (hardware) +STM32F1xx have to fall back to using PWM, but can do so in hardware; but again on currently only one speaker/pin. + +`AUDIO_DRIVER = pwm_hardware` in `rules.mk`, and in `config.h`: +`#define AUDIO_PIN A8` +`#define AUDIO_PWM_DRIVER PWMD1` +`#define AUDIO_PWM_CHANNEL 1` +(as well as `#define AUDIO_PWM_PAL_MODE 42` if you are on STM32F2 or larger) +which will use Timer 1 to directly drive pin PA8 through the PWM hardware (TIM1_CH1 = PA8). +Should you want to use the pwm-hardware on another pin and timer - be ready to dig into the STM32 data-sheet to pick the right TIMx_CHy and pin-alternate function. + + +## Tone Multiplexing +Since most drivers can only render one tone per speaker at a time (with the one exception: arm dac-additive) there also exists a "workaround-feature" that does time-slicing/multiplexing - which does what the name implies: cycle through a set of active tones (e.g. when playing chords in Music Mode) at a given rate, and put one tone at a time out through the one/few speakers that are available. + +To enable this feature, and configure a starting-rate, add the following defines to `config.h`: +```c +#define AUDIO_ENABLE_TONE_MULTIPLEXING +#define AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT 10 +``` + +The audio core offers interface functions to get/set/change the tone multiplexing rate from within `keymap.c`. + + +## Songs +There's a couple of different sounds that will automatically be enabled without any other configuration: +``` +STARTUP_SONG // plays when the keyboard starts up (audio.c) +GOODBYE_SONG // plays when you press the QK_BOOT key (quantum.c) +AG_NORM_SONG // plays when you press AG_NORM (quantum.c) +AG_SWAP_SONG // plays when you press AG_SWAP (quantum.c) +CG_NORM_SONG // plays when you press CG_NORM (quantum.c) +CG_SWAP_SONG // plays when you press CG_SWAP (quantum.c) +MUSIC_ON_SONG // plays when music mode is activated (process_music.c) +MUSIC_OFF_SONG // plays when music mode is deactivated (process_music.c) +CHROMATIC_SONG // plays when the chromatic music mode is selected (process_music.c) +GUITAR_SONG // plays when the guitar music mode is selected (process_music.c) +VIOLIN_SONG // plays when the violin music mode is selected (process_music.c) +MAJOR_SONG // plays when the major music mode is selected (process_music.c) +``` + +You can override the default songs by doing something like this in your `config.h`: + +```c +#ifdef AUDIO_ENABLE +# define STARTUP_SONG SONG(STARTUP_SOUND) +#endif +``` + +A full list of sounds can be found in [quantum/audio/song_list.h](https://github.com/qmk/qmk_firmware/blob/master/quantum/audio/song_list.h) - feel free to add your own to this list! All available notes can be seen in [quantum/audio/musical_notes.h](https://github.com/qmk/qmk_firmware/blob/master/quantum/audio/musical_notes.h). + +Additionally, if you with to maintain your own list of songs (such as ones that may be copyrighted) and not have them added to the repo, you can create a `user_song_list.h` file and place it in your keymap (or userspace) folder. This file will be automatically included, it just needs to exist. + +To play a custom sound at a particular time, you can define a song like this (near the top of the file): + +```c +float my_song[][2] = SONG(QWERTY_SOUND); +``` + +And then play your song like this: + +```c +PLAY_SONG(my_song); +``` + +Alternatively, you can play it in a loop like this: + +```c +PLAY_LOOP(my_song); +``` + +It's advised that you wrap all audio features in `#ifdef AUDIO_ENABLE` / `#endif` to avoid causing problems when audio isn't built into the keyboard. + +The available keycodes for audio are: + +|Key |Aliases |Description | +|-------------------------|---------|-------------------------------------------| +|`QK_AUDIO_ON` |`AU_ON` |Turns on Audio Feature | +|`QK_AUDIO_OFF` |`AU_OFF` |Turns off Audio Feature | +|`QK_AUDIO_TOGGLE` |`AU_TOGG`|Toggles Audio state | + +::: warning +These keycodes turn all of the audio functionality on and off. Turning it off means that audio feedback, audio clicky, music mode, etc. are disabled, completely. +::: + +## Audio Config + +| Settings | Default | Description | +|----------------------------------|----------------------|---------------------------------------------------------------------------------------------| +|`AUDIO_PIN` | *Not defined* |Configures the pin that the speaker is connected to. | +|`AUDIO_PIN_ALT` | *Not defined* |Configures the pin for a second speaker or second pin connected to one speaker. | +|`AUDIO_PIN_ALT_AS_NEGATIVE` | *Not defined* |Enables support for one speaker connected to two pins. | +|`AUDIO_INIT_DELAY` | *Not defined* |Enables delay during startup song to accomidate for USB startup issues. | +|`AUDIO_ENABLE_TONE_MULTIPLEXING` | *Not defined* |Enables time splicing/multiplexing to create multiple tones simutaneously. | +|`AUDIO_POWER_CONTROL_PIN` | *Not defined* |Enables power control code to enable or cut off power to speaker (such as with PAM8302 amp). | +|`AUDIO_POWER_CONTROL_PIN_ON_STATE`| `1` |The state of the audio power control pin when audio is "on" - `1` for high, `0` for low. | +|`STARTUP_SONG` | `STARTUP_SOUND` |Plays when the keyboard starts up (audio.c) | +|`GOODBYE_SONG` | `GOODBYE_SOUND` |Plays when you press the QK_BOOT key (quantum.c) | +|`AG_NORM_SONG` | `AG_NORM_SOUND` |Plays when you press AG_NORM (process_magic.c) | +|`AG_SWAP_SONG` | `AG_SWAP_SOUND` |Plays when you press AG_SWAP (process_magic.c) | +|`CG_NORM_SONG` | `AG_NORM_SOUND` |Plays when you press CG_NORM (process_magic.c) | +|`CG_SWAP_SONG` | `AG_SWAP_SOUND` |Plays when you press CG_SWAP (process_magic.c) | +|`MUSIC_ON_SONG` | `MUSIC_ON_SOUND` |Plays when music mode is activated (process_music.c) | +|`MUSIC_OFF_SONG` | `MUSIC_OFF_SOUND` |Plays when music mode is deactivated (process_music.c) | +|`MIDI_ON_SONG` | `MUSIC_ON_SOUND` |Plays when midi mode is activated (process_music.c) | +|`MIDI_OFF_SONG` | `MUSIC_OFF_SOUND` |Plays when midi mode is deactivated (process_music.c) | +|`CHROMATIC_SONG` | `CHROMATIC_SOUND` |Plays when the chromatic music mode is selected (process_music.c) | +|`GUITAR_SONG` | `GUITAR_SOUND` |Plays when the guitar music mode is selected (process_music.c) | +|`VIOLIN_SONG` | `VIOLIN_SOUND` |Plays when the violin music mode is selected (process_music.c) | +|`MAJOR_SONG` | `MAJOR_SOUND` |Plays when the major music mode is selected (process_music.c) | +|`DEFAULT_LAYER_SONGS` | *Not defined* |Plays song when switched default layers with [`set_single_persistent_default_layer(layer)`](../ref_functions#setting-the-persistent-default-layer)(quantum.c). | +|`SENDSTRING_BELL` | *Not defined* |Plays chime when the "enter" ("\a") character is sent (send_string.c) | + +## Tempo +the 'speed' at which SONGs are played is dictated by the set Tempo, which is measured in beats-per-minute. Note lengths are defined relative to that. +The initial/default tempo is set to 120 bpm, but can be configured by setting `TEMPO_DEFAULT` in `config.c`. +There is also a set of functions to modify the tempo from within the user/keymap code: +```c +void audio_set_tempo(uint8_t tempo); +void audio_increase_tempo(uint8_t tempo_change); +void audio_decrease_tempo(uint8_t tempo_change); +``` + +## ARM Audio Volume + +For ARM devices, you can adjust the DAC sample values. If your board is too loud for you or your coworkers, you can set the max using `AUDIO_DAC_SAMPLE_MAX` in your `config.h`: + +```c +#define AUDIO_DAC_SAMPLE_MAX 4095U +``` +the DAC usually runs in 12Bit mode, hence a volume of 100% = 4095U + +Note: this only adjusts the volume aka 'works' if you stick to WAVEFORM_SQUARE, since its samples are generated on the fly - any other waveform uses a hardcoded/precomputed sample-buffer. + +## Voices +Aka "audio effects", different ones can be enabled by setting in `config.h` these defines: +`#define AUDIO_VOICES` to enable the feature, and `#define AUDIO_VOICE_DEFAULT something` to select a specific effect +for details see quantum/audio/voices.h and .c + +Keycodes available: + +|Key |Aliases |Description | +|-------------------------|---------|-------------------------------------------| +|`QK_AUDIO_VOICE_NEXT` |`AU_NEXT`|Cycles through the audio voices | +|`QK_AUDIO_VOICE_PREVIOUS`|`AU_PREV`|Cycles through the audio voices in reverse | + +## Music Mode + +The music mode maps your columns to a chromatic scale, and your rows to octaves. This works best with ortholinear keyboards, but can be made to work with others. All keycodes less than `0xFF` get blocked, so you won't type while playing notes - if you have special keys/mods, those will still work. A work-around for this is to jump to a different layer with KC_NOs before (or after) enabling music mode. + +Recording is experimental due to some memory issues - if you experience some weird behavior, unplugging/replugging your keyboard will fix things. + +Keycodes available: + +|Key |Aliases |Description | +|-------------------------|---------|-------------------------------------------| +|`QK_MUSIC_ON` |`MU_ON` |Turns on Music Mode | +|`QK_MUSIC_OFF` |`MU_OFF` |Turns off Music Mode | +|`QK_MUSIC_TOGGLE` |`MU_TOGG`|Toggles Music Mode | +|`QK_MUSIC_MODE_NEXT` |`MU_NEXT`|Cycles through the music modes | + +Available Modes: + * `CHROMATIC_MODE` - Chromatic scale, row changes the octave + * `GUITAR_MODE` - Chromatic scale, but the row changes the string (+5 st) + * `VIOLIN_MODE` - Chromatic scale, but the row changes the string (+7 st) + * `MAJOR_MODE` - Major scale + +In music mode, the following keycodes work differently, and don't pass through: + +* `LCTL` - start a recording +* `LALT` - stop recording/stop playing +* `LGUI` - play recording +* `KC_UP` - speed-up playback +* `KC_DOWN` - slow-down playback + +The pitch standard (`PITCH_STANDARD_A`) is 440.0f by default - to change this, add something like this to your `config.h`: + +```c +#define PITCH_STANDARD_A 432.0f +``` + +You can completely disable Music Mode as well. This is useful, if you're pressed for space on your controller. To disable it, add this to your `config.h`: + +```c +#define NO_MUSIC_MODE +``` + +### Music Mask + +By default, `MUSIC_MASK` is set to `keycode < 0xFF` which means keycodes less than `0xFF` are turned into notes, and don't output anything. You can change this by defining this in your `config.h` like this: + +```c +#define MUSIC_MASK keycode != KC_NO +``` + +Which will capture all keycodes - be careful, this will get you stuck in music mode until you restart your keyboard! + +For a more advanced way to control which keycodes should still be processed, you can use `music_mask_kb(keycode)` in `<keyboard>.c` and `music_mask_user(keycode)` in your `keymap.c`: + +```c + bool music_mask_user(uint16_t keycode) { + switch (keycode) { + case RAISE: + case LOWER: + return false; + default: + return true; + } + } +``` + +Things that return false are not part of the mask, and are always processed. + +### Music Map + +By default, the Music Mode uses the columns and row to determine the scale for the keys. For a board that uses a rectangular matrix that matches the keyboard layout, this is just fine. However, for boards that use a more complicated matrix (such as the Planck Rev6, or many split keyboards) this would result in a very skewed experience. + +However, the Music Map option allows you to remap the scaling for the music mode, so it fits the layout, and is more natural. + +To enable this feature, add `#define MUSIC_MAP` to your `config.h` file, and then you will want to add a `uint8_t music_map` to your keyboard's `c` file, or your `keymap.c`. + +```c +const uint8_t music_map[MATRIX_ROWS][MATRIX_COLS] = LAYOUT_ortho_4x12( + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +); +``` + +You will want to use whichever `LAYOUT` macro that your keyboard uses here. This maps it to the correct key location. Start in the bottom left of the keyboard layout, and move to the right, and then upwards. Fill in all the entries until you have a complete matrix. + +You can look at the [Planck Keyboard](https://github.com/qmk/qmk_firmware/blob/e9ace1487887c1f8b4a7e8e6d87c322988bec9ce/keyboards/planck/planck.c#L24-L29) as an example of how to implement this. + +## Audio Click + +This adds a click sound each time you hit a button, to simulate click sounds from the keyboard. And the sounds are slightly different for each keypress, so it doesn't sound like a single long note, if you type rapidly. + +Keycodes available: + +|Key |Aliases |Description | +|-------------------------|---------|-------------------------------------------| +|`QK_AUDIO_CLICKY_TOGGLE` |`CK_TOGG`|Toggles Audio clicky mode | +|`QK_AUDIO_CLICKY_ON` |`CK_ON` |Turns on Audio clicky mode | +|`QK_AUDIO_CLICKY_OFF` |`CK_OFF` |Turns on Audio clicky mode | +|`QK_AUDIO_CLICKY_UP` |`CK_UP` |Increases frequency of the clicks | +|`QK_AUDIO_CLICKY_DOWN` |`CK_DOWN`|Decreases frequency of the clicks | +|`QK_AUDIO_CLICKY_RESET` |`CK_RST` |Resets frequency to default | + +The feature is disabled by default, to save space. To enable it, add this to your `config.h`: + +```c +#define AUDIO_CLICKY +``` + +You can configure the default, min and max frequencies, the stepping and built in randomness by defining these values: + +| Option | Default Value | Description | +|--------|---------------|-------------| +| `AUDIO_CLICKY_FREQ_DEFAULT` | 440.0f | Sets the default/starting audio frequency for the clicky sounds. | +| `AUDIO_CLICKY_FREQ_MIN` | 65.0f | Sets the lowest frequency (under 60f are a bit buggy). | +| `AUDIO_CLICKY_FREQ_MAX` | 1500.0f | Sets the highest frequency. Too high may result in coworkers attacking you. | +| `AUDIO_CLICKY_FREQ_FACTOR` | 1.18921f| Sets the stepping of UP/DOWN key codes. This is a multiplicative factor. The default steps the frequency up/down by a musical minor third. | +| `AUDIO_CLICKY_FREQ_RANDOMNESS` | 0.05f | Sets a factor of randomness for the clicks, Setting this to `0f` will make each click identical, and `1.0f` will make this sound much like the 90's computer screen scrolling/typing effect. | +| `AUDIO_CLICKY_DELAY_DURATION` | 1 | An integer note duration where 1 is 1/16th of the tempo, or a sixty-fourth note (see `quantum/audio/musical_notes.h` for implementation details). The main clicky effect will be delayed by this duration. Adjusting this to values around 6-12 will help compensate for loud switches. | + +## MIDI Functionality + +See [MIDI](midi) + +## Audio Keycodes + +|Key |Aliases |Description | +|-------------------------|---------|-------------------------------------------| +|`QK_AUDIO_ON` |`AU_ON` |Turns on Audio Feature | +|`QK_AUDIO_OFF` |`AU_OFF` |Turns off Audio Feature | +|`QK_AUDIO_TOGGLE` |`AU_TOGG`|Toggles Audio state | +|`QK_AUDIO_CLICKY_TOGGLE` |`CK_TOGG`|Toggles Audio clicky mode | +|`QK_AUDIO_CLICKY_ON` |`CK_ON` |Turns on Audio clicky mode | +|`QK_AUDIO_CLICKY_OFF` |`CK_OFF` |Turns on Audio clicky mode | +|`QK_AUDIO_CLICKY_UP` |`CK_UP` |Increases frequency of the clicks | +|`QK_AUDIO_CLICKY_DOWN` |`CK_DOWN`|Decreases frequency of the clicks | +|`QK_AUDIO_CLICKY_RESET` |`CK_RST` |Resets frequency to default | +|`QK_MUSIC_ON` |`MU_ON` |Turns on Music Mode | +|`QK_MUSIC_OFF` |`MU_OFF` |Turns off Music Mode | +|`QK_MUSIC_TOGGLE` |`MU_TOGG`|Toggles Music Mode | +|`QK_MUSIC_MODE_NEXT` |`MU_NEXT`|Cycles through the music modes | +|`QK_AUDIO_VOICE_NEXT` |`AU_NEXT`|Cycles through the audio voices | +|`QK_AUDIO_VOICE_PREVIOUS`|`AU_PREV`|Cycles through the audio voices in reverse | diff --git a/docs/features/auto_shift.md b/docs/features/auto_shift.md new file mode 100644 index 0000000000..45b9048f03 --- /dev/null +++ b/docs/features/auto_shift.md @@ -0,0 +1,398 @@ +# Auto Shift: Why Do We Need a Shift Key? + +Tap a key and you get its character. Tap a key, but hold it *slightly* longer +and you get its shifted state. Voilà! No shift key needed! + +## Why Auto Shift? + +Many people suffer from various forms of RSI. A common cause is stretching your +fingers repetitively long distances. For us on the keyboard, the pinky does that +all too often when reaching for the shift key. Auto Shift looks to alleviate that +problem. + +## How Does It Work? + +When you tap a key, it stays depressed for a short period of time before it is +then released. This depressed time is a different length for everyone. Auto Shift +defines a constant `AUTO_SHIFT_TIMEOUT` which is typically set to twice your +normal pressed state time. When you press a key, a timer starts, and if you +have not released the key after the `AUTO_SHIFT_TIMEOUT` period, then a shifted +version of the key is emitted. If the time is less than the `AUTO_SHIFT_TIMEOUT` +time, or you press another key, then the normal state is emitted. + +If `AUTO_SHIFT_REPEAT` is defined, there is keyrepeat support. Holding the key +down will repeat the shifted key, though this can be disabled with +`AUTO_SHIFT_NO_AUTO_REPEAT`. If you want to repeat the normal key, then tap it +once then immediately (within `TAPPING_TERM`) hold it down again (this works +with the shifted value as well if auto-repeat is disabled). + +There are also the `get_auto_shift_repeat` and `get_auto_shift_no_auto_repeat` +functions for more granular control. Neither will have an effect unless +`AUTO_SHIFT_REPEAT_PER_KEY` or `AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY` respectively +are defined. + +## Are There Limitations to Auto Shift? + +Yes, unfortunately. + +1. You will have characters that are shifted when you did not intend on shifting, and + other characters you wanted shifted, but were not. This simply comes down to + practice. As we get in a hurry, we think we have hit the key long enough for a + shifted version, but we did not. On the other hand, we may think we are tapping + the keys, but really we have held it for a little longer than anticipated. +2. Additionally, with keyrepeat the desired shift state can get mixed up. It will + always 'belong' to the last key pressed. For example, keyrepeating a capital + and then tapping something lowercase (whether or not it's an Auto Shift key) + will result in the capital's *key* still being held, but shift not. +3. Auto Shift does not apply to Tap Hold keys. For automatic shifting of Tap Hold + keys see [Retro Shift](#retro-shift). + +## How Do I Enable Auto Shift? + +Add to your `rules.mk` in the keymap folder: + +``` +AUTO_SHIFT_ENABLE = yes +``` + +If no `rules.mk` exists, you can create one. + +Then compile and install your new firmware with Auto Key enabled! That's it! + +## Modifiers + +By default, Auto Shift is disabled for any key press that is accompanied by one or more +modifiers. Thus, Ctrl+A that you hold for a really long time is not the same +as Ctrl+Shift+A. + +You can re-enable Auto Shift for modifiers by adding a define to your `config.h` + +```c +#define AUTO_SHIFT_MODIFIERS +``` + +In which case, Ctrl+A held past the `AUTO_SHIFT_TIMEOUT` will be sent as Ctrl+Shift+A + + +## Configuring Auto Shift + +If desired, there is some configuration that can be done to change the +behavior of Auto Shift. This is done by setting various variables the +`config.h` file located in your keymap folder. If no `config.h` file exists, you can create one. + +A sample is + +```c +#pragma once + +#define AUTO_SHIFT_TIMEOUT 150 +#define NO_AUTO_SHIFT_SPECIAL +``` + +### AUTO_SHIFT_TIMEOUT (Value in ms) + +This controls how long you have to hold a key before you get the shifted state. +Obviously, this is different for everyone. For the common person, a setting of +135 to 150 works great. However, one should start with a value of at least 175, which +is the default value. Then work down from there. The idea is to have the shortest time required to get the shifted state without having false positives. + +Play with this value until things are perfect. Many find that all will work well +at a given value, but one or two keys will still emit the shifted state on +occasion. This is simply due to habit and holding some keys a little longer +than others. Once you find this value, work on tapping your problem keys a little +quicker than normal and you will be set. + +::: tip +Auto Shift has three special keys that can help you get this value right very quick. See "Auto Shift Setup" for more details! +::: + +For more granular control of this feature, you can add the following to your `config.h`: + +```c +#define AUTO_SHIFT_TIMEOUT_PER_KEY +``` + +You can then add the following function to your keymap: + +```c +uint16_t get_autoshift_timeout(uint16_t keycode, keyrecord_t *record) { + switch(keycode) { + case AUTO_SHIFT_NUMERIC: + return 2 * get_generic_autoshift_timeout(); + case AUTO_SHIFT_SPECIAL: + return get_generic_autoshift_timeout() + 50; + case AUTO_SHIFT_ALPHA: + default: + return get_generic_autoshift_timeout(); + } +} +``` + +Note that you cannot override individual keys that are in one of those groups +if you are using them; trying to add a case for `KC_A` in the above example will +not compile as `AUTO_SHIFT_ALPHA` is there. A possible solution is a second switch +above to handle individual keys with no default case and only referencing the +groups in the below fallback switch. + +### NO_AUTO_SHIFT_SPECIAL (simple define) + +Do not Auto Shift special keys, which include -\_, =+, [{, ]}, ;:, '", ,<, .>, +/?, and the KC_TAB. + +### NO_AUTO_SHIFT_TAB (simple define) + +Do not Auto Shift KC_TAB but leave Auto Shift enabled for the other special +characters. + +### NO_AUTO_SHIFT_SYMBOLS (simple define) + +Do not Auto Shift symbol keys, which include -\_, =+, [{, ]}, ;:, '", ,<, .>, +and /?. + +### NO_AUTO_SHIFT_NUMERIC (simple define) + +Do not Auto Shift numeric keys, zero through nine. + +### NO_AUTO_SHIFT_ALPHA (simple define) + +Do not Auto Shift alpha characters, which include A through Z. + +### AUTO_SHIFT_ENTER (simple define) + +Auto Shift the enter key. + +### Auto Shift Per Key + +There are functions that allows you to determine which keys should be autoshifted, much like the tap-hold keys. + +The first of these, used to simply add a key to Auto Shift, is `get_custom_auto_shifted_key`: + +```c +bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + switch(keycode) { + case KC_DOT: + return true; + default: + return false; + } +} +``` + +For more granular control, there is `get_auto_shifted_key`. The default function looks like this: + +```c +bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { +# ifndef NO_AUTO_SHIFT_ALPHA + case AUTO_SHIFT_ALPHA: +# endif +# ifndef NO_AUTO_SHIFT_NUMERIC + case AUTO_SHIFT_NUMERIC: +# endif +# ifndef NO_AUTO_SHIFT_SPECIAL +# ifndef NO_AUTO_SHIFT_TAB + case KC_TAB: +# endif +# ifndef NO_AUTO_SHIFT_SYMBOLS + case AUTO_SHIFT_SYMBOLS: +# endif +# endif +# ifdef AUTO_SHIFT_ENTER + case KC_ENT: +# endif + return true; + } + return get_custom_auto_shifted_key(keycode, record); +} +``` + +This functionality is enabled by default, and does not need a define. + +### AUTO_SHIFT_REPEAT (simple define) + +Enables keyrepeat. + +### AUTO_SHIFT_NO_AUTO_REPEAT (simple define) + +Disables automatically keyrepeating when `AUTO_SHIFT_TIMEOUT` is exceeded. + + +### AUTO_SHIFT_ALPHA (predefined key group) + +A predefined group of keys representing A through Z. + +### AUTO_SHIFT_NUMERIC (predefined key group) + +A predefined group of keys representing 0 through 9. Note, these are defined as +1 through 0 since that is the order they normally appear in. + +### AUTO_SHIFT_SYMBOLS (predefined key group) + +A predefined group of keys representing symbolic characters which include -\_, =+, [{, ]}, ;:, '", ,<, .>, +and /?. + +### AUTO_SHIFT_SPECIAL (predefined key group) + +A predefined group of keys that combines AUTO_SHIFT_SYMBOLS and KC_TAB. + +## Custom Shifted Values + +Especially on small keyboards, the default shifted value for many keys is not +optimal. To provide more customizability, there are two user-definable +functions, `autoshift_press/release_user`. These register or unregister the +correct value for the passed key. Below is an example adding period to Auto +Shift and making its shifted value exclamation point. Make sure to use weak +mods - setting real would make any keys following it use their shifted values +as if you were holding the key. Clearing of modifiers is handled by Auto Shift, +and the OS-sent shift value if keyrepeating multiple keys is always that of +the last key pressed (whether or not it's an Auto Shift key). + +You can also have non-shifted keys for the shifted values (or even no shifted +value), just don't set a shift modifier! + +```c +bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + switch(keycode) { + case KC_DOT: + return true; + default: + return false; + } +} + +void autoshift_press_user(uint16_t keycode, bool shifted, keyrecord_t *record) { + switch(keycode) { + case KC_DOT: + register_code16((!shifted) ? KC_DOT : KC_EXLM); + break; + default: + if (shifted) { + add_weak_mods(MOD_BIT(KC_LSFT)); + } + // & 0xFF gets the Tap key for Tap Holds, required when using Retro Shift + register_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode); + } +} + +void autoshift_release_user(uint16_t keycode, bool shifted, keyrecord_t *record) { + switch(keycode) { + case KC_DOT: + unregister_code16((!shifted) ? KC_DOT : KC_EXLM); + break; + default: + // & 0xFF gets the Tap key for Tap Holds, required when using Retro Shift + // The IS_RETRO check isn't really necessary here, always using + // keycode & 0xFF would be fine. + unregister_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode); + } +} +``` + +## Retro Shift + +Holding and releasing a Tap Hold key without pressing another key will ordinarily +result in only the hold. With `retro shift` enabled this action will instead +produce a shifted version of the tap keycode on release. + +It does not require [Retro Tapping](../tap_hold#retro-tapping) to be enabled, and +if both are enabled the state of `retro tapping` will only apply if the tap keycode +is not matched by Auto Shift. `RETRO_TAPPING_PER_KEY` and its corresponding +function, however, are checked before `retro shift` is applied. + +To enable `retro shift`, add the following to your `config.h`: + +```c +#define RETRO_SHIFT +``` + +If `RETRO_SHIFT` is defined to a value, hold times greater than that value will +not produce a tap on release for Mod Taps, and instead triggers the hold action. +This enables modifiers to be held for combining with mouse clicks without +generating taps on release. For example: + +```c +#define RETRO_SHIFT 500 +``` + +Without a value set, holds of any length without an interrupting key will produce the shifted value. + +This value (if set) must be greater than one's `TAPPING_TERM`, as the key press +must be designated as a 'hold' by `process_tapping` before we send the modifier. +[Per-key tapping terms](../tap_hold#tapping-term) can be used as a workaround. +There is no such limitation in regards to `AUTO_SHIFT_TIMEOUT` for normal keys. + +**Note:** Tap Holds must be added to Auto Shift, see [here.](auto_shift#auto-shift-per-key) +`IS_RETRO` may be helpful if one wants all Tap Holds retro shifted. + +### Retro Shift and Tap Hold Configurations + +Tap Hold Configurations work a little differently when using Retro Shift. +Referencing `TAPPING_TERM` makes little sense, as holding longer would result in +shifting one of the keys. + +`RETRO_SHIFT` enables [`PERMISSIVE_HOLD`-like behaviour](../tap_hold#permissive-hold) (even if not explicitly enabled) on all mod-taps for which `RETRO_SHIFT` applies. + +## Using Auto Shift Setup + +This will enable you to define three keys temporarily to increase, decrease and report your `AUTO_SHIFT_TIMEOUT`. + +### Setup + +Map three keys temporarily in your keymap: + +|Keycode |Aliases |Description | +|----------------------|---------|--------------------------------------------| +|`QK_AUTO_SHIFT_DOWN` |`AS_DOWN`|Lower the Auto Shift timeout variable (down)| +|`QK_AUTO_SHIFT_UP` |`AS_UP` |Raise the Auto Shift timeout variable (up) | +|`QK_AUTO_SHIFT_REPORT`|`AS_RPT` |Report your current Auto Shift timeout value| +|`QK_AUTO_SHIFT_ON` |`AS_ON` |Turns on the Auto Shift Function | +|`QK_AUTO_SHIFT_OFF` |`AS_OFF` |Turns off the Auto Shift Function | +|`QK_AUTO_SHIFT_TOGGLE`|`AS_TOGG`|Toggles the state of the Auto Shift feature | + +Compile and upload your new firmware. + +### Use + +It is important to note that during these tests, you should be typing +completely normal and with no intention of shifted keys. + +1. Type multiple sentences of alphabetical letters. +2. Observe any upper case letters. +3. If there are none, press the key you have mapped to `AS_DOWN` to decrease + time Auto Shift timeout value and go back to step 1. +4. If there are some upper case letters, decide if you need to work on tapping + those keys with less down time, or if you need to increase the timeout. +5. If you decide to increase the timeout, press the key you have mapped to + `AS_UP` and go back to step 1. +6. Once you are happy with your results, press the key you have mapped to + `AS_RPT`. The keyboard will type by itself the value of your + `AUTO_SHIFT_TIMEOUT`. +7. Update `AUTO_SHIFT_TIMEOUT` in your `config.h` with the value reported. +8. Add `AUTO_SHIFT_NO_SETUP` to your `config.h`. +9. Remove the key bindings `AS_DOWN`, `AS_UP` and `AS_RPT`. +10. Compile and upload your new firmware. + +#### An Example Run + +``` +hello world. my name is john doe. i am a computer programmer playing with +keyboards right now. + +[PRESS AS_DOWN quite a few times] + +heLLo woRLd. mY nAMe is JOHn dOE. i AM A compUTeR proGRaMMER PlAYiNG witH +KEYboArDS RiGHT NOw. + +[PRESS AS_UP a few times] + +hello world. my name is john Doe. i am a computer programmer playing with +keyboarDs right now. + +[PRESS AS_RPT] + +115 +``` + +The keyboard typed `115` which represents your current `AUTO_SHIFT_TIMEOUT` +value. You are now set! Practice on the *D* key a little bit that showed up +in the testing and you'll be golden. diff --git a/docs/features/autocorrect.md b/docs/features/autocorrect.md new file mode 100644 index 0000000000..df3f2e0fd8 --- /dev/null +++ b/docs/features/autocorrect.md @@ -0,0 +1,324 @@ +# Autocorrect + +There are a lot of words that are prone to being typed incorrectly, due to habit, sequence or just user error. This feature leverages your firmware to automatically correct these errors, to help reduce typos. + +## How does it work? {#how-does-it-work} + +The feature maintains a small buffer of recent key presses. On each key press, it checks whether the buffer ends in a recognized typo, and if so, automatically sends keystrokes to correct it. + +The tricky part is how to efficiently check the buffer for typos. We don’t want to spend too much memory or time on storing or searching the typos. A good solution is to represent the typos with a trie data structure. A trie is a tree data structure where each node is a letter, and words are formed by following a path to one of the leaves. + +![An example trie](https://i.imgur.com/HL5DP8H.png) + +Since we search whether the buffer ends in a typo, we store the trie writing in reverse. The trie is queried starting from the last letter, then second to last letter, and so on, until either a letter doesn’t match or we reach a leaf, meaning a typo was found. + +## How do I enable Autocorrection {#how-do-i-enable-autocorrection} + +In your `rules.mk`, add this: + +```make +AUTOCORRECT_ENABLE = yes +``` + +Additionally, you will need a library for autocorrection. A small sample library is included by default, so that you can get up and running right away, but you can provide a customized library. + +By default, autocorrect is disabled. To enable it, you need to use the `AC_TOGG` keycode to enable it. The status is stored in persistent memory, so you shouldn't need to enabled it again. + +## Customizing autocorrect library {#customizing-autocorrect-library} + +To provide a custom library, you need to create a text file with the corrections. For instance: + +```text +:thier -> their +fitler -> filter +lenght -> length +ouput -> output +widht -> width +``` + +The syntax is `typo -> correction`. Typos and corrections are case insensitive, and any whitespace before or after the typo and correction is ignored. The typo must be only the letters a–z, or the special character : representing a word break. The correction may have any non-unicode characters. + +Then, run: + +```sh +qmk generate-autocorrect-data autocorrect_dictionary.txt +``` + +This will process the file and produce an `autocorrect_data.h` file with the trie library, in the folder that you are at. You can specify the keyboard and keymap (eg `-kb planck/rev6 -km jackhumbert`), and it will place the file in that folder instead. But as long as the file is located in your keymap folder, or user folder, it should be picked up automatically. + +This file will look like this: + +```c +// :thier -> their +// fitler -> filter +// lenght -> length +// ouput -> output +// widht -> width + +#define AUTOCORRECT_MIN_LENGTH 5 // "ouput" +#define AUTOCORRECT_MAX_LENGTH 6 // ":thier" + +#define DICTIONARY_SIZE 74 + +static const uint8_t autocorrect_data[DICTIONARY_SIZE] PROGMEM = {85, 7, 0, 23, 35, 0, 0, 8, 0, 76, 16, 0, 15, 25, 0, 0, + 11, 23, 44, 0, 130, 101, 105, 114, 0, 23, 12, 9, 0, 131, 108, 116, 101, 114, 0, 75, 42, 0, 24, 64, 0, 0, 71, 49, 0, + 10, 56, 0, 0, 12, 26, 0, 129, 116, 104, 0, 17, 8, 15, 0, 129, 116, 104, 0, 19, 24, 18, 0, 130, 116, 112, 117, 116, + 0}; +``` + +### Avoiding false triggers {#avoiding-false-triggers} + +By default, typos are searched within words, to find typos within longer identifiers like maxFitlerOuput. While this is useful, a consequence is that autocorrection will falsely trigger when a typo happens to be a substring of a correctly-spelled word. For instance, if we had thier -> their as an entry, it would falsely trigger on (correct, though relatively uncommon) words like “wealthier” and “filthier.” + +The solution is to set a word break : before and/or after the typo to constrain matching. : matches space, period, comma, underscore, digits, and most other non-alpha characters. + +|Text |thier |:thier |thier: |:thier: | +|-----------------|:------:|:------:|:------:|:------:| +|see `thier` typo |matches |matches |matches |matches | +|it’s `thiers` |matches |matches |no |no | +|wealthier words |matches |no |matches |no | + +:thier: is most restrictive, matching only when thier is a whole word. + +The `qmk generate-autocorrect-data` commands can make an effort to check for entries that would false trigger as substrings of correct words. It searches each typo against a dictionary of 25K English words from the english_words Python package, provided it’s installed. (run `python3 -m pip install english_words` to install it.) + +::: tip +Unfortunately, this is limited to just english words, at this point. +::: + +## Overriding Autocorrect + +Occasionally you might actually want to type a typo (for instance, while editing autocorrect_dict.txt) without being autocorrected. There are a couple of ways to do this: + +1. Begin typing the typo. +2. Before typing the last letter, press and release the Ctrl or Alt key. +3. Type the remaining letters. + +This works because the autocorrection implementation doesn’t understand hotkeys, so it resets itself whenever a modifier other than shift is held. + +Additionally, you can use the `AC_TOGG` keycode to toggle the on/off status for Autocorrect. + +### Keycodes {#keycodes} + +|Keycode |Aliases |Description | +|-----------------------|---------|----------------------------------------------| +|`QK_AUTOCORRECT_ON` |`AC_ON` |Turns on the Autocorrect feature. | +|`QK_AUTOCORRECT_OFF` |`AC_OFF` |Turns off the Autocorrect feature. | +|`QK_AUTOCORRECT_TOGGLE`|`AC_TOGG`|Toggles the status of the Autocorrect feature.| + +## User Callback Functions + +### Process Autocorrect + +Callback function `bool process_autocorrect_user(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods)` is available to customise incoming keycodes and handle exceptions. You can use this function to sanitise input before they are passed onto the autocorrect engine + +::: tip +Sanitisation of input is required because autocorrect will only match 8-bit [basic keycodes](../keycodes_basic) for typos. If valid modifier keys or 16-bit keycodes that are part of a user's word input (such as Shift + A) is passed through, they will fail typo letter detection. For example a [Mod-Tap](../mod_tap) key such as `LCTL_T(KC_A)` is 16-bit and should be masked for the 8-bit `KC_A`. +::: + +The default user callback function is found inside `quantum/process_keycode/process_autocorrect.c`. It covers most use-cases for QMK special functions and quantum keycodes, including [overriding autocorrect](#overriding-autocorrect) with a modifier other than shift. The `process_autocorrect_user` function is `weak` defined to allow user's copy inside `keymap.c` (or code files) to overwrite it. + +#### Process Autocorrect Example + +If you have a custom keycode `QMKBEST` that should be ignored as part of a word, and another custom keycode `QMKLAYER` that should override autocorrect, both can be added to the bottom of the `process_autocorrect_user` `switch` statement in your source code: + +```c +bool process_autocorrect_user(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods) { + // See quantum_keycodes.h for reference on these matched ranges. + switch (*keycode) { + // Exclude these keycodes from processing. + case KC_LSFT: + case KC_RSFT: + case KC_CAPS: + case QK_TO ... QK_ONE_SHOT_LAYER_MAX: + case QK_LAYER_TAP_TOGGLE ... QK_LAYER_MOD_MAX: + case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: + return false; + + // Mask for base keycode from shifted keys. + case QK_LSFT ... QK_LSFT + 255: + case QK_RSFT ... QK_RSFT + 255: + if (*keycode >= QK_LSFT && *keycode <= (QK_LSFT + 255)) { + *mods |= MOD_LSFT; + } else { + *mods |= MOD_RSFT; + } + *keycode &= 0xFF; // Get the basic keycode. + return true; +#ifndef NO_ACTION_TAPPING + // Exclude tap-hold keys when they are held down + // and mask for base keycode when they are tapped. + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: +# ifdef NO_ACTION_LAYER + // Exclude Layer Tap, if layers are disabled + // but action tapping is still enabled. + return false; +# endif + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + // Exclude hold if mods other than Shift is not active + if (!record->tap.count) { + return false; + } + *keycode &= 0xFF; + break; +#else + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: + // Exclude if disabled + return false; +#endif + // Exclude swap hands keys when they are held down + // and mask for base keycode when they are tapped. + case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: +#ifdef SWAP_HANDS_ENABLE + if (*keycode >= 0x56F0 || !record->tap.count) { + return false; + } + *keycode &= 0xFF; + break; +#else + // Exclude if disabled + return false; +#endif + // Handle custom keycodes + case QMKBEST: + return false; + case QMKLAYER: + *typo_buffer_size = 0; + return false; + } + + // Disable autocorrect while a mod other than shift is active. + if ((*mods & ~MOD_MASK_SHIFT) != 0) { + *typo_buffer_size = 0; + return false; + } + + return true; +} +``` + +::: tip +In this callback function, `return false` will skip processing of that keycode for autocorrect. Adding `*typo_buffer_size = 0` will also reset the autocorrect buffer at the same time, cancelling any current letters already stored in the buffer. +::: + +### Apply Autocorrect + +Additionally, `apply_autocorrect(uint8_t backspaces, const char *str, char *typo, char *correct)` allows for users to add additional handling to the autocorrection, or replace the functionality entirely. This passes on the number of backspaces needed to replace the words, as well as the replacement string (partial word, not the full word), and the typo and corrected strings (complete words). + +::: tip +Due to the way code works (no notion of words, just a stream of letters), the `typo` and `correct` strings are a best bet and could be "wrong". For example you may get `wordtpyo` & `wordtypo` instead of the expected `tpyo` & `typo`. +::: + +#### Apply Autocorrect Example + +This following example will play a sound when a typo is autocorrected and execute the autocorrection itself: + +```c +#ifdef AUDIO_ENABLE +float autocorrect_song[][2] = SONG(TERMINAL_SOUND); +#endif + +bool apply_autocorrect(uint8_t backspaces, const char *str, char *typo, char *correct) { +#ifdef AUDIO_ENABLE + PLAY_SONG(autocorrect_song); +#endif + for (uint8_t i = 0; i < backspaces; ++i) { + tap_code(KC_BSPC); + } + send_string_P(str); + return false; +} +``` + +::: tip +In this callback function, `return false` will stop the normal processing of autocorrect, which requires manually handling of removing the "bad" characters and typing the new characters. +::: + +::: warning +***IMPORTANT***: `str` is a pointer to `PROGMEM` data for the autocorrection. If you return false, and want to send the string, this needs to use `send_string_P` and not `send_string` nor `SEND_STRING`. +::: + +You can also use `apply_autocorrect` to detect and display the event but allow internal code to execute the autocorrection with `return true`: + +```c +bool apply_autocorrect(uint8_t backspaces, const char *str, char *typo, char *correct) { +#ifdef OLED_ENABLE + oled_write_P(PSTR("Auto-corrected"), false); +#endif +#ifdef CONSOLE_ENABLE + printf("'%s' was corrected to '%s'\n", typo, correct); +#endif + return true; +} +``` + +### Autocorrect Status + +Additional user callback functions to manipulate Autocorrect: + +| Function | Description | +|----------------------------|----------------------------------------------| +| `autocorrect_enable()` | Turns Autocorrect on. | +| `autocorrect_disable()` | Turns Autocorrect off. | +| `autocorrect_toggle()` | Toggles Autocorrect. | +| `autocorrect_is_enabled()` | Returns true if Autocorrect is currently on. | + + +## Appendix: Trie binary data format {#appendix} + +This section details how the trie is serialized to byte data in autocorrect_data. You don’t need to care about this to use this autocorrection implementation. But it is documented for the record in case anyone is interested in modifying the implementation, or just curious how it works. + +What I did here is fairly arbitrary, but it is simple to decode and gets the job done. + +### Encoding {#encoding} + +All autocorrection data is stored in a single flat array autocorrect_data. Each trie node is associated with a byte offset into this array, where data for that node is encoded, beginning with root at offset 0. There are three kinds of nodes. The highest two bits of the first byte of the node indicate what kind: + +* 00 ⇒ chain node: a trie node with a single child. +* 01 ⇒ branching node: a trie node with multiple children. +* 10 ⇒ leaf node: a leaf, corresponding to a typo and storing its correction. + +![An example trie](https://i.imgur.com/HL5DP8H.png) + +**Branching node**. Each branch is encoded with one byte for the keycode (KC_A–KC_Z) followed by a link to the child node. Links between nodes are 16-bit byte offsets relative to the beginning of the array, serialized in little endian order. + +All branches are serialized this way, one after another, and terminated with a zero byte. As described above, the node is identified as a branch by setting the two high bits of the first byte to 01, done by bitwise ORing the first keycode with 64. keycode. The root node for the above figure would be serialized like: + +``` ++-------+-------+-------+-------+-------+-------+-------+ +| R|64 | node 2 | T | node 3 | 0 | ++-------+-------+-------+-------+-------+-------+-------+ +``` + +**Chain node**. Tries tend to have long chains of single-child nodes, as seen in the example above with f-i-t-l in fitler. So to save space, we use a different format to encode chains than branching nodes. A chain is encoded as a string of keycodes, beginning with the node closest to the root, and terminated with a zero byte. The child of the last node in the chain is encoded immediately after. That child could be either a branching node or a leaf. + +In the figure above, the f-i-t-l chain is encoded as + +``` ++-------+-------+-------+-------+-------+ +| L | T | I | F | 0 | ++-------+-------+-------+-------+-------+ +``` + +If we were to encode this chain using the same format used for branching nodes, we would encode a 16-bit node link with every node, costing 8 more bytes in this example. Across the whole trie, this adds up. Conveniently, we can point to intermediate points in the chain and interpret the bytes in the same way as before. E.g. starting at the i instead of the l, and the subchain has the same format. + +**Leaf node**. A leaf node corresponds to a particular typo and stores data to correct the typo. The leaf begins with a byte for the number of backspaces to type, and is followed by a null-terminated ASCII string of the replacement text. The idea is, after tapping backspace the indicated number of times, we can simply pass this string to the `send_string_P` function. For fitler, we need to tap backspace 3 times (not 4, because we catch the typo as the final ‘r’ is pressed) and replace it with lter. To identify the node as a leaf, the two high bits are set to 10 by ORing the backspace count with 128: + +``` ++-------+-------+-------+-------+-------+-------+ +| 3|128 | 'l' | 't' | 'e' | 'r' | 0 | ++-------+-------+-------+-------+-------+-------+ +``` + +### Decoding {#decoding} + +This format is by design decodable with fairly simple logic. A 16-bit variable state represents our current position in the trie, initialized with 0 to start at the root node. Then, for each keycode, test the highest two bits in the byte at state to identify the kind of node. + +* 00 ⇒ **chain node**: If the node’s byte matches the keycode, increment state by one to go to the next byte. If the next byte is zero, increment again to go to the following node. +* 01 ⇒ **branching node**: Search the branches for one that matches the keycode, and follow its node link. +* 10 ⇒ **leaf node**: a typo has been found! We read its first byte for the number of backspaces to type, then pass its following bytes to send_string_P to type the correction. + +## Credits + +Credit goes to [getreuer](https://github.com/getreuer) for originally implementing this [here](https://getreuer.info/posts/keyboards/autocorrection/#how-does-it-work). As well as to [filterpaper](https://github.com/filterpaper) for converting the code to use PROGMEM, and additional improvements. diff --git a/docs/features/backlight.md b/docs/features/backlight.md new file mode 100644 index 0000000000..94726756fd --- /dev/null +++ b/docs/features/backlight.md @@ -0,0 +1,309 @@ +# Backlighting {#backlighting} + +Many keyboards support backlit keys by way of individual LEDs placed through or underneath the keyswitches. This feature is distinct from both the [RGB Underglow](rgblight) and [RGB Matrix](rgb_matrix) features as it usually allows for only a single colour per switch, though you can obviously install multiple different single coloured LEDs on a keyboard. + +QMK is able to control the brightness of these LEDs by switching them on and off rapidly in a certain ratio, a technique known as *Pulse Width Modulation*, or PWM. By altering the duty cycle of the PWM signal, it creates the illusion of dimming. + +## Usage {#usage} + +Most keyboards have backlighting enabled by default if they support it, but if it is not working for you (or you have added support), check that your `rules.mk` includes the following: + +```make +BACKLIGHT_ENABLE = yes +``` + +## Keycodes {#keycodes} + +|Key |Aliases |Description | +|-------------------------------|---------|-----------------------------------| +|`QK_BACKLIGHT_TOGGLE` |`BL_TOGG`|Turn the backlight on or off | +|`QK_BACKLIGHT_STEP` |`BL_STEP`|Cycle through backlight levels | +|`QK_BACKLIGHT_ON` |`BL_ON` |Set the backlight to max brightness| +|`QK_BACKLIGHT_OFF` |`BL_OFF` |Turn the backlight off | +|`QK_BACKLIGHT_UP` |`BL_UP` |Increase the backlight level | +|`QK_BACKLIGHT_DOWN` |`BL_DOWN`|Decrease the backlight level | +|`QK_BACKLIGHT_TOGGLE_BREATHING`|`BL_BRTG`|Toggle backlight breathing | + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|-----------------------------|------------------|-----------------------------------------------------------------------------------------------------------------| +|`BACKLIGHT_PIN` |*Not defined* |The pin that controls the LEDs | +|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) | +|`BACKLIGHT_CAPS_LOCK` |*Not defined* |Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | +|`BACKLIGHT_BREATHING` |*Not defined* |Enable backlight breathing, if supported | +|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | +|`BACKLIGHT_ON_STATE` |`1` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low | +|`BACKLIGHT_LIMIT_VAL` |`255` |The maximum duty cycle of the backlight -- `255` allows for full brightness, any lower will decrease the maximum.| +|`BACKLIGHT_DEFAULT_ON` |`true` |Enable backlight upon clearing the EEPROM | +|`BACKLIGHT_DEFAULT_BREATHING`|`false` |Whether to enable backlight breathing upon clearing the EEPROM | +|`BACKLIGHT_DEFAULT_LEVEL` |`BACKLIGHT_LEVELS`|The default backlight level to use upon clearing the EEPROM | + +Unless you are designing your own keyboard, you generally should not need to change the `BACKLIGHT_PIN` or `BACKLIGHT_ON_STATE`. + +### "On" State {#on-state} + +Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*. +Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead. + +To configure the "on" state of the backlight circuit, add the following to your `config.h`: + +```c +#define BACKLIGHT_ON_STATE 0 +``` + +### Multiple Backlight Pins {#multiple-backlight-pins} + +Most keyboards have only one backlight pin which controls all backlight LEDs (especially if the backlight is connected to a hardware PWM pin). +The `timer` and `software` drivers allow you to define multiple backlight pins, which will be turned on and off at the same time during the PWM duty cycle. + +This feature allows to set, for instance, the Caps Lock LED's (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped Control in place of Caps Lock and you need the Caps Lock LED to be part of the backlight instead of being activated when Caps Lock is on, as it is usually wired to a separate pin from the backlight. + +To configure multiple backlight pins, add something like this to your `config.h`, instead of `BACKLIGHT_PIN`: + +```c +#define BACKLIGHT_PINS { F5, B2 } +``` + +## Driver Configuration {#driver-configuration} + +Backlight driver selection is configured in `rules.mk`. Valid drivers are `pwm` (default), `timer`, `software`, or `custom`. See below for information on individual drivers. + +### PWM Driver {#pwm-driver} + +This is the default backlight driver, which leverages the hardware PWM output capability of the microcontroller. + +```make +BACKLIGHT_DRIVER = pwm +``` + +### Timer Driver {#timer-driver} + +This driver is similar to the PWM driver, but instead of directly configuring the pin to output a PWM signal, an interrupt handler is attached to the timer to turn the pin on and off as appropriate. + +```make +BACKLIGHT_DRIVER = timer +``` + +### Software Driver {#software-driver} + +In this mode, PWM is "emulated" while running other keyboard tasks. It offers maximum hardware compatibility without extra platform configuration. However, breathing is not supported, and the backlight can flicker when the keyboard is busy. + +```make +BACKLIGHT_DRIVER = software +``` + +### Custom Driver {#custom-driver} + +If none of the above drivers apply to your board (for example, you are using a separate IC to control the backlight), you can implement a custom backlight driver using a simple API. + +```make +BACKLIGHT_DRIVER = custom +``` + +```c +void backlight_init_ports(void) { + // Optional - runs on startup + // Usually you want to configure pins here +} +void backlight_set(uint8_t level) { + // Optional - runs on level change + // Usually you want to respond to the new value +} + +void backlight_task(void) { + // Optional - runs periodically + // Note that this is called in the main keyboard loop, + // so long running actions here can cause performance issues +} +``` + +## AVR Configuration {#avr-configuration} + +### PWM Driver {#avr-pwm-driver} + +The following table describes the supported pins for the PWM driver. Only cells marked with a timer number are capable of hardware PWM output; any others must use the `timer` driver. + +|Backlight Pin|AT90USB64/128|AT90USB162|ATmega16/32U4|ATmega16/32U2|ATmega32A|ATmega328/P| +|-------------|-------------|----------|-------------|-------------|---------|-----------| +|`B1` | | | | | |Timer 1 | +|`B2` | | | | | |Timer 1 | +|`B5` |Timer 1 | |Timer 1 | | | | +|`B6` |Timer 1 | |Timer 1 | | | | +|`B7` |Timer 1 |Timer 1 |Timer 1 |Timer 1 | | | +|`C4` |Timer 3 | | | | | | +|`C5` |Timer 3 |Timer 1 | |Timer 1 | | | +|`C6` |Timer 3 |Timer 1 |Timer 3 |Timer 1 | | | +|`D4` | | | | |Timer 1 | | +|`D5` | | | | |Timer 1 | | + +### Timer Driver {#avr-timer-driver} + +Any GPIO pin can be used with this driver. The following table describes the supported timers: + +|AT90USB64/128|AT90USB162|ATmega16/32U4|ATmega16/32U2|ATmega32A|ATmega328/P| +|-------------|----------|-------------|-------------|---------|-----------| +|Timers 1 & 3 |Timer 1 |Timers 1 & 3 |Timer 1 |Timer 1 |Timer 1 | + +The following `#define`s apply only to the `timer` driver: + +|Define |Default|Description | +|-----------------------|-------|----------------| +|`BACKLIGHT_PWM_TIMER` |`1` |The timer to use| + +Note that the choice of timer may conflict with the [Audio](audio) feature. + +## ChibiOS/ARM Configuration {#arm-configuration} + +### PWM Driver {#arm-pwm-driver} + +Depending on the ChibiOS board configuration, you may need to enable PWM at the keyboard level. For STM32, this would look like: + +`halconf.h`: +```c +#define HAL_USE_PWM TRUE +``` +`mcuconf.h`: +```c +#undef STM32_PWM_USE_TIM4 +#define STM32_PWM_USE_TIM4 TRUE +``` + +The following `#define`s apply only to the `pwm` driver: + +|Define |Default |Description | +|-----------------------|-------------|---------------------------------------------------------------| +|`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use | +|`BACKLIGHT_PWM_CHANNEL`|`3` |The PWM channel to use | +|`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use | +|`BACKLIGHT_PWM_PERIOD` |*Not defined*|The PWM period in counter ticks - Default is platform dependent| + + +Refer to the ST datasheet for your particular MCU to determine these values. For example, these defaults are set up for pin `B8` on a Proton-C (STM32F303) using `TIM4_CH3` on AF2. Unless you are designing your own keyboard, you generally should not need to change them. + +### Timer Driver {#arm-timer-driver} + +Depending on the ChibiOS board configuration, you may need to enable general-purpose timers at the keyboard level. For STM32, this would look like: + +`halconf.h`: +```c +#define HAL_USE_GPT TRUE +``` +`mcuconf.h`: +```c +#undef STM32_GPT_USE_TIM15 +#define STM32_GPT_USE_TIM15 TRUE +``` + +The following `#define`s apply only to the `timer` driver: + +|Define |Default |Description | +|----------------------|--------|----------------| +|`BACKLIGHT_GPT_DRIVER`|`GPTD15`|The timer to use| + +## Example Schematic + +Since the MCU can only supply so much current to its GPIO pins, instead of powering the backlight directly from the MCU, the backlight pin is connected to a transistor or MOSFET that switches the power to the LEDs. + +In this typical example, the backlight LEDs are all connected in parallel towards an N-channel MOSFET. Its gate pin is wired to one of the microcontroller's GPIO pins through a 470Ω resistor to avoid ringing. +A pulldown resistor is also placed between the gate pin and ground to keep it at a defined state when it is not otherwise being driven by the MCU. +The values of these resistors are not critical - see [this Electronics StackExchange question](https://electronics.stackexchange.com/q/68748) for more information. + +![Backlight example circuit](https://i.imgur.com/BmAvoUC.png) + +## API {#api} + +### `void backlight_toggle(void)` {#api-backlight-toggle} + +Toggle the backlight on or off. + +--- + +### `void backlight_enable(void)` {#api-backlight-enable} + +Turn the backlight on. + +--- + +### `void backlight_disable(void)` {#api-backlight-disable} + +Turn the backlight off. + +--- + +### `void backlight_step(void)` {#api-backlight-step} + +Cycle through backlight levels. + +--- + +### `void backlight_increase(void)` {#api-backlight-increase} + +Increase the backlight level. + +--- + +### `void backlight_decrease(void)` {#api-backlight-decrease} + +Decrease the backlight level. + +--- + +### `void backlight_level(uint8_t level)` {#api-backlight-level} + +Set the backlight level. + +#### Arguments {#api-backlight-level-arguments} + + - `uint8_t level` + The level to set, from 0 to `BACKLIGHT_LEVELS`. + +--- + +### `uint8_t get_backlight_level(void)` {#api-get-backlight-level} + +Get the current backlight level. + +#### Return Value {#api-get-backlight-level-return} + +The current backlight level, from 0 to `BACKLIGHT_LEVELS`. + +--- + +### `bool is_backlight_enabled(void)` {#api-is-backlight-enabled} + +Get the current backlight state. + +#### Return Value {#api-is-backlight-enabled-return} + +`true` if the backlight is enabled. + +--- + +### `void backlight_toggle_breathing(void)` {#api-backlight-toggle-breathing} + +Toggle backlight breathing on or off. + +--- + +### `void backlight_enable_breathing(void)` {#api-backlight-enable-breathing} + +Turn backlight breathing on. + +--- + +### `void backlight_disable_breathing(void)` {#api-backlight-disable-breathing} + +Turn backlight breathing off. + +--- + +### `bool is_backlight_breathing(void)` {#api-is-backlight-breathing} + +Get the current backlight breathing state. + +#### Return Value {#api-is-backlight-breathing-return} + +`true` if backlight breathing is enabled. diff --git a/docs/features/bluetooth.md b/docs/features/bluetooth.md new file mode 100644 index 0000000000..1dbf15f4e1 --- /dev/null +++ b/docs/features/bluetooth.md @@ -0,0 +1,46 @@ +# Bluetooth + +## Bluetooth Known Supported Hardware + +Currently Bluetooth support is limited to AVR based chips. For Bluetooth 2.1, QMK has support for RN-42 modules. For more recent BLE protocols, currently only the Adafruit Bluefruit SPI Friend is directly supported. BLE is needed to connect to iOS devices. Note iOS does not support mouse input. + +|Board |Bluetooth Protocol |Connection Type|rules.mk |Bluetooth Chip| +|----------------------------------------------------------------|--------------------|---------------|---------------------------------|--------------| +|Roving Networks RN-42 (Sparkfun Bluesmirf) |Bluetooth Classic |UART |`BLUETOOTH_DRIVER = rn42` |RN-42 | +|[Bluefruit LE SPI Friend](https://www.adafruit.com/product/2633)|Bluetooth Low Energy|SPI |`BLUETOOTH_DRIVER = bluefruit_le`|nRF51822 | + +Not Supported Yet but possible: +* [Bluefruit LE UART Friend](https://www.adafruit.com/product/2479). [Possible tmk implementation found in](https://github.com/tmk/tmk_keyboard/issues/514) +* HC-05 boards flashed with RN-42 firmware. They apparently both use the CSR BC417 Chip. Flashing it with RN-42 firmware gives it HID capability. +* Sparkfun Bluetooth Mate +* HM-13 based boards + +### Adafruit BLE SPI Friend +Currently The only bluetooth chipset supported by QMK is the Adafruit Bluefruit SPI Friend. It's a Nordic nRF51822 based chip running Adafruit's custom firmware. Data is transmitted via Adafruit's SDEP over Hardware SPI. The [Feather 32u4 Bluefruit LE](https://www.adafruit.com/product/2829) is supported as it's an AVR mcu connected via SPI to the Nordic BLE chip with Adafruit firmware. If Building a custom board with the SPI friend it would be easiest to just use the pin selection that the 32u4 feather uses but you can change the pins in the config.h options with the following defines: +* `#define BLUEFRUIT_LE_RST_PIN D4` +* `#define BLUEFRUIT_LE_CS_PIN B4` +* `#define BLUEFRUIT_LE_IRQ_PIN E6` + +A Bluefruit UART friend can be converted to an SPI friend, however this [requires](https://github.com/qmk/qmk_firmware/issues/2274) some reflashing and soldering directly to the MDBT40 chip. + +<!-- FIXME: Document bluetooth support more completely. --> +## Bluetooth Rules.mk Options + +The currently supported Bluetooth chipsets do not support [N-Key Rollover (NKRO)](../reference_glossary#n-key-rollover-nkro), so `rules.mk` must contain `NKRO_ENABLE = no`. + +Add the following to your `rules.mk`: + +```make +BLUETOOTH_ENABLE = yes +BLUETOOTH_DRIVER = bluefruit_le # or rn42 +``` + +## Bluetooth Keycodes + +This is used when multiple keyboard outputs can be selected. Currently this only allows for switching between USB and Bluetooth on keyboards that support both. + +|Key |Aliases |Description | +|---------------------|---------|----------------------------------------------| +|`QK_OUTPUT_AUTO` |`OU_AUTO`|Automatically switch between USB and Bluetooth| +|`QK_OUTPUT_USB` |`OU_USB` |USB only | +|`QK_OUTPUT_BLUETOOTH`|`OU_BT` |Bluetooth only | diff --git a/docs/features/bootmagic.md b/docs/features/bootmagic.md new file mode 100644 index 0000000000..49fae5ba45 --- /dev/null +++ b/docs/features/bootmagic.md @@ -0,0 +1,85 @@ +# Bootmagic {#bootmagic} + +The Bootmagic feature that only handles jumping into the bootloader. This is great for boards that don't have a physical reset button, giving you a way to jump into the bootloader + +On some keyboards Bootmagic is disabled by default. If this is the case, it must be explicitly enabled in your `rules.mk` with: + +```make +BOOTMAGIC_ENABLE = yes +``` + +Additionally, you may want to specify which key to use. This is especially useful for keyboards that have unusual matrices. To do so, you need to specify the row and column of the key that you want to use. Add these entries to your `config.h` file: + +```c +#define BOOTMAGIC_ROW 0 +#define BOOTMAGIC_COLUMN 1 +``` + +By default, these are set to 0 and 0, which is usually the "ESC" key on a majority of keyboards. + +And to trigger the bootloader, you hold this key down when plugging the keyboard in. Just the single key. + +::: warning +Using Bootmagic will **always reset** the EEPROM, so you will lose any settings that have been saved. +::: + +## Split Keyboards + +When [handedness](split_keyboard#setting-handedness) is predetermined via options like `SPLIT_HAND_PIN` or `EE_HANDS`, you might need to configure a different key between halves. To identify the correct key for the right half, examine the split key matrix defined in the `<keyboard>.h` file, e.g.: + +```c +#define LAYOUT_split_3x5_2( \ + L01, L02, L03, L04, L05, R01, R02, R03, R04, R05, \ + L06, L07, L08, L09, L10, R06, R07, R08, R09, R10, \ + L11, L12, L13, L14, L15, R11, R12, R13, R14, R15, \ + L16, L17, R16, R17 \ + ) \ + { \ + { L01, L02, L03, L04, L05 }, \ + { L06, L07, L08, L09, L10 }, \ + { L11, L12, L13, L14, L15 }, \ + { L16, L17, KC_NO, KC_NO, KC_NO }, \ + { R01, R02, R03, R04, R05 }, \ + { R06, R07, R08, R09, R10 }, \ + { R11, R12, R13, R14, R15 }, \ + { R16, R17, KC_NO, KC_NO, KC_NO } \ + } +``` + +If you pick the top right key for the right half, it is `R05` on the top layout. Within the key matrix below, `R05` is located on row 4 columnn 4. To use that key as the right half's Bootmagic trigger, add these entries to your `config.h` file: + +```c +#define BOOTMAGIC_ROW_RIGHT 4 +#define BOOTMAGIC_COLUMN_RIGHT 4 +``` + +::: tip +These values are not set by default. +::: + +## Advanced Bootmagic + +The `bootmagic_scan` function is defined weakly, so that you can replace this in your code, if you need. A great example of this is the Zeal60 boards that have some additional handling needed. + +To replace the function, all you need to do is add something like this to your code: + +```c +void bootmagic_scan(void) { + matrix_scan(); + wait_ms(DEBOUNCE * 2); + matrix_scan(); + + if (matrix_get_row(BOOTMAGIC_ROW) & (1 << BOOTMAGIC_COLUMN)) { + // Jump to bootloader. + bootloader_jump(); + } +} +``` + +You can define additional logic here. For instance, resetting the EEPROM or requiring additional keys to be pressed to trigger Bootmagic. Keep in mind that `bootmagic_scan` is called before a majority of features are initialized in the firmware. + +## Addenda + +To manipulate settings that were formerly configured through the now-deprecated full Bootmagic feature, see [Magic Keycodes](../keycodes_magic). + +The Command feature, formerly known as Magic, also allows you to control different aspects of your keyboard. While it shares some functionality with Magic Keycodes, it also allows you to do things that Magic Keycodes cannot, such as printing version information to the console. For more information, see [Command](command). diff --git a/docs/features/caps_word.md b/docs/features/caps_word.md new file mode 100644 index 0000000000..15361aab96 --- /dev/null +++ b/docs/features/caps_word.md @@ -0,0 +1,189 @@ +# Caps Word + +It is often useful to type a single word in all capitals, for instance +abbreviations like "QMK", or in code, identifiers like `KC_SPC`. "Caps Word" is +a modern alternative to Caps Lock: + +* While active, letters are capitalized and `-` becomes `_`. The `_` makes it easier + to type constant names (eg 'PROGRAM\_CONSTANTS'). + +* Caps Word automatically disables + itself at the end of the word. That is, it stops by default once a space or + any key other than `KC_A`--`KC_Z`, `KC_0`--`KC_9`, `KC_MINS`, `KC_UNDS`, + `KC_DELETE`, or `KC_BACKSPACE` is pressed. Caps Word also disables itself if + the keyboard is idle for 5 seconds. This is configurable, see below. + +* To avoid requiring a dedicated key for Caps Word, there is an option + (`BOTH_SHIFTS_TURNS_ON_CAPS_WORD`) to activate Caps Word by simultaneously + pressing both shift keys. See below for other options. + +* The implementation does not use the Caps Lock (`KC_CAPS`) keycode. Caps Word + works even if you're remapping Caps Lock at the OS level to Ctrl or something + else, as Emacs and Vim users often do. As a consequence, Caps Word does not + follow the typical Caps Lock behaviour and may thus act in potentially + unexpected ways, especially when using an *OS* keyboard layout other than US + or UK. For example, Dvorak's <kbd>, <</kbd> key (`DV_COMM` aka `KC_W`) will + get shifted because Caps Word interprets that keycode as the letter 'W' by + default, the Spanish <kbd>Ñ</kbd> key (`ES_NTIL` aka `KC_SCLN`) will not get + capitalized because Caps Word interprets it as the semicolon ';' punctuation + character, and the US hyphen key (`KC_MINS`), while unaffected by Caps Lock, + is shifted by Caps Word. However, this is not really a problem because you can + [configure which keys should Caps Word + shift](#configure-which-keys-are-word-breaking). + + +## How do I enable Caps Word {#how-do-i-enable-caps-word} + +In your `rules.mk`, add: + +```make +CAPS_WORD_ENABLE = yes +``` + +Next, use one the following methods to activate Caps Word: + +* **Activate by pressing a key**: Use the `QK_CAPS_WORD_TOGGLE` keycode (short + alias `CW_TOGG`) in your keymap. + +* **Activate by pressing Left Shift + Right Shift**: Add `#define + BOTH_SHIFTS_TURNS_ON_CAPS_WORD` to config.h. You may also need to disable or + reconfigure Command, details below. Then, simultaneously pressing both left + and right shifts turns on Caps Word. This method works with the plain + `KC_LSFT` and `KC_RSFT` keycodes as well as one-shot shifts and Space Cadet + shifts. If your shift keys are mod-taps, hold both shift mod-tap keys until + the tapping term, then release them. + +* **Activate by double tapping Left Shift**: Add `#define + DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD` config.h. Then, double tapping Left Shift + turns on Caps Word. This method works with `KC_LSFT` or one-shot Left Shift + `OSM(MOD_LSFT)`. To count as a double tap, the maximum time in milliseconds + between taps is `TAPPING_TERM`, or if using `TAPPING_TERM_PER_KEY`, the time + returned by `get_tapping_term()` for the shift keycode being tapped. + +* **Custom activation**: You can activate Caps Word from code by calling + `caps_word_on()`. This may be used to activate Caps Word through [a + combo](combo) or [tap dance](tap_dance) or any means + you like. + +### Troubleshooting: Command {#troubleshooting-command} + +When using `BOTH_SHIFTS_TURNS_ON_CAPS_WORD`, you might see a compile message +**"BOTH_SHIFTS_TURNS_ON_CAPS_WORD and Command should not be enabled at the same +time, since both use the Left Shift + Right Shift key combination."** + +Many keyboards enable the [Command feature](command), which by +default is also activated using the Left Shift + Right Shift key combination. To +fix this conflict, please disable Command by adding in rules.mk: + +```make +COMMAND_ENABLE = no +``` + +Or configure Command to use another key combination like Left Ctrl + Right Ctrl +by defining `IS_COMMAND()` in config.h: + +```c +// Activate Command with Left Ctrl + Right Ctrl. +#define IS_COMMAND() (get_mods() == MOD_MASK_CTRL) +``` + + +## Customizing Caps Word {#customizing-caps-word} + +### Invert on shift {#invert-on-shift} + +By default, Caps Word turns off when Shift keys are pressed, considering them as +word-breaking. Alternatively with the `CAPS_WORD_INVERT_ON_SHIFT` option, +pressing the Shift key continues Caps Word and inverts the shift state. This +is convenient for uncapitalizing one or a few letters within a word, for +example with Caps Word on, typing "D, B, Shift+A, Shift+A, S" produces "DBaaS", +or typing "P, D, F, Shift+S" produces "PDFs". + +Enable it by adding in config.h + +```c +#define CAPS_WORD_INVERT_ON_SHIFT +``` + +This option works with regular Shift keys `KC_LSFT` and `KC_RSFT`, mod-tap Shift +keys, and one-shot Shift keys. Note that while Caps Word is on, one-shot Shift +keys behave like regular Shift keys, and have effect only while they are held. + + +### Idle timeout {#idle-timeout} + +Caps Word turns off automatically if no keys are pressed for +`CAPS_WORD_IDLE_TIMEOUT` milliseconds. The default is 5000 (5 seconds). +Configure the timeout duration in config.h, for instance + +```c +#define CAPS_WORD_IDLE_TIMEOUT 3000 // 3 seconds. +``` + +Setting `CAPS_WORD_IDLE_TIMEOUT` to 0 configures Caps Word to never time out. +Caps Word then remains active indefinitely until a word breaking key is pressed. + + +### Functions {#functions} + +Functions to manipulate Caps Word: + +| Function | Description | +|-------------------------|------------------------------------------------| +| `caps_word_on()` | Turns Caps Word on. | +| `caps_word_off()` | Turns Caps Word off. | +| `caps_word_toggle()` | Toggles Caps Word. | +| `is_caps_word_on()` | Returns true if Caps Word is currently on. | + + +### Configure which keys are "word breaking" {#configure-which-keys-are-word-breaking} + +You can define the `caps_word_press_user(uint16_t keycode)` callback to +configure which keys should be shifted and which keys are considered "word +breaking" and stop Caps Word. + +The callback is called on every key press while Caps Word is active. When the +key should be shifted (that is, a letter key), the callback should call +`add_weak_mods(MOD_BIT(KC_LSFT))` to shift the key. Returning true continues the +current "word," while returning false is "word breaking" and deactivates Caps +Word. The default callback is + +```c +bool caps_word_press_user(uint16_t keycode) { + switch (keycode) { + // Keycodes that continue Caps Word, with shift applied. + case KC_A ... KC_Z: + case KC_MINS: + add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to next key. + return true; + + // Keycodes that continue Caps Word, without shifting. + case KC_1 ... KC_0: + case KC_BSPC: + case KC_DEL: + case KC_UNDS: + return true; + + default: + return false; // Deactivate Caps Word. + } +} +``` + + +### Representing Caps Word state {#representing-caps-word-state} + +Define `caps_word_set_user(bool active)` to get callbacks when Caps Word turns +on or off. This is useful to represent the current Caps Word state, e.g. by +setting an LED or playing a sound. In your keymap, define + +```c +void caps_word_set_user(bool active) { + if (active) { + // Do something when Caps Word activates. + } else { + // Do something when Caps Word deactivates. + } +} +``` + diff --git a/docs/features/combo.md b/docs/features/combo.md new file mode 100644 index 0000000000..bdb8c4b15f --- /dev/null +++ b/docs/features/combo.md @@ -0,0 +1,398 @@ +# Combos + +The Combo feature is a chording type solution for adding custom actions. It lets you hit multiple keys at once and produce a different effect. For instance, hitting `A` and `B` within the combo term would hit `ESC` instead, or have it perform even more complex tasks. + +To enable this feature, you need to add `COMBO_ENABLE = yes` to your `rules.mk`. + +Then, in your `keymap.c` file, you'll need to define a sequence of keys, terminated with `COMBO_END`, and a structure to list the combination of keys, and its resulting action. + +```c +const uint16_t PROGMEM test_combo1[] = {KC_A, KC_B, COMBO_END}; +const uint16_t PROGMEM test_combo2[] = {KC_C, KC_D, COMBO_END}; +combo_t key_combos[] = { + COMBO(test_combo1, KC_ESC), + COMBO(test_combo2, LCTL(KC_Z)), // keycodes with modifiers are possible too! +}; +``` + +This will send "Escape" if you hit the A and B keys, and Ctrl+Z when you hit the C and D keys. + +## 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.: + +```c +const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(1, KC_B), COMBO_END}; +const uint16_t PROGMEM test_combo2[] = {TD(TD_ESC_CAPS), KC_F1, COMBO_END}; +``` + +## Overlapping Combos +It is possible to overlap combos. Before, with the example below both combos would activate when all three keys were pressed. Now only the three key combo will activate. + +```c +const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(1, KC_B), COMBO_END}; +const uint16_t PROGMEM test_combo2[] = {LSFT_T(KC_A), LT(1, KC_B), KC_C, COMBO_END}; +combo_t key_combos[] = { + COMBO(test_combo1, KC_ESC) + COMBO(test_combo2, KC_TAB) +}; +``` + +## Examples + +A long list of combos can be defined in an `enum` list: + +```c +enum combos { + AB_ESC, + JK_TAB, + QW_SFT, + SD_LAYER +}; + +const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; +const uint16_t PROGMEM jk_combo[] = {KC_J, KC_K, COMBO_END}; +const uint16_t PROGMEM qw_combo[] = {KC_Q, KC_W, COMBO_END}; +const uint16_t PROGMEM sd_combo[] = {KC_S, KC_D, COMBO_END}; + +combo_t key_combos[] = { + [AB_ESC] = COMBO(ab_combo, KC_ESC), + [JK_TAB] = COMBO(jk_combo, KC_TAB), + [QW_SFT] = COMBO(qw_combo, KC_LSFT), + [SD_LAYER] = COMBO(sd_combo, MO(_LAYER)), +}; +``` + +For a more complicated implementation, you can use the `process_combo_event` function to add custom handling. + +```c +enum combo_events { + EM_EMAIL, + BSPC_LSFT_CLEAR, +}; + +const uint16_t PROGMEM email_combo[] = {KC_E, KC_M, COMBO_END}; +const uint16_t PROGMEM clear_line_combo[] = {KC_BSPC, KC_LSFT, COMBO_END}; + +combo_t key_combos[] = { + [EM_EMAIL] = COMBO_ACTION(email_combo), + [BSPC_LSFT_CLEAR] = COMBO_ACTION(clear_line_combo), +}; +/* COMBO_ACTION(x) is same as COMBO(x, KC_NO) */ + +void process_combo_event(uint16_t combo_index, bool pressed) { + switch(combo_index) { + case EM_EMAIL: + if (pressed) { + SEND_STRING("[email protected]"); + } + break; + case BSPC_LSFT_CLEAR: + if (pressed) { + tap_code16(KC_END); + tap_code16(S(KC_HOME)); + tap_code16(KC_BSPC); + } + break; + } +} +``` + +This will send "[email protected]" if you chord E and M together, and clear the current line with Backspace and Left-Shift. You could change this to do stuff like play sounds or change settings. + +It is worth noting that `COMBO_ACTION`s are not needed anymore. As of [PR#8591](https://github.com/qmk/qmk_firmware/pull/8591/), it is possible to run your own custom keycodes from combos. Just define the custom keycode, program its functionality in `process_record_user`, and define a combo with `COMBO(<key_array>, <your_custom_keycode>)`. See the first example in [Macros](../feature_macros). + +## Keycodes +You can enable, disable and toggle the Combo feature on the fly. This is useful if you need to disable them temporarily, such as for a game. The following keycodes are available for use in your `keymap.c` + +|Keycode |Aliases |Description | +|-----------------|---------|--------------------------------| +|`QK_COMBO_ON` |`CM_ON` |Turns on Combo feature | +|`QK_COMBO_OFF` |`CM_OFF` |Turns off Combo feature | +|`QK_COMBO_TOGGLE`|`CM_TOGG`|Toggles Combo feature on and off| + +## Advanced Configuration +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. + +### 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: + +| Keys | Define to be set | +|------|-----------------------------------| +| 6 | `#define EXTRA_SHORT_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: + +| Define | Default | +|-------------------------------------|------------------------------------------------------| +| `#define COMBO_KEY_BUFFER_LENGTH 8` | 8 (the key amount `(EXTRA_)EXTRA_LONG_COMBOS` gives) | +| `#define COMBO_BUFFER_LENGTH 4` | 4 | + +### 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. + +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. + +In order to use these features, the following configuration options and functions need to be defined. Coming up with useful timings and configuration is left as an exercise for the reader. + +| Config Flag | Function | Description | +|-----------------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| `COMBO_TERM_PER_COMBO` | uint16_t get_combo_term(uint16_t index, combo_t \*combo) | Optional per-combo timeout window. (default: `COMBO_TERM`) | +| `COMBO_MUST_HOLD_PER_COMBO` | bool get_combo_must_hold(uint16_t 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 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 index, combo_t \*combo) | Controls if a given combo should fire only if its keys are pressed in order. (default: `true`) | + +Examples: +```c +uint16_t get_combo_term(uint16_t index, combo_t *combo) { + // decide by combo->keycode + switch (combo->keycode) { + case KC_X: + return 50; + } + + // or with combo index, i.e. its name from enum. + switch (index) { + case COMBO_NAME_HERE: + return 9001; + } + + // And if you're feeling adventurous, you can even decide by the keys in the chord, + // 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 + return 150; + } + + return COMBO_TERM; +} + +bool get_combo_must_hold(uint16_t index, combo_t *combo) { + // Same as above, decide by keycode, the combo index, or by the keys in the chord. + + if (KEYCODE_IS_MOD(combo->keycode) || + (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX) // MO(kc) keycodes + ) { + return true; + } + + switch (index) { + case COMBO_NAME_HERE: + return true; + } + + return false; +} + +bool get_combo_must_tap(uint16_t index, combo_t *combo) { + // If you want all combos to be tap-only, just uncomment the next line + // return true + + // If you want *all* combos, that have Mod-Tap/Layer-Tap/Momentary keys in its chord, to be tap-only, this is for you: + uint16_t key; + uint8_t idx = 0; + while ((key = pgm_read_word(&combo->keys[idx])) != COMBO_END) { + switch (key) { + case QK_MOD_TAP...QK_MOD_TAP_MAX: + case QK_LAYER_TAP...QK_LAYER_TAP_MAX: + case QK_MOMENTARY...QK_MOMENTARY_MAX: + return true; + } + idx += 1; + } + return false; + +} + +bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) { + switch (combo_index) { + /* List combos here that you want to only activate if their keys + * are pressed in the same order as they are defined in the combo's key + * array. */ + case COMBO_NAME_HERE: + return true; + default: + return false; + } +} +``` + +### Generic hook to (dis)allow a combo activation + +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. + +Examples: +```c +bool combo_should_trigger(uint16_t combo_index, combo_t *combo, uint16_t keycode, keyrecord_t *record) { + /* Disable combo `SOME_COMBO` on layer `_LAYER_A` */ + switch (combo_index) { + case SOME_COMBO: + if (layer_state_is(_LAYER_A)) { + return false; + } + } + + return true; +} +``` + +### Combo timer + +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. + +The next two options alter the behaviour of the timer. + +#### `#define COMBO_STRICT_TIMER` + +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 + +By defining `COMBO_PROCESS_KEY_RELEASE` and implementing the function `bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode)`, you can run your custom code on each key release after a combo was activated. For example you could change the RGB colors, activate haptics, or alter the modifiers. + +You can also release a combo early by returning `true` from the function. + +Here's an example where a combo resolves to two modifiers, and on key releases the modifiers are unregistered one by one, depending on which key was released. + +```c +enum combos { + AB_MODS +}; + +const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; + +combo_t key_combos[] = { + [AB_MODS] = COMBO(ab_combo, LCTL(KC_LSFT)), +}; + +bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { + switch (combo_index) { + case AB_MODS: + switch(keycode) { + case KC_A: + unregister_mods(MOD_MASK_CTRL); + break; + case KC_B: + unregister_mods(MOD_MASK_SHIFT); + break; + } + return false; // do not release combo + } + return false; +} +``` +### Layer independent combos + +If you, for example, use multiple base layers for different key layouts, one for QWERTY, and another one for Colemak, you might want your combos to work from the same key positions on all layers. Defining the same combos again for another layout is redundant and takes more memory. The solution is to just check the keycodes from one layer. + +With `#define COMBO_ONLY_FROM_LAYER 0` in config.h, the combos' keys are always checked from layer `0`, even if other layers are active. + +#### Combo reference layers by layer. + +If not using `COMBO_ONLY_FROM_LAYER` it is possible to specify a +combo reference layer for any layer using the `combo_ref_from_layer` hook. +The combo macros automatically create this function from the `COMBO_REF_LAYER()` +entries given. + +This function returns the assigned reference layer for the current layer. +if there is no match, it returns the default reference layer if set, +or the current layer otherwise. A default layer can be set with +`DEFAULT_REF_LAYER(_MY_COMBO_REF_LAYER)` + +If not set, the default reference layer selection from the automatically generated +`combo-ref-from-layer()` will be the current layer. + +The following `combo_ref_from_layer` function +will give a reference layer of _QWERTY for the _DVORAK layer and +will give the _NAV layer as a reference to it's self. All other layers +will have the default for their combo reference layer. If the default +is not set, all other layers will reference themselves. + +```c +#define COMBO_REF_DEFAULT _MY_COMBO_LAYER + +uint8_t combo_ref_from_layer(uint8_t layer){ + switch (get_highest_layer(layer_state)){ + case _DVORAK: return _QWERTY; + case _NAV: return _NAV; + default: return _MY_COMBO_LAYER; + } + return layer; // important if default is not in case. +} +``` + +The equivalent definition using the combo macros is this: + +```c +COMBO_REF_LAYER(_DVORAK, _QWERTY) +COMBO_REF_LAYER(_NAV, _NAV) +DEFAULT_REF_LAYER(_MY_COMBO_LAYER). +``` + + +## User callbacks + +In addition to the keycodes, there are a few functions that you can use to set the status, or check it: + +|Function |Description | +|-----------|--------------------------------------------------------------------| +| `combo_enable()` | Enables the combo feature | +| `combo_disable()` | Disables the combo feature, and clears the combo buffer | +| `combo_toggle()` | Toggles the state of the combo feature | +| `is_combo_enabled()` | Returns the status of the combo feature state (true or false) | + + +## Dictionary Management + +Having 3 places to update when adding new combos or altering old ones does become cumbersome when you have a lot of combos. We can alleviate this with some magic! ... If you consider C macros magic. +First, you need to add `VPATH += keyboards/gboards` to your `rules.mk`. Next, include the file `g/keymap_combo.h` in your `keymap.c`. + +::: warning +This functionality uses the same `process_combo_event` function as `COMBO_ACTION` macros do, so you cannot use the function yourself in your keymap. Instead, you have to define the `case`s of the `switch` statement by themselves within `inject.h`, which `g/keymap_combo.h` will then include into the function. +::: + +Then, write your combos in `combos.def` file in the following manner: + +```c +// Alternate reference layers by layer +// Layer Reference layer +COMBO_REF_LAYER(_DVORAK, _QWERTY) // reference the qwerty layer for dvorak. +COMBO_REF_LAYER(_NAV, _NAV) // explicit reference to self instead of the default. + +// name result chord keys +COMB(AB_ESC, KC_ESC, KC_A, KC_B) +COMB(JK_TAB, KC_TAB, KC_J, KC_K) +COMB(JKL_SPC, KC_SPC, KC_J, KC_K, KC_L) +COMB(BSSL_CLR, KC_NO, KC_BSPC, KC_LSFT) // using KC_NO as the resulting keycode is the same as COMBO_ACTION before. +COMB(QW_UNDO, C(KC_Z), KC_Q, KC_W) +SUBS(TH_THE, "the", KC_T, KC_H) // SUBS uses SEND_STRING to output the given string. +... +``` + +For small to huge ready made dictionaries of combos, you can check out http://combos.gboards.ca/. diff --git a/docs/features/command.md b/docs/features/command.md new file mode 100644 index 0000000000..7ad45103c7 --- /dev/null +++ b/docs/features/command.md @@ -0,0 +1,51 @@ +# Command + +Command, formerly known as Magic, is a way to change your keyboard's behavior without having to flash or unplug it to use [Bootmagic Lite](bootmagic). There is a lot of overlap between this functionality and the [Magic Keycodes](../keycodes_magic). Wherever possible we encourage you to use that feature instead of Command. + +On some keyboards Command is disabled by default. If this is the case, it must be explicitly enabled in your `rules.mk`: + +```make +COMMAND_ENABLE = yes +``` + +## Usage + +To use Command, hold down the key combination defined by the `IS_COMMAND()` macro. By default this is Left Shift+Right Shift. Then, press the key corresponding to the command you want. For example, to output the current QMK version to the QMK Toolbox console, press Left Shift+Right Shift+`V`. + +## Configuration + +If you would like to change the key assignments for Command, `#define` these in your `config.h` at either the keyboard or keymap level. All keycode assignments here must omit the `KC_` prefix. + +|Define |Default |Description | +|------------------------------------|--------------------------------|------------------------------------------------| +|`IS_COMMAND()` |`(get_mods() == MOD_MASK_SHIFT)`|The key combination to activate Command | +|`MAGIC_KEY_SWITCH_LAYER_WITH_FKEYS` |`true` |Set default layer with the Function row | +|`MAGIC_KEY_SWITCH_LAYER_WITH_NKEYS` |`true` |Set default layer with the number keys | +|`MAGIC_KEY_SWITCH_LAYER_WITH_CUSTOM`|`false` |Set default layer with `MAGIC_KEY_LAYER0..9` | +|`MAGIC_KEY_DEBUG` |`D` |Toggle debugging over serial | +|`MAGIC_KEY_DEBUG_MATRIX` |`X` |Toggle key matrix debugging | +|`MAGIC_KEY_DEBUG_KBD` |`K` |Toggle keyboard debugging | +|`MAGIC_KEY_DEBUG_MOUSE` |`M` |Toggle mouse debugging | +|`MAGIC_KEY_CONSOLE` |`C` |Enable the Command console | +|`MAGIC_KEY_VERSION` |`V` |Print the running QMK version to the console | +|`MAGIC_KEY_STATUS` |`S` |Print the current keyboard status to the console| +|`MAGIC_KEY_HELP` |`H` |Print Command help to the console | +|`MAGIC_KEY_HELP_ALT` |`SLASH` |Print Command help to the console (alternate) | +|`MAGIC_KEY_LAYER0` |`0` |Make layer 0 the default layer | +|`MAGIC_KEY_LAYER0_ALT` |`GRAVE` |Make layer 0 the default layer (alternate) | +|`MAGIC_KEY_LAYER1` |`1` |Make layer 1 the default layer | +|`MAGIC_KEY_LAYER2` |`2` |Make layer 2 the default layer | +|`MAGIC_KEY_LAYER3` |`3` |Make layer 3 the default layer | +|`MAGIC_KEY_LAYER4` |`4` |Make layer 4 the default layer | +|`MAGIC_KEY_LAYER5` |`5` |Make layer 5 the default layer | +|`MAGIC_KEY_LAYER6` |`6` |Make layer 6 the default layer | +|`MAGIC_KEY_LAYER7` |`7` |Make layer 7 the default layer | +|`MAGIC_KEY_LAYER8` |`8` |Make layer 8 the default layer | +|`MAGIC_KEY_LAYER9` |`9` |Make layer 9 the default layer | +|`MAGIC_KEY_BOOTLOADER` |`B` |Jump to bootloader | +|`MAGIC_KEY_BOOTLOADER_ALT` |`ESC` |Jump to bootloader (alternate) | +|`MAGIC_KEY_LOCK` |`CAPS` |Lock the keyboard so nothing can be typed | +|`MAGIC_KEY_EEPROM` |`E` |Print stored EEPROM config to the console | +|`MAGIC_KEY_EEPROM_CLEAR` |`BSPACE` |Clear the EEPROM | +|`MAGIC_KEY_NKRO` |`N` |Toggle N-Key Rollover (NKRO) | +|`MAGIC_KEY_SLEEP_LED` |`Z` |Toggle LED when computer is sleeping | diff --git a/docs/features/digitizer.md b/docs/features/digitizer.md new file mode 100644 index 0000000000..e524877636 --- /dev/null +++ b/docs/features/digitizer.md @@ -0,0 +1,119 @@ +# Digitizer {#digitizer} + +Digitizers allow the mouse cursor to be placed at absolute coordinates, unlike the [Pointing Device](pointing_device) feature which applies relative displacements. + +This feature implements a stylus device with a tip switch and barrel switch (generally equivalent to the primary and secondary mouse buttons respectively). Tip pressure is not currently implemented. + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +DIGITIZER_ENABLE = yes +``` + +## Positioning {#positioning} + +The X and Y coordinates are normalized, meaning their value must be set between 0 and 1. For the X component, the value `0` is the leftmost position, whereas the value `1` is the rightmost position. Similarly for the Y component, `0` is at the top and `1` at the bottom. + +::: tip +Since there is no display attached, the OS will likely map these coordinates to the virtual desktop. This may be important to know if you have multiple monitors. +::: + +## Examples {#examples} + +This example simply places the cursor in the middle of the screen: + +```c +digitizer_in_range_on(); +digitizer_set_position(0.5, 0.5); +``` + +The "in range" indicator is required to be on for the change in coordinates to be taken. It can then be turned off again to signal the end of the digitizer interaction, but it is not strictly required. + +You can also modify the digitizer state directly, if you need to change multiple fields in a single report: + +```c +digitizer_state.in_range = true; +digitizer_state.dirty = true; +digitizer_flush(); +``` + +`digitizer_state` is a struct of type `digitizer_t`. + + +## API {#api} + +### `struct digitizer_t` {#api-digitizer-t} + +Contains the state of the digitizer. + +#### Members {#api-digitizer-t-members} + + - `bool in_range` + Indicates to the host that the contact is within range (ie. close to or in contact with the digitizer surface). + - `bool tip` + The state of the tip switch. + - `bool barrel` + The state of the barrel switch. + - `float x` + The X coordinate of the digitizer contact. + - `float y` + The Y coordinate of the digitizer contact. + - `bool dirty` + Whether the current state needs to be sent to the host. + +--- + +### `void digitizer_flush(void)` {#api-digitizer-flush} + +Send the digitizer report to the host if it is marked as dirty. + +--- + +### `void digitizer_in_range_on(void)` {#api-digitizer-in-range-on} + +Assert the "in range" indicator, and flush the report. + +--- + +### `void digitizer_in_range_off(void)` {#api-digitizer-in-range-off} + +Deassert the "in range" indicator, and flush the report. + +--- + +### `void digitizer_tip_switch_on(void)` {#api-digitizer-tip-switch-on} + +Assert the tip switch, and flush the report. + +--- + +### `void digitizer_tip_switch_off(void)` {#api-digitizer-tip-switch-off} + +Deassert the tip switch, and flush the report. + +--- + +### `void digitizer_barrel_switch_on(void)` {#api-digitizer-barrel-switch-on} + +Assert the barrel switch, and flush the report. + +--- + +### `void digitizer_barrel_switch_off(void)` {#api-digitizer-barrel-switch-off} + +Deassert the barrel switch, and flush the report. + +--- + +### `void digitizer_set_position(float x, float y)` {#api-digitizer-set-position} + +Set the absolute X and Y position of the digitizer contact, and flush the report. + +#### Arguments {#api-digitizer-set-position-arguments} + + - `float x` + The X value of the contact position, from 0 to 1. + - `float y` + The Y value of the contact position, from 0 to 1. diff --git a/docs/features/dip_switch.md b/docs/features/dip_switch.md new file mode 100644 index 0000000000..738331bef0 --- /dev/null +++ b/docs/features/dip_switch.md @@ -0,0 +1,132 @@ +# DIP Switches + +DIP switches are supported by adding this to your `rules.mk`: + + DIP_SWITCH_ENABLE = yes + +and this to your `config.h`: + +```c +// Connects each switch in the dip switch to the GPIO pin of the MCU +#define DIP_SWITCH_PINS { B14, A15, A10, B9 } +// For split keyboards, you can separately define the right side pins +#define DIP_SWITCH_PINS_RIGHT { ... } +``` + +or + +```c +// Connect each switch in the DIP switch to an unused intersections in the key matrix. +#define DIP_SWITCH_MATRIX_GRID { {0,6}, {1,6}, {2,6} } // List of row and col pairs +``` + +## DIP Switch map {#dip-switch-map} + +DIP Switch mapping may be added to your `keymap.c`, which replicates the normal keyswitch functionality, but with dip switches. Add this to your keymap's `rules.mk`: + +```make +DIP_SWITCH_MAP_ENABLE = yes +``` + +Your `keymap.c` will then need a dip switch mapping defined (for two dip switches): + +```c +#if defined(DIP_SWITCH_MAP_ENABLE) +const uint16_t PROGMEM dip_switch_map[NUM_DIP_SWITCHES][NUM_DIP_STATES] = { + DIP_SWITCH_OFF_ON(DF(0), DF(1)), + DIP_SWITCH_OFF_ON(EC_NORM, EC_SWAP) +}; +#endif +``` + +::: tip +This should only be enabled at the keymap level. +::: + +## Callbacks + +The callback functions can be inserted into your `<keyboard>.c`: + +```c +bool dip_switch_update_kb(uint8_t index, bool active) { + if (!dip_switch_update_user(index, active)) { return false; } + return true; +} +``` + + +or `keymap.c`: + +```c +bool dip_switch_update_user(uint8_t index, bool active) { + switch (index) { + case 0: + if(active) { audio_on(); } else { audio_off(); } + break; + case 1: + if(active) { clicky_on(); } else { clicky_off(); } + break; + case 2: + if(active) { music_on(); } else { music_off(); } + break; + case 3: + if (active) { + #ifdef AUDIO_ENABLE + PLAY_SONG(plover_song); + #endif + layer_on(_PLOVER); + } else { + #ifdef AUDIO_ENABLE + PLAY_SONG(plover_gb_song); + #endif + layer_off(_PLOVER); + } + break; + } + return true; +} +``` + +Additionally, we support bit mask functions which allow for more complex handling. + + +```c +bool dip_switch_update_mask_kb(uint32_t state) { + if (!dip_switch_update_mask_user(state)) { return false; } + return true; +} +``` + + +or `keymap.c`: + +```c +bool dip_switch_update_mask_user(uint32_t state) { + if (state & (1UL<<0) && state & (1UL<<1)) { + layer_on(_ADJUST); // C on esc + } else { + layer_off(_ADJUST); + } + if (state & (1UL<<0)) { + layer_on(_TEST_A); // A on ESC + } else { + layer_off(_TEST_A); + } + if (state & (1UL<<1)) { + layer_on(_TEST_B); // B on esc + } else { + layer_off(_TEST_B); + } + return true; +} +``` + +## Hardware + +### Connects each switch in the dip switch to the GPIO pin of the MCU + +One side of the DIP switch should be wired directly to the pin on the MCU, and the other side to ground. It should not matter which side is connected to which, as it should be functionally the same. + +### Connect each switch in the DIP switch to an unused intersections in the key matrix. + +As with the keyswitch, a diode and DIP switch connect the ROW line to the COL line. diff --git a/docs/features/dynamic_macros.md b/docs/features/dynamic_macros.md new file mode 100644 index 0000000000..a642ced43e --- /dev/null +++ b/docs/features/dynamic_macros.md @@ -0,0 +1,69 @@ +# Dynamic Macros: Record and Replay Macros in Runtime + +QMK supports temporary macros created on the fly. We call these Dynamic Macros. They are defined by the user from the keyboard and are lost when the keyboard is unplugged or otherwise rebooted. + +You can store one or two macros and they may have a combined total of 128 keypresses. You can increase this size at the cost of RAM. + +To enable them, first include `DYNAMIC_MACRO_ENABLE = yes` in your `rules.mk`. Then, add the following keys to your keymap: + +|Key |Alias |Description | +|---------------------------------|---------|--------------------------------------------------| +|`QK_DYNAMIC_MACRO_RECORD_START_1`|`DM_REC1`|Start recording Macro 1 | +|`QK_DYNAMIC_MACRO_RECORD_START_2`|`DM_REC2`|Start recording Macro 2 | +|`QK_DYNAMIC_MACRO_PLAY_1` |`DM_PLY1`|Replay Macro 1 | +|`QK_DYNAMIC_MACRO_PLAY_2` |`DM_PLY2`|Replay Macro 2 | +|`QK_DYNAMIC_MACRO_RECORD_STOP` |`DM_RSTP`|Finish the macro that is currently being recorded.| + +That should be everything necessary. + +To start recording the macro, press either `DM_REC1` or `DM_REC2`. + +To finish the recording, press the `DM_RSTP` layer button. You can also press `DM_REC1` or `DM_REC2` again to stop the recording. + +To replay the macro, press either `DM_PLY1` or `DM_PLY2`. + +It is possible to replay a macro as part of a macro. It's ok to replay macro 2 while recording macro 1 and vice versa but never create recursive macros i.e. macro 1 that replays macro 1. If you do so and the keyboard will get unresponsive, unplug the keyboard and plug it again. You can disable this completely by defining `DYNAMIC_MACRO_NO_NESTING` in your `config.h` file. + +::: tip +For the details about the internals of the dynamic macros, please read the comments in the `process_dynamic_macro.h` and `process_dynamic_macro.c` files. +::: + +## Customization + +There are a number of options added that should allow some additional degree of customization + +|Define |Default |Description | +|----------------------------|----------------|-----------------------------------------------------------------------------------------------------------------| +|`DYNAMIC_MACRO_SIZE` |128 |Sets the amount of memory that Dynamic Macros can use. This is a limited resource, dependent on the controller. | +|`DYNAMIC_MACRO_USER_CALL` |*Not defined* |Defining this falls back to using the user `keymap.c` file to trigger the macro behavior. | +|`DYNAMIC_MACRO_NO_NESTING` |*Not Defined* |Defining this disables the ability to call a macro from another macro (nested macros). | +|`DYNAMIC_MACRO_DELAY` |*Not Defined* |Sets the waiting time (ms unit) when sending each key. | + + +If the LEDs start blinking during the recording with each keypress, it means there is no more space for the macro in the macro buffer. To fit the macro in, either make the other macro shorter (they share the same buffer) or increase the buffer size by adding the `DYNAMIC_MACRO_SIZE` define in your `config.h` (default value: 128; please read the comments for it in the header). + + +### DYNAMIC_MACRO_USER_CALL + +For users of the earlier versions of dynamic macros: It is still possible to finish the macro recording using just the layer modifier used to access the dynamic macro keys, without a dedicated `DM_RSTP` key. If you want this behavior back, add `#define DYNAMIC_MACRO_USER_CALL` to your `config.h` and insert the following snippet at the beginning of your `process_record_user()` function: + +```c + uint16_t macro_kc = (keycode == MO(_DYN) ? DM_RSTP : keycode); + + if (!process_record_dynamic_macro(macro_kc, record)) { + return false; + } +``` + +### User Hooks + +There are a number of hooks that you can use to add custom functionality and feedback options to Dynamic Macro feature. This allows for some additional degree of customization. + +Note, that direction indicates which macro it is, with `1` being Macro 1, `-1` being Macro 2, and 0 being no macro. + +* `dynamic_macro_record_start_user(int8_t direction)` - Triggered when you start recording a macro. +* `dynamic_macro_play_user(int8_t direction)` - Triggered when you play back a macro. +* `dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record)` - Triggered on each keypress while recording a macro. +* `dynamic_macro_record_end_user(int8_t direction)` - Triggered when the macro recording is stopped. + +Additionally, you can call `dynamic_macro_led_blink()` to flash the backlights if that feature is enabled. diff --git a/docs/features/encoders.md b/docs/features/encoders.md new file mode 100644 index 0000000000..3d1cac79af --- /dev/null +++ b/docs/features/encoders.md @@ -0,0 +1,186 @@ +# Encoders + +Basic (EC11 compatible) encoders are supported by adding this to your `rules.mk`: + +```make +ENCODER_ENABLE = yes +``` + +and this to your `config.h`: + +```c +#define ENCODERS_PAD_A { B12 } +#define ENCODERS_PAD_B { B13 } +``` + +Each PAD_A/B variable defines an array so multiple encoders can be defined, e.g.: + +```c +#define ENCODERS_PAD_A { encoder1a, encoder2a } +#define ENCODERS_PAD_B { encoder1b, encoder2b } +``` + +If your encoder's clockwise directions are incorrect, you can swap the A & B pad definitions. They can also be flipped with a define: + +```c +#define ENCODER_DIRECTION_FLIP +``` + +Additionally, the resolution, which defines how many pulses the encoder registers between each detent, can be defined with: + +```c +#define ENCODER_RESOLUTION 4 +``` + +It can also be defined per-encoder, by instead defining: + +```c +#define ENCODER_RESOLUTIONS { 4, 2 } +``` + +For 4× encoders you also can assign default position if encoder skips pulses when it changes direction. For example, if your encoder send high level on both pins by default, define this: + +```c +#define ENCODER_DEFAULT_POS 0x3 +``` + +## Split Keyboards + +If you are using different pinouts for the encoders on each half of a split keyboard, you can define the pinout (and optionally, resolutions) for the right half like this: + +```c +#define ENCODERS_PAD_A_RIGHT { encoder1a, encoder2a } +#define ENCODERS_PAD_B_RIGHT { encoder1b, encoder2b } +#define ENCODER_RESOLUTIONS_RIGHT { 2, 4 } +``` + +If the `_RIGHT` definitions aren't specified in your `config.h`, then the non-`_RIGHT` versions will be applied to both sides of the split. + +Additionally, if one side does not have an encoder, you can specify `{}` for the pins/resolution -- for example, a split keyboard with only a right-side encoder: + +```c +#define ENCODERS_PAD_A { } +#define ENCODERS_PAD_B { } +#define ENCODER_RESOLUTIONS { } +#define ENCODERS_PAD_A_RIGHT { B12 } +#define ENCODERS_PAD_B_RIGHT { B13 } +#define ENCODER_RESOLUTIONS_RIGHT { 4 } +``` + +::: warning +Keep in mind that whenver you change the encoder resolution, you will need to reflash the half that has the encoder affected by the change. +::: + +## Encoder map {#encoder-map} + +Encoder mapping may be added to your `keymap.c`, which replicates the normal keyswitch layer handling functionality, but with encoders. Add this to your keymap's `rules.mk`: + +```make +ENCODER_MAP_ENABLE = yes +``` + +Your `keymap.c` will then need an encoder mapping defined (for four layers and two encoders): + +```c +#if defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + [0] = { ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, + [1] = { ENCODER_CCW_CW(RGB_HUD, RGB_HUI), ENCODER_CCW_CW(RGB_SAD, RGB_SAI) }, + [2] = { ENCODER_CCW_CW(RGB_VAD, RGB_VAI), ENCODER_CCW_CW(RGB_SPD, RGB_SPI) }, + [3] = { ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_RIGHT, KC_LEFT) }, +}; +#endif +``` + +::: tip +This should only be enabled at the keymap level. +::: + +Using encoder mapping pumps events through the normal QMK keycode processing pipeline, resulting in a _keydown/keyup_ combination pushed through `process_record_xxxxx()`. To configure the amount of time between the encoder "keyup" and "keydown", you can add the following to your `config.h`: + +```c +#define ENCODER_MAP_KEY_DELAY 10 +``` + +::: tip +By default, the encoder map delay matches the value of `TAP_CODE_DELAY`. +::: + +## Callbacks + +::: tip +[**Default Behaviour**](https://github.com/qmk/qmk_firmware/blob/master/quantum/encoder.c#L79-): all encoders installed will function as volume up (`KC_VOLU`) on clockwise rotation and volume down (`KC_VOLD`) on counter-clockwise rotation. If you do not wish to override this, no further configuration is necessary. +::: + +If you would like the alter the default behaviour, and are not using `ENCODER_MAP_ENABLE = yes`, the callback functions can be inserted into your `<keyboard>.c`: + +```c +bool encoder_update_kb(uint8_t index, bool clockwise) { + if (!encoder_update_user(index, clockwise)) { + return false; /* Don't process further events if user function exists and returns false */ + } + if (index == 0) { /* First encoder */ + if (clockwise) { + tap_code(KC_PGDN); + } else { + tap_code(KC_PGUP); + } + } else if (index == 1) { /* Second encoder */ + if (clockwise) { + rgb_matrix_increase_hue(); + } else { + rgb_matrix_decrease_hue(); + } + } + return true; +} +``` + +or `keymap.c`: + +```c +bool encoder_update_user(uint8_t index, bool clockwise) { + if (index == 0) { /* First encoder */ + if (clockwise) { + tap_code(KC_PGDN); + } else { + tap_code(KC_PGUP); + } + } else if (index == 1) { /* Second encoder */ + if (clockwise) { + rgb_matrix_increase_hue(); + } else { + rgb_matrix_decrease_hue(); + } + } + return false; +} +``` + +::: warning +If you return `true` in the keymap level `_user` function, it will allow the keyboard/core level encoder code to run on top of your own. Returning `false` will override the keyboard level function, if setup correctly. This is generally the safest option to avoid confusion. +::: + +## Hardware + +The A an B lines of the encoders should be wired directly to the MCU, and the C/common lines should be wired to ground. + +## Multiple Encoders + +Multiple encoders may share pins so long as each encoder has a distinct pair of pins when the following conditions are met: +- using detent encoders +- pads must be high at the detent stability point which is called 'default position' in QMK +- no more than two encoders sharing a pin can be turned at the same time + +For example you can support two encoders using only 3 pins like this +``` +#define ENCODERS_PAD_A { B1, B1 } +#define ENCODERS_PAD_B { B2, B3 } +``` + +You could even support three encoders using only three pins (one per encoder) however in this configuration, rotating two encoders which share pins simultaneously will often generate incorrect output. For example: +``` +#define ENCODERS_PAD_A { B1, B1, B2 } +#define ENCODERS_PAD_B { B2, B3, B3 } +``` +Here rotating Encoder 0 `B1 B2` and Encoder 1 `B1 B3` could be interpreted as rotating Encoder 2 `B2 B3` or `B3 B2` depending on the timing. This may still be a useful configuration depending on your use case diff --git a/docs/features/grave_esc.md b/docs/features/grave_esc.md new file mode 100644 index 0000000000..09d098ee4e --- /dev/null +++ b/docs/features/grave_esc.md @@ -0,0 +1,32 @@ +# Grave Escape + +If you're using a 60% keyboard, or any other layout with no F-row, you will have noticed that there is no dedicated Escape key. Grave Escape is a feature that allows you to share the grave key (<code>`</code> and `~`) with Escape. + +## Usage + +Replace the `KC_GRV` key in your keymap (usually to the left of the `1` key) with `QK_GESC`. Most of the time this key will output `KC_ESC` when pressed. However, when Shift or GUI are held down it will output `KC_GRV` instead. + +## What Your OS Sees + +If Mary presses `QK_GESC` on her keyboard, the OS will see an KC_ESC character. Now if Mary holds Shift down and presses `QK_GESC` it will output `~`, or a shifted backtick. Now if she holds GUI/CMD/WIN, it will output a simple <code>`</code> character. + +## Keycodes + +|Key |Aliases |Description | +|-----------------|---------|------------------------------------------------------------------| +|`QK_GRAVE_ESCAPE`|`QK_GESC`|Escape when pressed, <code>`</code> when Shift or GUI are held| + +### Caveats + +On macOS, Command+<code>`</code> is by default mapped to "Move focus to next window" so it will not output a backtick. Additionally, Terminal always recognises this shortcut to cycle between windows, even if the shortcut is changed in the Keyboard preferences. + +## Configuration + +There are several possible key combinations this will break, among them Control+Shift+Escape on Windows and Command+Option+Escape on macOS. To work around this, you can `#define` these options in your `config.h`: + +|Define |Description | +|--------------------------|-----------------------------------------| +|`GRAVE_ESC_ALT_OVERRIDE` |Always send Escape if Alt is pressed | +|`GRAVE_ESC_CTRL_OVERRIDE` |Always send Escape if Control is pressed | +|`GRAVE_ESC_GUI_OVERRIDE` |Always send Escape if GUI is pressed | +|`GRAVE_ESC_SHIFT_OVERRIDE`|Always send Escape if Shift is pressed | diff --git a/docs/features/haptic_feedback.md b/docs/features/haptic_feedback.md new file mode 100644 index 0000000000..52667965a0 --- /dev/null +++ b/docs/features/haptic_feedback.md @@ -0,0 +1,214 @@ +# Haptic Feedback + +## Haptic feedback rules.mk options + +The following options are currently available for haptic feedback in `rules.mk`: + +```make +HAPTIC_ENABLE = yes + +HAPTIC_DRIVER = drv2605l +# or +HAPTIC_DRIVER = solenoid +``` + +The following `config.h` settings are available for all types of haptic feedback: + +| Settings | Default | Description | +|--------------------------------------|---------------|---------------------------------------------------------------------------------------------------------------| +|`HAPTIC_ENABLE_PIN` | *Not defined* |Configures a pin to enable a boost converter for some haptic solution, often used with solenoid drivers. | +|`HAPTIC_ENABLE_PIN_ACTIVE_LOW` | *Not defined* |If defined then the haptic enable pin is active-low. | +|`HAPTIC_ENABLE_STATUS_LED` | *Not defined* |Configures a pin to reflect the current enabled/disabled status of haptic feedback. | +|`HAPTIC_ENABLE_STATUS_LED_ACTIVE_LOW` | *Not defined* |If defined then the haptic status led will be active-low. | +|`HAPTIC_OFF_IN_LOW_POWER` | `0` |If set to `1`, haptic feedback is disabled before the device is configured, and while the device is suspended. | + +## Known Supported Hardware + +| Name | Description | +|--------------------|-------------------------------------------------| +| [LV061228B-L65-A](https://www.digikey.com/product-detail/en/jinlong-machinery-electronics-inc/LV061228B-L65-A/1670-1050-ND/7732325) | z-axis 2v LRA | +| [Mini Motor Disc](https://www.adafruit.com/product/1201) | small 2-5v ERM | + +## Haptic Keycodes + +Not all keycodes below will work depending on which haptic mechanism you have chosen. + +| Key | Aliases | Description | +|-----------------------------|---------|-------------------------------------------------------| +|`QK_HAPTIC_ON` |`HF_ON` | Turn haptic feedback on | +|`QK_HAPTIC_OFF` |`HF_OFF` | Turn haptic feedback off | +|`QK_HAPTIC_TOGGLE` |`HF_TOGG`| Toggle haptic feedback on/off | +|`QK_HAPTIC_RESET` |`HF_RST` | Reset haptic feedback config to default | +|`QK_HAPTIC_FEEDBACK_TOGGLE` |`HF_FDBK`| Toggle feedback to occur on keypress, release or both | +|`QK_HAPTIC_BUZZ_TOGGLE` |`HF_BUZZ`| Toggle solenoid buzz on/off | +|`QK_HAPTIC_MODE_NEXT` |`HF_NEXT`| Go to next DRV2605L waveform | +|`QK_HAPTIC_MODE_PREVIOUS` |`HF_PREV`| Go to previous DRV2605L waveform | +|`QK_HAPTIC_CONTINUOUS_TOGGLE`|`HF_CONT`| Toggle continuous haptic mode on/off | +|`QK_HAPTIC_CONTINUOUS_UP` |`HF_CONU`| Increase DRV2605L continous haptic strength | +|`QK_HAPTIC_CONTINUOUS_DOWN` |`HF_COND`| Decrease DRV2605L continous haptic strength | +|`QK_HAPTIC_DWELL_UP` |`HF_DWLU`| Increase Solenoid dwell time | +|`QK_HAPTIC_DWELL_DOWN` |`HF_DWLD`| Decrease Solenoid dwell time | + +### Solenoids + +The solenoid code supports relay switches, and similar hardware, as well as solenoids. + +For a regular solenoid, you will need a build a circuit to drive the solenoid through a mosfet as most MCU will not be able to provide the current needed to drive the coil in the solenoid. + +[Wiring diagram provided by Adafruit](https://cdn-shop.adafruit.com/product-files/412/solenoid_driver.pdf) + +For relay switches, the hardware may already contain all of that ciruitry, and just require VCC, GND and a data pin. + +| Settings | Default | Description | +|----------------------------|----------------------|--------------------------------------------------------------| +|`SOLENOID_PIN` | *Not defined* |Configures the pin that the switch is connected to. | +|`SOLENOID_PIN_ACTIVE_LOW` | *Not defined* |If defined then the switch trigger pin is active low. | +|`SOLENOID_PINS` | *Not defined* |Configures an array of pins to be used for switch activation. | +|`SOLENOID_PINS_ACTIVE_LOW` | *Not defined* |Allows you to specify how each pin is pulled for activation. | +|`SOLENOID_RANDOM_FIRE` | *Not defined* |When there are multiple solenoids, will select a random one to fire.| +|`SOLENOID_DEFAULT_DWELL` | `12` ms |Configures the default dwell time for the switch. | +|`SOLENOID_MIN_DWELL` | `4` ms |Sets the lower limit for the dwell. | +|`SOLENOID_MAX_DWELL` | `100` ms |Sets the upper limit for the dwell. | +|`SOLENOID_DWELL_STEP_SIZE` | `1` ms |The step size to use when `HF_DWL*` keycodes are sent. | +|`SOLENOID_DEFAULT_BUZZ` | `0` (disabled) |On `HF_RST` buzz is set "on" if this is "1" | +|`SOLENOID_BUZZ_ACTUATED` | `SOLENOID_MIN_DWELL` |Actuated-time when the switch is in buzz mode. | +|`SOLENOID_BUZZ_NONACTUATED` | `SOLENOID_MIN_DWELL` |Non-Actuated-time when the switch is in buzz mode. | + +* If solenoid buzz is off, then dwell time is how long the "plunger" stays activated. The dwell time changes how the solenoid sounds. +* If solenoid buzz is on, then dwell time sets the length of the buzz, while `SOLENOID_BUZZ_ACTUATED` and `SOLENOID_BUZZ_NONACTUATED` set the (non-)actuation times withing the buzz period. +* With the current implementation, for any of the above time settings, the precision of these settings may be affected by how fast the keyboard is able to scan the matrix. + Therefore, if the keyboards scanning routine is slow, it may be preferable to set `SOLENOID_DWELL_STEP_SIZE` to a value slightly smaller than the time it takes to scan the keyboard. + +Beware that some pins may be powered during bootloader (ie. A13 on the STM32F303 chip) and will result in the solenoid kept in the on state through the whole flashing process. This may overheat and damage the solenoid. If you find that the pin the solenoid is connected to is triggering the solenoid during bootloader/DFU, select another pin. + +### DRV2605L + +DRV2605L is controlled over i2c protocol, and has to be connected to the SDA and SCL pins, these varies depending on the MCU in use. + +#### Feedback motor setup + +This driver supports 2 different feedback motors. Set the following in your `config.h` based on which motor you have selected. + +##### ERM + +Eccentric Rotating Mass vibration motors (ERM) is motor with a off-set weight attached so when drive signal is attached, the off-set weight spins and causes a sinusoidal wave that translate into vibrations. + +```c +#define DRV2605L_FB_ERM_LRA 0 +#define DRV2605L_FB_BRAKEFACTOR 3 /* For 1x:0, 2x:1, 3x:2, 4x:3, 6x:4, 8x:5, 16x:6, Disable Braking:7 */ +#define DRV2605L_FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */ + +/* Please refer to your datasheet for the optimal setting for your specific motor. */ +#define DRV2605L_RATED_VOLTAGE 3 +#define DRV2605L_V_PEAK 5 +``` +##### LRA + +Linear resonant actuators (LRA, also know as a linear vibrator) works different from a ERM. A LRA has a weight and magnet suspended by springs and a voice coil. When the drive signal is applied, the weight would be vibrate on a single axis (side to side or up and down). Since the weight is attached to a spring, there is a resonance effect at a specific frequency. This frequency is where the LRA will operate the most efficiently. Refer to the motor's datasheet for the recommanded range for this frequency. + +```c +#define DRV2605L_FB_ERM_LRA 1 +#define DRV2605L_FB_BRAKEFACTOR 3 /* For 1x:0, 2x:1, 3x:2, 4x:3, 6x:4, 8x:5, 16x:6, Disable Braking:7 */ +#define DRV2605L_FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */ + +/* Please refer to your datasheet for the optimal setting for your specific motor. */ +#define DRV2605L_RATED_VOLTAGE 2 +#define DRV2605L_V_PEAK 2.8 +#define DRV2605L_V_RMS 2.0 +#define DRV2605L_V_PEAK 2.1 +#define DRV2605L_F_LRA 205 /* resonance freq */ +``` + +#### DRV2605L waveform library + +DRV2605L comes with preloaded library of various waveform sequences that can be called and played. If writing a macro, these waveforms can be played using `DRV_pulse(*sequence name or number*)` + +List of waveform sequences from the datasheet: + +|seq# | Sequence name |seq# | Sequence name |seq# |Sequence name | +|-----|---------------------|-----|-----------------------------------|-----|--------------------------------------| +| 1 | strong_click | 43 | lg_dblclick_med_60 | 85 | transition_rampup_med_smooth2 | +| 2 | strong_click_60 | 44 | lg_dblsharp_tick | 86 | transition_rampup_short_smooth1 | +| 3 | strong_click_30 | 45 | lg_dblsharp_tick_80 | 87 | transition_rampup_short_smooth2 | +| 4 | sharp_click | 46 | lg_dblsharp_tick_60 | 88 | transition_rampup_long_sharp1 | +| 5 | sharp_click_60 | 47 | buzz | 89 | transition_rampup_long_sharp2 | +| 6 | sharp_click_30 | 48 | buzz_80 | 90 | transition_rampup_med_sharp1 | +| 7 | soft_bump | 49 | buzz_60 | 91 | transition_rampup_med_sharp2 | +| 8 | soft_bump_60 | 50 | buzz_40 | 92 | transition_rampup_short_sharp1 | +| 9 | soft_bump_30 | 51 | buzz_20 | 93 | transition_rampup_short_sharp2 | +| 10 | dbl_click | 52 | pulsing_strong | 94 | transition_rampdown_long_smooth1_50 | +| 11 | dbl_click_60 | 53 | pulsing_strong_80 | 95 | transition_rampdown_long_smooth2_50 | +| 12 | trp_click | 54 | pulsing_medium | 96 | transition_rampdown_med_smooth1_50 | +| 13 | soft_fuzz | 55 | pulsing_medium_80 | 97 | transition_rampdown_med_smooth2_50 | +| 14 | strong_buzz | 56 | pulsing_sharp | 98 | transition_rampdown_short_smooth1_50 | +| 15 | alert_750ms | 57 | pulsing_sharp_80 | 99 | transition_rampdown_short_smooth2_50 | +| 16 | alert_1000ms | 58 | transition_click | 100 | transition_rampdown_long_sharp1_50 | +| 17 | strong_click1 | 59 | transition_click_80 | 101 | transition_rampdown_long_sharp2_50 | +| 18 | strong_click2_80 | 60 | transition_click_60 | 102 | transition_rampdown_med_sharp1_50 | +| 19 | strong_click3_60 | 61 | transition_click_40 | 103 | transition_rampdown_med_sharp2_50 | +| 20 | strong_click4_30 | 62 | transition_click_20 | 104 | transition_rampdown_short_sharp1_50 | +| 21 | medium_click1 | 63 | transition_click_10 | 105 | transition_rampdown_short_sharp2_50 | +| 22 | medium_click2_80 | 64 | transition_hum | 106 | transition_rampup_long_smooth1_50 | +| 23 | medium_click3_60 | 65 | transition_hum_80 | 107 | transition_rampup_long_smooth2_50 | +| 24 | sharp_tick1 | 66 | transition_hum_60 | 108 | transition_rampup_med_smooth1_50 | +| 25 | sharp_tick2_80 | 67 | transition_hum_40 | 109 | transition_rampup_med_smooth2_50 | +| 26 | sharp_tick3_60 | 68 | transition_hum_20 | 110 | transition_rampup_short_smooth1_50 | +| 27 | sh_dblclick_str | 69 | transition_hum_10 | 111 | transition_rampup_short_smooth2_50 | +| 28 | sh_dblclick_str_80 | 70 | transition_rampdown_long_smooth1 | 112 | transition_rampup_long_sharp1_50 | +| 29 | sh_dblclick_str_60 | 71 | transition_rampdown_long_smooth2 | 113 | transition_rampup_long_sharp2_50 | +| 30 | sh_dblclick_str_30 | 72 | transition_rampdown_med_smooth1 | 114 | transition_rampup_med_sharp1_50 | +| 31 | sh_dblclick_med | 73 | transition_rampdown_med_smooth2 | 115 | transition_rampup_med_sharp2_50 | +| 32 | sh_dblclick_med_80 | 74 | transition_rampdown_short_smooth1 | 116 | transition_rampup_short_sharp1_50 | +| 33 | sh_dblclick_med_60 | 75 | transition_rampdown_short_smooth2 | 117 | transition_rampup_short_sharp2_50 | +| 34 | sh_dblsharp_tick | 76 | transition_rampdown_long_sharp1 | 118 | long_buzz_for_programmatic_stopping | +| 35 | sh_dblsharp_tick_80 | 77 | transition_rampdown_long_sharp2 | 119 | smooth_hum1_50 | +| 36 | sh_dblsharp_tick_60 | 78 | transition_rampdown_med_sharp1 | 120 | smooth_hum2_40 | +| 37 | lg_dblclick_str | 79 | transition_rampdown_med_sharp2 | 121 | smooth_hum3_30 | +| 38 | lg_dblclick_str_80 | 80 | transition_rampdown_short_sharp1 | 122 | smooth_hum4_20 | +| 39 | lg_dblclick_str_60 | 81 | transition_rampdown_short_sharp2 | 123 | smooth_hum5_10 | +| 40 | lg_dblclick_str_30 | 82 | transition_rampup_long_smooth1 | | | +| 41 | lg_dblclick_med | 83 | transition_rampup_long_smooth2 | | | +| 42 | lg_dblclick_med_80 | 84 | transition_rampup_med_smooth1 | | | +### Optional DRV2605L defines + +```c +#define DRV2605L_GREETING *sequence name or number* +``` +If haptic feedback is enabled, the keyboard will vibrate to a specific sequence during startup. That can be selected using the following define: + +```c +#define DRV2605L_DEFAULT_MODE *sequence name or number* +``` +This will set what sequence `HF_RST` will set as the active mode. If not defined, mode will be set to 1 when `HF_RST` is pressed. + +### DRV2605L Continuous Haptic Mode + +This mode sets continuous haptic feedback with the option to increase or decrease strength. + +## Haptic Key Exclusion +The Haptic Exclusion is implemented as `__attribute__((weak)) bool get_haptic_enabled_key(uint16_t keycode, keyrecord_t *record)` in haptic.c. This allows a re-definition at the required level with the specific requirement / exclusion. + +### NO_HAPTIC_MOD +With the entry of `#define NO_HAPTIC_MOD` in config.h, the following keys will not trigger feedback: + +* Usual modifier keys such as Control/Shift/Alt/Gui (For example `KC_LCTL`) +* `MO()` momentary keys. See also [Layers](../feature_layers). +* `LM()` momentary keys with mod active. +* `LT()` layer tap keys, when held to activate a layer. However when tapped, and the key is quickly released, and sends a keycode, haptic feedback is still triggered. +* `TT()` layer tap toggle keys, when held to activate a layer. However when tapped `TAPPING_TOGGLE` times to permanently toggle the layer, on the last tap haptic feedback is still triggered. +* `MT()` mod tap keys, when held to keep a usual modifier key pressed. However when tapped, and the key is quickly released, and sends a keycode, haptic feedback is still triggered. See also [Mod-Tap](../mod_tap). + +### NO_HAPTIC_ALPHA +With the entry of `#define NO_HAPTIC_ALPHA` in config.h, none of the alpha keys (A ... Z) will trigger a feedback. + +### NO_HAPTIC_PUNCTUATION +With the entry of `#define NO_HAPTIC_PUNCTUATION` in config.h, none of the following keys will trigger a feedback: Enter, ESC, Backspace, Space, Minus, Equal, Left Bracket, Right Bracket, Backslash, Non-US Hash, Semicolon, Quote, Grave, Comma, Slash, Dot, Non-US Backslash. + +### NO_HAPTIC_LOCKKEYS +With the entry of `#define NO_HAPTIC_LOCKKEYS` in config.h, none of the following keys will trigger a feedback: Caps Lock, Scroll Lock, Num Lock. + +### NO_HAPTIC_NAV +With the entry of `#define NO_HAPTIC_NAV` in config.h, none of the following keys will trigger a feedback: Print Screen, Pause, Insert, Delete, Page Down, Page Up, Left Arrow, Up Arrow, Right Arrow, Down Arrow, End, Home. + +### NO_HAPTIC_NUMERIC +With the entry of `#define NO_HAPTIC_NUMERIC` in config.h, none of the following keys between 0 and 9 (KC_1 ... KC_0) will trigger a feedback. diff --git a/docs/features/hd44780.md b/docs/features/hd44780.md new file mode 100644 index 0000000000..6b4209333b --- /dev/null +++ b/docs/features/hd44780.md @@ -0,0 +1,298 @@ +# HD44780 LCD Driver {#hd44780-lcd-driver} + +## Supported Hardware {#supported-hardware} + +LCD modules using [HD44780U](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) IC or equivalent, communicating in 4-bit mode. + +|Module|Size |Notes | +|------|--------------|---------------------------------| +|1602A |16x2, 5x8 dots| | +|2004A |20x4, 5x8 dots|Untested, not currently supported| + +To run these modules at 3.3V, an additional MAX660 voltage converter IC must be soldered on, along with two 10µF capacitors. See [this page](https://www.codrey.com/electronic-circuits/hack-your-16x2-lcd/) for more details. + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +HD44780_ENABLE = yes +``` + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|-----------------------|--------------|-----------------------------------------------------------------------------------------------------| +|`HD44780_DATA_PINS` |*Not defined* |(Required) An array of four GPIO pins connected to the display's D4-D7 pins, eg. `{ B1, B3, B2, B6 }`| +|`HD44780_RS_PIN` |*Not defined* |(Required) The GPIO connected to the display's RS pin | +|`HD44780_RW_PIN` |*Not defined* |(Required) The GPIO connected to the display's RW pin | +|`HD44780_E_PIN` |*Not defined* |(Required) The GPIO connected to the display's E pin | +|`HD44780_DISPLAY_COLS` |`16` |The number of visible characters on a single line of the display | +|`HD44780_DISPLAY_LINES`|`2` |The number of visible lines on the display | +|`HD44780_WRAP_LINES` |*Not defined* |If defined, input characters will wrap to the next line | + +## Examples {#examples} + +### Hello World {#example-hello-world} + +Add the following to your `keymap.c`: + +```c +void keyboard_post_init_user(void) { + hd44780_init(true, true); // Show blinking cursor + hd44780_puts_P(PSTR("Hello, world!\n")); +} +``` + +### Custom Character Definition {#example-custom-character} + +Up to eight custom characters can be defined. This data is stored in the Character Generator RAM (CGRAM), and is not persistent across power cycles. + +This example defines the QMK Psi as the first custom character. The first 16 positions in the character set are reserved for the eight custom characters duplicated. + +``` +Byte | 16 8 4 2 1 + 1 | x x x ■ □ ■ □ ■ + 2 | x x x ■ □ ■ □ ■ + 3 | x x x ■ □ ■ □ ■ + 4 | x x x □ ■ ■ ■ □ + 5 | x x x □ □ ■ □ □ + 6 | x x x □ □ ■ □ □ + 7 | x x x □ □ ■ □ □ + 8 | x x x □ □ □ □ □ +``` + +```c +const uint8_t PROGMEM psi[8] = { 0x15, 0x15, 0x15, 0x0E, 0x04, 0x04, 0x04, 0x00 }; + +void keyboard_post_init_user(void) { + hd44780_init(false, false); + hd44780_define_char_P(0, psi); + // Cursor is incremented while defining characters so must be reset + hd44780_home(); + // 0x08 to avoid null terminator + hd44780_puts_P(PSTR("\x08 QMK Firmware")); +} +``` + +## API {#api} + +### `void hd44780_init(bool cursor, bool blink)` {#api-hd44780-init} + +Initialize the display. + +This function should be called only once, before any of the other functions can be called. + +#### Arguments {#api-hd44780-init-arguments} + + - `bool cursor` + Whether to show the cursor. + - `bool blink` + Whether to blink the cursor, if shown. + +--- + +### `void hd44780_clear(void)` {#api-hd44780-clear} + +Clear the display. + +This function is called on init. + +--- + +### `void hd44780_home(void)` {#api-hd44780-home} + +Move the cursor to the home position. + +This function is called on init. + +--- + +### `void hd44780_on(bool cursor, bool blink)` {#api-hd44780-on} + +Turn the display on, and/or set the cursor properties. + +This function is called on init. + +#### Arguments {#api-hd44780-on-arguments} + + - `bool cursor` + Whether to show the cursor. + - `bool blink` + Whether to blink the cursor, if shown. + +--- + +### `void hd44780_off(void)` {#api-hd44780-off} + +Turn the display off. + +--- + +### `void hd44780_set_cursor(uint8_t col, uint8_t line)` {#api-hd44780-set-cursor} + +Move the cursor to the specified position on the display. + +#### Arguments {#api-hd44780-set-cursor-arguments} + + - `uint8_t col` + The column number to move to, from 0 to 15 on 16x2 displays. + - `bool line` + The line number to move to, either 0 or 1 on 16x2 displays. + +--- + +### `void hd44780_putc(char c)` {#api-hd44780-putc} + +Print a character to the display. The newline character `\n` will move the cursor to the start of the next line. + +The exact character shown may depend on the ROM code of your particular display - refer to the datasheet for the full character set. + +#### Arguments {#api-hd44780-putc-arguments} + + - `char c` + The character to print. + +--- + +### `void hd44780_puts(const char *s)` {#api-hd44780-puts} + +Print a string of characters to the display. + +#### Arguments {#api-hd44780-puts-arguments} + + - `const char *s` + The string to print. + +--- + +### `void hd44780_puts_P(const char *s)` {#api-hd44780-puts-p} + +Print a string of characters from PROGMEM to the display. + +On ARM devices, this function is simply an alias of `hd44780_puts()`. + +#### Arguments {#api-hd44780-puts-p-arguments} + + - `const char *s` + The PROGMEM string to print (ie. `PSTR("Hello")`). + +--- + +### `void hd44780_define_char(uint8_t index, uint8_t *data)` {#api-hd44780-define-char} + +Define a custom character. + +#### Arguments {#api-hd44780-define-char-arguments} + + - `uint8_t index` + The index of the custom character to define, from 0 to 7. + - `uint8_t *data` + An array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column. + +--- + +### `void hd44780_define_char_P(uint8_t index, const uint8_t *data)` {#api-hd44780-define-char-p} + +Define a custom character from PROGMEM. + +On ARM devices, this function is simply an alias of `hd44780_define_char()`. + +#### Arguments {#api-hd44780-define-char-p-arguments} + + - `uint8_t index` + The index of the custom character to define, from 0 to 7. + - `const uint8_t *data` + A PROGMEM array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column. + +--- + +### `bool hd44780_busy(void)` {#api-hd44780-busy} + +Indicates whether the display is currently processing, and cannot accept instructions. + +#### Return Value {#api-hd44780-busy-arguments} + +`true` if the display is busy. + +--- + +### `void hd44780_write(uint8_t data, bool isData)` {#api-hd44780-write} + +Write a byte to the display. + +#### Arguments {#api-hd44780-write-arguments} + + - `uint8_t data` + The byte to send to the display. + - `bool isData` + Whether the byte is an instruction or character data. + +--- + +### `uint8_t hd44780_read(bool isData)` {#api-hd44780-read} + +Read a byte from the display. + +#### Arguments {#api-hd44780-read-arguments} + + - `bool isData` + Whether to read the current cursor position, or the character at the cursor. + +#### Return Value {#api-hd44780-read-return} + +If `isData` is `true`, the returned byte will be the character at the current DDRAM address. Otherwise, it will be the current DDRAM address and the busy flag. + +--- + +### `void hd44780_command(uint8_t command)` {#api-hd44780-command} + +Send a command to the display. Refer to the datasheet and `hd44780.h` for the valid commands and defines. + +This function waits for the display to clear the busy flag before sending the command. + +#### Arguments {#api-hd44780-command-arguments} + + - `uint8_t command` + The command to send. + +--- + +### `void hd44780_data(uint8_t data)` {#api-hd44780-data} + +Send a byte of data to the display. + +This function waits for the display to clear the busy flag before sending the data. + +#### Arguments {#api-hd44780-data-arguments} + + - `uint8_t data` + The byte of data to send. + +--- + +### `void hd44780_set_cgram_address(uint8_t address)` {#api-hd44780-set-cgram-address} + +Set the CGRAM address. + +This function is used when defining custom characters. + +#### Arguments {#api-hd44780-set-cgram-address-arguments} + + - `uint8_t address` + The CGRAM address to move to, from `0x00` to `0x3F`. + +--- + +### `void hd44780_set_ddram_address(uint8_t address)` {#api-hd44780-set-ddram-address} + +Set the DDRAM address. + +This function is used when printing characters to the display, and setting the cursor. + +#### Arguments {#api-hd44780-set-ddram-address-arguments} + + - `uint8_t address` + The DDRAM address to move to, from `0x00` to `0x7F`. diff --git a/docs/features/joystick.md b/docs/features/joystick.md new file mode 100644 index 0000000000..f3fd209d5a --- /dev/null +++ b/docs/features/joystick.md @@ -0,0 +1,222 @@ +# Joystick {#joystick} + +This feature provides game controller input as a joystick device supporting up to 6 axes and 32 buttons. Axes can be read either from an [ADC-capable input pin](../drivers/adc), or can be virtual, so that its value is provided by your code. + +An analog device such as a [potentiometer](https://en.wikipedia.org/wiki/Potentiometer) found on an analog joystick's axes is based on a voltage divider, where adjusting the movable wiper controls the output voltage which can then be read by the microcontroller's ADC. + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +JOYSTICK_ENABLE = yes +``` + +By default the joystick driver is `analog`, but you can change this with: + +```make +JOYSTICK_DRIVER = digital +``` + +## Configuration {#configuration} + +By default, two axes and eight buttons are defined, with a reported resolution of 8 bits (-127 to +127). This can be changed in your `config.h`: + +```c +// Min 0, max 32 +#define JOYSTICK_BUTTON_COUNT 16 +// Min 0, max 6: X, Y, Z, Rx, Ry, Rz +#define JOYSTICK_AXIS_COUNT 3 +// Min 8, max 16 +#define JOYSTICK_AXIS_RESOLUTION 10 +``` + +::: tip +You must define at least one button or axis. Also note that the maximum ADC resolution of the supported AVR MCUs is 10-bit, and 12-bit for most STM32 MCUs. +::: + +### Axes {#axes} + +When defining axes for your joystick, you must provide a definition array typically in your `keymap.c`. + +For instance, the below example configures two axes. The X axis is read from the `A4` pin. With the default axis resolution of 8 bits, the range of values between 900 and 575 are scaled to -127 through 0, and values 575 to 285 are scaled to 0 through 127. The Y axis is configured as a virtual axis, and its value is not read from any pin. Instead, the user must update the axis value programmatically. + +```c +joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = { + JOYSTICK_AXIS_IN(A4, 900, 575, 285), + JOYSTICK_AXIS_VIRTUAL +}; +``` + +Axes can be configured using one of the following macros: + + * `JOYSTICK_AXIS_IN(input_pin, low, rest, high)` + The ADC samples the provided pin. `low`, `high` and `rest` correspond to the minimum, maximum, and resting (or centered) analog values of the axis, respectively. + * `JOYSTICK_AXIS_VIRTUAL` + No ADC reading is performed. The value should be provided by user code. + +The `low` and `high` values can be swapped to effectively invert the axis. + +#### Virtual Axes {#virtual-axes} + +The following example adjusts two virtual axes (X and Y) based on keypad presses, with `KC_P0` as a precision modifier: + +```c +joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = { + JOYSTICK_AXIS_VIRTUAL, // x + JOYSTICK_AXIS_VIRTUAL // y +}; + +static bool precision = false; +static uint16_t precision_mod = 64; +static uint16_t axis_val = 127; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + int16_t precision_val = axis_val; + if (precision) { + precision_val -= precision_mod; + } + + switch (keycode) { + case KC_P8: + joystick_set_axis(1, record->event.pressed ? -precision_val : 0); + return false; + case KC_P2: + joystick_set_axis(1, record->event.pressed ? precision_val : 0); + return false; + case KC_P4: + joystick_set_axis(0, record->event.pressed ? -precision_val : 0); + return false; + case KC_P6: + joystick_set_axis(0, record->event.pressed ? precision_val : 0); + return false; + case KC_P0: + precision = record->event.pressed; + return false; + } + return true; +} +``` + +## Keycodes {#keycodes} + +|Key |Aliases|Description| +|-----------------------|-------|-----------| +|`QK_JOYSTICK_BUTTON_0` |`JS_0` |Button 0 | +|`QK_JOYSTICK_BUTTON_1` |`JS_1` |Button 1 | +|`QK_JOYSTICK_BUTTON_2` |`JS_2` |Button 2 | +|`QK_JOYSTICK_BUTTON_3` |`JS_3` |Button 3 | +|`QK_JOYSTICK_BUTTON_4` |`JS_4` |Button 4 | +|`QK_JOYSTICK_BUTTON_5` |`JS_5` |Button 5 | +|`QK_JOYSTICK_BUTTON_6` |`JS_6` |Button 6 | +|`QK_JOYSTICK_BUTTON_7` |`JS_7` |Button 7 | +|`QK_JOYSTICK_BUTTON_8` |`JS_8` |Button 8 | +|`QK_JOYSTICK_BUTTON_9` |`JS_9` |Button 9 | +|`QK_JOYSTICK_BUTTON_10`|`JS_10`|Button 10 | +|`QK_JOYSTICK_BUTTON_11`|`JS_11`|Button 11 | +|`QK_JOYSTICK_BUTTON_12`|`JS_12`|Button 12 | +|`QK_JOYSTICK_BUTTON_13`|`JS_13`|Button 13 | +|`QK_JOYSTICK_BUTTON_14`|`JS_14`|Button 14 | +|`QK_JOYSTICK_BUTTON_15`|`JS_15`|Button 15 | +|`QK_JOYSTICK_BUTTON_16`|`JS_16`|Button 16 | +|`QK_JOYSTICK_BUTTON_17`|`JS_17`|Button 17 | +|`QK_JOYSTICK_BUTTON_18`|`JS_18`|Button 18 | +|`QK_JOYSTICK_BUTTON_19`|`JS_19`|Button 19 | +|`QK_JOYSTICK_BUTTON_20`|`JS_20`|Button 20 | +|`QK_JOYSTICK_BUTTON_21`|`JS_21`|Button 21 | +|`QK_JOYSTICK_BUTTON_22`|`JS_22`|Button 22 | +|`QK_JOYSTICK_BUTTON_23`|`JS_23`|Button 23 | +|`QK_JOYSTICK_BUTTON_24`|`JS_24`|Button 24 | +|`QK_JOYSTICK_BUTTON_25`|`JS_25`|Button 25 | +|`QK_JOYSTICK_BUTTON_26`|`JS_26`|Button 26 | +|`QK_JOYSTICK_BUTTON_27`|`JS_27`|Button 27 | +|`QK_JOYSTICK_BUTTON_28`|`JS_28`|Button 28 | +|`QK_JOYSTICK_BUTTON_29`|`JS_29`|Button 29 | +|`QK_JOYSTICK_BUTTON_30`|`JS_30`|Button 30 | +|`QK_JOYSTICK_BUTTON_31`|`JS_31`|Button 31 | + +## API {#api} + +### `struct joystick_t` {#api-joystick-t} + +Contains the state of the joystick. + +#### Members {#api-joystick-t-members} + + - `uint8_t buttons[]` + A bit-packed array containing the joystick button states. The size is calculated as `(JOYSTICK_BUTTON_COUNT - 1) / 8 + 1`. + - `int16_t axes[]` + An array of analog values for each defined axis. + - `bool dirty` + Whether the current state needs to be sent to the host. + +--- + +### `struct joystick_config_t` {#api-joystick-config-t} + +Describes a single axis. + +#### Members {#api-joystick-config-t-members} + + - `pin_t input_pin` + The pin to read the analog value from, or `JS_VIRTUAL_AXIS`. + - `uint16_t min_digit` + The minimum analog value. + - `uint16_t mid_digit` + The resting or midpoint analog value. + - `uint16_t max_digit` + The maximum analog value. + +--- + +### `void joystick_flush(void)` {#api-joystick-flush} + +Send the joystick report to the host, if it has been marked as dirty. + +--- + +### `void register_joystick_button(uint8_t button)` {#api-register-joystick-button} + +Set the state of a button, and flush the report. + +#### Arguments {#api-register-joystick-button-arguments} + + - `uint8_t button` + The index of the button to press, from 0 to 31. + +--- + +### `void unregister_joystick_button(uint8_t button)` {#api-unregister-joystick-button} + +Reset the state of a button, and flush the report. + +#### Arguments {#api-unregister-joystick-button-arguments} + + - `uint8_t button` + The index of the button to release, from 0 to 31. + +--- + +### `int16_t joystick_read_axis(uint8_t axis)` {#api-joystick-read-axis} + +Sample and process the analog value of the given axis. + +#### Arguments {#api-joystick-read-axis-arguments} + + - `uint8_t axis` + The axis to read. + +#### Return Value {#api-joystick-read-axis-return} + +A signed 16-bit integer, where 0 is the resting or mid point. + +### `void joystick_set_axis(uint8_t axis, int16_t value)` {#api-joystick-set-axis} + +Set the value of the given axis. + +#### Arguments {#api-joystick-set-axis-arguments} + + - `uint8_t axis` + The axis to set the value of. + - `int16_t value` + The value to set. diff --git a/docs/features/key_lock.md b/docs/features/key_lock.md new file mode 100644 index 0000000000..ba3b71ef06 --- /dev/null +++ b/docs/features/key_lock.md @@ -0,0 +1,23 @@ +# Key Lock + +Sometimes you may find yourself needing to hold down a specific key for a long period of time. Key Lock holds down the next key you press for you. Press it again, and it will be released. + +Let's say you need to type in ALL CAPS for a few sentences. Hit `QK_LOCK`, and then Shift. Now, Shift will be considered held until you tap it again. You can think of Key Lock as Caps Lock, but supercharged. + +## Usage + +First, enable Key Lock by setting `KEY_LOCK_ENABLE = yes` in your `rules.mk`. Then pick a key in your keymap and assign it the keycode `QK_LOCK`. + +## Keycodes + +|Keycode |Description | +|---------|--------------------------------------------------------------| +|`QK_LOCK`|Hold down the next key pressed, until the key is pressed again| + +## Caveats + +Key Lock is only able to hold standard action keys and [One Shot modifier](../one_shot_keys) keys (for example, if you have your Shift defined as `OSM(MOD_LSFT)`). +This does not include any of the QMK special functions (except One Shot modifiers), or shifted versions of keys such as `KC_LPRN`. If it's in the [Basic Keycodes](../keycodes_basic) list, it can be held. + +Switching layers will not cancel the Key Lock. The Key Lock can be cancelled by calling the `cancel_key_lock()` function. + diff --git a/docs/features/key_overrides.md b/docs/features/key_overrides.md new file mode 100644 index 0000000000..4c568f1679 --- /dev/null +++ b/docs/features/key_overrides.md @@ -0,0 +1,256 @@ +# Key Overrides {#key-overrides} + +Key overrides allow you to override modifier-key combinations to send a different modifier-key combination or perform completely custom actions. Don't want `shift` + `1` to type `!` on your computer? Use a key override to make your keyboard type something different when you press `shift` + `1`. The general behavior is like this: If `modifiers w` + `key x` are pressed, replace these keys with `modifiers y` + `key z` in the keyboard report. + +You can use key overrides in a similar way to momentary layer/fn keys to activate custom keycodes/shortcuts, with a number of benefits: You completely keep the original use of the modifier keys, while being able to save space by removing fn keys from your keyboard. You can also easily configure _combinations of modifiers_ to trigger different actions than individual modifiers, and much more. The possibilities are quite vast and this documentation contains a few examples for inspiration throughout. + +##### A few more examples to get started: You could use key overrides to... +- Send `brightness up/down` when pressing `ctrl` + `volume up/down`. +- Send `delete` when pressing `shift` + `backspace`. +- Create custom shortcuts or change existing ones: E.g. Send `ctrl`+`shift`+`z` when `ctrl`+`y` is pressed. +- Run custom code when `ctrl` + `alt` + `esc` is pressed. + +## Setup {#setup} + +To enable this feature, you need to add `KEY_OVERRIDE_ENABLE = yes` to your `rules.mk`. + +Then, in your `keymap.c` file, you'll need to define the array `key_overrides`, which defines all key overrides to be used. Each override is a value of type `key_override_t`. The array `key_overrides` is `NULL`-terminated and contains pointers to `key_override_t` values (`const key_override_t **`). + +## Creating Key Overrides {#creating-key-overrides} + +The `key_override_t` struct has many options that allow you to precisely tune your overrides. The full reference is shown below. Instead of manually creating a `key_override_t` value, it is recommended to use these dedicated initializers: + +#### `ko_make_basic(modifiers, key, replacement)` +Returns a `key_override_t`, which sends `replacement` (can be a key-modifier combination), when `key` and `modifiers` are all pressed down. This override still activates if any additional modifiers not specified in `modifiers` are also pressed down. See `ko_make_with_layers_and_negmods` to customize this behavior. + +#### `ko_make_with_layers(modifiers, key, replacement, layers)` +Additionally takes a bitmask `layers` that defines on which layers the override is used. + +#### `ko_make_with_layers_and_negmods(modifiers, key, replacement, layers, negative_mods)` +Additionally takes a bitmask `negative_mods` that defines which modifiers may not be pressed for this override to activate. + +#### `ko_make_with_layers_negmods_and_options(modifiers, key, replacement, layers, negative_mods, options)` +Additionally takes a bitmask `options` that specifies additional options. See `ko_option_t` for available options. + +For more customization possibilities, you may directly create a `key_override_t`, which allows you to customize even more behavior. Read further below for details and examples. + +## Simple Example {#simple-example} + +This shows how the mentioned example of sending `delete` when `shift` + `backspace` are pressed is realized: + +```c +const key_override_t delete_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_BSPC, KC_DEL); + +// This globally defines all key overrides to be used +const key_override_t **key_overrides = (const key_override_t *[]){ + &delete_key_override, + NULL // Null terminate the array of overrides! +}; +``` + +## Intermediate Difficulty Examples {#intermediate-difficulty-examples} + +### Media Controls & Screen Brightness {#media-controls-amp-screen-brightness} + +In this example a single key is configured to control media, volume and screen brightness by using key overrides. + +- The key is set to send `play/pause` in the keymap. + +The following key overrides will be configured: + +- `Ctrl` + `play/pause` will send `next track`. +- `Ctrl` + `Shift` + `play/pause` will send `previous track`. +- `Alt` + `play/pause` will send `volume up`. +- `Alt` + `Shift` + `play/pause` will send `volume down`. +- `Ctrl` + `Alt` + `play/pause` will send `brightness up`. +- `Ctrl` + `Alt` + `Shift` + `play/pause` will send `brightness down`. + + +```c +const key_override_t next_track_override = + ko_make_with_layers_negmods_and_options( + MOD_MASK_CTRL, // Trigger modifiers: ctrl + KC_MPLY, // Trigger key: play/pause + KC_MNXT, // Replacement key + ~0, // Activate on all layers + MOD_MASK_SA, // Do not activate when shift or alt are pressed + ko_option_no_reregister_trigger); // Specifies that the play key is not registered again after lifting ctrl + +const key_override_t prev_track_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CS, KC_MPLY, + KC_MPRV, ~0, MOD_MASK_ALT, ko_option_no_reregister_trigger); + +const key_override_t vol_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_ALT, KC_MPLY, + KC_VOLU, ~0, MOD_MASK_CS, ko_option_no_reregister_trigger); + +const key_override_t vol_down_override = ko_make_with_layers_negmods_and_options(MOD_MASK_SA, KC_MPLY, + KC_VOLD, ~0, MOD_MASK_CTRL, ko_option_no_reregister_trigger); + +const key_override_t brightness_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CA, KC_MPLY, + KC_BRIU, ~0, MOD_MASK_SHIFT, ko_option_no_reregister_trigger); + +const key_override_t brightness_down_override = ko_make_basic(MOD_MASK_CSA, KC_MPLY, KC_BRID); + +// This globally defines all key overrides to be used +const key_override_t **key_overrides = (const key_override_t *[]){ + &next_track_override, + &prev_track_override, + &vol_up_override, + &vol_down_override, + &brightness_up_override, + &brightness_down_override, + NULL +}; +``` + +### Flexible macOS-friendly Grave Escape {#flexible-macos-friendly-grave-escape} +The [Grave Escape feature](grave_esc) is limited in its configurability and has [bugs when used on macOS](grave_esc#caveats). Key overrides can be used to achieve a similar functionality as Grave Escape, but with more customization and without bugs on macOS. + +```c +// Shift + esc = ~ +const key_override_t tilde_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, S(KC_GRV)); + +// GUI + esc = ` +const key_override_t grave_esc_override = ko_make_basic(MOD_MASK_GUI, KC_ESC, KC_GRV); + +const key_override_t **key_overrides = (const key_override_t *[]){ + &tilde_esc_override, + &grave_esc_override, + NULL +}; +``` + +In addition to not encountering unexpected bugs on macOS, you can also change the behavior as you wish. Instead setting `GUI` + `ESC` = `` ` `` you may change it to an arbitrary other modifier, for example `Ctrl` + `ESC` = `` ` ``. + +## Advanced Examples {#advanced-examples} +### Modifiers as Layer Keys {#modifiers-as-layer-keys} + +Do you really need a dedicated key to toggle your fn layer? With key overrides, perhaps not. This example shows how you can configure to use `rGUI` + `rAlt` (right GUI and right alt) to access a momentary layer like an fn layer. With this you completely eliminate the need to use a dedicated layer key. Of course the choice of modifier keys can be changed as needed, `rGUI` + `rAlt` is just an example here. + +```c +// This is called when the override activates and deactivates. Enable the fn layer on activation and disable on deactivation +bool momentary_layer(bool key_down, void *layer) { + if (key_down) { + layer_on((uint8_t)(uintptr_t)layer); + } else { + layer_off((uint8_t)(uintptr_t)layer); + } + + return false; +} + +const key_override_t fn_override = {.trigger_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), // + .layers = ~(1 << LAYER_FN), // + .suppressed_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), // + .options = ko_option_no_unregister_on_other_key_down, // + .negative_mod_mask = (uint8_t) ~(MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL)), // + .custom_action = momentary_layer, // + .context = (void *)LAYER_FN, // + .trigger = KC_NO, // + .replacement = KC_NO, // + .enabled = NULL}; +``` + +## Keycodes {#keycodes} + +|Keycode |Aliases |Description | +|------------------------|---------|----------------------| +|`QK_KEY_OVERRIDE_TOGGLE`|`KO_TOGG`|Toggle key overrides | +|`QK_KEY_OVERRIDE_ON` |`KO_ON` |Turn on key overrides | +|`QK_KEY_OVERRIDE_OFF` |`KO_OFF` |Turn off key overrides| + +## Reference for `key_override_t` {#reference-for-key_override_t} + +Advanced users may need more customization than what is offered by the simple `ko_make` initializers. For this, directly create a `key_override_t` value and set all members. Below is a reference for all members of `key_override_t`. + +| Member | Description | +|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `uint16_t trigger` | The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (`trigger_mods`) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to `KC_NO` to require only the necessary modifiers to be pressed and no non-modifier. | +| `uint8_t trigger_mods` | Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the `MOD_MASK_XXX` and `MOD_BIT()` macros for this. | +| `layer_state_t layers` | This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit `(1 << i)`. | +| `uint8_t negative_mod_mask` | Which modifiers cannot be down. It must hold that `(active_modifiers & negative_mod_mask) == 0`, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true. | +| `uint8_t suppressed_mods` | Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example. | +| `uint16_t replacement` | The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. `C(KC_A)`), or `KC_NO` (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent. | +| `ko_option_t options` | Options controlling the behavior of the override, such as what actions are allowed to activate the override. | +| `bool (*custom_action)(bool activated, void *context)` | If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally. | +| `void *context` | A context that will be passed to the custom action function. | +| `bool *enabled` | If this points to false this override will not be used. Set to NULL to always have this override enabled. | + +## Reference for `ko_option_t` {#reference-for-ko_option_t} + +Bitfield with various options controlling the behavior of a key override. + +| Value | Description | +|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ko_option_activation_trigger_down` | Allow activating when the trigger key is pressed down. | +| `ko_option_activation_required_mod_down` | Allow activating when a necessary modifier is pressed down. | +| `ko_option_activation_negative_mod_up` | Allow activating when a negative modifier is released. | +| `ko_option_one_mod` | If set, any of the modifiers in `trigger_mods` will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in `trigger_mods` have to be pressed (logical AND of modifiers). | +| `ko_option_no_unregister_on_other_key_down` | If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. | +| `ko_option_no_reregister_trigger` | If set, the trigger key will never be registered again after the override is deactivated. | +| `ko_options_default` | The default options used by the `ko_make_xxx` functions | + +## For Advanced Users: Inner Workings {#for-advanced-users-inner-workings} + +This section explains how a key override works in detail, explaining where each member of `key_override_t` comes into play. Understanding this is essential to be able to take full advantage of all the options offered by key overrides. + +#### Activation {#activation} + +When the necessary keys are pressed (`trigger_mods` + `trigger`), the override is 'activated' and the replacement key is registered in the keyboard report (`replacement`), while the `trigger` key is removed from the keyboard report. The trigger modifiers may also be removed from the keyboard report upon activation of an override (`suppressed_mods`). The override will not activate if any of the `negative_modifiers` are pressed. + +Overrides can activate in three different cases: + +1. The trigger key is pressed down and necessary modifiers are already down. +2. A necessary modifier is pressed down, while the trigger key and other necessary modifiers are already down. +3. A negative modifier is released, while all necessary modifiers and the trigger key are already down. + +Use the `option` member to customize which of these events are allowed to activate your overrides (default: all three). + +In any case, a key override can only activate if the `trigger` key is the _last_ non-modifier key that was pressed down. This emulates the behavior of how standard OSes (macOS, Windows, Linux) handle normal key input (to understand: Hold down `a`, then also hold down `b`, then hold down `shift`; `B` will be typed but not `A`). + +#### Deactivation {#deactivation} + +An override is 'deactivated' when one of the trigger keys (`trigger_mods`, `trigger`) is lifted, another non-modifier key is pressed down, or one of the `negative_modifiers` is pressed down. When an override deactivates, the `replacement` key is removed from the keyboard report, while the `suppressed_mods` that are still held down are re-added to the keyboard report. By default, the `trigger` key is re-added to the keyboard report if it is still held down and no other non-modifier key has been pressed since. This again emulates the behavior of how standard OSes handle normal key input (To understand: hold down `a`, then also hold down `b`, then also `shift`, then release `b`; `A` will not be typed even though you are holding the `a` and `shift` keys). Use the `option` field `ko_option_no_reregister_trigger` to prevent re-registering the trigger key in all cases. + +#### Key Repeat Delay {#key-repeat-delay} + +A third way in which standard OS-handling of modifier-key input is emulated in key overrides is with a ['key repeat delay'](https://www.dummies.com/computers/pcs/set-your-keyboards-repeat-delay-and-repeat-rate/). To explain what this is, let's look at how normal keyboard input is handled by mainstream OSes again: If you hold down `a`, followed by `shift`, you will see the letter `a` is first typed, then for a short moment nothing is typed and then repeating `A`s are typed. Take note that, although shift is pressed down just after `a` is pressed, it takes a moment until `A` is typed. This is caused by the aforementioned key repeat delay, and it is a feature that prevents unwanted repeated characters from being typed. + +This applies equally to releasing a modifier: When you hold `shift`, then press `a`, the letter `A` is typed. Now if you release `shift` first, followed by `a` shortly after, you will not see the letter `a` being typed, even though for a short moment of time you were just holding down the key `a`. This is because no modified characters are typed until the key repeat delay has passed. + + This exact behavior is implemented in key overrides as well: If a key override for `shift` + `a` = `b` exists, and `a` is pressed and held, followed by `shift`, you will not immediately see the letter `b` being typed. Instead, this event is deferred for a short moment, until the key repeat delay has passed, measured from the moment when the trigger key (`a`) was pressed down. + +The duration of the key repeat delay is controlled with the `KEY_OVERRIDE_REPEAT_DELAY` macro. Define this value in your `config.h` file to change it. It is 500ms by default. + + +## Difference to Combos {#difference-to-combos} + +Note that key overrides are very different from [combos](combo). Combos require that you press down several keys almost _at the same time_ and can work with any combination of non-modifier keys. Key overrides work like keyboard shortcuts (e.g. `ctrl` + `z`): They take combinations of _multiple_ modifiers and _one_ non-modifier key to then perform some custom action. Key overrides are implemented with much care to behave just like normal keyboard shortcuts would in regards to the order of pressed keys, timing, and interaction with other pressed keys. There are a number of optional settings that can be used to really fine-tune the behavior of each key override as well. Using key overrides also does not delay key input for regular key presses, which inherently happens in combos and may be undesirable. + +## Solution to the problem of flashing modifiers {#neutralize-flashing-modifiers} + +If the programs you use bind an action to taps of modifier keys (e.g. tapping left GUI to bring up the applications menu or tapping left Alt to focus the menu bar), you may find that using key overrides with suppressed mods falsely triggers those actions. To counteract this, you can define a `DUMMY_MOD_NEUTRALIZER_KEYCODE` in `config.h` that will get sent in between the register and unregister events of a suppressed modifier. That way, the programs on your computer will no longer interpret the mod suppression induced by key overrides as a lone tap of a modifier key and will thus not falsely trigger the undesired action. + +Naturally, for this technique to be effective, you must choose a `DUMMY_MOD_NEUTRALIZER_KEYCODE` for which no keyboard shortcuts are bound to. Recommended values are: `KC_RIGHT_CTRL` or `KC_F18`. +Please note that `DUMMY_MOD_NEUTRALIZER_KEYCODE` must be a basic, unmodified, HID keycode so values like `KC_NO`, `KC_TRANSPARENT` or `KC_PIPE` aka `S(KC_BACKSLASH)` are not permitted. + +By default, only left Alt and left GUI are neutralized. If you want to change the list of applicable modifier masks, use the following in your `config.h`: + +```c +#define MODS_TO_NEUTRALIZE { <mod_mask_1>, <mod_mask_2>, ... } +``` + +Examples: + +```c +#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_RIGHT_CTRL + +// Neutralize left alt and left GUI (Default value) +#define MODS_TO_NEUTRALIZE { MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI) } + +// Neutralize left alt, left GUI, right GUI and left Control+Shift +#define MODS_TO_NEUTRALIZE { MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI), MOD_BIT(KC_RIGHT_GUI), MOD_BIT(KC_LEFT_CTRL)|MOD_BIT(KC_LEFT_SHIFT) } +``` + +::: warning +Do not use `MOD_xxx` constants like `MOD_LSFT` or `MOD_RALT`, since they're 5-bit packed bit-arrays while `MODS_TO_NEUTRALIZE` expects a list of 8-bit packed bit-arrays. Use `MOD_BIT(<kc>)` or `MOD_MASK_xxx` instead. +::: diff --git a/docs/features/leader_key.md b/docs/features/leader_key.md new file mode 100644 index 0000000000..a36e630a36 --- /dev/null +++ b/docs/features/leader_key.md @@ -0,0 +1,297 @@ +# The Leader Key: A New Kind of Modifier {#the-leader-key} + +If you're a Vim user, you probably know what a Leader key is. In contrast to [Combos](combo), the Leader key allows you to hit a *sequence* of up to five keys instead, which triggers some custom functionality once complete. + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +LEADER_ENABLE = yes +``` + +Then add the `QK_LEAD` keycode to your keymap. + +## Callbacks {#callbacks} + +These callbacks are invoked when the leader sequence begins and ends. In the latter you can implement your custom functionality based on the contents of the sequence buffer. + +```c +void leader_start_user(void) { + // Do something when the leader key is pressed +} + +void leader_end_user(void) { + if (leader_sequence_one_key(KC_F)) { + // Leader, f => Types the below string + SEND_STRING("QMK is awesome."); + } else if (leader_sequence_two_keys(KC_D, KC_D)) { + // Leader, d, d => Ctrl+A, Ctrl+C + SEND_STRING(SS_LCTL("a") SS_LCTL("c")); + } else if (leader_sequence_three_keys(KC_D, KC_D, KC_S)) { + // Leader, d, d, s => Types the below string + SEND_STRING("https://start.duckduckgo.com\n"); + } else if (leader_sequence_two_keys(KC_A, KC_S)) { + // Leader, a, s => GUI+S + tap_code16(LGUI(KC_S)); + } +} +``` + +## Basic Configuration {#basic-configuration} + +### Timeout {#timeout} + +This is the amount of time you have to complete a sequence once the leader key has been pressed. The default value is 300 milliseconds, but you can change this by adding the following to your `config.h`: + +```c +#define LEADER_TIMEOUT 350 +``` + +### Per-Key Timeout {#per-key-timeout} + +Rather than relying on an incredibly high timeout for long leader key strings or those of us without 200 wpm typing skills, you can enable per-key timing to ensure that each key pressed provides you with more time to finish the sequence. This is incredibly helpful with leader key emulation of tap dance (such as multiple taps of the same key like C, C, C). + +To enable this, add the following to your `config.h`: + +```c +#define LEADER_PER_KEY_TIMING +``` + +After this, it's recommended that you lower your timeout below 300 ms: + +```c +#define LEADER_TIMEOUT 250 +``` + +Now, something like this won't seem impossible to do without a 1000 millisecond timeout: + +```c +if (leader_sequence_three_keys(KC_C, KC_C, KC_C)) { + SEND_STRING("Per key timing is great!!!"); +} +``` + +### Disabling Initial Timeout {#disabling-initial-timeout} + +Sometimes your leader key may be too far away from the rest of the keys in the sequence. Imagine that your leader key is one of your outer top right keys - you may need to reposition your hand just to reach your leader key. This can make typing the entire sequence on time hard difficult if you are able to type most of the sequence fast. For example, if your sequence is `Leader + asd`, typing `asd` fast is very easy once you have your hands in your home row, but starting the sequence in time after moving your hand out of the home row to reach the leader key and back is not. + +To remove the stress this situation produces to your hands, you can disable the timeout just for the leader key. Add the following to your `config.h`: + +```c +#define LEADER_NO_TIMEOUT +``` + +Now, after you hit the leader key, you will have an infinite amount of time to start the rest of the sequence, allowing you to properly position your hands to type the rest of the sequence comfortably. This way you can configure a very short `LEADER_TIMEOUT`, but still have plenty of time to position your hands. + +### Strict Key Processing {#strict-key-processing} + +By default, only the "tap keycode" portions of [Mod-Taps](../mod_tap) and [Layer Taps](../feature_layers#switching-and-toggling-layers) are added to the sequence buffer. This means if you press eg. `LT(3, KC_A)` as part of a sequence, `KC_A` will be added to the buffer, rather than the entire `LT(3, KC_A)` keycode. + +This gives a more expected behaviour for most users, however you may want to change this. + +To enable this, add the following to your `config.h`: + +```c +#define LEADER_KEY_STRICT_KEY_PROCESSING +``` + +## Example {#example} + +This example will play the Mario "One Up" sound when you hit `QK_LEAD` to start the leader sequence. When the sequence ends, it will play "All Star" if it completes successfully or "Rick Roll" you if it fails (in other words, no sequence matched). + +```c +#ifdef AUDIO_ENABLE +float leader_start_song[][2] = SONG(ONE_UP_SOUND); +float leader_succeed_song[][2] = SONG(ALL_STAR); +float leader_fail_song[][2] = SONG(RICK_ROLL); +#endif + +void leader_start_user(void) { +#ifdef AUDIO_ENABLE + PLAY_SONG(leader_start_song); +#endif +} + +void leader_end_user(void) { + bool did_leader_succeed = false; + + if (leader_sequence_one_key(KC_E)) { + SEND_STRING(SS_LCTL(SS_LSFT("t"))); + did_leader_succeed = true; + } else if (leader_sequence_two_keys(KC_E, KC_D)) { + SEND_STRING(SS_LGUI("r") "cmd\n" SS_LCTL("c")); + did_leader_succeed = true; + } + +#ifdef AUDIO_ENABLE + if (did_leader_succeed) { + PLAY_SONG(leader_succeed_song); + } else { + PLAY_SONG(leader_fail_song); + } +#endif +} +``` + +## Keycodes {#keycodes} + +|Key |Aliases |Description | +|-----------------------|---------|-------------------------| +|`QK_LEADER` |`QK_LEAD`|Begin the leader sequence| + +## API {#api} + +### `void leader_start_user(void)` {#api-leader-start-user} + +User callback, invoked when the leader sequence begins. + +--- + +### `void leader_end_user(void)` {#api-leader-end-user} + +User callback, invoked when the leader sequence ends. + +--- + +### `void leader_start(void)` {#api-leader-start} + +Begin the leader sequence, resetting the buffer and timer. + +--- + +### `void leader_end(void)` {#api-leader-end} + +End the leader sequence. + +--- + +### `bool leader_sequence_active(void)` {#api-leader-sequence-active} + +Whether the leader sequence is active. + +--- + +### `bool leader_sequence_add(uint16_t keycode)` {#api-leader-sequence-add} + +Add the given keycode to the sequence buffer. + +If `LEADER_NO_TIMEOUT` is defined, the timer is reset if the buffer is empty. + +#### Arguments {#api-leader-sequence-add-arguments} + + - `uint16_t keycode` + The keycode to add. + +#### Return Value {#api-leader-sequence-add-return} + +`true` if the keycode was added, `false` if the buffer is full. + +--- + +### `bool leader_sequence_timed_out(void)` {#api-leader-sequence-timed-out} + +Whether the leader sequence has reached the timeout. + +If `LEADER_NO_TIMEOUT` is defined, the buffer must also contain at least one key. + +--- + +### `bool leader_reset_timer(void)` {#api-leader-reset-timer} + +Reset the leader sequence timer. + +--- + +### `bool leader_sequence_one_key(uint16_t kc)` {#api-leader-sequence-one-key} + +Check the sequence buffer for the given keycode. + +#### Arguments {#api-leader-sequence-one-key-arguments} + + - `uint16_t kc` + The keycode to check. + +#### Return Value {#api-leader-sequence-one-key-return} + +`true` if the sequence buffer matches. + +--- + +### `bool leader_sequence_two_keys(uint16_t kc1, uint16_t kc2)` {#api-leader-sequence-two-keys} + +Check the sequence buffer for the given keycodes. + +#### Arguments {#api-leader-sequence-two-keys-arguments} + + - `uint16_t kc1` + The first keycode to check. + - `uint16_t kc2` + The second keycode to check. + +#### Return Value {#api-leader-sequence-two-keys-return} + +`true` if the sequence buffer matches. + +--- + +### `bool leader_sequence_three_keys(uint16_t kc1, uint16_t kc2, uint16_t kc3)` {#api-leader-sequence-three-keys} + +Check the sequence buffer for the given keycodes. + +#### Arguments {#api-leader-sequence-three-keys-arguments} + + - `uint16_t kc1` + The first keycode to check. + - `uint16_t kc2` + The second keycode to check. + - `uint16_t kc3` + The third keycode to check. + +#### Return Value {#api-leader-sequence-three-keys-return} + +`true` if the sequence buffer matches. + +--- + +### `bool leader_sequence_four_keys(uint16_t kc1, uint16_t kc2, uint16_t kc3, uint16_t kc4)` {#api-leader-sequence-four-keys} + +Check the sequence buffer for the given keycodes. + +#### Arguments {#api-leader-sequence-four-keys-arguments} + + - `uint16_t kc1` + The first keycode to check. + - `uint16_t kc2` + The second keycode to check. + - `uint16_t kc3` + The third keycode to check. + - `uint16_t kc4` + The fourth keycode to check. + +#### Return Value {#api-leader-sequence-four-keys-return} + +`true` if the sequence buffer matches. + +--- + +### `bool leader_sequence_five_keys(uint16_t kc1, uint16_t kc2, uint16_t kc3, uint16_t kc4, uint16_t kc5)` {#api-leader-sequence-five-keys} + +Check the sequence buffer for the given keycodes. + +#### Arguments {#api-leader-sequence-five-keys-arguments} + + - `uint16_t kc1` + The first keycode to check. + - `uint16_t kc2` + The second keycode to check. + - `uint16_t kc3` + The third keycode to check. + - `uint16_t kc4` + The fourth keycode to check. + - `uint16_t kc5` + The fifth keycode to check. + +#### Return Value {#api-leader-sequence-five-keys-return} + +`true` if the sequence buffer matches. diff --git a/docs/features/led_indicators.md b/docs/features/led_indicators.md new file mode 100644 index 0000000000..8435c69a55 --- /dev/null +++ b/docs/features/led_indicators.md @@ -0,0 +1,127 @@ +# LED Indicators + +::: tip +LED indicators on split keyboards will require state information synced to the slave half (e.g. `#define SPLIT_LED_STATE_ENABLE`). See [data sync options](split_keyboard#data-sync-options) for more details. +::: + +QMK provides methods to read 5 of the LEDs defined in the HID spec: + +* Num Lock +* Caps Lock +* Scroll Lock +* Compose +* Kana + +There are three ways to get the lock LED state: +* Configuration options in `config.h` +* Implement `led_update_*` function +* Call `led_t host_keyboard_led_state()` + +::: warning +The `host_keyboard_led_state()` may reflect an updated state before `led_update_user()` is called. +::: + +Two deprecated functions that provide the LED state as `uint8_t`: + +* `uint8_t led_set_user(uint8_t usb_led)` +* `uint8_t host_keyboard_leds()` + +## Configuration Options + +To configure the indicators, `#define` these in your `config.h`: + +|Define |Default |Description | +|---------------------|-------------|-------------------------------------------| +|`LED_NUM_LOCK_PIN` |*Not defined*|The pin that controls the `Num Lock` LED | +|`LED_CAPS_LOCK_PIN` |*Not defined*|The pin that controls the `Caps Lock` LED | +|`LED_SCROLL_LOCK_PIN`|*Not defined*|The pin that controls the `Scroll Lock` LED| +|`LED_COMPOSE_PIN` |*Not defined*|The pin that controls the `Compose` LED | +|`LED_KANA_PIN` |*Not defined*|The pin that controls the `Kana` LED | +|`LED_PIN_ON_STATE` |`1` |The state of the indicator pins when the LED is "on" - `1` for high, `0` for low| + +Unless you are designing your own keyboard, you generally should not need to change the above config options. + +## LED update function + +When the configuration options do not provide enough flexibility, the following callbacks allow custom control of the LED behavior. These functions will be called when one of those 5 LEDs changes state: + +* Keyboard/revision: `bool led_update_kb(led_t led_state)` +* Keymap: `bool led_update_user(led_t led_state)` + +Both receives LED state as a struct parameter. Returning `true` in `led_update_user()` will allow the keyboard level code in `led_update_kb()` to run as well. Returning `false` will override the keyboard level code, depending on how the keyboard level function is set up. + +::: tip +This boolean return type of `led_update_user` allows for overriding keyboard LED controls, and is thus recommended over the void `led_set_user` function. +::: + +### Example of keyboard LED update implementation + +This is a template indicator function that can be implemented on keyboard level code: + +```c +bool led_update_kb(led_t led_state) { + bool res = led_update_user(led_state); + if(res) { + // gpio_write_pin sets the pin high for 1 and low for 0. + // In this example the pins are inverted, setting + // it low/0 turns it on, and high/1 turns the LED off. + // This behavior depends on whether the LED is between the pin + // and VCC or the pin and GND. + gpio_write_pin(B0, !led_state.num_lock); + gpio_write_pin(B1, !led_state.caps_lock); + gpio_write_pin(B2, !led_state.scroll_lock); + gpio_write_pin(B3, !led_state.compose); + gpio_write_pin(B4, !led_state.kana); + } + return res; +} +``` + +### Example of user LED update implementation + +This is an incomplete example will play a sound if Caps Lock is turned on or off. It returns `true` to allow keyboard LED function to maintain their state. + +```c +#ifdef AUDIO_ENABLE + float caps_on[][2] = SONG(CAPS_LOCK_ON_SOUND); + float caps_off[][2] = SONG(CAPS_LOCK_OFF_SOUND); +#endif + +bool led_update_user(led_t led_state) { + #ifdef AUDIO_ENABLE + static uint8_t caps_state = 0; + if (caps_state != led_state.caps_lock) { + led_state.caps_lock ? PLAY_SONG(caps_on) : PLAY_SONG(caps_off); + caps_state = led_state.caps_lock; + } + #endif + return true; +} +``` + +## Host keyboard LED state + +The `host_keyboard_led_state()` function will report the LED state returned from the host computer as `led_t`. This is useful for reading the LED state outside `led_update_*`. For example, you can get the boolean state of Caps Lock from the host with: + +```c +bool caps = host_keyboard_led_state().caps_lock; +``` + +## `led_update_ports()` + +This function writes the LED state to the actual hardware. Call it manually +from your `led_update_*()` callbacks to modify the handling of the standard +keyboard LEDs. +For example when repurposing a standard LED indicator as layer indicator. + +## Setting Physical LED State + +Some keyboard implementations provide convenient methods for setting the state of the physical LEDs. + +### Ergodox Boards + +The Ergodox implementations provide `ergodox_right_led_1`/`2`/`3_on`/`off()` to turn individual LEDs on or off, as well as `ergodox_right_led_on`/`off(uint8_t led)` to turn them on or off by their index. + +In addition, it is possible to specify the brightness level of all LEDs with `ergodox_led_all_set(uint8_t n)`; of individual LEDs with `ergodox_right_led_1`/`2`/`3_set(uint8_t n)`; or by index with `ergodox_right_led_set(uint8_t led, uint8_t n)`. + +Ergodox boards also define `LED_BRIGHTNESS_LO` for the lowest brightness and `LED_BRIGHTNESS_HI` for the highest brightness (which is the default). diff --git a/docs/features/led_matrix.md b/docs/features/led_matrix.md new file mode 100644 index 0000000000..fee7b139bc --- /dev/null +++ b/docs/features/led_matrix.md @@ -0,0 +1,455 @@ +# LED Matrix Lighting {#led-matrix-lighting} + +This feature allows you to use LED matrices driven by external drivers. It hooks into the backlight system so you can use the same keycodes as backlighting to control it. + +If you want to use RGB LED's you should use the [RGB Matrix Subsystem](rgb_matrix) instead. + +## Driver configuration {#driver-configuration} +--- +### IS31FL3731 {#is31fl3731} + +There is basic support for addressable LED matrix lighting with the I2C IS31FL3731 LED controller. To enable it, add this to your `rules.mk`: + +```make +LED_MATRIX_ENABLE = yes +LED_MATRIX_DRIVER = is31fl3731 +``` + +You can use between 1 and 4 IS31FL3731 IC's. Do not specify `LED_DRIVER_ADDR_<N>` defines for IC's that are not present on your keyboard. You can define the following items in `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `IS31FL3731_I2C_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `IS31FL3731_I2C_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `LED_MATRIX_LED_COUNT` | (Required) How many LED lights are present across all drivers | | +| `IS31FL3731_I2C_ADDRESS_1` | (Required) Address for the first LED driver | | +| `IS31FL3731_I2C_ADDRESS_2` | (Optional) Address for the second LED driver | | +| `IS31FL3731_I2C_ADDRESS_3` | (Optional) Address for the third LED driver | | +| `IS31FL3731_I2C_ADDRESS_4` | (Optional) Address for the fourth LED driver | | + +Here is an example using 2 drivers. + +```c +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 00 AD <-> GND +// 01 AD <-> SCL +// 10 AD <-> SDA +// 11 AD <-> VCC +// ADDR represents A1:A0 of the 7-bit address. +// The result is: 0b11101(ADDR) +#define IS31FL3731_I2C_ADDRESS_1 IS31FL3731_I2C_ADDRESS_GND +#define IS31FL3731_I2C_ADDRESS_2 IS31FL3731_I2C_ADDRESS_SDA + +#define LED_DRIVER_1_LED_TOTAL 25 +#define LED_DRIVER_2_LED_TOTAL 24 +#define LED_MATRIX_LED_COUNT (LED_DRIVER_1_LED_TOTAL + LED_DRIVER_2_LED_TOTAL) +``` + +::: warning +Note the parentheses, this is so when `LED_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (LED_DRIVER_1_LED_TOTAL + LED_DRIVER_2_LED_TOTAL)` will give very different results than `rand() % LED_DRIVER_1_LED_TOTAL + LED_DRIVER_2_LED_TOTAL`. +::: + +For split keyboards using `LED_MATRIX_SPLIT` with an LED driver, you can either have the same driver address or different driver addresses. If using different addresses, use `IS31FL3731_I2C_ADDRESS_1` for one and `IS31FL3731_I2C_ADDRESS_2` for the other one. Then, in `g_is31fl3731_leds`, fill out the correct driver index (0 or 1). If using one address, use `IS31FL3731_I2C_ADDRESS_1` for both, and use index 0 for `g_is31fl3731_leds`. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31fl3731_led_t PROGMEM g_is31fl3731_leds[IS31FL3731_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | LED address + * | | */ + { 0, C1_1 }, + { 0, C1_15 }, + // ... +} +``` + +Where `Cx_y` is the location of the LED in the matrix defined by [the datasheet](https://www.issi.com/WW/pdf/31FL3731.pdf) and the header file `drivers/led/issi/is31fl3731-mono.h`. The `driver` is the index of the driver you defined in your `config.h` (`0`, `1`, `2`, or `3` ). + +--- +### IS31FLCOMMON {#is31flcommon} + +There is basic support for addressable LED matrix lighting with a selection of I2C ISSI Lumissil LED controllers through a shared common driver. To enable it, add this to your `rules.mk`: + +```makefile +LED_MATRIX_ENABLE = yes +LED_MATRIX_DRIVER = <driver name> +``` + +Where `<driver name>` is the applicable LED driver chip as below + +| Driver Name | Data Sheet | Capability | +|-------------|------------|------------| +| `IS31FL3742A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3742A_DS.pdf) | 180 LED, 30x6 Matrix | +| `IS31FL3743A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3743A_DS.pdf) | 198 LED, 18x11 Matrix | +| `IS31FL3745` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3745_DS.pdf) | 144 LED, 18x8 Matrix | +| `IS31FL3746A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3746A_DS.pdf) | 72 LED, 18x4 Matrix | + +You can use between 1 and 4 IC's. Do not specify `DRIVER_ADDR_<N>` define for IC's if not present on your keyboard. The `DRIVER_ADDR_1` default assumes that all Address pins on the controller have been connected to GND. Drivers that have SYNC functionality have the default settings to disable if 1 driver. If more than 1 drivers then `DRIVER_ADDR_1` will be set to Master and the remaiing ones set to Slave. + +Configure the hardware via your `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `ISSI_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `ISSI_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `LED_MATRIX_LED_COUNT` | (Required) How many LED lights are present across all drivers | | +| `DRIVER_ADDR_1` | (Optional) Address for the first LED driver | | +| `DRIVER_ADDR_<N>` | (Required) Address for the additional LED drivers | | +| `ISSI_SSR_<N>` | (Optional) Configuration for the Spread Spectrum Register | | +| `ISSI_CONFIGURATION` | (Optional) Configuration for the Configuration Register | | +| `ISSI_GLOBALCURRENT` | (Optional) Configuration for the Global Current Register | 0xFF | +| `ISSI_PULLDOWNUP` | (Optional) Configuration for the Pull Up & Pull Down Register | | +| `ISSI_TEMP` | (Optional) Configuration for the Tempature Register | | +| `ISSI_PWM_ENABLE` | (Optional) Configuration for the PWM Enable Register | | +| `ISSI_PWM_SET` | (Optional) Configuration for the PWM Setting Register | | +| `ISSI_SCAL_LED ` | (Optional) Configuration for the LEDs Scaling Registers | 0xFF | +| `ISSI_MANUAL_SCALING` | (Optional) If you wish to configure the Scaling Registers manually | | + + +Defaults + +| Variable | IS31FL3742A | IS31FL3743A | IS31FL3745 | IS31FL3746 | +|----------|-------------|-------------|------------|------------| +| `DRIVER_ADDR_1` | 0b0110000 | 0b0100000 | 0b0100000 | 0b1100000 | +| `ISSI_SSR_1` | 0x00 | 0x00 / 0x60 | 0x00 / 0xC0 | 0x00 | +| `ISSI_SSR_<2-4>` | 0x00 | 0x40 | 0x80 | 0x00 | +| `ISSI_CONFIGURATION` | 0x31 | 0x01 | 0x31 | 0x01 | +| `ISSI_PULLDOWNUP` | 0x55 | 0x33 | 0x33 | 0x33 | +| `ISSI_TEMP` | N/A | 0x00 | 0x00 | 0x00 | +| `ISSI_PWM_ENABLE` | N/A | N/A | N/A | 0x00 | +| `ISSI_PWM_SET` | 0x00 | N/A | N/A | 0x00 | + +Here is an example using 2 drivers. + +```c +#define DRIVER_ADDR_2 0b0100001 + +#define DRIVER_1_LED_TOTAL 66 +#define DRIVER_2_LED_TOTAL 42 +#define LED_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` +::: warning +Note the parentheses, this is so when `LED_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Currently only 4 drivers are supported, but it would be trivial to support for more. Note that using a combination of different drivers is not supported. All drivers must be of the same model. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31_led PROGMEM g_is31_leds[LED_MATRIX_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | LED address + * | | */ + { 0, SW1_CS1 }, + { 0, SW1_CS2 }, + // ... +} +``` + +Where `CSx_SWx` is the location of the LED in the matrix defined by the datasheet. The `driver` is the index of the driver you defined in your `config.h` (`0`, `1`, `2`, or `3` for now). + +`ISSI_MANUAL_SCALING` is used to override the Scaling for individual LED's. By default they will be set as per `ISSI_SCAL_LED`. In `config.h` set how many LED's you want to manually set scaling for. +Eg `#define ISSI_MANUAL_SCALING 3` + +Then Define the array listing all the LEDs you want to override in your `<keyboard>.c`: + +```c +const is31_led PROGMEM g_is31_scaling[ISSI_MANUAL_SCALING] = { +/* LED Index + * | Scaling + * | | */ + {5, 120}, + {9, 120}, + .... +} +``` + +Where LED Index is the position of the LED in the `g_is31_leds` array. The `scaling` value between 0 and 255 to be written to the Scaling Register. + +--- + +## Common Configuration {#common-configuration} + +From this point forward the configuration is the same for all the drivers. The `led_config_t` struct provides a key electrical matrix to led index lookup table, what the physical position of each LED is on the board, and what type of key or usage the LED if the LED represents. Here is a brief example: + +```c +led_config_t g_led_config = { { + // Key Matrix to LED Index + { 5, NO_LED, NO_LED, 0 }, + { NO_LED, NO_LED, NO_LED, NO_LED }, + { 4, NO_LED, NO_LED, 1 }, + { 3, NO_LED, NO_LED, 2 } +}, { + // LED Index to Physical Position + { 188, 16 }, { 187, 48 }, { 149, 64 }, { 112, 64 }, { 37, 48 }, { 38, 16 } +}, { + // LED Index to Flag + 1, 4, 4, 4, 4, 1 +} }; +``` + +The first part, `// Key Matrix to LED Index`, tells the system what key this LED represents by using the key's electrical matrix row & col. The second part, `// LED Index to Physical Position` represents the LED's physical `{ x, y }` position on the keyboard. The default expected range of values for `{ x, y }` is the inclusive range `{ 0..224, 0..64 }`. This default expected range is due to effects that calculate the center of the keyboard for their animations. The easiest way to calculate these positions is imagine your keyboard is a grid, and the top left of the keyboard represents `{ x, y }` coordinate `{ 0, 0 }` and the bottom right of your keyboard represents `{ 224, 64 }`. Using this as a basis, you can use the following formula to calculate the physical position: + +```c +x = 224 / (NUMBER_OF_COLS - 1) * COL_POSITION +y = 64 / (NUMBER_OF_ROWS - 1) * ROW_POSITION +``` + +Where NUMBER_OF_COLS, NUMBER_OF_ROWS, COL_POSITION, & ROW_POSITION are all based on the physical layout of your keyboard, not the electrical layout. + +As mentioned earlier, the center of the keyboard by default is expected to be `{ 112, 32 }`, but this can be changed if you want to more accurately calculate the LED's physical `{ x, y }` positions. Keyboard designers can implement `#define LED_MATRIX_CENTER { 112, 32 }` in their config.h file with the new center point of the keyboard, or where they want it to be allowing more possibilities for the `{ x, y }` values. Do note that the maximum value for x or y is 255, and the recommended maximum is 224 as this gives animations runoff room before they reset. + +`// LED Index to Flag` is a bitmask, whether or not a certain LEDs is of a certain type. It is recommended that LEDs are set to only 1 type. + +## Flags {#flags} + +|Define |Value |Description | +|----------------------------|------|-------------------------------------------------| +|`HAS_FLAGS(bits, flags)` |*n/a* |Evaluates to `true` if `bits` has all `flags` set| +|`HAS_ANY_FLAGS(bits, flags)`|*n/a* |Evaluates to `true` if `bits` has any `flags` set| +|`LED_FLAG_NONE` |`0x00`|If this LED has no flags | +|`LED_FLAG_ALL` |`0xFF`|If this LED has all flags | +|`LED_FLAG_MODIFIER` |`0x01`|If the LED is on a modifier key | +|`LED_FLAG_KEYLIGHT` |`0x04`|If the LED is for key backlight | +|`LED_FLAG_INDICATOR` |`0x08`|If the LED is for keyboard state indication | + +## Keycodes {#keycodes} + +|Key |Aliases |Description | +|-------------------------------|---------|-----------------------------------| +|`QK_LED_MATRIX_ON` |`LM_ON` |Turn on LED Matrix | +|`QK_LED_MATRIX_OFF` |`LM_OFF` |Turn off LED Matrix | +|`QK_LED_MATRIX_TOGGLE` |`LM_TOGG`|Toggle LED Matrix on or off | +|`QK_LED_MATRIX_MODE_NEXT` |`LM_NEXT`|Cycle through animations | +|`QK_LED_MATRIX_MODE_PREVIOUS` |`LM_PREV`|Cycle through animations in reverse| +|`QK_LED_MATRIX_BRIGHTNESS_UP` |`LM_BRIU`|Increase the brightness level | +|`QK_LED_MATRIX_BRIGHTNESS_DOWN`|`LM_BRID`|Decrease the brightness level | +|`QK_LED_MATRIX_SPEED_UP` |`LM_SPDU`|Increase the animation speed | +|`QK_LED_MATRIX_SPEED_DOWN` |`LM_SPDD`|Decrease the animation speed | + +## LED Matrix Effects {#led-matrix-effects} + +These are the effects that are currently available: + +```c +enum led_matrix_effects { + LED_MATRIX_NONE = 0, + LED_MATRIX_SOLID = 1, // Static single val, no speed support + LED_MATRIX_ALPHAS_MODS, // Static dual val, speed is val for LEDs marked as modifiers + LED_MATRIX_BREATHING, // Cycling brightness animation + LED_MATRIX_BAND, // Band fading brightness scrolling left to right + LED_MATRIX_BAND_PINWHEEL, // 3 blade spinning pinwheel fades brightness + LED_MATRIX_BAND_SPIRAL, // Spinning spiral fades brightness + LED_MATRIX_CYCLE_LEFT_RIGHT, // Full gradient scrolling left to right + LED_MATRIX_CYCLE_UP_DOWN, // Full gradient scrolling top to bottom + LED_MATRIX_CYCLE_OUT_IN, // Full gradient scrolling out to in + LED_MATRIX_DUAL_BEACON, // Full gradient spinning around center of keyboard + LED_MATRIX_SOLID_REACTIVE_SIMPLE, // Pulses keys hit then fades out + LED_MATRIX_SOLID_REACTIVE_WIDE, // Value pulses near a single key hit then fades out + LED_MATRIX_SOLID_REACTIVE_MULTIWIDE, // Value pulses near multiple key hits then fades out + LED_MATRIX_SOLID_REACTIVE_CROSS, // Value pulses the same column and row of a single key hit then fades out + LED_MATRIX_SOLID_REACTIVE_MULTICROSS, // Value pulses the same column and row of multiple key hits then fades out + LED_MATRIX_SOLID_REACTIVE_NEXUS, // Value pulses away on the same column and row of a single key hit then fades out + LED_MATRIX_SOLID_REACTIVE_MULTINEXUS, // Value pulses away on the same column and row of multiple key hits then fades out + LED_MATRIX_SOLID_SPLASH, // Value pulses away from a single key hit then fades out + LED_MATRIX_SOLID_MULTISPLASH, // Value pulses away from multiple key hits then fades out + LED_MATRIX_WAVE_LEFT_RIGHT, // Sine wave scrolling from left to right + LED_MATRIX_WAVE_UP_DOWN, // Sine wave scrolling from up to down + LED_MATRIX_EFFECT_MAX +}; +``` + +You can enable a single effect by defining `ENABLE_[EFFECT_NAME]` in your `config.h`: + + +|Define |Description | +|-------------------------------------------------------|----------------------------------------------| +|`#define ENABLE_LED_MATRIX_ALPHAS_MODS` |Enables `LED_MATRIX_ALPHAS_MODS` | +|`#define ENABLE_LED_MATRIX_BREATHING` |Enables `LED_MATRIX_BREATHING` | +|`#define ENABLE_LED_MATRIX_BAND` |Enables `LED_MATRIX_BAND` | +|`#define ENABLE_LED_MATRIX_BAND_PINWHEEL` |Enables `LED_MATRIX_BAND_PINWHEEL` | +|`#define ENABLE_LED_MATRIX_BAND_SPIRAL` |Enables `LED_MATRIX_BAND_SPIRAL` | +|`#define ENABLE_LED_MATRIX_CYCLE_LEFT_RIGHT` |Enables `LED_MATRIX_CYCLE_LEFT_RIGHT` | +|`#define ENABLE_LED_MATRIX_CYCLE_UP_DOWN` |Enables `LED_MATRIX_CYCLE_UP_DOWN` | +|`#define ENABLE_LED_MATRIX_CYCLE_OUT_IN` |Enables `LED_MATRIX_CYCLE_OUT_IN` | +|`#define ENABLE_LED_MATRIX_DUAL_BEACON` |Enables `LED_MATRIX_DUAL_BEACON` | +|`#define ENABLE_LED_MATRIX_WAVE_LEFT_RIGHT` |Enables `LED_MATRIX_WAVE_LEFT_RIGHT` | +|`#define ENABLE_LED_MATRIX_WAVE_UP_DOWN` |Enables `LED_MATRIX_WAVE_UP_DOWN` | + +|Reactive Defines |Description | +|-------------------------------------------------------|----------------------------------------------| +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_SIMPLE` |Enables `LED_MATRIX_SOLID_REACTIVE_SIMPLE` | +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_WIDE` |Enables `LED_MATRIX_SOLID_REACTIVE_WIDE` | +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTIWIDE` |Enables `LED_MATRIX_SOLID_REACTIVE_MULTIWIDE` | +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_CROSS` |Enables `LED_MATRIX_SOLID_REACTIVE_CROSS` | +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTICROSS` |Enables `LED_MATRIX_SOLID_REACTIVE_MULTICROSS`| +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_NEXUS` |Enables `LED_MATRIX_SOLID_REACTIVE_NEXUS` | +|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTINEXUS` |Enables `LED_MATRIX_SOLID_REACTIVE_MULTINEXUS`| +|`#define ENABLE_LED_MATRIX_SOLID_SPLASH` |Enables `LED_MATRIX_SOLID_SPLASH` | +|`#define ENABLE_LED_MATRIX_SOLID_MULTISPLASH` |Enables `LED_MATRIX_SOLID_MULTISPLASH` | + +::: tip +These modes introduce additional logic that can increase firmware size. +::: + +## Custom LED Matrix Effects {#custom-led-matrix-effects} + +By setting `LED_MATRIX_CUSTOM_USER` (and/or `LED_MATRIX_CUSTOM_KB`) in `rules.mk`, new effects can be defined directly from userspace, without having to edit any QMK core files. + +To declare new effects, create a new `led_matrix_user/kb.inc` that looks something like this: + +`led_matrix_user.inc` should go in the root of the keymap directory. +`led_matrix_kb.inc` should go in the root of the keyboard directory. + +To use custom effects in your code, simply prepend `LED_MATRIX_CUSTOM_` to the effect name specified in `LED_MATRIX_EFFECT()`. For example, an effect declared as `LED_MATRIX_EFFECT(my_cool_effect)` would be referenced with: + +```c +led_matrix_mode(led_MATRIX_CUSTOM_my_cool_effect); +``` + +```c +// !!! DO NOT ADD #pragma once !!! // + +// Step 1. +// Declare custom effects using the LED_MATRIX_EFFECT macro +// (note the lack of semicolon after the macro!) +LED_MATRIX_EFFECT(my_cool_effect) +LED_MATRIX_EFFECT(my_cool_effect2) + +// Step 2. +// Define effects inside the `LED_MATRIX_CUSTOM_EFFECT_IMPLS` ifdef block +#ifdef LED_MATRIX_CUSTOM_EFFECT_IMPLS + +// e.g: A simple effect, self-contained within a single method +static bool my_cool_effect(effect_params_t* params) { + LED_MATRIX_USE_LIMITS(led_min, led_max); + for (uint8_t i = led_min; i < led_max; i++) { + led_matrix_set_value(i, 0xFF); + } + return led_matrix_check_finished_leds(led_max); +} + +// e.g: A more complex effect, relying on external methods and state, with +// dedicated init and run methods +static uint8_t some_global_state; +static void my_cool_effect2_complex_init(effect_params_t* params) { + some_global_state = 1; +} +static bool my_cool_effect2_complex_run(effect_params_t* params) { + LED_MATRIX_USE_LIMITS(led_min, led_max); + for (uint8_t i = led_min; i < led_max; i++) { + led_matrix_set_value(i, some_global_state++); + } + return led_matrix_check_finished_leds(led_max); +} +static bool my_cool_effect2(effect_params_t* params) { + if (params->init) my_cool_effect2_complex_init(params); + return my_cool_effect2_complex_run(params); +} + +#endif // LED_MATRIX_CUSTOM_EFFECT_IMPLS +``` + +For inspiration and examples, check out the built-in effects under `quantum/led_matrix/animations/`. + + +## Additional `config.h` Options {#additional-configh-options} + +```c +#define LED_MATRIX_KEYRELEASES // reactive effects respond to keyreleases (instead of keypresses) +#define LED_MATRIX_TIMEOUT 0 // number of milliseconds to wait until led automatically turns off +#define LED_MATRIX_SLEEP // turn off effects when suspended +#define LED_MATRIX_LED_PROCESS_LIMIT (LED_MATRIX_LED_COUNT + 4) / 5 // limits the number of LEDs to process in an animation per task run (increases keyboard responsiveness) +#define LED_MATRIX_LED_FLUSH_LIMIT 16 // limits in milliseconds how frequently an animation will update the LEDs. 16 (16ms) is equivalent to limiting to 60fps (increases keyboard responsiveness) +#define LED_MATRIX_MAXIMUM_BRIGHTNESS 255 // limits maximum brightness of LEDs +#define LED_MATRIX_DEFAULT_ON true // Sets the default enabled state, if none has been set +#define LED_MATRIX_DEFAULT_MODE LED_MATRIX_SOLID // Sets the default mode, if none has been set +#define LED_MATRIX_DEFAULT_VAL LED_MATRIX_MAXIMUM_BRIGHTNESS // Sets the default brightness value, if none has been set +#define LED_MATRIX_DEFAULT_SPD 127 // Sets the default animation speed, if none has been set +#define LED_MATRIX_DEFAULT_FLAGS LED_FLAG_ALL // Sets the default LED flags, if none has been set +#define LED_MATRIX_SPLIT { X, Y } // (Optional) For split keyboards, the number of LEDs connected on each half. X = left, Y = Right. + // If reactive effects are enabled, you also will want to enable SPLIT_TRANSPORT_MIRROR +``` + +## EEPROM storage {#eeprom-storage} + +The EEPROM for it is currently shared with the RGB Matrix system (it's generally assumed only one feature would be used at a time). + +### Direct Operation {#direct-operation} +|Function |Description | +|--------------------------------------------|-------------| +|`led_matrix_set_value_all(v)` |Set all of the LEDs to the given value, where `v` is between 0 and 255 (not written to EEPROM) | +|`led_matrix_set_value(index, v)` |Set a single LED to the given value, where `v` is between 0 and 255, and `index` is between 0 and `LED_MATRIX_LED_COUNT` (not written to EEPROM) | + +### Disable/Enable Effects {#disable-enable-effects} +|Function |Description | +|--------------------------------------------|-------------| +|`led_matrix_toggle()` |Toggle effect range LEDs between on and off | +|`led_matrix_toggle_noeeprom()` |Toggle effect range LEDs between on and off (not written to EEPROM) | +|`led_matrix_enable()` |Turn effect range LEDs on, based on their previous state | +|`led_matrix_enable_noeeprom()` |Turn effect range LEDs on, based on their previous state (not written to EEPROM) | +|`led_matrix_disable()` |Turn effect range LEDs off, based on their previous state | +|`led_matrix_disable_noeeprom()` |Turn effect range LEDs off, based on their previous state (not written to EEPROM) | + +### Change Effect Mode {#change-effect-mode} +|Function |Description | +|--------------------------------------------|-------------| +|`led_matrix_mode(mode)` |Set the mode, if LED animations are enabled | +|`led_matrix_mode_noeeprom(mode)` |Set the mode, if LED animations are enabled (not written to EEPROM) | +|`led_matrix_step()` |Change the mode to the next LED animation in the list of enabled LED animations | +|`led_matrix_step_noeeprom()` |Change the mode to the next LED animation in the list of enabled LED animations (not written to EEPROM) | +|`led_matrix_step_reverse()` |Change the mode to the previous LED animation in the list of enabled LED animations | +|`led_matrix_step_reverse_noeeprom()` |Change the mode to the previous LED animation in the list of enabled LED animations (not written to EEPROM) | +|`led_matrix_increase_speed()` |Increase the speed of the animations | +|`led_matrix_increase_speed_noeeprom()` |Increase the speed of the animations (not written to EEPROM) | +|`led_matrix_decrease_speed()` |Decrease the speed of the animations | +|`led_matrix_decrease_speed_noeeprom()` |Decrease the speed of the animations (not written to EEPROM) | +|`led_matrix_set_speed(speed)` |Set the speed of the animations to the given value where `speed` is between 0 and 255 | +|`led_matrix_set_speed_noeeprom(speed)` |Set the speed of the animations to the given value where `speed` is between 0 and 255 (not written to EEPROM) | + +### Change Value {#change-value} +|Function |Description | +|--------------------------------------------|-------------| +|`led_matrix_increase_val()` |Increase the value for effect range LEDs. This wraps around at maximum value | +|`led_matrix_increase_val_noeeprom()` |Increase the value for effect range LEDs. This wraps around at maximum value (not written to EEPROM) | +|`led_matrix_decrease_val()` |Decrease the value for effect range LEDs. This wraps around at minimum value | +|`led_matrix_decrease_val_noeeprom()` |Decrease the value for effect range LEDs. This wraps around at minimum value (not written to EEPROM) | + +### Query Current Status {#query-current-status} +|Function |Description | +|---------------------------------|---------------------------| +|`led_matrix_is_enabled()` |Gets current on/off status | +|`led_matrix_get_mode()` |Gets current mode | +|`led_matrix_get_val()` |Gets current val | +|`led_matrix_get_speed()` |Gets current speed | +|`led_matrix_get_suspend_state()` |Gets current suspend state | + +## Callbacks {#callbacks} + +### Indicators {#indicators} + +If you want to set custom indicators, such as an LED for Caps Lock, or layer indication, then you can use the `led_matrix_indicators_kb` function on the keyboard level source file, or `led_matrix_indicators_user` function in the user `keymap.c`. +```c +bool led_matrix_indicators_kb(void) { + if (!led_matrix_indicators_user()) { + return false; + } + led_matrix_set_value(index, value); + return true; +} +``` + +In addition, there are the advanced indicator functions. These are aimed at those with heavily customized displays, where rendering every LED per cycle is expensive. This includes a special macro to help make this easier to use: `LED_MATRIX_INDICATOR_SET_VALUE(i, v)`. + +```c +void led_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + LED_MATRIX_INDICATOR_SET_VALUE(index, value); + return false; +} +``` diff --git a/docs/features/midi.md b/docs/features/midi.md new file mode 100644 index 0000000000..32c062e54d --- /dev/null +++ b/docs/features/midi.md @@ -0,0 +1,264 @@ +# MIDI + +## Usage + +First, enable MIDI by adding the following to your `rules.mk`: + +```make +MIDI_ENABLE = yes +``` + +There are two MIDI systems in QMK: basic and advanced. With basic MIDI you will only be able to send Note On and Note Off messages using the note keycodes, meaning that keycodes like `MI_OCTU` and `MI_OCTD` will not work. Advanced MIDI allows you to do things like octave shifts, channel changes, velocity changes, modulation, and more. + +### Caveats + +MIDI requires 2 USB endpoints and as such may not work on some hardware such as V-USB controllers. + +### Basic MIDI + +To enable basic MIDI, add the following to your `config.h`: + +```c +#define MIDI_BASIC +``` + +### Advanced MIDI + +To enable advanced MIDI, add the following to your `config.h`: + +```c +#define MIDI_ADVANCED +``` + +#### Sending Control Change (CC) Messages + +If you're aiming to emulate the features of something like a Launchpad or other MIDI controller you'll need to access the internal MIDI device directly. + +Because there are so many possible CC messages, not all of them are implemented as keycodes. Additionally, you might need to provide more than just two values that you would get from a keycode (pressed and released) - for example, the analog values from a fader or a potentiometer. So, you will need to implement [custom keycodes](../feature_macros) if you want to use them in your keymap directly using `process_record_user()`. + + +For reference of all the possible control code numbers see [MIDI Specification](#midi-specification) + +#### Example code for using Generic On Off Switches as per MIDI Specification. +```c +#include QMK_KEYBOARD_H + +extern MidiDevice midi_device; + +// MIDI CC codes for generic on/off switches (80, 81, 82, 83) +// Off: 0-63 +// On: 64-127 + +#define MIDI_CC_OFF 0 +#define MIDI_CC_ON 127 + +enum custom_keycodes { + MIDI_CC80 = SAFE_RANGE, +}; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case MIDI_CC80: + if (record->event.pressed) { + midi_send_cc(&midi_device, midi_config.channel, 80, MIDI_CC_ON); + } else { + midi_send_cc(&midi_device, midi_config.channel, 80, MIDI_CC_OFF); + } + return true; + } + return true; +}; + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + LAYOUT( + // ... + MIDI_CC80, + // ... + ) +}; +``` + +### Keycodes + +|Keycode |Aliases |Description | +|-------------------------------|------------------|---------------------------------| +|`QK_MIDI_ON` |`MI_ON` |Turn MIDI on | +|`QK_MIDI_OFF` |`MI_OFF` |Turn MIDI off | +|`QK_MIDI_TOGGLE` |`MI_TOGG` |Toggle MIDI enabled | +|`QK_MIDI_NOTE_C_0` |`MI_C` |C octave 0 | +|`QK_MIDI_NOTE_C_SHARP_0` |`MI_Cs`, `MI_Db` |C♯/D♭ octave 0 | +|`QK_MIDI_NOTE_D_0` |`MI_D` |D octave 0 | +|`QK_MIDI_NOTE_D_SHARP_0` |`MI_Ds`, `MI_Eb` |D♯/E♭ octave 0 | +|`QK_MIDI_NOTE_E_0` |`MI_E` |E octave 0 | +|`QK_MIDI_NOTE_F_0` |`MI_F` |F octave 0 | +|`QK_MIDI_NOTE_F_SHARP_0` |`MI_Fs`, `MI_Gb` |F♯/G♭ octave 0 | +|`QK_MIDI_NOTE_G_0` |`MI_G` |G octave 0 | +|`QK_MIDI_NOTE_G_SHARP_0` |`MI_Gs`, `MI_Ab` |G♯/A♭ octave 0 | +|`QK_MIDI_NOTE_A_0` |`MI_A` |A octave 0 | +|`QK_MIDI_NOTE_A_SHARP_0` |`MI_As`, `MI_Bb` |A♯/B♭ octave 0 | +|`QK_MIDI_NOTE_B_0` |`MI_B` |B octave 0 | +|`QK_MIDI_NOTE_C_1` |`MI_C1` |C octave 1 | +|`QK_MIDI_NOTE_C_SHARP_1` |`MI_Cs1`, `MI_Db1`|C♯/D♭ octave 1 | +|`QK_MIDI_NOTE_D_1` |`MI_D1` |D octave 1 | +|`QK_MIDI_NOTE_D_SHARP_1` |`MI_Ds1`, `MI_Eb1`|D♯/E♭ octave 1 | +|`QK_MIDI_NOTE_E_1` |`MI_E1` |E octave 1 | +|`QK_MIDI_NOTE_F_1` |`MI_F1` |F octave 1 | +|`QK_MIDI_NOTE_F_SHARP_1` |`MI_Fs1`, `MI_Gb1`|F♯/G♭ octave 1 | +|`QK_MIDI_NOTE_G_1` |`MI_G1` |G octave 1 | +|`QK_MIDI_NOTE_G_SHARP_1` |`MI_Gs1`, `MI_Ab1`|G♯/A♭ octave 1 | +|`QK_MIDI_NOTE_A_1` |`MI_A1` |A octave 1 | +|`QK_MIDI_NOTE_A_SHARP_1` |`MI_As1`, `MI_Bb1`|A♯/B♭ octave 1 | +|`QK_MIDI_NOTE_B_1` |`MI_B1` |B octave 1 | +|`QK_MIDI_NOTE_C_2` |`MI_C2` |C octave 2 | +|`QK_MIDI_NOTE_C_SHARP_2` |`MI_Cs2`, `MI_Db2`|C♯/D♭ octave 2 | +|`QK_MIDI_NOTE_D_2` |`MI_D2` |D octave 2 | +|`QK_MIDI_NOTE_D_SHARP_2` |`MI_Ds2`, `MI_Eb2`|D♯/E♭ octave 2 | +|`QK_MIDI_NOTE_E_2` |`MI_E2` |E octave 2 | +|`QK_MIDI_NOTE_F_2` |`MI_F2` |F octave 2 | +|`QK_MIDI_NOTE_F_SHARP_2` |`MI_Fs2`, `MI_Gb2`|F♯/G♭ octave 2 | +|`QK_MIDI_NOTE_G_2` |`MI_G2` |G octave 2 | +|`QK_MIDI_NOTE_G_SHARP_2` |`MI_Gs2`, `MI_Ab2`|G♯/A♭ octave 2 | +|`QK_MIDI_NOTE_A_2` |`MI_A2` |A octave 2 | +|`QK_MIDI_NOTE_A_SHARP_2` |`MI_As2`, `MI_Bb2`|A♯/B♭ octave 2 | +|`QK_MIDI_NOTE_B_2` |`MI_B2` |B octave 2 | +|`QK_MIDI_NOTE_C_3` |`MI_C3` |C octave 3 | +|`QK_MIDI_NOTE_C_SHARP_3` |`MI_Cs3`, `MI_Db3`|C♯/D♭ octave 3 | +|`QK_MIDI_NOTE_D_3` |`MI_D3` |D octave 3 | +|`QK_MIDI_NOTE_D_SHARP_3` |`MI_Ds3`, `MI_Eb3`|D♯/E♭ octave 3 | +|`QK_MIDI_NOTE_E_3` |`MI_E3` |E octave 3 | +|`QK_MIDI_NOTE_F_3` |`MI_F3` |F octave 3 | +|`QK_MIDI_NOTE_F_SHARP_3` |`MI_Fs3`, `MI_Gb3`|F♯/G♭ octave 3 | +|`QK_MIDI_NOTE_G_3` |`MI_G3` |G octave 3 | +|`QK_MIDI_NOTE_G_SHARP_3` |`MI_Gs3`, `MI_Ab3`|G♯/A♭ octave 3 | +|`QK_MIDI_NOTE_A_3` |`MI_A3` |A octave 3 | +|`QK_MIDI_NOTE_A_SHARP_3` |`MI_As3`, `MI_Bb3`|A♯/B♭ octave 3 | +|`QK_MIDI_NOTE_B_3` |`MI_B3` |B octave 3 | +|`QK_MIDI_NOTE_C_4` |`MI_C4` |C octave 4 | +|`QK_MIDI_NOTE_C_SHARP_4` |`MI_Cs4`, `MI_Db4`|C♯/D♭ octave 4 | +|`QK_MIDI_NOTE_D_4` |`MI_D4` |D octave 4 | +|`QK_MIDI_NOTE_D_SHARP_4` |`MI_Ds4`, `MI_Eb4`|D♯/E♭ octave 4 | +|`QK_MIDI_NOTE_E_4` |`MI_E4` |E octave 4 | +|`QK_MIDI_NOTE_F_4` |`MI_F4` |F octave 4 | +|`QK_MIDI_NOTE_F_SHARP_4` |`MI_Fs4`, `MI_Gb4`|F♯/G♭ octave 4 | +|`QK_MIDI_NOTE_G_4` |`MI_G4` |G octave 4 | +|`QK_MIDI_NOTE_G_SHARP_4` |`MI_Gs4`, `MI_Ab4`|G♯/A♭ octave 4 | +|`QK_MIDI_NOTE_A_4` |`MI_A4` |A octave 4 | +|`QK_MIDI_NOTE_A_SHARP_4` |`MI_As4`, `MI_Bb4`|A♯/B♭ octave 4 | +|`QK_MIDI_NOTE_B_4` |`MI_B4` |B octave 4 | +|`QK_MIDI_NOTE_C_5` |`MI_C5` |C octave 5 | +|`QK_MIDI_NOTE_C_SHARP_5` |`MI_Cs5`, `MI_Db5`|C♯/D♭ octave 5 | +|`QK_MIDI_NOTE_D_5` |`MI_D5` |D octave 5 | +|`QK_MIDI_NOTE_D_SHARP_5` |`MI_Ds5`, `MI_Eb5`|D♯/E♭ octave 5 | +|`QK_MIDI_NOTE_E_5` |`MI_E5` |E octave 5 | +|`QK_MIDI_NOTE_F_5` |`MI_F5` |F octave 5 | +|`QK_MIDI_NOTE_F_SHARP_5` |`MI_Fs5`, `MI_Gb5`|F♯/G♭ octave 5 | +|`QK_MIDI_NOTE_G_5` |`MI_G5` |G octave 5 | +|`QK_MIDI_NOTE_G_SHARP_5` |`MI_Gs5`, `MI_Ab5`|G♯/A♭ octave 5 | +|`QK_MIDI_NOTE_A_5` |`MI_A5` |A octave 5 | +|`QK_MIDI_NOTE_A_SHARP_5` |`MI_As5`, `MI_Bb5`|A♯/B♭ octave 5 | +|`QK_MIDI_NOTE_B_5` |`MI_B5` |B octave 5 | +|`QK_MIDI_OCTAVE_N2` |`MI_OCN2` |Set octave to -2 | +|`QK_MIDI_OCTAVE_N1` |`MI_OCN1` |Set octave to -1 | +|`QK_MIDI_OCTAVE_0` |`MI_OC0` |Set octave to 0 | +|`QK_MIDI_OCTAVE_1` |`MI_OC1` |Set octave to 1 | +|`QK_MIDI_OCTAVE_2` |`MI_OC2` |Set octave to 2 | +|`QK_MIDI_OCTAVE_3` |`MI_OC3` |Set octave to 3 | +|`QK_MIDI_OCTAVE_4` |`MI_OC4` |Set octave to 4 | +|`QK_MIDI_OCTAVE_5` |`MI_OC5` |Set octave to 5 | +|`QK_MIDI_OCTAVE_6` |`MI_OC6` |Set octave to 6 | +|`QK_MIDI_OCTAVE_7` |`MI_OC7` |Set octave to 7 | +|`QK_MIDI_OCTAVE_DOWN` |`MI_OCTD` |Move down an octave | +|`QK_MIDI_OCTAVE_UP` |`MI_OCTU` |Move up an octave | +|`QK_MIDI_TRANSPOSE_N6` |`MI_TRN6` |Set transposition to -6 semitones| +|`QK_MIDI_TRANSPOSE_N5` |`MI_TRN5` |Set transposition to -5 semitones| +|`QK_MIDI_TRANSPOSE_N4` |`MI_TRN4` |Set transposition to -4 semitones| +|`QK_MIDI_TRANSPOSE_N3` |`MI_TRN3` |Set transposition to -3 semitones| +|`QK_MIDI_TRANSPOSE_N2` |`MI_TRN2` |Set transposition to -2 semitones| +|`QK_MIDI_TRANSPOSE_N1` |`MI_TRN1` |Set transposition to -1 semitone | +|`QK_MIDI_TRANSPOSE_0` |`MI_TR0` |No transposition | +|`QK_MIDI_TRANSPOSE_1` |`MI_TR1` |Set transposition to +1 semitone | +|`QK_MIDI_TRANSPOSE_2` |`MI_TR2` |Set transposition to +2 semitones| +|`QK_MIDI_TRANSPOSE_3` |`MI_TR3` |Set transposition to +3 semitones| +|`QK_MIDI_TRANSPOSE_4` |`MI_TR4` |Set transposition to +4 semitones| +|`QK_MIDI_TRANSPOSE_5` |`MI_TR5` |Set transposition to +5 semitones| +|`QK_MIDI_TRANSPOSE_6` |`MI_TR6` |Set transposition to +6 semitones| +|`QK_MIDI_TRANSPOSE_DOWN` |`MI_TRSD` |Decrease transposition | +|`QK_MIDI_TRANSPOSE_UP` |`MI_TRSU` |Increase transposition | +|`QK_MIDI_VELOCITY_0` |`MI_VL0` |Set velocity to 0 | +|`QK_MIDI_VELOCITY_1` |`MI_VL1` |Set velocity to 12 | +|`QK_MIDI_VELOCITY_2` |`MI_VL2` |Set velocity to 25 | +|`QK_MIDI_VELOCITY_3` |`MI_VL3` |Set velocity to 38 | +|`QK_MIDI_VELOCITY_4` |`MI_VL4` |Set velocity to 51 | +|`QK_MIDI_VELOCITY_5` |`MI_VL5` |Set velocity to 64 | +|`QK_MIDI_VELOCITY_6` |`MI_VL6` |Set velocity to 76 | +|`QK_MIDI_VELOCITY_7` |`MI_VL7` |Set velocity to 89 | +|`QK_MIDI_VELOCITY_8` |`MI_VL8` |Set velocity to 102 | +|`QK_MIDI_VELOCITY_9` |`MI_VL9` |Set velocity to 114 | +|`QK_MIDI_VELOCITY_10` |`MI_VL10` |Set velocity to 127 | +|`QK_MIDI_VELOCITY_DOWN` |`MI_VELD` |Decrease velocity | +|`QK_MIDI_VELOCITY_UP` |`MI_VELU` |Increase velocity | +|`QK_MIDI_CHANNEL_1` |`MI_CH1` |Set channel to 1 | +|`QK_MIDI_CHANNEL_2` |`MI_CH2` |Set channel to 2 | +|`QK_MIDI_CHANNEL_3` |`MI_CH3` |Set channel to 3 | +|`QK_MIDI_CHANNEL_4` |`MI_CH4` |Set channel to 4 | +|`QK_MIDI_CHANNEL_5` |`MI_CH5` |Set channel to 5 | +|`QK_MIDI_CHANNEL_6` |`MI_CH6` |Set channel to 6 | +|`QK_MIDI_CHANNEL_7` |`MI_CH7` |Set channel to 7 | +|`QK_MIDI_CHANNEL_8` |`MI_CH8` |Set channel to 8 | +|`QK_MIDI_CHANNEL_9` |`MI_CH9` |Set channel to 9 | +|`QK_MIDI_CHANNEL_10` |`MI_CH10` |Set channel to 10 | +|`QK_MIDI_CHANNEL_11` |`MI_CH11` |Set channel to 11 | +|`QK_MIDI_CHANNEL_12` |`MI_CH12` |Set channel to 12 | +|`QK_MIDI_CHANNEL_13` |`MI_CH13` |Set channel to 13 | +|`QK_MIDI_CHANNEL_14` |`MI_CH14` |Set channel to 14 | +|`QK_MIDI_CHANNEL_15` |`MI_CH15` |Set channel to 15 | +|`QK_MIDI_CHANNEL_16` |`MI_CH16` |Set channel to 16 | +|`QK_MIDI_CHANNEL_DOWN` |`MI_CHND` |Decrease channel | +|`QK_MIDI_CHANNEL_UP` |`MI_CHNU` |Increase channel | +|`QK_MIDI_ALL_NOTES_OFF` |`MI_AOFF` |Stop all notes | +|`QK_MIDI_SUSTAIN` |`MI_SUST` |Sustain | +|`QK_MIDI_PORTAMENTO` |`MI_PORT` |Portmento | +|`QK_MIDI_SOSTENUTO` |`MI_SOST` |Sostenuto | +|`QK_MIDI_SOFT` |`MI_SOFT` |Soft Pedal | +|`QK_MIDI_LEGATO` |`MI_LEG` |Legato | +|`QK_MIDI_MODULATION` |`MI_MOD` |Modulation | +|`QK_MIDI_MODULATION_SPEED_DOWN`|`MI_MODD` |Decrease modulation speed | +|`QK_MIDI_MODULATION_SPEED_UP` |`MI_MODU` |Increase modulation speed | +|`QK_MIDI_PITCH_BEND_DOWN` |`MI_BNDD` |Bend pitch down | +|`QK_MIDI_PITCH_BEND_UP` |`MI_BNDU` |Bend pitch up | + +### Configuration + +Certain values are stored in the `midi_config` struct. This configuration is not persisted to EEPROM. By default, these values are: + +|Configuration |Value|Comments | +|-------------------|-----|-----------------------| +|Octave |`4` |Corresponds to `MI_OC2`| +|Transposition |`0` | | +|Velocity |`127`| | +|Channel |`0` | | +|Modulation Interval|`8` | | + +For the above, the `MI_C` keycode will produce a C3 (note number 48), and so on. + +### References +#### MIDI Specification + + * [MIDI.org](https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message) + * [CMU MIDI Programmer's Reference](https://www.cs.cmu.edu/~music/cmsip/readings/MIDI%20tutorial%20for%20programmers.html) +#### QMK C Files + + * `quantum/process_keycode/process_midi.c` + * `quantum/quantum_keycodes.h` + * `quantum/midi/midi.h` + * `quantum/midi/midi.c` + * `quantum/midi/qmk_midi.c` + * `quantum/midi/midi_device.h` + +<!-- +#### QMK Internals (Autogenerated) + + * [Internals/MIDI Device Setup Process](internals/midi_device_setup_process) + * [Internals/MIDI Device](internals/midi_device) + * [Internals/MIDI Util](internals/midi_util) +--> diff --git a/docs/features/mouse_keys.md b/docs/features/mouse_keys.md new file mode 100644 index 0000000000..c2b3e98f42 --- /dev/null +++ b/docs/features/mouse_keys.md @@ -0,0 +1,208 @@ +# Mouse keys + +Mouse keys is a feature that allows you to emulate a mouse using your keyboard. You can move the pointer at different speeds, press 5 buttons and scroll in 8 directions. + +## Adding mouse keys to your keyboard + +To use mouse keys, you must at least enable mouse keys support and map mouse actions to keys on your keyboard. + +### Enabling mouse keys + +To enable mouse keys, add the following line to your keymap’s `rules.mk`: + +```c +MOUSEKEY_ENABLE = yes +``` + +### Mapping mouse actions + +In your keymap you can use the following keycodes to map key presses to mouse actions: + +|Key |Aliases |Description | +|----------------|---------|-----------------| +|`KC_MS_UP` |`KC_MS_U`|Move cursor up | +|`KC_MS_DOWN` |`KC_MS_D`|Move cursor down | +|`KC_MS_LEFT` |`KC_MS_L`|Move cursor left | +|`KC_MS_RIGHT` |`KC_MS_R`|Move cursor right| +|`KC_MS_BTN1` |`KC_BTN1`|Press button 1 | +|`KC_MS_BTN2` |`KC_BTN2`|Press button 2 | +|`KC_MS_BTN3` |`KC_BTN3`|Press button 3 | +|`KC_MS_BTN4` |`KC_BTN4`|Press button 4 | +|`KC_MS_BTN5` |`KC_BTN5`|Press button 5 | +|`KC_MS_BTN6` |`KC_BTN6`|Press button 6 | +|`KC_MS_BTN7` |`KC_BTN7`|Press button 7 | +|`KC_MS_BTN8` |`KC_BTN8`|Press button 8 | +|`KC_MS_WH_UP` |`KC_WH_U`|Move wheel up | +|`KC_MS_WH_DOWN` |`KC_WH_D`|Move wheel down | +|`KC_MS_WH_LEFT` |`KC_WH_L`|Move wheel left | +|`KC_MS_WH_RIGHT`|`KC_WH_R`|Move wheel right | +|`KC_MS_ACCEL0` |`KC_ACL0`|Set speed to 0 | +|`KC_MS_ACCEL1` |`KC_ACL1`|Set speed to 1 | +|`KC_MS_ACCEL2` |`KC_ACL2`|Set speed to 2 | + +## Configuring mouse keys + +Mouse keys supports three different modes to move the cursor: + +* **Accelerated (default):** Holding movement keys accelerates the cursor until it reaches its maximum speed. +* **Kinetic:** Holding movement keys accelerates the cursor with its speed following a quadratic curve until it reaches its maximum speed. +* **Constant:** Holding movement keys moves the cursor at constant speeds. +* **Combined:** Holding movement keys accelerates the cursor until it reaches its maximum speed, but holding acceleration and movement keys simultaneously moves the cursor at constant speeds. +* **Inertia:** Cursor accelerates when key held, and decelerates after key release. Tracks X and Y velocity separately for more nuanced movements. Applies to cursor only, not scrolling. + +The same principle applies to scrolling, in most modes. + +Configuration options that are times, intervals or delays are given in milliseconds. Scroll speed is given as multiples of the default scroll step. For example, a scroll speed of 8 means that each scroll action covers 8 times the length of the default scroll step as defined by your operating system or application. + +### Accelerated mode + +This is the default mode. You can adjust the cursor and scrolling acceleration using the following settings in your keymap’s `config.h` file: + +|Define |Default|Description | +|----------------------------|-------|---------------------------------------------------------| +|`MOUSEKEY_DELAY` |10 |Delay between pressing a movement key and cursor movement| +|`MOUSEKEY_INTERVAL` |20 |Time between cursor movements in milliseconds | +|`MOUSEKEY_MOVE_DELTA` |8 |Step size | +|`MOUSEKEY_MAX_SPEED` |10 |Maximum cursor speed at which acceleration stops | +|`MOUSEKEY_TIME_TO_MAX` |30 |Time until maximum cursor speed is reached | +|`MOUSEKEY_WHEEL_DELAY` |10 |Delay between pressing a wheel key and wheel movement | +|`MOUSEKEY_WHEEL_INTERVAL` |80 |Time between wheel movements | +|`MOUSEKEY_WHEEL_DELTA` |1 |Wheel movement step size | +|`MOUSEKEY_WHEEL_MAX_SPEED` |8 |Maximum number of scroll steps per scroll action | +|`MOUSEKEY_WHEEL_TIME_TO_MAX`|40 |Time until maximum scroll speed is reached | + +Tips: + +* Setting `MOUSEKEY_DELAY` too low makes the cursor unresponsive. Setting it too high makes small movements difficult. +* For smoother cursor movements, lower the value of `MOUSEKEY_INTERVAL`. If the refresh rate of your display is 60Hz, you could set it to `16` (1/60). As this raises the cursor speed significantly, you may want to lower `MOUSEKEY_MAX_SPEED`. +* Setting `MOUSEKEY_TIME_TO_MAX` or `MOUSEKEY_WHEEL_TIME_TO_MAX` to `0` will disable acceleration for the cursor or scrolling respectively. This way you can make one of them constant while keeping the other accelerated, which is not possible in constant speed mode. +* Setting `MOUSEKEY_WHEEL_INTERVAL` too low will make scrolling too fast. Setting it too high will make scrolling too slow when the wheel key is held down. + +Cursor acceleration uses the same algorithm as the X Window System MouseKeysAccel feature. You can read more about it [on Wikipedia](https://en.wikipedia.org/wiki/Mouse_keys). + +### Kinetic Mode + +This is an extension of the accelerated mode. The kinetic mode uses a quadratic curve on the cursor speed which allows precise movements at the beginning and allows to cover large distances by increasing cursor speed quickly thereafter. You can adjust the cursor and scrolling acceleration using the following settings in your keymap’s `config.h` file: + +|Define |Default |Description | +|--------------------------------------|---------|---------------------------------------------------------------| +|`MK_KINETIC_SPEED` |undefined|Enable kinetic mode | +|`MOUSEKEY_DELAY` |5 |Delay between pressing a movement key and cursor movement | +|`MOUSEKEY_INTERVAL` |10 |Time between cursor movements in milliseconds | +|`MOUSEKEY_MOVE_DELTA` |16 |Step size for accelerating from initial to base speed | +|`MOUSEKEY_INITIAL_SPEED` |100 |Initial speed of the cursor in pixel per second | +|`MOUSEKEY_BASE_SPEED` |5000 |Maximum cursor speed at which acceleration stops | +|`MOUSEKEY_DECELERATED_SPEED` |400 |Decelerated cursor speed | +|`MOUSEKEY_ACCELERATED_SPEED` |3000 |Accelerated cursor speed | +|`MOUSEKEY_WHEEL_INITIAL_MOVEMENTS` |16 |Initial number of movements of the mouse wheel | +|`MOUSEKEY_WHEEL_BASE_MOVEMENTS` |32 |Maximum number of movements at which acceleration stops | +|`MOUSEKEY_WHEEL_ACCELERATED_MOVEMENTS`|48 |Accelerated wheel movements | +|`MOUSEKEY_WHEEL_DECELERATED_MOVEMENTS`|8 |Decelerated wheel movements | + +Tips: + +* The smoothness of the cursor movement depends on the `MOUSEKEY_INTERVAL` setting. The shorter the interval is set the smoother the movement will be. Setting the value too low makes the cursor unresponsive. Lower settings are possible if the micro processor is fast enough. For example: At an interval of `8` milliseconds, `125` movements per second will be initiated. With a base speed of `1000` each movement will move the cursor by `8` pixels. +* Mouse wheel movements are implemented differently from cursor movements. While it's okay for the cursor to move multiple pixels at once for the mouse wheel this would lead to jerky movements. Instead, the mouse wheel operates at step size `1`. Setting mouse wheel speed is done by adjusting the number of wheel movements per second. + +### Constant mode + +In this mode you can define multiple different speeds for both the cursor and the mouse wheel. There is no acceleration. `KC_ACL0`, `KC_ACL1` and `KC_ACL2` change the cursor and scroll speed to their respective setting. + +You can choose whether speed selection is momentary or tap-to-select: + +* **Momentary:** The chosen speed is only active while you hold the respective key. When the key is raised, mouse keys returns to the unmodified speed. +* **Tap-to-select:** The chosen speed is activated when you press the respective key and remains active even after the key has been raised. The default speed is that of `KC_ACL1`. There is no unmodified speed. + +The default speeds from slowest to fastest are as follows: + +* **Momentary:** `KC_ACL0` < `KC_ACL1` < *unmodified* < `KC_ACL2` +* **Tap-to-select:** `KC_ACL0` < `KC_ACL1` < `KC_ACL2` + +To use constant speed mode, you must at least define `MK_3_SPEED` in your keymap’s `config.h` file: + +```c +#define MK_3_SPEED +``` + +To enable momentary mode, also define `MK_MOMENTARY_ACCEL`: + +```c +#define MK_MOMENTARY_ACCEL +``` + +Use the following settings if you want to adjust cursor movement or scrolling: + +|Define |Default |Description | +|---------------------|-------------|-------------------------------------------| +|`MK_3_SPEED` |*Not defined*|Enable constant cursor speeds | +|`MK_MOMENTARY_ACCEL` |*Not defined*|Enable momentary speed selection | +|`MK_C_OFFSET_UNMOD` |16 |Cursor offset per movement (unmodified) | +|`MK_C_INTERVAL_UNMOD`|16 |Time between cursor movements (unmodified) | +|`MK_C_OFFSET_0` |1 |Cursor offset per movement (`KC_ACL0`) | +|`MK_C_INTERVAL_0` |32 |Time between cursor movements (`KC_ACL0`) | +|`MK_C_OFFSET_1` |4 |Cursor offset per movement (`KC_ACL1`) | +|`MK_C_INTERVAL_1` |16 |Time between cursor movements (`KC_ACL1`) | +|`MK_C_OFFSET_2` |32 |Cursor offset per movement (`KC_ACL2`) | +|`MK_C_INTERVAL_2` |16 |Time between cursor movements (`KC_ACL2`) | +|`MK_W_OFFSET_UNMOD` |1 |Scroll steps per scroll action (unmodified)| +|`MK_W_INTERVAL_UNMOD`|40 |Time between scroll steps (unmodified) | +|`MK_W_OFFSET_0` |1 |Scroll steps per scroll action (`KC_ACL0`) | +|`MK_W_INTERVAL_0` |360 |Time between scroll steps (`KC_ACL0`) | +|`MK_W_OFFSET_1` |1 |Scroll steps per scroll action (`KC_ACL1`) | +|`MK_W_INTERVAL_1` |120 |Time between scroll steps (`KC_ACL1`) | +|`MK_W_OFFSET_2` |1 |Scroll steps per scroll action (`KC_ACL2`) | +|`MK_W_INTERVAL_2` |20 |Time between scroll steps (`KC_ACL2`) | + +### Combined mode + +This mode functions like **Accelerated** mode, however, you can hold `KC_ACL0`, `KC_ACL1` and `KC_ACL2` +to momentarily (while held) set the cursor and scroll speeds to constant speeds. When no acceleration +keys are held, this mode is identical to **Accelerated** mode, and can be modified using all of the +relevant settings. + +* **KC_ACL0:** This acceleration sets your cursor to the slowest possible speed. This is useful for very +small and detailed movements of the cursor. +* **KC_ACL1:** This acceleration sets your cursor to half the maximum (user defined) speed. +* **KC_ACL2:** This acceleration sets your cursor to the maximum (computer defined) speed. This is +useful for moving the cursor large distances without much accuracy. + +To use combined speed mode, you must at least define `MK_COMBINED` in your keymap’s `config.h` file: + +```c +#define MK_COMBINED +``` + +### Inertia mode + +This mode provides smooth motion, like sliding on ice. The cursor accelerates +along a quadratic curve while a key is held, then glides to a stop after the +key is released. Vertical and horizontal movements are tracked independently, +so the cursor can move in many directions and make curves. + +Cannot be used at the same time as Kinetic mode, Constant mode, or Combined mode. + +Recommended settings in your keymap’s `config.h` file: + +|Define |Default |Description | +|----------------------------|---------|-----------------------------------------------------------| +|`MOUSEKEY_INERTIA` |undefined|Enable Inertia mode | +|`MOUSEKEY_DELAY` |150 |Delay between pressing a movement key and cursor movement | +|`MOUSEKEY_INTERVAL` |16 |Time between cursor movements in milliseconds (16 = 60fps) | +|`MOUSEKEY_MAX_SPEED` |32 |Maximum cursor speed at which acceleration stops | +|`MOUSEKEY_TIME_TO_MAX` |32 |Number of frames until maximum cursor speed is reached | +|`MOUSEKEY_FRICTION` |24 |How quickly the cursor stops after releasing a key | +|`MOUSEKEY_MOVE_DELTA` |1 |How much to move on first frame (1 strongly recommended) | + +Tips: + +* Set `MOUSEKEY_DELAY` to roughly the same value as your host computer's key repeat delay, in ms. Recommended values are 100 to 300. +* Set `MOUSEKEY_INTERVAL` to a value of 1000 / your monitor's FPS. For 60 FPS, 1000/60 = 16. +* Set `MOUSEKEY_MAX_SPEED` based on your screen resolution and refresh rate, like Width / FPS. For example, 1920 pixels / 60 FPS = 32 pixels per frame. +* Set `MOUSEKEY_TIME_TO_MAX` to a value of approximately FPS / 2, to make it reach full speed in half a second (or so). +* Set `MOUSEKEY_FRICTION` to something between 1 and 255. Lower makes the cursor glide longer. Values from 8 to 40 are the most effective. +* Keep `MOUSEKEY_MOVE_DELTA` at 1. This allows precise movements before the gliding effect starts. +* Mouse wheel options are the same as the default accelerated mode, and do not use inertia. + +## Use with PS/2 Mouse and Pointing Device + +Mouse keys button state is shared with [PS/2 mouse](ps2_mouse) and [pointing device](pointing_device) so mouse keys button presses can be used for clicks and drags. diff --git a/docs/features/oled_driver.md b/docs/features/oled_driver.md new file mode 100644 index 0000000000..cb7a2f13f3 --- /dev/null +++ b/docs/features/oled_driver.md @@ -0,0 +1,499 @@ +# OLED Driver + +## Supported Hardware + +OLED modules using SSD1306, SH1106 or SH1107 driver ICs, communicating over I2C or SPI. +Tested combinations: + +|IC |Size |Platform|Notes | +|---------|-------|--------|------------------------| +|SSD1306 |128x32 |AVR |Primary support | +|SSD1306 |128x64 |AVR |Verified working | +|SSD1306 |128x32 |Arm | | +|SSD1306 |128x64 |Arm |Verified working | +|SH1106 |128x64 |AVR |No scrolling | +|SH1107 |64x128 |AVR |No scrolling | +|SH1107 |64x128 |Arm |No scrolling | +|SH1107 |128x128|Arm |No scrolling | + +Hardware configurations using Arm-based microcontrollers or different sizes of OLED modules may be compatible, but are untested. + +## Usage + +To enable the OLED feature, there are two steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`: + +```make +OLED_ENABLE = yes +``` + +## OLED type + +|OLED Driver |Supported Device | +|-------------------|------------------------------------| +|`ssd1306` (default)|For both SSD1306, SH1106, and SH1107| + +e.g. +```make +OLED_DRIVER = ssd1306 +``` + +|OLED Transport | | +|---------------|------------------------------------------------| +|`i2c` (default)| Uses I2C for communication with the OLED panel | +|`spi` | Uses SPI for communication with the OLED panel | + +e.g. +```make +OLED_TRANSPORT = i2c +``` + +Then in your `keymap.c` file, implement the OLED task call. This example assumes your keymap has three layers named `_QWERTY`, `_FN` and `_ADJ`: + +```c +#ifdef OLED_ENABLE +bool oled_task_user(void) { + // Host Keyboard Layer Status + oled_write_P(PSTR("Layer: "), false); + + switch (get_highest_layer(layer_state)) { + case _QWERTY: + oled_write_P(PSTR("Default\n"), false); + break; + case _FN: + oled_write_P(PSTR("FN\n"), false); + break; + case _ADJ: + oled_write_P(PSTR("ADJ\n"), false); + break; + default: + // Or use the write_ln shortcut over adding '\n' to the end of your string + oled_write_ln_P(PSTR("Undefined"), false); + } + + // Host Keyboard LED Status + led_t led_state = host_keyboard_led_state(); + oled_write_P(led_state.num_lock ? PSTR("NUM ") : PSTR(" "), false); + oled_write_P(led_state.caps_lock ? PSTR("CAP ") : PSTR(" "), false); + oled_write_P(led_state.scroll_lock ? PSTR("SCR ") : PSTR(" "), false); + + return false; +} +#endif +``` + +## Logo Example + +In the default font, certain ranges of characters are reserved for a QMK logo. To render this logo to the OLED screen, use the following code example: + +```c +static void render_logo(void) { + static const char PROGMEM qmk_logo[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x00 + }; + + oled_write_P(qmk_logo, false); +} + +bool oled_task_user(void) { + render_logo(); + return false; +} +``` + +::: tip +The default font file is located at `drivers/oled/glcdfont.c` and its location can be overwritten with the `OLED_FONT_H` configuration option. Font file content can be edited with external tools such as [Helix Font Editor](https://helixfonteditor.netlify.app/) and [Logo Editor](https://joric.github.io/qle/). +::: + +## Buffer Read Example +For some purposes, you may need to read the current state of the OLED display +buffer. The `oled_read_raw` function can be used to safely read bytes from the +buffer. + +In this example, calling `fade_display` in the `oled_task_user` function will +slowly fade away whatever is on the screen by turning random pixels black over +time. +```c +//Setup some mask which can be or'd with bytes to turn off pixels +const uint8_t single_bit_masks[8] = {127, 191, 223, 239, 247, 251, 253, 254}; + +static void fade_display(void) { + //Define the reader structure + oled_buffer_reader_t reader; + uint8_t buff_char; + if (random() % 30 == 0) { + srand(timer_read()); + // Fetch a pointer for the buffer byte at index 0. The return structure + // will have the pointer and the number of bytes remaining from this + // index position if we want to perform a sequential read by + // incrementing the buffer pointer + reader = oled_read_raw(0); + //Loop over the remaining buffer and erase pixels as we go + for (uint16_t i = 0; i < reader.remaining_element_count; i++) { + //Get the actual byte in the buffer by dereferencing the pointer + buff_char = *reader.current_element; + if (buff_char != 0) { + oled_write_raw_byte(buff_char & single_bit_masks[rand() % 8], i); + } + //increment the pointer to fetch a new byte during the next loop + reader.current_element++; + } + } +} +``` + +## Other Examples + +In split keyboards, it is very common to have two OLED displays that each render different content and are oriented or flipped differently. You can do this by switching which content to render by using the return value from `is_keyboard_master()` or `is_keyboard_left()` found in `split_util.h`, e.g: + +```c +#ifdef OLED_ENABLE +oled_rotation_t oled_init_user(oled_rotation_t rotation) { + if (!is_keyboard_master()) { + return OLED_ROTATION_180; // flips the display 180 degrees if offhand + } + + return rotation; +} + +bool oled_task_user(void) { + if (is_keyboard_master()) { + render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc) + } else { + render_logo(); // Renders a static logo + oled_scroll_left(); // Turns on scrolling + } + return false; +} +#endif +``` + +Render a message before booting into bootloader mode. +```c +void oled_render_boot(bool bootloader) { + oled_clear(); + for (int i = 0; i < 16; i++) { + oled_set_cursor(0, i); + if (bootloader) { + oled_write_P(PSTR("Awaiting New Firmware "), false); + } else { + oled_write_P(PSTR("Rebooting "), false); + } + } + + oled_render_dirty(true); +} + +bool shutdown_user(bool jump_to_bootloader) { + oled_render_boot(jump_to_bootloader); +} + +``` + +## Basic Configuration + +These configuration options should be placed in `config.h`. Example: +```c +#define OLED_BRIGHTNESS 128 +``` + +|Define |Default |Description | +|---------------------------|-------------------------------|---------------------------------------------------------------------------------------------------------------------| +|`OLED_BRIGHTNESS` |`255` |The default brightness level of the OLED, from 0 to 255. | +|`OLED_COLUMN_OFFSET` |`0` |Shift output to the right this many pixels.<br />Useful for 128x64 displays centered on a 132x64 SH1106 IC. | +|`OLED_DISPLAY_CLOCK` |`0x80` |Set the display clock divide ratio/oscillator frequency. | +|`OLED_FONT_H` |`"glcdfont.c"` |The font code file to use for custom fonts | +|`OLED_FONT_START` |`0` |The starting character index for custom fonts | +|`OLED_FONT_END` |`223` |The ending character index for custom fonts | +|`OLED_FONT_WIDTH` |`6` |The font width | +|`OLED_FONT_HEIGHT` |`8` |The font height (untested) | +|`OLED_IC` |`OLED_IC_SSD1306` |Set to `OLED_IC_SH1106` or `OLED_IC_SH1107` if the corresponding controller chip is used. | +|`OLED_FADE_OUT` |*Not defined* |Enables fade out animation. Use together with `OLED_TIMEOUT`. | +|`OLED_FADE_OUT_INTERVAL` |`0` |The speed of fade out animation, from 0 to 15. Larger values are slower. | +|`OLED_SCROLL_TIMEOUT` |`0` |Scrolls the OLED screen after 0ms of OLED inactivity. Helps reduce OLED Burn-in. Set to 0 to disable. | +|`OLED_SCROLL_TIMEOUT_RIGHT`|*Not defined* |Scroll timeout direction is right when defined, left when undefined. | +|`OLED_TIMEOUT` |`60000` |Turns off the OLED screen after 60000ms of screen update inactivity. Helps reduce OLED Burn-in. Set to 0 to disable. | +|`OLED_UPDATE_INTERVAL` |`0` (`50` for split keyboards) |Set the time interval for updating the OLED display in ms. This will improve the matrix scan rate. | +|`OLED_UPDATE_PROCESS_LIMIT`|`1` |Set the number of dirty blocks to render per loop. Increasing may degrade performance. | + +### I2C Configuration +|Define |Default |Description | +|---------------------------|-----------------|--------------------------------------------------------------------------------------------------------------------------| +|`OLED_DISPLAY_ADDRESS` |`0x3C` |The i2c address of the OLED Display | + +### SPI Configuration + +|Define |Default |Description | +|---------------------------|-----------------|--------------------------------------------------------------------------------------------------------------------------| +|`OLED_DC_PIN` | Required |The pin used for the DC connection of the OLED Display. | +|`OLED_CS_PIN` | Required |The pin used for the CS connection of the OLED Display. | +|`OLED_RST_PIN` | *Not defined* |The pin used for the RST connection of the OLED Display (may be left undefined if the RST pin is not connected). | +|`OLED_SPI_MODE` |`3` (default) |The SPI Mode for the OLED Display (not typically changed). | +|`OLED_SPI_DIVISOR` |`2` (default) |The SPI Multiplier to use for the OLED Display. | + +## 128x64 & Custom sized OLED Displays + + The default display size for this feature is 128x32, and the defaults are set with that in mind. However, there are a number of additional presets for common sizes that we have added. You can define one of these values to use the presets. If your display doesn't match one of these presets, you can define `OLED_DISPLAY_CUSTOM` to manually specify all of the values. + +|Define |Default |Description | +|----------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------| +|`OLED_DISPLAY_128X64` |*Not defined* |Changes the display defines for use with 128x64 displays. | +|`OLED_DISPLAY_64X32` |*Not defined* |Changes the display defines for use with 64x32 displays. | +|`OLED_DISPLAY_64X48` |*Not defined* |Changes the display defines for use with 64x48 displays. | +|`OLED_DISPLAY_64X128` |*Not defined* |Changes the display defines for use with 64x128 displays. | +|`OLED_DISPLAY_128X128`|*Not defined* |Changes the display defines for use with 128x128 displays. | +|`OLED_DISPLAY_CUSTOM` |*Not defined* |Changes the display defines for use with custom displays.<br>Requires user to implement the below defines. | + +::: warning +64x128 and 128x128 displays default to the SH1107 IC type, as these heights are not supported by the other IC types. +::: + +|Define |Default |Description | +| --------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------| +|`OLED_DISPLAY_WIDTH` |`128` |The width of the OLED display. | +|`OLED_DISPLAY_HEIGHT`|`32` |The height of the OLED display. | +|`OLED_MATRIX_SIZE` |`512` |The local buffer size to allocate.<br>`(OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH)`. | +|`OLED_BLOCK_TYPE` |`uint16_t` |The unsigned integer type to use for dirty rendering. | +|`OLED_BLOCK_COUNT` |`16` |The number of blocks the display is divided into for dirty rendering.<br>`(sizeof(OLED_BLOCK_TYPE) * 8)`. | +|`OLED_BLOCK_SIZE` |`32` |The size of each block for dirty rendering<br>`(OLED_MATRIX_SIZE / OLED_BLOCK_COUNT)`. | +|`OLED_COM_PINS` |`COM_PINS_SEQ` |How the SSD1306 chip maps it's memory to display.<br>Options are `COM_PINS_SEQ`, `COM_PINS_ALT`, `COM_PINS_SEQ_LR`, & `COM_PINS_ALT_LR`.| +|`OLED_COM_PIN_COUNT` |*Not defined* |Number of COM pins supported by the controller.<br>If not defined, the value appropriate for the defined `OLED_IC` is used. | +|`OLED_COM_PIN_OFFSET`|`0` |Number of the first COM pin used by the OLED matrix. | +|`OLED_SOURCE_MAP` |`{ 0, ... N }` |Precalculated source array to use for mapping source buffer to target OLED memory in 90 degree rendering. | +|`OLED_TARGET_MAP` |`{ 24, ... N }`|Precalculated target array to use for mapping source buffer to target OLED memory in 90 degree rendering. | + +### 90 Degree Rotation - Technical Mumbo Jumbo + +```c +// OLED Rotation enum values are flags +typedef enum { + OLED_ROTATION_0 = 0, + OLED_ROTATION_90 = 1, + OLED_ROTATION_180 = 2, + OLED_ROTATION_270 = 3, // OLED_ROTATION_90 | OLED_ROTATION_180 +} oled_rotation_t; +``` + +OLED displays driven by SSD1306, SH1106 or SH1107 drivers only natively support in hardware 0 degree and 180 degree rendering. This feature is done in software and not free. Using this feature will increase the time to calculate what data to send over i2c to the OLED. If you are strapped for cycles, this can cause keycodes to not register. In testing however, the rendering time on an ATmega32U4 board only went from 2ms to 5ms and keycodes not registering was only noticed once we hit 15ms. + +90 degree rotation is achieved by using bitwise operations to rotate each 8 block of memory and uses two precalculated arrays to remap buffer memory to OLED memory. The memory map defines are precalculated for remap performance and are calculated based on the display height, width, and block size. For example, in the 128x32 implementation with a `uint8_t` block type, we have a 64 byte block size. This gives us eight 8 byte blocks that need to be rotated and rendered. The OLED renders horizontally two 8 byte blocks before moving down a page, e.g: + +| | | | | | | +|---|---|---|---|---|---| +| 0 | 1 | | | | | +| 2 | 3 | | | | | +| 4 | 5 | | | | | +| 6 | 7 | | | | | + +However the local buffer is stored as if it was Height x Width display instead of Width x Height, e.g: + +| | | | | | | +|---|---|---|---|---|---| +| 3 | 7 | | | | | +| 2 | 6 | | | | | +| 1 | 5 | | | | | +| 0 | 4 | | | | | + +So those precalculated arrays just index the memory offsets in the order in which each one iterates its data. + +Rotation on SH1106 and SH1107 is noticeably less efficient than on SSD1306, because these controllers do not support the “horizontal addressing mode”, which allows transferring the data for the whole rotated block at once; instead, separate address setup commands for every page in the block are required. The screen refresh time for SH1107 is therefore about 45% higher than for a same size screen with SSD1306 when using STM32 MCUs (on AVR the slowdown is about 20%, because the code which actually rotates the bitmap consumes more time). + +## OLED API + +```c +// OLED Rotation enum values are flags +typedef enum { + OLED_ROTATION_0 = 0, + OLED_ROTATION_90 = 1, + OLED_ROTATION_180 = 2, + OLED_ROTATION_270 = 3, // OLED_ROTATION_90 | OLED_ROTATION_180 +} oled_rotation_t; + +// Initialize the oled display, rotating the rendered output based on the define passed in. +// Returns true if the OLED was initialized successfully +bool oled_init(oled_rotation_t rotation); + +// Called at the start of oled_init, weak function overridable by the user +// rotation - the value passed into oled_init +// Return new oled_rotation_t if you want to override default rotation +oled_rotation_t oled_init_kb(oled_rotation_t rotation); +oled_rotation_t oled_init_user(oled_rotation_t rotation); + +// Send commands/data to screen +bool oled_send_cmd(const uint8_t *data, uint16_t size); +bool oled_send_cmd_P(const uint8_t *data, uint16_t size); +bool oled_send_data(const uint8_t *data, uint16_t size); + +// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering +void oled_clear(void); + +// Alias to oled_render_dirty to avoid a change in api. +#define oled_render() oled_render_dirty(false) + +// Renders all dirty blocks to the display at one time or a subset depending on the value of +// all. +void oled_render_dirty(bool all); + +// Moves cursor to character position indicated by column and line, wraps if out of bounds +// Max column denoted by 'oled_max_chars()' and max lines by 'oled_max_lines()' functions +void oled_set_cursor(uint8_t col, uint8_t line); + +// Advances the cursor to the next page, writing ' ' if true +// Wraps to the beginning when out of bounds +void oled_advance_page(bool clearPageRemainder); + +// Moves the cursor forward 1 character length +// Advance page if there is not enough room for the next character +// Wraps to the beginning when out of bounds +void oled_advance_char(void); + +// Writes a single character to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Main handler that writes character data to the display buffer +void oled_write_char(const char data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +void oled_write(const char *data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +void oled_write_ln(const char *data, bool invert); + +// Pans the buffer to the right (or left by passing true) by moving contents of the buffer +// Useful for moving the screen in preparation for new drawing +void oled_pan(bool left); + +// Returns a pointer to the requested start index in the buffer plus remaining +// buffer length as struct +oled_buffer_reader_t oled_read_raw(uint16_t start_index); + +// Writes a string to the buffer at current cursor position +void oled_write_raw(const char *data, uint16_t size); + +// Writes a single byte into the buffer at the specified index +void oled_write_raw_byte(const char data, uint16_t index); + +// Sets a specific pixel on or off +// Coordinates start at top-left and go right and down for positive x and y +void oled_write_pixel(uint8_t x, uint8_t y, bool on); + +#if defined(__AVR__) +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Remapped to call 'void oled_write(const char *data, bool invert);' on ARM +void oled_write_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +// Remapped to call 'void oled_write_ln(const char *data, bool invert);' on ARM +void oled_write_ln_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +void oled_write_raw_P(const char *data, uint16_t size); +#else +# define oled_write_P(data, invert) oled_write(data, invert) +# define oled_write_ln_P(data, invert) oled_write_ln(data, invert) +# define oled_write_raw_P(data, size) oled_write_raw(data, size) +#endif // defined(__AVR__) + +// Can be used to manually turn on the screen if it is off +// Returns true if the screen was on or turns on +bool oled_on(void); + +// Can be used to manually turn off the screen if it is on +// Returns true if the screen was off or turns off +bool oled_off(void); + +// Returns true if the oled is currently on, false if it is +// not +bool is_oled_on(void); + +// Sets the brightness level of the display +uint8_t oled_set_brightness(uint8_t level); + +// Gets the current brightness level of the display +uint8_t oled_get_brightness(void); + +// Basically it's oled_render, but with timeout management and oled_task_user calling! +void oled_task(void); + +// Called at the start of oled_task, weak function overridable by the user +bool oled_task_kb(void); +bool oled_task_user(void); + +// Set the specific 8 lines rows of the screen to scroll. +// 0 is the default for start, and 7 for end, which is the entire +// height of the screen. For 128x32 screens, rows 4-7 are not used. +void oled_scroll_set_area(uint8_t start_line, uint8_t end_line); + +// Sets scroll speed, 0-7, fastest to slowest. Default is three. +// Does not take effect until scrolling is either started or restarted +// the ssd1306 supports 8 speeds with the delay +// listed below between each frame of the scrolling effect +// 0=2, 1=3, 2=4, 3=5, 4=25, 5=64, 6=128, 7=256 +void oled_scroll_set_speed(uint8_t speed); + +// Begin scrolling the entire display right +// Returns true if the screen was scrolling or starts scrolling +// NOTE: display contents cannot be changed while scrolling +bool oled_scroll_right(void); + +// Begin scrolling the entire display left +// Returns true if the screen was scrolling or starts scrolling +// NOTE: display contents cannot be changed while scrolling +bool oled_scroll_left(void); + +// Turns off display scrolling +// Returns true if the screen was not scrolling or stops scrolling +bool oled_scroll_off(void); + +// Returns true if the oled is currently scrolling, false if it is +// not +bool is_oled_scrolling(void); + +// Inverts the display +// Returns true if the screen was or is inverted +bool oled_invert(bool invert); + +// Returns the maximum number of characters that will fit on a line +uint8_t oled_max_chars(void); + +// Returns the maximum number of lines that will fit on the OLED +uint8_t oled_max_lines(void); +``` + +::: warning +Scrolling is unsupported on the SH1106 and SH1107. +::: + +::: warning +Scrolling does not work properly on the SSD1306 if the display width is smaller than 128. +::: + +## SSD1306.h Driver Conversion Guide + +|Old API |Recommended New API | +|-------------------------|---------------------------------| +|`struct CharacterMatrix` |*removed - delete all references*| +|`iota_gfx_init` |`oled_init` | +|`iota_gfx_on` |`oled_on` | +|`iota_gfx_off` |`oled_off` | +|`iota_gfx_flush` |`oled_render` | +|`iota_gfx_write_char` |`oled_write_char` | +|`iota_gfx_write` |`oled_write` | +|`iota_gfx_write_P` |`oled_write_P` | +|`iota_gfx_clear_screen` |`oled_clear` | +|`matrix_clear` |*removed - delete all references*| +|`matrix_write_char_inner`|`oled_write_char` | +|`matrix_write_char` |`oled_write_char` | +|`matrix_write` |`oled_write` | +|`matrix_write_ln` |`oled_write_ln` | +|`matrix_write_P` |`oled_write_P` | +|`matrix_write_ln_P` |`oled_write_ln_P` | +|`matrix_render` |`oled_render` | +|`iota_gfx_task` |`oled_task` | +|`iota_gfx_task_user` |`oled_task_user` | diff --git a/docs/features/os_detection.md b/docs/features/os_detection.md new file mode 100644 index 0000000000..d0556d2549 --- /dev/null +++ b/docs/features/os_detection.md @@ -0,0 +1,132 @@ +# OS Detection + +This feature makes a best guess at the host OS based on OS specific behavior during USB setup. It may not always get the correct OS, and shouldn't be relied on as for critical functionality. + +Using it you can have OS specific key mappings or combos which work differently on different devices. + +It is available for keyboards which use ChibiOS, LUFA and V-USB. + +## Usage + +In your `rules.mk` add: + +```make +OS_DETECTION_ENABLE = yes +``` + +It will automatically include the required headers file. +It declares `os_variant_t detected_host_os(void);` which you can call to get detected OS. + +It returns one of the following values: + +```c +enum { + OS_UNSURE, + OS_LINUX, + OS_WINDOWS, + OS_MACOS, + OS_IOS, +} os_variant_t; +``` + +::: tip +Note that it takes some time after firmware is booted to detect the OS. +::: +This time is quite short, probably hundreds of milliseconds, but this data may be not ready in keyboard and layout setup functions which run very early during firmware startup. + +## Callbacks {#callbacks} + +If you want to perform custom actions when the OS is detected, then you can use the `process_detected_host_os_kb` function on the keyboard level source file, or `process_detected_host_os_user` function in the user `keymap.c`. + +```c +bool process_detected_host_os_kb(os_variant_t detected_os) { + if (!process_detected_host_os_user(detected_os)) { + return false; + } + switch (detected_os) { + case OS_MACOS: + case OS_IOS: + rgb_matrix_set_color_all(RGB_WHITE); + break; + case OS_WINDOWS: + rgb_matrix_set_color_all(RGB_BLUE); + break; + case OS_LINUX: + rgb_matrix_set_color_all(RGB_ORANGE); + break; + case OS_UNSURE: + rgb_matrix_set_color_all(RGB_RED); + break; + } + + return true; +} +``` + +## OS detection stability + +The OS detection is currently handled while the USB device descriptor is being assembled. +The process is done in steps, generating a number of intermediate results until it stabilizes. +We therefore resort to debouncing the result until it has been stable for a given amount of milliseconds. +This amount can be configured, in case your board is not stable within the default debouncing time of 200ms. + +## KVM and USB switches + +Some KVM and USB switches may not trigger the USB controller on the keyboard to fully reset upon switching machines. +If your keyboard does not redetect the OS in this situation, you can force the keyboard to reset when the USB initialization event is detected, forcing the USB controller to be reconfigured. + +## Configuration Options + +* `#define OS_DETECTION_DEBOUNCE 200` + * defined the debounce time for OS detection, in milliseconds +* `#define OS_DETECTION_KEYBOARD_RESET` + * enables the keyboard reset upon a USB device reinitilization, such as switching devices on some KVMs + +## Debug + +If OS is guessed incorrectly, you may want to collect data about USB setup packets to refine the detection logic. + +To do so in your `config.h` add: + +```c +#define OS_DETECTION_DEBUG_ENABLE +``` + +And in your `rules.mk` add: + +```make +CONSOLE_ENABLE = yes +``` + +And also include `"os_detection.h"` in your `keymap.c`. + +Then you can define custom keycodes to store data about USB setup packets in EEPROM (persistent memory) and to print it later on host where you can run `qmk console`: + +```c +enum custom_keycodes { + STORE_SETUPS = SAFE_RANGE, + PRINT_SETUPS, +}; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case STORE_SETUPS: + if (record->event.pressed) { + store_setups_in_eeprom(); + } + return false; + case PRINT_SETUPS: + if (record->event.pressed) { + print_stored_setups(); + } + return false; + } +} +``` + +Then please open an issue on Github with this information and tell what OS was not detected correctly and if you have any intermediate devices between keyboard and your computer. + + +## Credits + +Original idea is coming from [FingerprintUSBHost](https://github.com/keyboardio/FingerprintUSBHost) project. diff --git a/docs/features/pointing_device.md b/docs/features/pointing_device.md new file mode 100644 index 0000000000..a6bf521a18 --- /dev/null +++ b/docs/features/pointing_device.md @@ -0,0 +1,928 @@ +# Pointing Device {#pointing-device} + +Pointing Device is a generic name for a feature intended to be generic: moving the system pointer around. There are certainly other options for it - like mousekeys - but this aims to be easily modifiable and hardware driven. You can implement custom keys to control functionality, or you can gather information from other peripherals and insert it directly here - let QMK handle the processing for you. + +To enable Pointing Device, add the following line in your rules.mk and specify one of the driver options below. + +```make +POINTING_DEVICE_ENABLE = yes +``` + +## Sensor Drivers + +There are a number of sensors that are supported by default. Note that only one sensor can be enabled by `POINTING_DEVICE_DRIVER` at a time. If you need to enable more than one sensor, then you need to implement it manually, using the `custom` driver. + +### ADNS 5050 Sensor + +To use the ADNS 5050 sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = adns5050 +``` + +The ADNS 5050 sensor uses a serial type protocol for communication, and requires an additional light source. + +| Setting (`config.h`) | Description | Default | +| -------------------- | ------------------------------------------------------------------ | -------------------------- | +| `ADNS5050_SCLK_PIN` | (Required) The pin connected to the clock pin of the sensor. | `POINTING_DEVICE_SCLK_PIN` | +| `ADNS5050_SDIO_PIN` | (Required) The pin connected to the data pin of the sensor. | `POINTING_DEVICE_SDIO_PIN` | +| `ADNS5050_CS_PIN` | (Required) The pin connected to the Chip Select pin of the sensor. | `POINTING_DEVICE_CS_PIN` | + + + +The CPI range is 125-1375, in increments of 125. Defaults to 500 CPI. + +### ADNS 9800 Sensor + +To use the ADNS 9800 sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = adns9800 +``` + +The ADNS 9800 is an SPI driven optical sensor, that uses laser output for surface tracking. + +| Setting (`config.h`) | Description | Default | +| ----------------------- | ---------------------------------------------------------------------- | ------------------------ | +| `ADNS9800_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `2000000` | +| `ADNS9800_SPI_LSBFIRST` | (Optional) Sets the Least/Most Significant Byte First setting for SPI. | `false` | +| `ADNS9800_SPI_MODE` | (Optional) Sets the SPI Mode for the sensor. | `3` | +| `ADNS9800_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ | +| `ADNS9800_CS_PIN` | (Required) Sets the Chip Select pin connected to the sensor. | `POINTING_DEVICE_CS_PIN` | + + +The CPI range is 800-8200, in increments of 200. Defaults to 1800 CPI. + +### Analog Joystick + +To use an analog joystick to control the pointer, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = analog_joystick +``` + +The Analog Joystick is an analog (ADC) driven sensor. There are a variety of joysticks that you can use for this. + +| Setting (`config.h`) | Description | Default | +| --------------------------------- | -------------------------------------------------------------------------- | ------------- | +| `ANALOG_JOYSTICK_X_AXIS_PIN` | (Required) The pin used for the vertical/X axis. | _not defined_ | +| `ANALOG_JOYSTICK_Y_AXIS_PIN` | (Required) The pin used for the horizontal/Y axis. | _not defined_ | +| `ANALOG_JOYSTICK_AXIS_MIN` | (Optional) Sets the lower range to be considered movement. | `0` | +| `ANALOG_JOYSTICK_AXIS_MAX` | (Optional) Sets the upper range to be considered movement. | `1023` | +| `ANALOG_JOYSTICK_AUTO_AXIS` | (Optional) Sets ranges to be considered movement automatically. | _not defined_ | +| `ANALOG_JOYSTICK_SPEED_REGULATOR` | (Optional) The divisor used to slow down movement. (lower makes it faster) | `20` | +| `ANALOG_JOYSTICK_READ_INTERVAL` | (Optional) The interval in milliseconds between reads. | `10` | +| `ANALOG_JOYSTICK_SPEED_MAX` | (Optional) The maximum value used for motion. | `2` | +| `ANALOG_JOYSTICK_CLICK_PIN` | (Optional) The pin wired up to the press switch of the analog stick. | _not defined_ | +| `ANALOG_JOYSTICK_WEIGHTS` | (Optional) Use custom weights for lever positions. | _not defined_ | +| `ANALOG_JOYSTICK_CUTOFF` | (Optional) Cut off movement when joystick returns to start position. | _not defined_ | + +If `ANALOG_JOYSTICK_AUTO_AXIS` is used, then `ANALOG_JOYSTICK_AXIS_MIN` and `ANALOG_JOYSTICK_AXIS_MAX` are ignored. + +By default analog joystick implementation uses `x^2` weighting for lever positions. `ANALOG_JOYSTICK_WEIGHTS` allows to experiment with different configurations that might feel better. + +E.g. This is weights for `((x-0.4)^3+0.064)/0.282`: + +```c +#define ANALOG_JOYSTICK_WEIGHTS {0,2,4,5,7,8,9,10,12,13,14,15,15,16,17,18,18,19,19,20,20,21,21,21,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,25,25,25,26,26,26,27,28,28,29,29,30,31,32,33,34,35,36,37,38,40,41,43,44,46,48,49,51,53,56,58,60,62,65,68,70,73,76,79,82,85,89,92,96,100} +``` + +You can use following JS code to generate weights for different formulas: + +```js +JSON.stringify(Array.from(Array(101).keys()).map(x => Math.ceil((((x/100-0.4)**3+0.064)/0.282*100)))) +``` + +### Azoteq IQS5XX Trackpad + +To use a Azoteq IQS5XX trackpad, add this to your `rules.mk`: + +```make +POINTING_DEVICE_DRIVER = azoteq_iqs5xx +``` + +This supports the IQS525, IQS550 and IQS572 controllers, with the latter two being used in the TPS43 and TPS65 trackpads. + +#### Device settings + +Specific device profiles are provided which set the required values for dimensions and resolution. + +| Setting | Description | +| -------------------------------- | ---------------------------------------------------------- | +| `AZOTEQ_IQS5XX_TPS43` | (Pick One) Sets resolution/mm to TPS43 specifications. | +| `AZOTEQ_IQS5XX_TPS65` | (Pick One) Sets resolution/mm to TPS65 specifications. | + +::: tip +If using one of the above defines you can skip to gesture settings. +::: + +| Setting | Description | Default | +| -------------------------------- | ---------------------------------------------------------- | ------------- | +| `AZOTEQ_IQS5XX_WIDTH_MM` | (Required) Width of the trackpad sensor in millimeters. | _not defined_ | +| `AZOTEQ_IQS5XX_HEIGHT_MM` | (Required) Height of the trackpad sensor in millimeters. | _not defined_ | +| `AZOTEQ_IQS5XX_RESOLUTION_X` | (Optional) Specify X resolution for CPI calculation. | _not defined_ | +| `AZOTEQ_IQS5XX_RESOLUTION_Y` | (Optional) Specify Y resolution for CPI calculation. | _not defined_ | + +**`AZOTEQ_IQS5XX_RESOLUTION_X/Y`** fall back resolutions are provided within the driver based on controller model. + +| I2C Setting | Description | Default | +| ------------------------- | ------------------------------------------------------------------------------- | ------- | +| `AZOTEQ_IQS5XX_ADDRESS` | (Optional) Sets the I2C Address for the Azoteq trackpad | `0xE8` | +| `AZOTEQ_IQS5XX_TIMEOUT_MS`| (Optional) The timeout for i2c communication with in milliseconds. | `10` | + +#### Gesture settings + +| Setting | Description | Default | +| ----------------------------------------- | ------------------------------------------------------------------------------------ | ----------- | +| `AZOTEQ_IQS5XX_TAP_ENABLE` | (Optional) Enable single finger tap. (Left click) | `true` | +| `AZOTEQ_IQS5XX_TWO_FINGER_TAP_ENABLE` | (Optional) Enable two finger tap. (Right click) | `true` | +| `AZOTEQ_IQS5XX_PRESS_AND_HOLD_ENABLE` | (Optional) Emulates holding left click to select text. | `false` | +| `AZOTEQ_IQS5XX_SWIPE_X_ENABLE` | (Optional) Enable swipe gestures X+ (Mouse Button 5) / X- (Mouse Button 4) | `false` | +| `AZOTEQ_IQS5XX_SWIPE_Y_ENABLE` | (Optional) Enable swipe gestures Y+ (Mouse Button 3) / Y- (Mouse Button 6) | `false` | +| `AZOTEQ_IQS5XX_ZOOM_ENABLE` | (Optional) Enable zoom gestures Zoom Out (Mouse Button 7) / Zoom In (Mouse Button 8) | `false` | +| `AZOTEQ_IQS5XX_SCROLL_ENABLE` | (Optional) Enable scrolling using two fingers. | `true` | +| `AZOTEQ_IQS5XX_TAP_TIME` | (Optional) Maximum time in ms for tap to be registered. | `150` | +| `AZOTEQ_IQS5XX_TAP_DISTANCE` | (Optional) Maximum deviation in pixels before single tap is no longer valid. | `25` | +| `AZOTEQ_IQS5XX_HOLD_TIME` | (Optional) Minimum time in ms for press and hold. | `300` | +| `AZOTEQ_IQS5XX_SWIPE_INITIAL_TIME` | (Optional) Maximum time to travel initial distance before swipe is registered. | `150` | +| `AZOTEQ_IQS5XX_SWIPE_INITIAL_DISTANCE` | (Optional) Minimum travel in pixels before swipe is registered. | `300` | +| `AZOTEQ_IQS5XX_SWIPE_CONSECUTIVE_TIME` | (Optional) Maximum time to travel consecutive distance before swipe is registered. | `0` | +| `AZOTEQ_IQS5XX_SWIPE_CONSECUTIVE_DISTANCE`| (Optional) Minimum travel in pixels before a consecutive swipe is registered. | `2000` | +| `AZOTEQ_IQS5XX_SCROLL_INITIAL_DISTANCE` | (Optional) Minimum travel in pixels before scroll is registered. | `50` | +| `AZOTEQ_IQS5XX_ZOOM_INITIAL_DISTANCE` | (Optional) Minimum travel in pixels before zoom is registered. | `50` | +| `AZOTEQ_IQS5XX_ZOOM_CONSECUTIVE_DISTANCE` | (Optional) Maximum time to travel zoom distance before zoom is registered. | `25` | + +#### Rotation settings + +| Setting | Description | Default | +| ---------------------------- | ---------------------------------------------------------- | ------------- | +| `AZOTEQ_IQS5XX_ROTATION_90` | (Optional) Configures hardware for 90 degree rotation. | _not defined_ | +| `AZOTEQ_IQS5XX_ROTATION_180` | (Optional) Configures hardware for 180 degree rotation. | _not defined_ | +| `AZOTEQ_IQS5XX_ROTATION_270` | (Optional) Configures hardware for 270 degree rotation. | _not defined_ | + +### Cirque Trackpad + +To use the Cirque Trackpad sensor, add this to your `rules.mk`: + +```make +POINTING_DEVICE_DRIVER = cirque_pinnacle_i2c +``` + +or + +```make +POINTING_DEVICE_DRIVER = cirque_pinnacle_spi +``` + + +This supports the Cirque Pinnacle 1CA027 Touch Controller, which is used in the TM040040, TM035035 and the TM023023 trackpads. These are I2C or SPI compatible, and both configurations are supported. + +#### Common settings + +| Setting | Description | Default | +| ------------------------------------ | ---------------------------------------------------------- | ------------------------------------------- | +| `CIRQUE_PINNACLE_DIAMETER_MM` | (Optional) Diameter of the trackpad sensor in millimeters. | `40` | +| `CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X` | +| `CIRQUE_PINNACLE_CURVED_OVERLAY` | (Optional) Applies settings tuned for curved overlay. | _not defined_ | +| `CIRQUE_PINNACLE_POSITION_MODE` | (Optional) Mode of operation. | _not defined_ | +| `CIRQUE_PINNACLE_SKIP_SENSOR_CHECK` | (Optional) Skips sensor presence check | _not defined_ | + +**`CIRQUE_PINNACLE_ATTENUATION`** is a measure of how much data is suppressed in regards to sensitivity. The higher the attenuation, the less sensitive the touchpad will be. + +Default attenuation is set to 4X, although if you are using a thicker overlay (such as the curved overlay) you will want a lower attenuation such as 2X. The possible values are: +* `EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X`: Least sensitive +* `EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_3X` +* `EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_2X` +* `EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_1X`: Most sensitive + +**`CIRQUE_PINNACLE_POSITION_MODE`** can be `CIRQUE_PINNACLE_ABSOLUTE_MODE` or `CIRQUE_PINNACLE_RELATIVE_MODE`. Modes differ in supported features/gestures. + +* `CIRQUE_PINNACLE_ABSOLUTE_MODE`: Reports absolute x, y, z (touch pressure) coordinates and up to 5 hw buttons connected to the trackpad +* `CIRQUE_PINNACLE_RELATIVE_MODE`: Reports x/y deltas, scroll and up to 3 buttons (2 of them can be from taps, see gestures) connected to trackpad. Supports taps on secondary side of split. Saves about 2k of flash compared to absolute mode with all features. + +| I2C Setting | Description | Default | +| ------------------------- | ------------------------------------------------------------------------------- | ------- | +| `CIRQUE_PINNACLE_ADDR` | (Required) Sets the I2C Address for the Cirque Trackpad | `0x2A` | +| `CIRQUE_PINNACLE_TIMEOUT` | (Optional) The timeout for i2c communication with the trackpad in milliseconds. | `20` | + +| SPI Setting | Description | Default | +| ------------------------------ | ---------------------------------------------------------------------- | ------------------------ | +| `CIRQUE_PINNACLE_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `1000000` | +| `CIRQUE_PINNACLE_SPI_LSBFIRST` | (Optional) Sets the Least/Most Significant Byte First setting for SPI. | `false` | +| `CIRQUE_PINNACLE_SPI_MODE` | (Optional) Sets the SPI Mode for the sensor. | `1` | +| `CIRQUE_PINNACLE_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ | +| `CIRQUE_PINNACLE_SPI_CS_PIN` | (Required) Sets the Chip Select pin connected to the sensor. | `POINTING_DEVICE_CS_PIN` | + +Default Scaling is 1024. Actual CPI depends on trackpad diameter. + +Also see the `POINTING_DEVICE_TASK_THROTTLE_MS`, which defaults to 10ms when using Cirque Pinnacle, which matches the internal update rate of the position registers (in standard configuration). Advanced configuration for pen/stylus usage might require lower values. + +#### Absolute mode settings + +| Setting | Description | Default | +|-----------------------------------------|-------------------------------------------------------------------------|-------------| +| `CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` | +| `CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` | +| `CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` | +| `CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` | +| `CIRQUE_PINNACLE_REACHABLE_CALIBRATION` | (Optional) Enable console messages to aide in calibrating above values. | not defined | + +#### Absolute mode gestures + +| Gesture Setting | Description | Default | +| ---------------------------------------------- | ------------------------------------------------------------------------------ | -------------------- | +| `CIRQUE_PINNACLE_TAP_ENABLE` | (Optional) Enable tap to click. This currently only works on the master side. | _not defined_ | +| `CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | +| `CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | + +`POINTING_DEVICE_GESTURES_SCROLL_ENABLE` in this mode enables circular scroll. Touch originating in outer ring can trigger scroll by moving along the perimeter. Near side triggers vertical scroll and far side triggers horizontal scroll. + +Additionally, `POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE` is supported in this mode. + +#### Relative mode gestures + +| Gesture Setting (`config.h`) | Description | Default | +| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `CIRQUE_PINNACLE_TAP_ENABLE` | (Optional) Enable tap to "left click". Works on both sides of a split keyboard. | _not defined_ | +| `CIRQUE_PINNACLE_SECONDARY_TAP_ENABLE` | (Optional) Tap in upper right corner (half of the finger needs to be outside of the trackpad) of the trackpad will result in "right click". `CIRQUE_PINNACLE_TAP_ENABLE` must be enabled. | _not defined_ | + +Tapping term and debounce are not configurable in this mode since it's handled by trackpad internally. + +`POINTING_DEVICE_GESTURES_SCROLL_ENABLE` in this mode enables side scroll. Touch originating on the right side can trigger vertical scroll (IntelliSense trackpad style). + +### PAW 3204 Sensor + +To use the paw 3204 sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = paw3204 +``` + +The paw 3204 sensor uses a serial type protocol for communication, and requires an additional light source. + +| Setting (`config.h`) | Description | Default | +| -------------------- |--------------------------------------------------------------- | -------------------------- | +| `PAW3204_SCLK_PIN` | (Required) The pin connected to the clock pin of the sensor. | `POINTING_DEVICE_SCLK_PIN` | +| `PAW3204_SDIO_PIN` | (Required) The pin connected to the data pin of the sensor. | `POINTING_DEVICE_SDIO_PIN` | + +The CPI range is 400-1600, with supported values of (400, 500, 600, 800, 1000, 1200 and 1600). Defaults to 1000 CPI. + +### Pimoroni Trackball + +To use the Pimoroni Trackball module, add this to your `rules.mk`: + +```make +POINTING_DEVICE_DRIVER = pimoroni_trackball +``` + +The Pimoroni Trackball module is a I2C based breakout board with an RGB enable trackball. + +| Setting (`config.h`) | Description | Default | +| ------------------------------------ | ---------------------------------------------------------------------------------- | ------- | +| `PIMORONI_TRACKBALL_ADDRESS` | (Required) Sets the I2C Address for the Pimoroni Trackball. | `0x0A` | +| `PIMORONI_TRACKBALL_TIMEOUT` | (Optional) The timeout for i2c communication with the trackball in milliseconds. | `100` | +| `PIMORONI_TRACKBALL_SCALE` | (Optional) The multiplier used to generate reports from the sensor. | `5` | +| `PIMORONI_TRACKBALL_DEBOUNCE_CYCLES` | (Optional) The number of scan cycles used for debouncing on the ball press. | `20` | +| `PIMORONI_TRACKBALL_ERROR_COUNT` | (Optional) Specifies the number of read/write errors until the sensor is disabled. | `10` | + +### PMW3320 Sensor + +To use the PMW3320 sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = pmw3320 +``` + +The PMW3320 sensor uses a serial type protocol for communication, and requires an additional light source (it could work without one, but expect it to be out of service early). + +| Setting | Description | Default | +| ------------------- | ------------------------------------------------------------------- | -------------------------- | +| `PMW3320_SCLK_PIN` | (Required) The pin connected to the clock pin of the sensor. | `POINTING_DEVICE_SCLK_PIN` | +| `PMW3320_SDIO_PIN` | (Required) The pin connected to the data pin of the sensor. | `POINTING_DEVICE_SDIO_PIN` | +| `PMW3320_CS_PIN` | (Required) The pin connected to the cable select pin of the sensor. | `POINTING_DEVICE_CS_PIN` | + +The CPI range is 500-3500, in increments of 250. Defaults to 1000 CPI. + +### PMW 3360 and PMW 3389 Sensor + +This drivers supports both the PMW 3360 and PMW 3389 sensor as well as multiple sensors of the same type _per_ controller, so 2 can be attached at the same side for split keyboards (or unsplit keyboards). + +To use the **PMW 3360** sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = pmw3360 +``` + +The CPI range is 100-12000, in increments of 100. Defaults to 1600 CPI. + +To use the **PMW 3389** sensor, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = pmw3389 +``` + +The CPI range is 50-16000, in increments of 50. Defaults to 2000 CPI. + +Both PMW 3360 and PMW 3389 are SPI driven optical sensors, that use a built in IR LED for surface tracking. +If you have different CS wiring on each half you can use `PMW33XX_CS_PIN_RIGHT` or `PMW33XX_CS_PINS_RIGHT` in combination with `PMW33XX_CS_PIN` or `PMW33XX_CS_PINS` to configure both sides independently. If `_RIGHT` values aren't provided, they default to be the same as the left ones. + +| Setting (`config.h`) | Description | Default | +| ---------------------------- | ------------------------------------------------------------------------------------------- | ------------------------ | +| `PMW33XX_CS_PIN` | (Required) Sets the Chip Select pin connected to the sensor. | `POINTING_DEVICE_CS_PIN` | +| `PMW33XX_CS_PINS` | (Alternative) Sets the Chip Select pins connected to multiple sensors. | `{PMW33XX_CS_PIN}` | +| `PMW33XX_CS_PIN_RIGHT` | (Optional) Sets the Chip Select pin connected to the sensor on the right half. | `PMW33XX_CS_PIN` | +| `PMW33XX_CS_PINS_RIGHT` | (Optional) Sets the Chip Select pins connected to multiple sensors on the right half. | `{PMW33XX_CS_PIN_RIGHT}` | +| `PMW33XX_CPI` | (Optional) Sets counts per inch sensitivity of the sensor. | _varies_ | +| `PMW33XX_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `2000000` | +| `PMW33XX_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ | +| `PMW33XX_LIFTOFF_DISTANCE` | (Optional) Sets the lift off distance at run time | `0x02` | +| `ROTATIONAL_TRANSFORM_ANGLE` | (Optional) Allows for the sensor data to be rotated +/- 127 degrees directly in the sensor. | `0` | + +To use multiple sensors, instead of setting `PMW33XX_CS_PIN` you need to set `PMW33XX_CS_PINS` and also handle and merge the read from this sensor in user code. +Note that different (per sensor) values of CPI, speed liftoff, rotational angle or flipping of X/Y is not currently supported. + +```c +// in config.h: +#define PMW33XX_CS_PINS { B5, B6 } +// in keyboard.c: +#ifdef POINTING_DEVICE_ENABLE +void pointing_device_init_kb(void) { + pmw33xx_init(1); // index 1 is the second device. + pmw33xx_set_cpi(0, 800); // applies to first sensor + pmw33xx_set_cpi(1, 800); // applies to second sensor + pointing_device_init_user(); +} + +// Contains report from sensor #0 already, need to merge in from sensor #1 +report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { + pmw33xx_report_t report = pmw33xx_read_burst(1); + if (!report.motion.b.is_lifted && report.motion.b.is_motion) { +// From quantum/pointing_device_drivers.c +#define constrain_hid(amt) ((amt) < -127 ? -127 : ((amt) > 127 ? 127 : (amt))) + mouse_report.x = constrain_hid(mouse_report.x + report.delta_x); + mouse_report.y = constrain_hid(mouse_report.y + report.delta_y); + } + return pointing_device_task_user(mouse_report); +} +#endif + +``` + +### Custom Driver + +If you have a sensor type that isn't supported above, a custom option is available by adding the following to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = custom +``` + +Using the custom driver will require implementing the following functions: + +```c +void pointing_device_driver_init(void) {} +report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) { return mouse_report; } +uint16_t pointing_device_driver_get_cpi(void) { return 0; } +void pointing_device_driver_set_cpi(uint16_t cpi) {} +``` + +::: warning +Ideally, new sensor hardware should be added to `drivers/sensors/` and `quantum/pointing_device_drivers.c`, but there may be cases where it's very specific to the hardware. So these functions are provided, just in case. +::: + +## Common Configuration + +| Setting | Description | Default | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `MOUSE_EXTENDED_REPORT` | (Optional) Enables support for extended mouse reports. (-32767 to 32767, instead of just -127 to 127). | _not defined_ | +| `POINTING_DEVICE_ROTATION_90` | (Optional) Rotates the X and Y data by 90 degrees. | _not defined_ | +| `POINTING_DEVICE_ROTATION_180` | (Optional) Rotates the X and Y data by 180 degrees. | _not defined_ | +| `POINTING_DEVICE_ROTATION_270` | (Optional) Rotates the X and Y data by 270 degrees. | _not defined_ | +| `POINTING_DEVICE_INVERT_X` | (Optional) Inverts the X axis report. | _not defined_ | +| `POINTING_DEVICE_INVERT_Y` | (Optional) Inverts the Y axis report. | _not defined_ | +| `POINTING_DEVICE_MOTION_PIN` | (Optional) If supported, will only read from sensor if pin is active. | _not defined_ | +| `POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW` | (Optional) If defined then the motion pin is active-low. | _varies_ | +| `POINTING_DEVICE_TASK_THROTTLE_MS` | (Optional) Limits the frequency that the sensor is polled for motion. | _not defined_ | +| `POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE` | (Optional) Enable inertial cursor. Cursor continues moving after a flick gesture and slows down by kinetic friction. | _not defined_ | +| `POINTING_DEVICE_GESTURES_SCROLL_ENABLE` | (Optional) Enable scroll gesture. The gesture that activates the scroll is device dependent. | _not defined_ | +| `POINTING_DEVICE_CS_PIN` | (Optional) Provides a default CS pin, useful for supporting multiple sensor configs. | _not defined_ | +| `POINTING_DEVICE_SDIO_PIN` | (Optional) Provides a default SDIO pin, useful for supporting multiple sensor configs. | _not defined_ | +| `POINTING_DEVICE_SCLK_PIN` | (Optional) Provides a default SCLK pin, useful for supporting multiple sensor configs. | _not defined_ | + +::: warning +When using `SPLIT_POINTING_ENABLE` the `POINTING_DEVICE_MOTION_PIN` functionality is not supported and `POINTING_DEVICE_TASK_THROTTLE_MS` will default to `1`. Increasing this value will increase transport performance at the cost of possible mouse responsiveness. +::: + +The `POINTING_DEVICE_CS_PIN`, `POINTING_DEVICE_SDIO_PIN`, and `POINTING_DEVICE_SCLK_PIN` provide a convenient way to define a single pin that can be used for an interchangeable sensor config. This allows you to have a single config, without defining each device. Each sensor allows for this to be overridden with their own defines. + +::: warning +Any pointing device with a lift/contact status can integrate inertial cursor feature into its driver, controlled by `POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE`. e.g. PMW3360 can use Lift_Stat from Motion register. Note that `POINTING_DEVICE_MOTION_PIN` cannot be used with this feature; continuous polling of `get_report()` is needed to generate glide reports. +::: + +## Split Keyboard Configuration + +The following configuration options are only available when using `SPLIT_POINTING_ENABLE` see [data sync options](split_keyboard#data-sync-options). The rotation and invert `*_RIGHT` options are only used with `POINTING_DEVICE_COMBINED`. If using `POINTING_DEVICE_LEFT` or `POINTING_DEVICE_RIGHT` use the common configuration above to configure your pointing device. + +| Setting | Description | Default | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------- | ------------- | +| `POINTING_DEVICE_LEFT` | Pointing device on the left side (Required - pick one only) | _not defined_ | +| `POINTING_DEVICE_RIGHT` | Pointing device on the right side (Required - pick one only) | _not defined_ | +| `POINTING_DEVICE_COMBINED` | Pointing device on both sides (Required - pick one only) | _not defined_ | +| `POINTING_DEVICE_ROTATION_90_RIGHT` | (Optional) Rotates the X and Y data by 90 degrees. | _not defined_ | +| `POINTING_DEVICE_ROTATION_180_RIGHT` | (Optional) Rotates the X and Y data by 180 degrees. | _not defined_ | +| `POINTING_DEVICE_ROTATION_270_RIGHT` | (Optional) Rotates the X and Y data by 270 degrees. | _not defined_ | +| `POINTING_DEVICE_INVERT_X_RIGHT` | (Optional) Inverts the X axis report. | _not defined_ | +| `POINTING_DEVICE_INVERT_Y_RIGHT` | (Optional) Inverts the Y axis report. | _not defined_ | + +::: warning +If there is a `_RIGHT` configuration option or callback, the [common configuration](pointing_device#common-configuration) option will work for the left. For correct left/right detection you should setup a [handedness option](split_keyboard#setting-handedness), `EE_HANDS` is usually a good option for an existing board that doesn't do handedness by hardware. +::: + + +## Callbacks and Functions + +| Function | Description | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `pointing_device_init_kb(void)` | Callback to allow for keyboard level initialization. Useful for additional hardware sensors. | +| `pointing_device_init_user(void)` | Callback to allow for user level initialization. Useful for additional hardware sensors. | +| `pointing_device_task_kb(mouse_report)` | Callback that sends sensor data, so keyboard code can intercept and modify the data. Returns a mouse report. | +| `pointing_device_task_user(mouse_report)` | Callback that sends sensor data, so user code can intercept and modify the data. Returns a mouse report. | +| `pointing_device_handle_buttons(buttons, pressed, button)` | Callback to handle hardware button presses. Returns a `uint8_t`. | +| `pointing_device_get_cpi(void)` | Gets the current CPI/DPI setting from the sensor, if supported. | +| `pointing_device_set_cpi(uint16_t)` | Sets the CPI/DPI, if supported. | +| `pointing_device_get_report(void)` | Returns the current mouse report (as a `report_mouse_t` data structure). | +| `pointing_device_set_report(mouse_report)` | Sets the mouse report to the assigned `report_mouse_t` data structured passed to the function. | +| `pointing_device_send(void)` | Sends the current mouse report to the host system. Function can be replaced. | +| `has_mouse_report_changed(new_report, old_report)` | Compares the old and new `report_mouse_t` data and returns true only if it has changed. | +| `pointing_device_adjust_by_defines(mouse_report)` | Applies rotations and invert configurations to a raw mouse report. | + + +## Split Keyboard Callbacks and Functions + +The combined functions below are only available when using `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED`. The 2 callbacks `pointing_device_task_combined_*` replace the single sided equivalents above. See the [combined pointing devices example](pointing_device#combined-pointing-devices) + +| Function | Description | +| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `pointing_device_set_shared_report(mouse_report)` | Sets the shared mouse report to the assigned `report_mouse_t` data structured passed to the function. | +| `pointing_device_set_cpi_on_side(bool, uint16_t)` | Sets the CPI/DPI of one side, if supported. Passing `true` will set the left and `false` the right | +| `pointing_device_combine_reports(left_report, right_report)` | Returns a combined mouse_report of left_report and right_report (as a `report_mouse_t` data structure) | +| `pointing_device_task_combined_kb(left_report, right_report)` | Callback, so keyboard code can intercept and modify the data. Returns a combined mouse report. | +| `pointing_device_task_combined_user(left_report, right_report)` | Callback, so user code can intercept and modify. Returns a combined mouse report using `pointing_device_combine_reports` | +| `pointing_device_adjust_by_defines_right(mouse_report)` | Applies right side rotations and invert configurations to a raw mouse report. | + + +# Manipulating Mouse Reports + +The report_mouse_t (here "mouseReport") has the following properties: + +* `mouseReport.x` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ to the right, - to the left) on the x axis. +* `mouseReport.y` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ upward, - downward) on the y axis. +* `mouseReport.v` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing vertical scrolling (+ upward, - downward). +* `mouseReport.h` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing horizontal scrolling (+ right, - left). +* `mouseReport.buttons` - this is a uint8_t in which all 8 bits are used. These bits represent the mouse button state - bit 0 is mouse button 1, and bit 7 is mouse button 8. + +To manually manipulate the mouse reports outside of the `pointing_device_task_*` functions, you can use: + +* `pointing_device_get_report()` - Returns the current report_mouse_t that represents the information sent to the host computer +* `pointing_device_set_report(report_mouse_t mouse_report)` - Overrides and saves the report_mouse_t to be sent to the host computer +* `pointing_device_send()` - Sends the mouse report to the host and zeroes out the report. + +When the mouse report is sent, the x, y, v, and h values are set to 0 (this is done in `pointing_device_send()`, which can be overridden to avoid this behavior). This way, button states persist, but movement will only occur once. For further customization, both `pointing_device_init` and `pointing_device_task` can be overridden. + +Additionally, by default, `pointing_device_send()` will only send a report when the report has actually changed. This prevents it from continuously sending mouse reports, which will keep the host system awake. This behavior can be changed by creating your own `pointing_device_send()` function. + +Also, you use the `has_mouse_report_changed(new_report, old_report)` function to check to see if the report has changed. + +## Examples + +### Custom Mouse Keycode + +In this example, a custom key is used to click the mouse and scroll 127 units vertically and horizontally, then undo all of that when released - because that's a totally useful function. + +```c +case MS_SPECIAL: + report_mouse_t currentReport = pointing_device_get_report(); + if (record->event.pressed) { + currentReport.v = 127; + currentReport.h = 127; + currentReport.buttons |= MOUSE_BTN1; // this is defined in report.h + } else { + currentReport.v = -127; + currentReport.h = -127; + currentReport.buttons &= ~MOUSE_BTN1; + } + pointing_device_set_report(currentReport); + pointing_device_send(); + break; +``` + +Recall that the mouse report is set to zero (except the buttons) whenever it is sent, so the scrolling would only occur once in each case. + +### Drag Scroll or Mouse Scroll + +A very common implementation is to use the mouse movement to scroll instead of moving the cursor on the system. This uses the `pointing_device_task_user` callback to intercept and modify the mouse report before it's sent to the host system. + +```c +enum custom_keycodes { + DRAG_SCROLL = SAFE_RANGE, +}; + +bool set_scrolling = false; + +report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { + if (set_scrolling) { + mouse_report.h = mouse_report.x; + mouse_report.v = mouse_report.y; + mouse_report.x = 0; + mouse_report.y = 0; + } + return mouse_report; +} + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + if (keycode == DRAG_SCROLL && record->event.pressed) { + set_scrolling = !set_scrolling; + } + return true; +} +``` + +This allows you to toggle between scrolling and cursor movement by pressing the DRAG_SCROLL key. + +### Advanced Drag Scroll + +Sometimes, like with the Cirque trackpad, you will run into issues where the scrolling may be too fast. + +Here is a slightly more advanced example of drag scrolling. You will be able to change the scroll speed based on the values in set in `SCROLL_DIVISOR_H` and `SCROLL_DIVISOR_V`. This bit of code is also set up so that instead of toggling the scrolling state with set_scrolling = !set_scrolling, the set_scrolling variable is set directly to record->event.pressed. This way, the drag scrolling will only be active while the DRAG_SCROLL button is held down. + +```c +enum custom_keycodes { + DRAG_SCROLL = SAFE_RANGE, +}; + +bool set_scrolling = false; + +// Modify these values to adjust the scrolling speed +#define SCROLL_DIVISOR_H 8.0 +#define SCROLL_DIVISOR_V 8.0 + +// Variables to store accumulated scroll values +float scroll_accumulated_h = 0; +float scroll_accumulated_v = 0; + +// Function to handle mouse reports and perform drag scrolling +report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { + // Check if drag scrolling is active + if (set_scrolling) { + // Calculate and accumulate scroll values based on mouse movement and divisors + scroll_accumulated_h += (float)mouse_report.x / SCROLL_DIVISOR_H; + scroll_accumulated_v += (float)mouse_report.y / SCROLL_DIVISOR_V; + + // Assign integer parts of accumulated scroll values to the mouse report + mouse_report.h = (int8_t)scroll_accumulated_h; + mouse_report.v = (int8_t)scroll_accumulated_v; + + // Update accumulated scroll values by subtracting the integer parts + scroll_accumulated_h -= (int8_t)scroll_accumulated_h; + scroll_accumulated_v -= (int8_t)scroll_accumulated_v; + + // Clear the X and Y values of the mouse report + mouse_report.x = 0; + mouse_report.y = 0; + } + return mouse_report; +} + +// Function to handle key events and enable/disable drag scrolling +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case DRAG_SCROLL: + // Toggle set_scrolling when DRAG_SCROLL key is pressed or released + set_scrolling = record->event.pressed; + break; + default: + break; + } + return true; +} + +// Function to handle layer changes and disable drag scrolling when not in AUTO_MOUSE_DEFAULT_LAYER +layer_state_t layer_state_set_user(layer_state_t state) { + // Disable set_scrolling if the current layer is not the AUTO_MOUSE_DEFAULT_LAYER + if (get_highest_layer(state) != AUTO_MOUSE_DEFAULT_LAYER) { + set_scrolling = false; + } + return state; +} + +``` + + +## Split Examples + +The following examples make use the `SPLIT_POINTING_ENABLE` functionality and show how to manipulate the mouse report for a scrolling mode. + +### Single Pointing Device + +The following example will work with either `POINTING_DEVICE_LEFT` or `POINTING_DEVICE_RIGHT` and enables scrolling mode while on a particular layer. + +```c + +static bool scrolling_mode = false; + +layer_state_t layer_state_set_user(layer_state_t state) { + switch (get_highest_layer(state)) { + case _RAISE: // If we're on the _RAISE layer enable scrolling mode + scrolling_mode = true; + pointing_device_set_cpi(2000); + break; + default: + if (scrolling_mode) { // check if we were scrolling before and set disable if so + scrolling_mode = false; + pointing_device_set_cpi(8000); + } + break; + } + return state; +} + +report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { + if (scrolling_mode) { + mouse_report.h = mouse_report.x; + mouse_report.v = mouse_report.y; + mouse_report.x = 0; + mouse_report.y = 0; + } + return mouse_report; +} +``` + +### Combined Pointing Devices + +The following example requires `POINTING_DEVICE_COMBINED` and sets the left side pointing device to scroll only. + +```c +void keyboard_post_init_user(void) { + pointing_device_set_cpi_on_side(true, 1000); //Set cpi on left side to a low value for slower scrolling. + pointing_device_set_cpi_on_side(false, 8000); //Set cpi on right side to a reasonable value for mousing. +} + +report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report) { + left_report.h = left_report.x; + left_report.v = left_report.y; + left_report.x = 0; + left_report.y = 0; + return pointing_device_combine_reports(left_report, right_report); +} +``` + +# Troubleshooting + +If you are having issues with pointing device drivers debug messages can be enabled that will give you insights in the inner workings. To enable these add to your keyboards `config.h` file: + +```c +#define POINTING_DEVICE_DEBUG +``` + +::: tip +The messages will be printed out to the `CONSOLE` output. For additional information, refer to [Debugging/Troubleshooting QMK](../faq_debug). +::: + + +--- +# Automatic Mouse Layer {#pointing-device-auto-mouse} + +When using a pointing device combined with a keyboard the mouse buttons are often kept on a separate layer from the default keyboard layer, which requires pressing or holding a key to change layers before using the mouse. To make this easier and more efficient an additional pointing device feature may be enabled that will automatically activate a target layer as soon as the pointing device is active _(in motion, mouse button pressed etc.)_ and deactivate the target layer after a set time. + +Additionally if any key that is defined as a mouse key is pressed then the layer will be held as long as the key is pressed and the timer will be reset on key release. When a non-mouse key is pressed then the layer is deactivated early _(with some exceptions see below)_. Mod, mod tap, and one shot mod keys are ignored _(i.e. don't hold or activate layer but do not deactivate the layer either)_ when sending a modifier keycode _(e.g. hold for mod tap)_ allowing for mod keys to be used with the mouse without activating the target layer when typing. + +All of the standard layer keys (tap toggling, toggle, toggle on, one_shot, layer tap, layer mod) that activate the current target layer are uniquely handled to ensure they behave as expected _(see layer key table below)_. The target layer that can be changed at any point during by calling the `set_auto_mouse_layer(<new_target_layer>);` function. + +### Behaviour of Layer keys that activate the target layer +| Layer key as in `keymap.c` | Auto Mouse specific behaviour | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `MO(<target_layer>)` | Treated as a mouse key holding the layer while pressed | +| `LT(<target_layer>)` | When tapped will be treated as non mouse key and mouse key when held | +| `LM(<target_layer>)` | Treated as a mouse key | +| `TG(<target_layer>)` | Will set flag preventing target layer deactivation or removal until pressed again | +| `TO(<target_layer>)` | Same as `TG(<target_layer>)` | +| `TT(<target_layer>)` | Treated as a mouse key when `tap.count < TAPPING_TOGGLE` and as `TG` when `tap.count == TAPPING_TOGGLE` | +| `DF(<target_layer>)` | Skips auto mouse key processing similar to mod keys | +| `OSL(<target_layer>)` | Skips, but if current one shot layer is the target layer then it will prevent target layer deactivation or removal | + + +## How to enable: + +```c +// in config.h: +#define POINTING_DEVICE_AUTO_MOUSE_ENABLE +// only required if not setting mouse layer elsewhere +#define AUTO_MOUSE_DEFAULT_LAYER <index of your mouse layer> + +// in keymap.c: +void pointing_device_init_user(void) { + set_auto_mouse_layer(<mouse_layer>); // only required if AUTO_MOUSE_DEFAULT_LAYER is not set to index of <mouse_layer> + set_auto_mouse_enable(true); // always required before the auto mouse feature will work +} +``` + +Because the auto mouse feature can be disabled/enabled during runtime and starts as disabled by default it must be enabled by calling `set_auto_mouse_enable(true);` somewhere in firmware before the feature will work. +_Note: for setting the target layer during initialization either setting `AUTO_MOUSE_DEFAULT_LAYER` in `config.h` or calling `set_auto_mouse_layer(<mouse_layer>)` can be used._ + + +## How to Customize: + +There are a few ways to control the auto mouse feature with both `config.h` options and functions for controlling it during runtime. + +### `config.h` Options: +| Define | Description | Range | Units | Default | +| ----------------------------------- | --------------------------------------------------------------------- | :------------------: | :---------: | -------------------------: | +| `POINTING_DEVICE_AUTO_MOUSE_ENABLE` | (Required) Enables auto mouse layer feature | | _None_ | _Not defined_ | +| `AUTO_MOUSE_DEFAULT_LAYER` | (Optional) Index of layer to use as default target layer | 0 - `LAYER_MAX` | _`uint8_t`_ | `1` | +| `AUTO_MOUSE_TIME` | (Optional) Time layer remains active after activation | _ideally_ (250-1000) | _ms_ | `650 ms` | +| `AUTO_MOUSE_DELAY` | (Optional) Lockout time after non-mouse key is pressed | _ideally_ (100-1000) | _ms_ | `TAPPING_TERM` or `200 ms` | +| `AUTO_MOUSE_DEBOUNCE` | (Optional) Time delay from last activation to next update | _ideally_ (10 - 100) | _ms_ | `25 ms` | +| `AUTO_MOUSE_THRESHOLD` | (Optional) Amount of mouse movement required to switch layers | 0 - | _units_ | `10 units` | + +### Adding mouse keys + +While all default mouse keys and layer keys(for current mouse layer) are treated as mouse keys, additional Keyrecords can be added to mouse keys by adding them to the is_mouse_record_* stack. + +#### Callbacks for setting up additional key codes as mouse keys: +| Callback | Description | +| -------------------------------------------------------------------- | -------------------------------------------------- | +| `bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record)` | keyboard level callback for adding mouse keys | +| `bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record)` | user/keymap level callback for adding mouse keys | + +##### To use the callback function to add mouse keys: + +The following code will cause the enter key and all of the arrow keys to be treated as mouse keys (hold target layer while they are pressed and reset active layer timer). +```c + +// in <keyboard>.c: +bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) { + switch(keycode) { + case KC_ENT: + return true; + case KC_RIGHT ... KC_UP: + return true; + default: + return false; + } + return is_mouse_record_user(keycode, record); +} +``` + + +## Advanced control + +There are several functions that allow for more advanced interaction with the auto mouse feature allowing for greater control. + +### Functions to control auto mouse enable and target layer: +| Function | Description | Aliases | Return type | +| :--------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------- | --------------: | +| `set_auto_mouse_enable(bool enable)` | Enable or disable auto mouse (true:enable, false:disable) | | `void`(None) | +| `get_auto_mouse_enable(void)` | Return auto mouse enable state (true:enabled, false:disabled) | `AUTO_MOUSE_ENABLED` | `bool` | +| `set_auto_mouse_layer(uint8_t LAYER)` | Change/set the target layer for auto mouse | | `void`(None) | +| `get_auto_mouse_layer(void)` | Return auto mouse target layer index | `AUTO_MOUSE_TARGET_LAYER` | `uint8_t` | +| `remove_auto_mouse_layer(layer_state_t state, bool force)` | Return `state` with target layer removed if appropriate (ignore criteria if `force`) | | `layer_state_t` | +| `auto_mouse_layer_off(void)` | Disable target layer if appropriate will call (makes call to `layer_state_set`) | | `void`(None) | +| `auto_mouse_toggle(void)` | Toggle on/off target toggle state (disables layer deactivation when true) | | `void`(None) | +| `get_auto_mouse_toggle(void)` | Return value of toggling state variable | | `bool` | +| `set_auto_mouse_timeout(uint16_t timeout)` | Change/set the timeout for turing off the layer | | `void`(None) | +| `get_auto_mouse_timeout(void)` | Return the current timeout for turing off the layer | | `uint16_t` | +| `set_auto_mouse_debounce(uint16_t timeout)` | Change/set the debounce for preventing layer activation | | `void`(None) | +| `get_auto_mouse_debounce(void)` | Return the current debounce for preventing layer activation | | `uint8_t` | +| `is_auto_mouse_active(void)` | Returns the active state of the auto mouse layer (eg if the layer has been triggered)| | `bool` | +| `get_auto_mouse_key_tracker(void)` | Gets the current count for the auto mouse key tracker. | | `int8_t` | +| `set_auto_mouse_key_tracker(int8_t key_tracker)` | Sets/Overrides the current count for the auto mouse key tracker. | | `void`(None) | + +_NOTES:_ + - _Due to the nature of how some functions work, the `auto_mouse_trigger_reset`, and `auto_mouse_layer_off` functions should never be called in the `layer_state_set_*` stack as this can cause indefinite loops._ + - _It is recommended that `remove_auto_mouse_layer` is used in the `layer_state_set_*` stack of functions and `auto_mouse_layer_off` is used everywhere else_ + - _`remove_auto_mouse_layer(state, false)` or `auto_mouse_layer_off()` should be called before any instance of `set_auto_mouse_enabled(false)` or `set_auto_mouse_layer(layer)` to ensure that the target layer will be removed appropriately before disabling auto mouse or changing target to avoid a stuck layer_ + +### Functions for handling custom key events: +| Function | Description | Return type | +| :--------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------: | +| `auto_mouse_keyevent(bool pressed)` | Auto mouse mouse key event (true: key down, false: key up) | `void`(None) | +| `auto_mouse_trigger_reset(bool pressed)` | Reset auto mouse status on key down and start delay timer (non-mouse key event) | `void`(None) | +| `auto_mouse_toggle(void)` | Toggle on/off target toggle state (disables layer deactivation when true) | `void`(None) | +| `get_auto_mouse_toggle(void)` | Return value of toggling state variable | `bool` | +_NOTE: Generally it would be preferable to use the `is_mouse_record_*` functions to add any additional keys that should act as mouse keys rather than adding `auto_mouse_keyevent(record.event->pressed)` to `process_records_*`_ + +### Advanced control examples + +#### Disable auto mouse on certain layers: + +The auto mouse feature can be disabled any time and this can be helpful if you want to disable the auto mouse feature under certain circumstances such as when particular layers are active. One issue however is the handling of the target layer, it needs to be removed appropriately **before** disabling auto mouse _(see notes under control functions above)_. The following function would disable the auto_mouse feature whenever the layers `_LAYER5` through `_LAYER7` are active as the top most layer _(ignoring target layer)_. + +```c +// in keymap.c: +layer_state_t layer_state_set_user(layer_state_t state) { + // checks highest layer other than target layer + switch(get_highest_layer(remove_auto_mouse_layer(state, true))) { + case _LAYER5 ... _LAYER7: + // remove_auto_mouse_target must be called to adjust state *before* setting enable + state = remove_auto_mouse_layer(state, false); + set_auto_mouse_enable(false); + break; + default: + set_auto_mouse_enable(true); + break; + } + // recommend that any code that makes adjustment based on auto mouse layer state would go here + return state; +} +``` + +#### Set different target layer when a particular layer is active: + +The below code will change the auto mouse layer target to `_MOUSE_LAYER_2` when `_DEFAULT_LAYER_2` is highest default layer state. + +*NOTE: that `auto_mouse_layer_off` is used here instead of `remove_auto_mouse_layer` as `default_layer_state_set_*` stack is separate from the `layer_state_set_*` stack*, if something similar was to be done in `layer_state_set_user`, `state = remove_auto_mouse_layer(state, false)` should be used instead. + +*ADDITIONAL NOTE: `AUTO_MOUSE_TARGET_LAYER` is checked if already set to avoid deactivating the target layer unless needed*. + +```c +// in keymap.c +layer_state_t default_layer_state_set_user(layer_state_t state) { + // switch on change in default layer need to check if target layer already set to avoid turning off layer needlessly + switch(get_highest_layer(state)) { + case _DEFAULT_LAYER_2: + if ((AUTO_MOUSE_TARGET_LAYER) == _MOUSE_LAYER_2) break; + auto_mouse_layer_off(); + set_auto_mouse_layer(_MOUSE_LAYER_2); + break; + + default: + if((AUTO_MOUSE_TARGET_LAYER) == _MOUSE_LAYER_1) break; + auto_mouse_layer_off(); + set_auto_mouse_layer(_MOUSE_LAYER_1); + } + return state; +} +``` + +### Use custom keys to control auto mouse: +Custom key records could also be created that control the auto mouse feature. +The code example below would create a custom key that would toggle the auto mouse feature on and off when pressed while also setting a bool that could be used to disable other code that may turn it on such as the layer code above. + +```c +// in config.h: +enum user_custom_keycodes { + AM_Toggle = SAFE_RANGE +}; + +// in keymap.c: +// set up global bool to adjust other user code +bool auto_mouse_tg_off = !AUTO_MOUSE_ENABLED; + +bool process_record_user(uint16_t keycode, keyrecord_t* record) { + switch (keycode) { + // toggle auto mouse enable key + case AM_Toggle: + if(record->event.pressed) { // key down + auto_mouse_layer_off(); // disable target layer if needed + set_auto_mouse_enabled((AUTO_MOUSE_ENABLED) ^ 1); + auto_mouse_tg_off = !get_auto_mouse_enabled(); + } // do nothing on key up + return false; // prevent further processing of keycode + } +} +``` + + +## Customize Target Layer Activation + +Layer activation can be customized by overwriting the `auto_mouse_activation` function. This function is checked every time `pointing_device_task` is called when inactive and every `AUTO_MOUSE_DEBOUNCE` ms when active, and will evaluate pointing device level conditions that trigger target layer activation. When it returns true, the target layer will be activated barring the usual exceptions _(e.g. delay time has not expired)_. + +By default it will return true if any of the `mouse_report` axes `x`,`y`,`h`,`v` are non zero, or if there is any mouse buttons active in `mouse_report`. +_Note: The Cirque pinnacle track pad already implements a custom activation function that will activate on touchdown as well as movement all of the default conditions, currently this only works for the master side of split keyboards._ + +| Function | Description | Return type | +| :--------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------: | +| `auto_mouse_activation(report_mouse_t mouse_report)` | Overwritable function that controls target layer activation (when true) | `bool` | + +## Auto Mouse for Custom Pointing Device Task + +When using a custom pointing device (overwriting `pointing_device_task`) the following code should be somewhere in the `pointing_device_task_*` stack: + +```c +bool pointing_device_task(void) { + //...Custom pointing device task code + + // handle automatic mouse layer (needs report_mouse_t as input) + pointing_device_task_auto_mouse(local_mouse_report); + + //...More custom pointing device task code + + return pointing_device_send(); +} +``` + +In general the following two functions must be implemented in appropriate locations for auto mouse to function: + +| Function | Description | Suggested location | +| -------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------: | +| `pointing_device_task_auto_mouse(report_mouse_t mouse_report)` | handles target layer activation and is_active status updates | `pointing_device_task` stack | +| `process_auto_mouse(uint16_t keycode, keyrecord_t* record)` | Keycode processing for auto mouse | `process_record` stack | diff --git a/docs/features/programmable_button.md b/docs/features/programmable_button.md new file mode 100644 index 0000000000..1fdf44c29e --- /dev/null +++ b/docs/features/programmable_button.md @@ -0,0 +1,146 @@ +# Programmable Button {#programmable-button} + +Programmable Buttons are keys that have no predefined meaning. This means they can be processed on the host side by custom software without the operating system trying to interpret them. + +The keycodes are emitted according to the HID Telephony Device page (`0x0B`), Programmable Button usage (`0x09`). On Linux (> 5.14) they are handled automatically and translated to `KEY_MACRO#` keycodes (up to `KEY_MACRO30`). + +::: tip +Currently there is no known support in Windows or macOS. It may be possible to write a custom HID driver to receive these usages, but this is out of the scope of the QMK documentation. +::: + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +PROGRAMMABLE_BUTTON_ENABLE = yes +``` + +## Keycodes {#keycodes} + +|Key |Aliases|Description | +|---------------------------|-------|----------------------| +|`QK_PROGRAMMABLE_BUTTON_1` |`PB_1` |Programmable button 1 | +|`QK_PROGRAMMABLE_BUTTON_2` |`PB_2` |Programmable button 2 | +|`QK_PROGRAMMABLE_BUTTON_3` |`PB_3` |Programmable button 3 | +|`QK_PROGRAMMABLE_BUTTON_4` |`PB_4` |Programmable button 4 | +|`QK_PROGRAMMABLE_BUTTON_5` |`PB_5` |Programmable button 5 | +|`QK_PROGRAMMABLE_BUTTON_6` |`PB_6` |Programmable button 6 | +|`QK_PROGRAMMABLE_BUTTON_7` |`PB_7` |Programmable button 7 | +|`QK_PROGRAMMABLE_BUTTON_8` |`PB_8` |Programmable button 8 | +|`QK_PROGRAMMABLE_BUTTON_9` |`PB_9` |Programmable button 9 | +|`QK_PROGRAMMABLE_BUTTON_10`|`PB_10`|Programmable button 10| +|`QK_PROGRAMMABLE_BUTTON_11`|`PB_11`|Programmable button 11| +|`QK_PROGRAMMABLE_BUTTON_12`|`PB_12`|Programmable button 12| +|`QK_PROGRAMMABLE_BUTTON_13`|`PB_13`|Programmable button 13| +|`QK_PROGRAMMABLE_BUTTON_14`|`PB_14`|Programmable button 14| +|`QK_PROGRAMMABLE_BUTTON_15`|`PB_15`|Programmable button 15| +|`QK_PROGRAMMABLE_BUTTON_16`|`PB_16`|Programmable button 16| +|`QK_PROGRAMMABLE_BUTTON_17`|`PB_17`|Programmable button 17| +|`QK_PROGRAMMABLE_BUTTON_18`|`PB_18`|Programmable button 18| +|`QK_PROGRAMMABLE_BUTTON_19`|`PB_19`|Programmable button 19| +|`QK_PROGRAMMABLE_BUTTON_20`|`PB_20`|Programmable button 20| +|`QK_PROGRAMMABLE_BUTTON_21`|`PB_21`|Programmable button 21| +|`QK_PROGRAMMABLE_BUTTON_22`|`PB_22`|Programmable button 22| +|`QK_PROGRAMMABLE_BUTTON_23`|`PB_23`|Programmable button 23| +|`QK_PROGRAMMABLE_BUTTON_24`|`PB_24`|Programmable button 24| +|`QK_PROGRAMMABLE_BUTTON_25`|`PB_25`|Programmable button 25| +|`QK_PROGRAMMABLE_BUTTON_26`|`PB_26`|Programmable button 26| +|`QK_PROGRAMMABLE_BUTTON_27`|`PB_27`|Programmable button 27| +|`QK_PROGRAMMABLE_BUTTON_28`|`PB_28`|Programmable button 28| +|`QK_PROGRAMMABLE_BUTTON_29`|`PB_29`|Programmable button 29| +|`QK_PROGRAMMABLE_BUTTON_30`|`PB_30`|Programmable button 30| +|`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31| +|`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32| + +## API {#api} + +### `void programmable_button_clear(void)` {#api-programmable-button-clear} + +Clear the programmable button report. + +--- + +### `void programmable_button_add(uint8_t index)` {#api-programmable-button-add} + +Set the state of a button. + +#### Arguments {#api-programmable-button-add-arguments} + + - `uint8_t index` + The index of the button to press, from 0 to 31. + +--- + +### `void programmable_button_remove(uint8_t index)` {#api-programmable-button-remove} + +Reset the state of a button. + +#### Arguments {#api-programmable-button-remove-arguments} + + - `uint8_t index` + The index of the button to release, from 0 to 31. + +--- + +### `void programmable_button_register(uint8_t index)` {#api-programmable-button-register} + +Set the state of a button, and flush the report. + +#### Arguments {#api-programmable-button-register-arguments} + + - `uint8_t index` + The index of the button to press, from 0 to 31. + +--- + +### `void programmable_button_unregister(uint8_t index)` {#api-programmable-button-unregister} + +Reset the state of a button, and flush the report. + +#### Arguments {#api-programmable-button-unregister-arguments} + + - `uint8_t index` + The index of the button to release, from 0 to 31. + +--- + +### `bool programmable_button_is_on(uint8_t index)` {#api-programmable-button-is-on} + +Get the state of a button. + +#### Arguments {#api-programmable-button-is-on-arguments} + + - `uint8_t index` + The index of the button to check, from 0 to 31. + +#### Return Value {#api-programmable-button-is-on-return} + +`true` if the button is pressed. + +--- + +### `void programmable_button_flush(void)` {#api-programmable-button-flush} + +Send the programmable button report to the host. + +--- + +### `uint32_t programmable_button_get_report(void)` {#api-programmable-button-get-report} + +Get the programmable button report. + +#### Return Value {#api-programmable-button-get-report-return} + +The bitmask of programmable button states. + +--- + +### `void programmable_button_set_report(uint32_t report)` {#api-programmable-button-set-report} + +Set the programmable button report. + +#### Arguments {#api-programmable-button-set-report-arguments} + + - `uint32_t report` + A bitmask of programmable button states. diff --git a/docs/features/ps2_mouse.md b/docs/features/ps2_mouse.md new file mode 100644 index 0000000000..90f4cca827 --- /dev/null +++ b/docs/features/ps2_mouse.md @@ -0,0 +1,349 @@ +# PS/2 Mouse Support {#ps2-mouse-support} + +Its possible to hook up a PS/2 mouse (for example touchpads or trackpoints) to your keyboard as a composite device. + +To hook up a Trackpoint, you need to obtain a Trackpoint module (i.e. harvest from a Thinkpad keyboard), identify the function of each pin of the module, and make the necessary circuitry between controller and Trackpoint module. For more information, please refer to [Trackpoint Hardware](https://deskthority.net/wiki/TrackPoint_Hardware) page on Deskthority Wiki. + +There are three available modes for hooking up PS/2 devices: USART (best), interrupts (better) or busywait (not recommended). + +## The Circuitry between Trackpoint and Controller {#the-circuitry-between-trackpoint-and-controller} + +To get the things working, a 4.7K drag is needed between the two lines DATA and CLK and the line 5+. + +``` + + DATA ----------+--------- PIN + | + 4.7K + | +MODULE 5+ --------+--+--------- PWR CONTROLLER + | + 4.7K + | + CLK ------+------------ PIN +``` + + +## Busywait Version {#busywait-version} + +Note: This is not recommended, you may encounter jerky movement or unsent inputs. Please use interrupt or USART version if possible. + +In rules.mk: + +```make +PS2_MOUSE_ENABLE = yes +PS2_ENABLE = yes +PS2_DRIVER = busywait +``` + +In your keyboard config.h: + +```c +#ifdef PS2_DRIVER_BUSYWAIT +# define PS2_CLOCK_PIN D1 +# define PS2_DATA_PIN D2 +#endif +``` + +### Interrupt Version (AVR/ATMega32u4) {#interrupt-version-avr} + +The following example uses D2 for clock and D5 for data. You can use any INT or PCINT pin for clock, and any pin for data. + +In rules.mk: + +```make +PS2_MOUSE_ENABLE = yes +PS2_ENABLE = yes +PS2_DRIVER = interrupt +``` + +In your keyboard config.h: + +```c +#ifdef PS2_DRIVER_INTERRUPT +#define PS2_CLOCK_PIN D2 +#define PS2_DATA_PIN D5 + +#define PS2_INT_INIT() do { \ + EICRA |= ((1<<ISC21) | \ + (0<<ISC20)); \ +} while (0) +#define PS2_INT_ON() do { \ + EIMSK |= (1<<INT2); \ +} while (0) +#define PS2_INT_OFF() do { \ + EIMSK &= ~(1<<INT2); \ +} while (0) +#define PS2_INT_VECT INT2_vect +#endif +``` + +### Interrupt Version (ARM chibios) {#interrupt-version-chibios} + +Pretty much any two pins can be used for the (software) interrupt variant on ARM cores. The example below uses A8 for clock, and A9 for data. + +In rules.mk: + +``` +PS2_MOUSE_ENABLE = yes +PS2_ENABLE = yes +PS2_DRIVER = interrupt +``` + +In your keyboard config.h: + +```c +#define PS2_CLOCK_PIN A8 +#define PS2_DATA_PIN A9 +``` + +And in the chibios specifig halconf.h: +```c +#define PAL_USE_CALLBACKS TRUE +``` + + +### USART Version {#usart-version} + +To use USART on the ATMega32u4, you have to use PD5 for clock and PD2 for data. If one of those are unavailable, you need to use interrupt version. + +In rules.mk: + +```make +PS2_MOUSE_ENABLE = yes +PS2_ENABLE = yes +PS2_DRIVER = usart +``` + +In your keyboard config.h: + +```c +#ifdef PS2_DRIVER_USART +#define PS2_CLOCK_PIN D5 +#define PS2_DATA_PIN D2 + +/* synchronous, odd parity, 1-bit stop, 8-bit data, sample at falling edge */ +/* set DDR of CLOCK as input to be slave */ +#define PS2_USART_INIT() do { \ + PS2_CLOCK_DDR &= ~(1<<PS2_CLOCK_BIT); \ + PS2_DATA_DDR &= ~(1<<PS2_DATA_BIT); \ + UCSR1C = ((1 << UMSEL10) | \ + (3 << UPM10) | \ + (0 << USBS1) | \ + (3 << UCSZ10) | \ + (0 << UCPOL1)); \ + UCSR1A = 0; \ + UBRR1H = 0; \ + UBRR1L = 0; \ +} while (0) +#define PS2_USART_RX_INT_ON() do { \ + UCSR1B = ((1 << RXCIE1) | \ + (1 << RXEN1)); \ +} while (0) +#define PS2_USART_RX_POLL_ON() do { \ + UCSR1B = (1 << RXEN1); \ +} while (0) +#define PS2_USART_OFF() do { \ + UCSR1C = 0; \ + UCSR1B &= ~((1 << RXEN1) | \ + (1 << TXEN1)); \ +} while (0) +#define PS2_USART_RX_READY (UCSR1A & (1<<RXC1)) +#define PS2_USART_RX_DATA UDR1 +#define PS2_USART_ERROR (UCSR1A & ((1<<FE1) | (1<<DOR1) | (1<<UPE1))) +#define PS2_USART_RX_VECT USART1_RX_vect +#endif +``` + +### RP2040 PIO Version {#rp2040-pio-version} + +The `PIO` subsystem is a Raspberry Pi RP2040 specific implementation, using the integrated PIO peripheral and is therefore only available on this MCU. + +There are strict requirements for pin ordering but any pair of GPIO pins can be used. The GPIO used for clock must be directly after data, see the included info.json snippet for an example of correct order. + +You may optionally switch the PIO peripheral used with the following define in config.h: +```c +#define PS2_PIO_USE_PIO1 // Force the usage of PIO1 peripheral, by default the PS2 implementation uses the PIO0 peripheral +``` + +Example info.json content: + +```json + "ps2": { + "clock_pin": "GP1", + "data_pin": "GP0", + "driver": "vendor", + "enabled": true, + "mouse_enabled": true + } +``` + +## Additional Settings {#additional-settings} + +### PS/2 Mouse Features {#ps2-mouse-features} + +These enable settings supported by the PS/2 mouse protocol. + +```c +/* Use remote mode instead of the default stream mode (see link) */ +#define PS2_MOUSE_USE_REMOTE_MODE + +/* Enable the scrollwheel or scroll gesture on your mouse or touchpad */ +#define PS2_MOUSE_ENABLE_SCROLLING + +/* Some mice will need a scroll mask to be configured. The default is 0xFF. */ +#define PS2_MOUSE_SCROLL_MASK 0x0F + +/* Applies a transformation to the movement before sending to the host (see link) */ +#define PS2_MOUSE_USE_2_1_SCALING + +/* The time to wait after initializing the ps2 host */ +#define PS2_MOUSE_INIT_DELAY 1000 /* Default */ +``` + +You can also call the following functions from ps2_mouse.h + +```c +void ps2_mouse_disable_data_reporting(void); + +void ps2_mouse_enable_data_reporting(void); + +void ps2_mouse_set_remote_mode(void); + +void ps2_mouse_set_stream_mode(void); + +void ps2_mouse_set_scaling_2_1(void); + +void ps2_mouse_set_scaling_1_1(void); + +void ps2_mouse_set_resolution(ps2_mouse_resolution_t resolution); + +void ps2_mouse_set_sample_rate(ps2_mouse_sample_rate_t sample_rate); +``` + +### Fine Control {#fine-control} + +Use the following defines to change the sensitivity and speed of the mouse. +Note: you can also use `ps2_mouse_set_resolution` for the same effect (not supported on most touchpads). + +```c +#define PS2_MOUSE_X_MULTIPLIER 3 +#define PS2_MOUSE_Y_MULTIPLIER 3 +#define PS2_MOUSE_V_MULTIPLIER 1 +``` + +### Scroll Button {#scroll-button} + +If you're using a trackpoint, you will likely want to be able to use it for scrolling. +It's possible to enable a "scroll button/s" that when pressed will cause the mouse to scroll instead of moving. +To enable the feature, you must set a scroll button mask as follows: + +```c +#define PS2_MOUSE_SCROLL_BTN_MASK (1<<PS2_MOUSE_BTN_MIDDLE) /* Default */ +``` + +To disable the scroll button feature: + +```c +#define PS2_MOUSE_SCROLL_BTN_MASK 0 +``` + +The available buttons are: + +```c +#define PS2_MOUSE_BTN_LEFT 0 +#define PS2_MOUSE_BTN_RIGHT 1 +#define PS2_MOUSE_BTN_MIDDLE 2 +``` + +You can also combine buttons in the mask by `|`ing them together. + +Once you've configured your scroll button mask, you must configure the scroll button send interval. +This is the interval before which if the scroll buttons were released they would be sent to the host. +After this interval, they will cause the mouse to scroll and will not be sent. + +```c +#define PS2_MOUSE_SCROLL_BTN_SEND 300 /* Default */ +``` + +To disable sending the scroll buttons: + +```c +#define PS2_MOUSE_SCROLL_BTN_SEND 0 +``` + +Fine control over the scrolling is supported with the following defines: + +```c +#define PS2_MOUSE_SCROLL_DIVISOR_H 2 +#define PS2_MOUSE_SCROLL_DIVISOR_V 2 +``` + +### Invert Mouse buttons {#invert-buttons} + +To invert the left & right buttons you can put: + +```c +#define PS2_MOUSE_INVERT_BUTTONS +``` + +into config.h. + +### Invert Mouse and Scroll Axes {#invert-mouse-and-scroll-axes} + +To invert the X and Y axes you can put: + +```c +#define PS2_MOUSE_INVERT_X +#define PS2_MOUSE_INVERT_Y +``` + +into config.h. + +To reverse the scroll axes you can put: + +```c +#define PS2_MOUSE_INVERT_H +#define PS2_MOUSE_INVERT_V +``` + +into config.h. + +### Rotate Mouse Axes {#rotate-mouse-axes} + +Transform the output of the device with a clockwise rotation of 90, 180, or 270 +degrees. + +When compensating for device orientation, rotate the output the same amount in +the opposite direction. E.g. if the normal device orientation is considered to +be North-facing, compensate as follows: + +```c +#define PS2_MOUSE_ROTATE 270 /* Compensate for East-facing device orientation. */ +``` +```c +#define PS2_MOUSE_ROTATE 180 /* Compensate for South-facing device orientation. */ +``` +```c +#define PS2_MOUSE_ROTATE 90 /* Compensate for West-facing device orientation. */ +``` + +### Debug Settings {#debug-settings} + +To debug the mouse, add `debug_mouse = true` or enable via bootmagic. + +```c +/* To debug the mouse reports */ +#define PS2_MOUSE_DEBUG_HID +#define PS2_MOUSE_DEBUG_RAW +``` + +### Movement Hook {#movement-hook} + +Process mouse movement in the keymap before it is sent to the host. Example +uses include filtering noise, adding acceleration, and automatically activating +a layer. To use, define the following function in your keymap: + +```c +void ps2_mouse_moved_user(report_mouse_t *mouse_report); +``` diff --git a/docs/features/rawhid.md b/docs/features/rawhid.md new file mode 100644 index 0000000000..c3ecfbe099 --- /dev/null +++ b/docs/features/rawhid.md @@ -0,0 +1,158 @@ +# Raw HID {#raw-hid} + +The Raw HID feature allows for bidirectional communication between QMK and the host computer over an HID interface. This has many potential use cases, such as switching keymaps on the fly or sending useful metrics like CPU/RAM usage. + +In order to communicate with the keyboard using this feature, you will need to write a program that runs on the host. As such, some basic programming skills are required - more if you intend to implement complex behaviour. + +## Usage {#usage} + +Add the following to your `rules.mk`: + +```make +RAW_ENABLE = yes +``` + +## Basic Configuration {#basic-configuration} + +By default, the HID Usage Page and Usage ID for the Raw HID interface are `0xFF60` and `0x61`. However, they can be changed if necessary by adding the following to your `config.h`: + +|Define |Default |Description | +|----------------|--------|---------------------------------------| +|`RAW_USAGE_PAGE`|`0xFF60`|The usage page of the Raw HID interface| +|`RAW_USAGE_ID` |`0x61` |The usage ID of the Raw HID interface | + +## Sending Data to the Keyboard {#sending-data-to-the-keyboard} + +To send data to the keyboard, you must first find a library for communicating with HID devices in the programming language of your choice. Here are some examples: + +* **Node.js:** [node-hid](https://github.com/node-hid/node-hid) +* **C/C++:** [hidapi](https://github.com/libusb/hidapi) +* **Java:** [purejavahidapi](https://github.com/nyholku/purejavahidapi) and [hid4java](https://github.com/gary-rowe/hid4java) +* **Python:** [pyhidapi](https://pypi.org/project/hid/) and [pywinusb](https://pypi.org/project/pywinusb) + +Please refer to these libraries' own documentation for instructions on usage. Remember to close the device once you are finished with it! + +Next, you will need to know the USB Vendor and Product IDs of the device. These can easily be found by looking at your keyboard's `info.json`, under the `usb` object (alternatively, you can also use Device Manager on Windows, System Information on macOS, or `lsusb` on Linux). For example, the Vendor ID for the Planck Rev 6 is `0x03A8`, and the Product ID is `0xA4F9`. + +It's also a good idea to narrow down the list of potential HID devices the library may give you by filtering on the usage page and usage ID, to avoid accidentally opening the interface on the same device for the keyboard, or mouse, or media keys, etc. + +Once you are able to open the HID device and send reports to it, it's time to handle them on the keyboard side. Implement the following function in your `keymap.c` and start coding: + +```c +void raw_hid_receive(uint8_t *data, uint8_t length) { + // Your code goes here + // `data` is a pointer to the buffer containing the received HID report + // `length` is the length of the report - always `RAW_EPSIZE` +} +``` + +::: warning +Because the HID specification does not support variable length reports, all reports in both directions must be exactly `RAW_EPSIZE` (currently 32) bytes long, regardless of actual payload length. However, variable length payloads can potentially be implemented on top of this by creating your own data structure that may span multiple reports. +::: + +## Receiving Data from the Keyboard {#receiving-data-from-the-keyboard} + +If you need the keyboard to send data back to the host, simply call the `raw_hid_send()` function. It requires two arguments - a pointer to a 32-byte buffer containing the data you wish to send, and the length (which should always be `RAW_EPSIZE`). + +The received report can then be handled in whichever way your HID library provides. + +## Simple Example {#simple-example} + +The following example reads the first byte of the received report from the host, and if it is an ASCII "A", responds with "B". `memset()` is used to fill the response buffer (which could still contain the previous response) with null bytes. + +```c +void raw_hid_receive(uint8_t *data, uint8_t length) { + uint8_t response[length]; + memset(response, 0, length); + response[0] = 'B'; + + if(data[0] == 'A') { + raw_hid_send(response, length); + } +} +``` + +On the host side (here we are using Python and the `pyhidapi` library), the HID device is opened by enumerating the interfaces on the USB device, then filtering on the usage page and usage ID. Then, a report containing a single ASCII "A" (hex `0x41`) is constructed and sent. + +For demonstration purposes, the manufacturer and product strings of the device, along with the request and response, are also printed. + +```python +import sys +import hid + +vendor_id = 0x4335 +product_id = 0x0002 + +usage_page = 0xFF60 +usage = 0x61 +report_length = 32 + +def get_raw_hid_interface(): + device_interfaces = hid.enumerate(vendor_id, product_id) + raw_hid_interfaces = [i for i in device_interfaces if i['usage_page'] == usage_page and i['usage'] == usage] + + if len(raw_hid_interfaces) == 0: + return None + + interface = hid.Device(path=raw_hid_interfaces[0]['path']) + + print(f"Manufacturer: {interface.manufacturer}") + print(f"Product: {interface.product}") + + return interface + +def send_raw_report(data): + interface = get_raw_hid_interface() + + if interface is None: + print("No device found") + sys.exit(1) + + request_data = [0x00] * (report_length + 1) # First byte is Report ID + request_data[1:len(data) + 1] = data + request_report = bytes(request_data) + + print("Request:") + print(request_report) + + try: + interface.write(request_report) + + response_report = interface.read(report_length, timeout=1000) + + print("Response:") + print(response_report) + finally: + interface.close() + +if __name__ == '__main__': + send_raw_report([ + 0x41 + ]) +``` + +## API {#api} + +### `void raw_hid_receive(uint8_t *data, uint8_t length)` {#api-raw-hid-receive} + +Callback, invoked when a raw HID report has been received from the host. + +#### Arguments {#api-raw-hid-receive-arguments} + + - `uint8_t *data` + A pointer to the received data. Always 32 bytes in length. + - `uint8_t length` + The length of the buffer. Always 32. + +--- + +### `void raw_hid_send(uint8_t *data, uint8_t length)` {#api-raw-hid-send} + +Send an HID report. + +#### Arguments {#api-raw-hid-send-arguments} + + - `uint8_t *data` + A pointer to the data to send. Must always be 32 bytes in length. + - `uint8_t length` + The length of the buffer. Must always be 32. diff --git a/docs/features/repeat_key.md b/docs/features/repeat_key.md new file mode 100644 index 0000000000..53495e0f4d --- /dev/null +++ b/docs/features/repeat_key.md @@ -0,0 +1,456 @@ +# Repeat Key + +The Repeat Key performs the action of the last pressed key. Tapping the Repeat +Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for +typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd> +can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is +potentially faster and more comfortable. The Repeat Key is also useful for +hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word. + +Repeat Key remembers mods that were active with the last key press. These mods +are combined with any additional mods while pressing the Repeat Key. If the last +press key was <kbd>Ctrl</kbd> + <kbd>Z</kbd>, then <kbd>Shift</kbd> + +<kbd>Repeat</kbd> performs Ctrl + Shift + `Z`. + +## How do I enable Repeat Key + +In your `rules.mk`, add: + +```make +REPEAT_KEY_ENABLE = yes +``` + +Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short +alias `QK_REP`). Optionally, use the keycode `QK_ALT_REPEAT_KEY` (short alias +`QK_AREP`) on another key. + +## Keycodes + +|Keycode |Aliases |Description | +|-----------------------|---------|-------------------------------------| +|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key | +|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key | + +## Alternate Repeating + +The Alternate Repeat Key performs the "alternate" action of the last pressed key +if it is defined. By default, Alternate Repeat is defined for navigation keys to +act in the reverse direction. When the last key is the common "select by word" +hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl + +Shift + Left Arrow, which together with the Repeat Key enables convenient +selection by words in either direction. + +Alternate Repeat is enabled with the Repeat Key by default. Optionally, to +reduce firmware size, Alternate Repeat may be disabled by adding in config.h: + +```c +#define NO_ALT_REPEAT_KEY +``` + +The following alternate keys are defined by default. See +`get_alt_repeat_key_keycode_user()` below for how to change or add to these +definitions. Where it makes sense, these definitions also include combinations +with mods, like Ctrl + Left ↔ Ctrl + Right Arrow. + +**Navigation** + +|Keycodes |Description | +|-----------------------------------|-----------------------------------| +|`KC_LEFT` ↔ `KC_RGHT` | Left ↔ Right Arrow | +|`KC_UP` ↔ `KC_DOWN` | Up ↔ Down Arrow | +|`KC_HOME` ↔ `KC_END` | Home ↔ End | +|`KC_PGUP` ↔ `KC_PGDN` | Page Up ↔ Page Down | +|`KC_MS_L` ↔ `KC_MS_R` | Mouse Cursor Left ↔ Right | +|`KC_MS_U` ↔ `KC_MS_D` | Mouse Cursor Up ↔ Down | +|`KC_WH_L` ↔ `KC_WH_R` | Mouse Wheel Left ↔ Right | +|`KC_WH_U` ↔ `KC_WH_D` | Mouse Wheel Up ↔ Down | + +**Misc** + +|Keycodes |Description | +|-----------------------------------|-----------------------------------| +|`KC_BSPC` ↔ `KC_DEL` | Backspace ↔ Delete | +|`KC_LBRC` ↔ `KC_RBRC` | `[` ↔ `]` | +|`KC_LCBR` ↔ `KC_RCBR` | `{` ↔ `}` | + +**Media** + +|Keycodes |Description | +|-----------------------------------|-----------------------------------| +|`KC_WBAK` ↔ `KC_WFWD` | Browser Back ↔ Forward | +|`KC_MNXT` ↔ `KC_MPRV` | Next ↔ Previous Media Track | +|`KC_MFFD` ↔ `KC_MRWD` | Fast Forward ↔ Rewind Media | +|`KC_VOLU` ↔ `KC_VOLD` | Volume Up ↔ Down | +|`KC_BRIU` ↔ `KC_BRID` | Brightness Up ↔ Down | + +**Hotkeys in Vim, Emacs, and other programs** + +|Keycodes |Description | +|-----------------------------------|-----------------------------------| +|mod + `KC_F` ↔ mod + `KC_B` | Forward ↔ Backward | +|mod + `KC_D` ↔ mod + `KC_U` | Down ↔ Up | +|mod + `KC_N` ↔ mod + `KC_P` | Next ↔ Previous | +|mod + `KC_A` ↔ mod + `KC_E` | Home ↔ End | +|mod + `KC_O` ↔ mod + `KC_I` | Vim jump list Older ↔ Newer | +|`KC_J` ↔ `KC_K` | Down ↔ Up | +|`KC_H` ↔ `KC_L` | Left ↔ Right | +|`KC_W` ↔ `KC_B` | Forward ↔ Backward by Word | + +(where above, "mod" is Ctrl, Alt, or GUI) + + +## Defining alternate keys + +Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate" +for additional keys or override the default definitions. For example, to define +Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in +keymap.c: + +```c +uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { + if ((mods & MOD_MASK_CTRL)) { // Was Ctrl held? + switch (keycode) { + case KC_Y: return C(KC_Z); // Ctrl + Y reverses to Ctrl + Z. + case KC_Z: return C(KC_Y); // Ctrl + Z reverses to Ctrl + Y. + } + } + + return KC_TRNS; // Defer to default definitions. +} +``` + +The `keycode` and `mods` args are the keycode and mods that were active with the +last pressed key. The meaning of the return value from this function is: + +* `KC_NO` – do nothing (any predefined alternate key is not used); +* `KC_TRNS` – use the default alternate key if it exists; +* anything else – use the specified keycode. Any keycode may be returned + as an alternate key, including custom keycodes. + +Another example, defining Shift + Tab as the alternate of Tab, and vice versa: + +```c +uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { + bool shifted = (mods & MOD_MASK_SHIFT); // Was Shift held? + switch (keycode) { + case KC_TAB: + if (shifted) { // If the last key was Shift + Tab, + return KC_TAB; // ... the reverse is Tab. + } else { // Otherwise, the last key was Tab, + return S(KC_TAB); // ... and the reverse is Shift + Tab. + } + } + + return KC_TRNS; +} +``` + +#### Eliminating SFBs + +Alternate Repeat can be configured more generally to perform an action that +"complements" the last key. Alternate Repeat is not limited to reverse +repeating, and it need not be symmetric. You can use it to eliminate cases of +same-finger bigrams in your layout, that is, pairs of letters typed by the same +finger. The following addresses the top 5 same-finger bigrams in English on +QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt +Repeat</kbd>. + +```c +uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { + switch (keycode) { + case KC_E: return KC_D; // For "ED" bigram. + case KC_D: return KC_E; // For "DE" bigram. + case KC_C: return KC_E; // For "CE" bigram. + case KC_L: return KC_O; // For "LO" bigram. + case KC_U: return KC_N; // For "UN" bigram. + } + + return KC_TRNS; +} +``` + +#### Typing shortcuts + +A useful possibility is having Alternate Repeat press [a +macro](../feature_macros). This way macros can be used without having to +dedicate keys to them. The following defines a couple shortcuts. + +* Typing <kbd>K</kbd>, <kbd>Alt Repeat</kbd> produces "`keyboard`," with the + initial "`k`" typed as usual and the "`eybord`" produced by the macro. +* Typing <kbd>.</kbd>, <kbd>Alt Repeat</kbd> produces "`../`," handy for "up + directory" on the shell. Similary, <kbd>.</kbd> types the initial "`.`" and + "`./`" is produced by the macro. + +```c +enum custom_keycodes { + M_KEYBOARD = SAFE_RANGE, + M_UPDIR, + // Other custom keys... +}; + +uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { + switch (keycode) { + case KC_K: return M_KEYBOARD; + case KC_DOT: return M_UPDIR; + } + + return KC_TRNS; +} + +bool process_record_user(uint16_t keycode, keyrecord_t* record) { + switch (keycode) { + case M_KEYBOARD: SEND_STRING(/*k*/"eyboard"); break; + case M_UPDIR: SEND_STRING(/*.*/"./"); break; + } + return true; +} +``` + +## Ignoring certain keys and mods + +In tracking what is "the last key" to be repeated or alternate repeated, +modifier and layer switch keys are always ignored. This makes it possible to set +some mods and change layers between pressing a key and repeating it. By default, +all other (non-modifier, non-layer switch) keys are remembered so that they are +eligible for repeating. To configure additional keys to be ignored, define +`remember_last_key_user()` in your keymap.c. + +#### Ignoring a key + +The following ignores the Backspace key: + +```c +bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, + uint8_t* remembered_mods) { + switch (keycode) { + case KC_BSPC: + return false; // Ignore backspace. + } + + return true; // Other keys can be repeated. +} +``` + +Then for instance, the Repeat key in <kbd>Left Arrow</kbd>, +<kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of +repeating Backspace. + +The `remember_last_key_user()` callback is called on every key press excluding +modifiers and layer switches. Returning true indicates the key is remembered, +while false means it is ignored. + +#### Filtering remembered mods + +The `remembered_mods` arg represents the mods that will be remembered with +this key. It can be modified to forget certain mods. This may be +useful to forget capitalization when repeating shifted letters, so that "Aaron" +does not becom "AAron": + +```c +bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, + uint8_t* remembered_mods) { + // Forget Shift on letter keys when Shift or AltGr are the only mods. + switch (keycode) { + case KC_A ... KC_Z: + if ((*remembered_mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { + *remembered_mods &= ~MOD_MASK_SHIFT; + } + break; + } + + return true; +} +``` + +#### Further conditions + +Besides checking the keycode, this callback could also make conditions based on +the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For +example, the following ignores keys on layer 2 as well as key combinations +involving GUI: + +```c +bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, + uint8_t* remembered_mods) { + if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) { + return false; // Ignore layer 2 keys and GUI chords. + } + + return true; // Other keys can be repeated. +} +``` + +::: tip +See [Layer Functions](../feature_layers#functions) and [Checking Modifier State](../feature_advanced_keycodes#checking-modifier-state) for further details. +::: + +## Handle how a key is repeated + +By default, pressing the Repeat Key will simply behave as if the last key +were pressed again. This also works with macro keys with custom handlers, +invoking the macro again. In case fine-tuning is needed for sensible repetition, +you can handle how a key is repeated with `get_repeat_key_count()` within +`process_record_user()`. + +The `get_repeat_key_count()` function returns a signed count of times the key +has been repeated or alternate repeated. When a key is pressed as usual, +`get_repeat_key_count()` is 0. On the first repeat, it is 1, then the second +repeat, 2, and so on. Negative counts are used similarly for alternate +repeating. For instance supposing `MY_MACRO` is a custom keycode used in the +layout: + +```c +bool process_record_user(uint16_t keycode, keyrecord_t* record) { + switch (keycode) { + case MY_MACRO: + if (get_repeat_key_count() > 0) { + // MY_MACRO is being repeated! + if (record->event.pressed) { + SEND_STRING("repeat!"); + } + } else { + // MY_MACRO is being used normally. + if (record->event.pressed) { + SEND_STRING("macro"); + } + } + return false; + + // Other macros... + } + return true; +} +``` + +## Handle how a key is alternate repeated + +Pressing the Alternate Repeat Key behaves as if the "alternate" of the last +pressed key were pressed, if an alternate is defined. To define how a particular +key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback +as described above to define which keycode to use as its alternate. Beyond this, +`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior +when alternate repeating. + +The following example defines `MY_MACRO` as its own alternate, and specially +handles repeating and alternate repeating: + +```c +uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { + switch (keycode) { + case MY_MACRO: return MY_MACRO; // MY_MACRO is its own alternate. + } + return KC_TRNS; +} + +bool process_record_user(uint16_t keycode, keyrecord_t* record) { + switch (keycode) { + case MY_MACRO: + if (get_repeat_key_count() > 0) { // Repeating. + if (record->event.pressed) { + SEND_STRING("repeat!"); + } + } else if (get_repeat_key_count() < 0) { // Alternate repeating. + if (record->event.pressed) { + SEND_STRING("alt repeat!"); + } + } else { // Used normally. + if (record->event.pressed) { + SEND_STRING("macro"); + } + } + return false; + + // Other macros... + } + return true; +} +``` + + +## Functions + +| Function | Description | +|--------------------------------|------------------------------------------------------------------------| +| `get_last_keycode()` | The last key's keycode, the key to be repeated. | +| `get_last_mods()` | Mods to apply when repeating. | +| `set_last_keycode(kc)` | Set the keycode to be repeated. | +| `set_last_mods(mods)` | Set the mods to apply when repeating. | +| `get_repeat_key_count()` | Signed count of times the key has been repeated or alternate repeated. | +| `get_alt_repeat_key_keycode()` | Keycode to be used for alternate repeating. | + + +## Additional "Alternate" keys + +By leveraging `get_last_keycode()` in macros, it is possible to define +additional, distinct "Alternate Repeat"-like keys. The following defines two +keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common +English 5-gram letter patterns, taking inspiration from +[Stenotype](stenography): + + +| Typing | Produces | Typing | Produces | +|----------------------------------|----------|----------------------------------|----------| +| <kbd>A</kbd>, <kbd>ALTREP2</kbd> | `ation` | <kbd>A</kbd>, <kbd>ALTREP3</kbd> | `about` | +| <kbd>I</kbd>, <kbd>ALTREP2</kbd> | `ition` | <kbd>I</kbd>, <kbd>ALTREP3</kbd> | `inter` | +| <kbd>S</kbd>, <kbd>ALTREP2</kbd> | `ssion` | <kbd>S</kbd>, <kbd>ALTREP3</kbd> | `state` | +| <kbd>T</kbd>, <kbd>ALTREP2</kbd> | `their` | <kbd>T</kbd>, <kbd>ALTREP3</kbd> | `there` | +| <kbd>W</kbd>, <kbd>ALTREP2</kbd> | `which` | <kbd>W</kbd>, <kbd>ALTREP3</kbd> | `would` | + +```c +enum custom_keycodes { + ALTREP2 = SAFE_RANGE, + ALTREP3, +}; + +// Use ALTREP2 and ALTREP3 in your layout... + +bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, + uint8_t* remembered_mods) { + switch (keycode) { + case ALTREP2: + case ALTREP3: + return false; // Ignore ALTREP keys. + } + + return true; // Other keys can be repeated. +} + +static void process_altrep2(uint16_t keycode, uint8_t mods) { + switch (keycode) { + case KC_A: SEND_STRING(/*a*/"tion"); break; + case KC_I: SEND_STRING(/*i*/"tion"); break; + case KC_S: SEND_STRING(/*s*/"sion"); break; + case KC_T: SEND_STRING(/*t*/"heir"); break; + case KC_W: SEND_STRING(/*w*/"hich"); break; + } +} + +static void process_altrep3(uint16_t keycode, uint8_t mods) { + switch (keycode) { + case KC_A: SEND_STRING(/*a*/"bout"); break; + case KC_I: SEND_STRING(/*i*/"nter"); break; + case KC_S: SEND_STRING(/*s*/"tate"); break; + case KC_T: SEND_STRING(/*t*/"here"); break; + case KC_W: SEND_STRING(/*w*/"ould"); break; + } +} + +bool process_record_user(uint16_t keycode, keyrecord_t* record) { + switch (keycode) { + case ALTREP2: + if (record->event.pressed) { + process_altrep2(get_last_keycode(), get_last_mods()); + } + return false; + + case ALTREP3: + if (record->event.pressed) { + process_altrep3(get_last_keycode(), get_last_mods()); + } + return false; + } + + return true; +} +``` + diff --git a/docs/features/rgb_matrix.md b/docs/features/rgb_matrix.md new file mode 100644 index 0000000000..f7f693f239 --- /dev/null +++ b/docs/features/rgb_matrix.md @@ -0,0 +1,1119 @@ +# RGB Matrix Lighting {#rgb-matrix-lighting} + +This feature allows you to use RGB LED matrices driven by external drivers. It hooks into the RGBLIGHT system so you can use the same keycodes as RGBLIGHT to control it. + +If you want to use single color LED's you should use the [LED Matrix Subsystem](led_matrix) instead. + +## Driver configuration {#driver-configuration} +--- +### IS31FL3731 {#is31fl3731} + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3731 RGB controller. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = is31fl3731 +``` + +You can use between 1 and 4 IS31FL3731 IC's. Do not specify `DRIVER_ADDR_<N>` defines for IC's that are not present on your keyboard. You can define the following items in `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `IS31FL3731_I2C_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `IS31FL3731_I2C_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `IS31FL3731_DEGHOST` | (Optional) Set this define to enable de-ghosting by halving Vcc during blanking time | | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `IS31FL3731_I2C_ADDRESS_1` | (Required) Address for the first RGB driver | | +| `IS31FL3731_I2C_ADDRESS_2` | (Optional) Address for the second RGB driver | | +| `IS31FL3731_I2C_ADDRESS_3` | (Optional) Address for the third RGB driver | | +| `IS31FL3731_I2C_ADDRESS_4` | (Optional) Address for the fourth RGB driver | | + +Here is an example using 2 drivers. + +```c +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 00 AD <-> GND +// 01 AD <-> SCL +// 10 AD <-> SDA +// 11 AD <-> VCC +// ADDR represents A1:A0 of the 7-bit address. +// The result is: 0b11101(ADDR) +#define IS31FL3731_I2C_ADDRESS_1 IS31FL3731_I2C_ADDRESS_GND +#define IS31FL3731_I2C_ADDRESS_2 IS31FL3731_I2C_ADDRESS_SDA + +#define DRIVER_1_LED_TOTAL 25 +#define DRIVER_2_LED_TOTAL 24 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` + +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +For split keyboards using `RGB_MATRIX_SPLIT` with an LED driver, you can either have the same driver address or different driver addresses. If using different addresses, use `IS31FL3731_I2C_ADDRESS_1` for one and `IS31FL3731_I2C_ADDRESS_2` for the other one. Then, in `g_is31fl3731_leds`, fill out the correct driver index (0 or 1). If using one address, use `IS31FL3731_I2C_ADDRESS_1` for both, and use index 0 for `g_is31fl3731_leds`. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31fl3731_led_t PROGMEM g_is31fl3731_leds[IS31FL3731_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, C1_3, C2_3, C3_3}, + .... +} +``` + +Where `Cx_y` is the location of the LED in the matrix defined by [the datasheet](https://www.issi.com/WW/pdf/31FL3731.pdf) and the header file `drivers/led/issi/is31fl3731.h`. The `driver` is the index of the driver you defined in your `config.h` (`0`, `1`, `2`, or `3`). + +--- +### IS31FL3733 {#is31fl3733} + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3733 RGB controller. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = is31fl3733 +``` + +You can use between 1 and 4 IS31FL3733 IC's. Do not specify `DRIVER_ADDR_<N>` defines for IC's that are not present on your keyboard. You can define the following items in `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `IS31FL3733_I2C_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `IS31FL3733_I2C_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `IS31FL3733_PWM_FREQUENCY` | (Optional) PWM Frequency Setting - IS31FL3733B only | 0 | +| `IS31FL3733_GLOBALCURRENT` | (Optional) Configuration for the Global Current Register | 0xFF | +| `IS31FL3733_SWPULLUP` | (Optional) Set the value of the SWx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `IS31FL3733_CSPULLUP` | (Optional) Set the value of the CSx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `IS31FL3733_I2C_ADDRESS_1` | (Required) Address for the first RGB driver | | +| `IS31FL3733_I2C_ADDRESS_2` | (Optional) Address for the second RGB driver | | +| `IS31FL3733_I2C_ADDRESS_3` | (Optional) Address for the third RGB driver | | +| `IS31FL3733_I2C_ADDRESS_4` | (Optional) Address for the fourth RGB driver | | +| `IS31FL3733_SYNC_1` | (Optional) Sync configuration for the first RGB driver | 0 | +| `IS31FL3733_SYNC_2` | (Optional) Sync configuration for the second RGB driver | 0 | +| `IS31FL3733_SYNC_3` | (Optional) Sync configuration for the third RGB driver | 0 | +| `IS31FL3733_SYNC_4` | (Optional) Sync configuration for the fourth RGB driver | 0 | + +The IS31FL3733 IC's have on-chip resistors that can be enabled to allow for de-ghosting of the RGB matrix. By default these resistors are not enabled (`IS31FL3733_SWPULLUP`/`IS31FL3733_CSPULLUP` are given the value of `IS31FL3733_PUR_0R`), the values that can be set to enable de-ghosting are as follows: + +| `IS31FL3733_SWPULLUP/IS31FL3733_CSPULLUP` | Description | +|----------------------|-------------| +| `IS31FL3733_PUR_0R` | (default) Do not use the on-chip resistors/enable de-ghosting | +| `IS31FL3733_PUR_05KR` | The 0.5k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3733_PUR_3KR` | The 3k Ohm resistor used at all times | +| `IS31FL3733_PUR_4KR` | The 4k Ohm resistor used at all times | +| `IS31FL3733_PUR_8KR` | The 8k Ohm resistor used at all times | +| `IS31FL3733_PUR_16KR` | The 16k Ohm resistor used at all times | +| `IS31FL3733_PUR_32KR` | The 32k Ohm resistor used during blanking period (t_NOL) | + +Here is an example using 2 drivers. + +```c +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 00 ADDRn <-> GND +// 01 ADDRn <-> SCL +// 10 ADDRn <-> SDA +// 11 ADDRn <-> VCC +// ADDR1 represents A1:A0 of the 7-bit address. +// ADDR2 represents A3:A2 of the 7-bit address. +// The result is: 0b101(ADDR2)(ADDR1) +#define IS31FL3733_I2C_ADDRESS_1 IS31FL3733_I2C_ADDRESS_GND_GND +#define IS31FL3733_I2C_ADDRESS_2 IS31FL3733_I2C_ADDRESS_GND_VCC + +#define DRIVER_1_LED_TOTAL 58 +#define DRIVER_2_LED_TOTAL 10 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` + +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Currently only 4 drivers are supported, but it would be trivial to support all 8 combinations. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31fl3733_led_t PROGMEM g_is31fl3733_leds[IS31FL3733_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, SW1_CS1, SW1_CS2, SW1_CS3}, + .... +} +``` + +Where `SWx_CSy` is the location of the LED in the matrix defined by [the datasheet](https://www.issi.com/WW/pdf/31FL3733.pdf) and the header file `drivers/led/issi/is31fl3733.h`. The `driver` is the index of the driver you defined in your `config.h` (`0`, `1`, `2`, or `3` for now). + +--- +### IS31FL3736 {#is31fl3736} + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3736 RGB controller. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = is31fl3736 +``` +You can use between 1 and 4 IS31FL3736 IC's. Do not specify `DRIVER_ADDR_<N>` defines for IC's that are not present on your keyboard. + +Configure the hardware via your `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `IS31FL3736_I2C_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `IS31FL3736_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `IS31FL3736_PWM_FREQUENCY` | (Optional) PWM Frequency Setting - IS31FL3736B only | 0 | +| `IS31FL3736_GLOBALCURRENT` | (Optional) Configuration for the Global Current Register | 0xFF | +| `IS31FL3736_SWPULLUP` | (Optional) Set the value of the SWx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `IS31FL3736_CSPULLUP` | (Optional) Set the value of the CSx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `IS31FL3736_I2C_ADDRESS_1` | (Required) Address for the first RGB driver | | +| `IS31FL3736_I2C_ADDRESS_2` | (Optional) Address for the second RGB driver | | +| `IS31FL3736_I2C_ADDRESS_3` | (Optional) Address for the third RGB driver | | +| `IS31FL3736_I2C_ADDRESS_4` | (Optional) Address for the fourth RGB driver | | + +The IS31FL3736 IC's have on-chip resistors that can be enabled to allow for de-ghosting of the RGB matrix. By default these resistors are not enabled (`IS31FL3736_SWPULLUP`/`IS31FL3736_CSPULLUP` are given the value of `IS31FL3736_PUR_0R`), the values that can be set to enable de-ghosting are as follows: + +| `IS31FL3736_SWPULLUP/IS31FL3736_CSPULLUP` | Description | +|----------------------|-------------| +| `IS31FL3736_PUR_0R` | (default) Do not use the on-chip resistors/enable de-ghosting | +| `IS31FL3736_PUR_05KR` | The 0.5k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3736_PUR_1KR` | The 1k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3736_PUR_2KR` | The 2k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3736_PUR_4KR` | The 4k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3736_PUR_8KR` | The 8k Ohm resistor during blanking period (t_NOL) | +| `IS31FL3736_PUR_16KR` | The 16k Ohm resistor during blanking period (t_NOL) | +| `IS31FL3736_PUR_32KR` | The 32k Ohm resistor used during blanking period (t_NOL) | + +Here is an example using 2 drivers. + +```c +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 00 ADDRn <-> GND +// 01 ADDRn <-> SCL +// 10 ADDRn <-> SDA +// 11 ADDRn <-> VCC +// ADDR1 represents A1:A0 of the 7-bit address. +// ADDR2 represents A3:A2 of the 7-bit address. +// The result is: 0b101(ADDR2)(ADDR1) +#define IS31FL3736_I2C_ADDRESS_1 IS31FL3736_I2C_ADDRESS_GND_GND +#define IS31FL3736_I2C_ADDRESS_2 IS31FL3736_I2C_ADDRESS_GND_SCL + +#define DRIVER_1_LED_TOTAL 30 +#define DRIVER_2_LED_TOTAL 32 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31fl3736_led_t PROGMEM g_is31fl3736_leds[IS31FL3736_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, SW1_CS1, SW1_CS2, SW1_CS3}, + .... +} +``` +### IS31FL3737 {#is31fl3737} + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3737 RGB controller. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = is31fl3737 +``` +You can use between 1 and 4 IS31FL3737 IC's. Do not specify `DRIVER_ADDR_<N>` defines for IC's that are not present on your keyboard. + +Configure the hardware via your `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `IS31FL3737_I2C_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `IS31FL3737_I2C_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `IS31FL3737_PWM_FREQUENCY` | (Optional) PWM Frequency Setting - IS31FL3737B only | 0 | +| `IS31FL3737_GLOBALCURRENT` | (Optional) Configuration for the Global Current Register | 0xFF | +| `IS31FL3737_SWPULLUP` | (Optional) Set the value of the SWx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `IS31FL3737_CSPULLUP` | (Optional) Set the value of the CSx lines on-chip de-ghosting resistors | PUR_0R (Disabled) | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `IS31FL3737_I2C_ADDRESS_1` | (Required) Address for the first RGB driver | | +| `IS31FL3737_I2C_ADDRESS_2` | (Optional) Address for the second RGB driver | | +| `IS31FL3737_I2C_ADDRESS_3` | (Optional) Address for the third RGB driver | | +| `IS31FL3737_I2C_ADDRESS_4` | (Optional) Address for the fourth RGB driver | | + +The IS31FL3737 IC's have on-chip resistors that can be enabled to allow for de-ghosting of the RGB matrix. By default these resistors are not enabled (`IS31FL3737_SWPULLUP`/`IS31FL3737_CSPULLUP` are given the value of `IS31FL3737_PUR_0R`), the values that can be set to enable de-ghosting are as follows: + +| `IS31FL3737_SWPULLUP/IS31FL3737_CSPULLUP` | Description | +|----------------------|-------------| +| `IS31FL3737_PUR_0R` | (default) Do not use the on-chip resistors/enable de-ghosting | +| `IS31FL3737_PUR_05KR` | The 0.5k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3737_PUR_1KR` | The 1k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3737_PUR_2KR` | The 2k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3737_PUR_4KR` | The 4k Ohm resistor used during blanking period (t_NOL) | +| `IS31FL3737_PUR_8KR` | The 8k Ohm resistor during blanking period (t_NOL) | +| `IS31FL3737_PUR_16KR` | The 16k Ohm resistor during blanking period (t_NOL) | +| `IS31FL3737_PUR_32KR` | The 32k Ohm resistor used during blanking period (t_NOL) | + +Here is an example using 2 drivers. + +```c +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 0000 ADDR <-> GND +// 0101 ADDR <-> SCL +// 1010 ADDR <-> SDA +// 1111 ADDR <-> VCC +// ADDR represents A3:A0 of the 7-bit address. +// The result is: 0b101(ADDR) +#define IS31FL3737_I2C_ADDRESS_1 IS31FL3737_I2C_ADDRESS_GND +#define IS31FL3737_I2C_ADDRESS_2 IS31FL3737_I2C_ADDRESS_SCL + +#define DRIVER_1_LED_TOTAL 30 +#define DRIVER_2_LED_TOTAL 36 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31fl3737_led_t PROGMEM g_is31fl3737_leds[IS31FL3737_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, SW1_CS1, SW1_CS2, SW1_CS3}, + .... +} +``` + +Where `SWx_CSy` is the location of the LED in the matrix defined by [the datasheet](https://www.issi.com/WW/pdf/31FL3737.pdf) and the header file `drivers/led/issi/is31fl3737.h`. The `driver` is the index of the driver you defined in your `config.h` (Only `0`, `1`, `2`, or `3` for now). + +--- +### IS31FLCOMMON {#is31flcommon} + +There is basic support for addressable RGB matrix lighting with a selection of I2C ISSI Lumissil RGB controllers through a shared common driver. To enable it, add this to your `rules.mk`: + +```makefile +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = <driver name> +``` + +Where `<driver name>` is the applicable LED driver chip as below + +| Driver Name | Data Sheet | Capability | +|-------------|------------|------------| +| `IS31FL3742A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3742A_DS.pdf) | 60 RGB, 30x6 Matrix | +| `IS31FL3743A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3743A_DS.pdf) | 66 RGB, 18x11 Matrix | +| `IS31FL3745` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3745_DS.pdf) | 48 RGB, 18x8 Matrix | +| `IS31FL3746A` | [datasheet](https://www.lumissil.com/assets/pdf/core/IS31FL3746A_DS.pdf) | 24 RGB, 18x4 Matrix | + +You can use between 1 and 4 IC's. Do not specify `DRIVER_ADDR_<N>` define for IC's if not present on your keyboard. The `DRIVER_ADDR_1` default assumes that all Address pins on the controller have been connected to GND. Drivers that have SYNC functionality have the default settings to disable if 1 driver. If more than 1 drivers then `DRIVER_ADDR_1` will be set to Master and the remaining ones set to Slave. + +Configure the hardware via your `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `ISSI_TIMEOUT` | (Optional) How long to wait for i2c messages, in milliseconds | 100 | +| `ISSI_PERSISTENCE` | (Optional) Retry failed messages this many times | 0 | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `DRIVER_ADDR_1` | (Optional) Address for the first RGB driver | | +| `DRIVER_ADDR_<N>` | (Required) Address for the additional RGB drivers | | +| `ISSI_SSR_<N>` | (Optional) Configuration for the Spread Spectrum Register | | +| `ISSI_CONFIGURATION` | (Optional) Configuration for the Configuration Register | | +| `ISSI_GLOBALCURRENT` | (Optional) Configuration for the Global Current Register | 0xFF | +| `ISSI_PULLDOWNUP` | (Optional) Configuration for the Pull Up & Pull Down Register | | +| `ISSI_TEMP` | (Optional) Configuration for the Temperature Register | | +| `ISSI_PWM_ENABLE` | (Optional) Configuration for the PWM Enable Register | | +| `ISSI_PWM_SET` | (Optional) Configuration for the PWM Setting Register | | +| `ISSI_SCAL_RED` | (Optional) Configuration for the RED LEDs in Scaling Registers | 0xFF | +| `ISSI_SCAL_BLUE` | (Optional) Configuration for the BLUE LEDs in Scaling Registers | 0xFF | +| `ISSI_SCAL_GREEN` | (Optional) Configuration for the GREEN LEDs in Scaling Registers | 0xFF | +| `ISSI_MANUAL_SCALING` | (Optional) If you wish to configure the Scaling Registers manually | | + + +Defaults + +| Variable | IS31FL3742A | IS31FL3743A | IS31FL3745 | IS31FL3746 | +|----------|-------------|-------------|------------|------------| +| `DRIVER_ADDR_1` | 0b0110000 | 0b0100000 | 0b0100000 | 0b1100000 | +| `ISSI_SSR_1` | 0x00 | 0x00 / 0x60 | 0x00 / 0xC0 | 0x00 | +| `ISSI_SSR_<2-4>` | 0x00 | 0x40 | 0x80 | 0x00 | +| `ISSI_CONFIGURATION` | 0x31 | 0x01 | 0x31 | 0x01 | +| `ISSI_PULLDOWNUP` | 0x55 | 0x33 | 0x33 | 0x33 | +| `ISSI_TEMP` | N/A | 0x00 | 0x00 | 0x00 | +| `ISSI_PWM_ENABLE` | N/A | N/A | N/A | 0x00 | +| `ISSI_PWM_SET` | 0x00 | N/A | N/A | 0x00 | + +Here is an example using 2 drivers. + +```c +#define DRIVER_ADDR_2 0b0100001 + +#define DRIVER_1_LED_TOTAL 66 +#define DRIVER_2_LED_TOTAL 42 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` + +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Currently only 4 drivers are supported, but it would be trivial to support for more. Note that using a combination of different drivers is not supported. All drivers must be of the same model. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const is31_led PROGMEM g_is31_leds[RGB_MATRIX_LED_COUNT] = { +/* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, SW1_CS1, SW1_CS2, SW1_CS3}, + .... +} +``` + +Where `CSx_SWx` is the location of the LED in the matrix defined by the datasheet. The `driver` is the index of the driver you defined in your `config.h` (`0`, `1`, `2`, or `3` for now). + +`ISSI_MANUAL_SCALING` is used to override the Scaling for individual LED's. By default they will be set as per `ISSI_SCAL_<colour>`. In `config.h` set how many LED's you want to manually set scaling for. +Eg `#define ISSI_MANUAL_SCALING 3` + +Then Define the array listing all the LEDs you want to override in your `<keyboard>.c`: + +```c +const is31_led PROGMEM g_is31_scaling[ISSI_MANUAL_SCALING] = { +/* LED Index + * | R scaling + * | | G scaling + * | | | B scaling + * | | | | */ + {5, 120, 155, 167}, + {9, 120, 155, 167}, + .... +} +``` + +Where LED Index is the position of the LED in the `g_is31_leds` array. The `scaling` value between 0 and 255 to be written to the Scaling Register. + +--- + +### WS2812 {#ws2812} + +There is basic support for addressable RGB matrix lighting with a WS2811/WS2812{a,b,c} addressable LED strand. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = ws2812 +``` + +Configure the hardware via your `config.h`: + +```c +// The pin connected to the data pin of the LEDs +#define WS2812_DI_PIN D7 +// The number of LEDs connected +#define RGB_MATRIX_LED_COUNT 70 +``` + +::: tip +There are additional configuration options for ARM controllers that offer increased performance over the default bitbang driver. Please see [WS2812 Driver](../drivers/ws2812) for more information. +::: + +--- + +### APA102 {#apa102} + +There is basic support for APA102 based addressable LED strands. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = apa102 +``` + +Configure the hardware via your `config.h`: + +```c +// The pin connected to the data pin of the LEDs +#define APA102_DI_PIN D7 +// The pin connected to the clock pin of the LEDs +#define APA102_CI_PIN D6 +// The number of LEDs connected +#define RGB_MATRIX_LED_COUNT 70 +``` + +--- +### AW20216S {#aw20216s} +There is basic support for addressable RGB matrix lighting with the SPI AW20216S RGB controller. To enable it, add this to your `rules.mk`: + +```make +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = aw20216s +``` + +You can use up to 2 AW20216S IC's. Do not specify `DRIVER_<N>_xxx` defines for IC's that are not present on your keyboard. You can define the following items in `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `AW20216S_CS_PIN_1` | (Required) MCU pin connected to first RGB driver chip select line | B13 | +| `AW20216S_CS_PIN_2` | (Optional) MCU pin connected to second RGB driver chip select line | | +| `AW20216S_EN_PIN_1` | (Required) MCU pin connected to first RGB driver hardware enable line | C13 | +| `AW20216S_EN_PIN_2` | (Optional) MCU pin connected to second RGB driver hardware enable line | | +| `DRIVER_1_LED_TOTAL` | (Required) How many RGB lights are connected to first RGB driver | | +| `DRIVER_2_LED_TOTAL` | (Optional) How many RGB lights are connected to second RGB driver | | +| `RGB_MATRIX_LED_COUNT` | (Required) How many RGB lights are present across all drivers | | +| `AW20216S_SCALING_MAX` | (Optional) LED current scaling value (0-255, higher values mean LED is brighter at full PWM) | 150 | +| `AW20216S_GLOBAL_CURRENT_MAX` | (Optional) Driver global current limit (0-255, higher values means the driver may consume more power) | 150 | +| `AW20216S_SPI_MODE` | (Optional) Mode for SPI communication (0-3, defines polarity and phase of the clock) | 3 | +| `AW20216S_SPI_DIVISOR` | (Optional) Clock divisor for SPI communication (powers of 2, smaller numbers means faster communication, should not be less than 4) | 4 | + +Here is an example using 2 drivers. + +```c +#define AW20216S_CS_PIN_1 B13 +#define AW20216S_CS_PIN_2 B14 +// Hardware enable lines may be connected to the same pin +#define AW20216S_EN_PIN_1 C13 +#define AW20216S_EN_PIN_2 C13 + +#define DRIVER_1_LED_TOTAL 66 +#define DRIVER_2_LED_TOTAL 32 +#define RGB_MATRIX_LED_COUNT (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` + +::: warning +Note the parentheses, this is so when `RGB_MATRIX_LED_COUNT` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. +::: + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const aw20216s_led_t PROGMEM g_aw20216s_leds[AW20216S_LED_COUNT] = { +/* Each AW20216S channel is controlled by a register at some offset between 0x00 + * and 0xD7 inclusive. + * See drivers/led/aw20216s.h for the mapping between register offsets and + * driver pin locations. + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + { 0, SW1_CS1, SW1_CS2, SW1_CS3 }, + { 0, SW1_CS4, SW1_CS5, SW1_CS6 }, + { 0, SW1_CS7, SW1_CS8, SW1_CS9 }, + { 0, SW1_CS10, SW1_CS11, SW1_CS12 }, + { 0, SW1_CS13, SW1_CS14, SW1_CS15 }, + ... + { 1, SW1_CS1, SW1_CS2, SW1_CS3 }, + { 1, SW1_CS13, SW1_CS14, SW1_CS15 }, + { 1, SW1_CS16, SW1_CS17, SW1_CS18 }, + { 1, SW2_CS4, SW2_CS5, SW2_CS6 }, + ... +}; +``` + +--- + +## Common Configuration {#common-configuration} + +From this point forward the configuration is the same for all the drivers. The `led_config_t` struct provides a key electrical matrix to led index lookup table, what the physical position of each LED is on the board, and what type of key or usage the LED if the LED represents. Here is a brief example: + +```c +led_config_t g_led_config = { { + // Key Matrix to LED Index + { 5, NO_LED, NO_LED, 0 }, + { NO_LED, NO_LED, NO_LED, NO_LED }, + { 4, NO_LED, NO_LED, 1 }, + { 3, NO_LED, NO_LED, 2 } +}, { + // LED Index to Physical Position + { 188, 16 }, { 187, 48 }, { 149, 64 }, { 112, 64 }, { 37, 48 }, { 38, 16 } +}, { + // LED Index to Flag + 1, 4, 4, 4, 4, 1 +} }; +``` + +The first part, `// Key Matrix to LED Index`, tells the system what key this LED represents by using the key's electrical matrix row & col. The second part, `// LED Index to Physical Position` represents the LED's physical `{ x, y }` position on the keyboard. The default expected range of values for `{ x, y }` is the inclusive range `{ 0..224, 0..64 }`. This default expected range is due to effects that calculate the center of the keyboard for their animations. The easiest way to calculate these positions is imagine your keyboard is a grid, and the top left of the keyboard represents `{ x, y }` coordinate `{ 0, 0 }` and the bottom right of your keyboard represents `{ 224, 64 }`. Using this as a basis, you can use the following formula to calculate the physical position: + +```c +x = 224 / (NUMBER_OF_COLS - 1) * COL_POSITION +y = 64 / (NUMBER_OF_ROWS - 1) * ROW_POSITION +``` + +Where NUMBER_OF_COLS, NUMBER_OF_ROWS, COL_POSITION, & ROW_POSITION are all based on the physical layout of your keyboard, not the electrical layout. + +As mentioned earlier, the center of the keyboard by default is expected to be `{ 112, 32 }`, but this can be changed if you want to more accurately calculate the LED's physical `{ x, y }` positions. Keyboard designers can implement `#define RGB_MATRIX_CENTER { 112, 32 }` in their config.h file with the new center point of the keyboard, or where they want it to be allowing more possibilities for the `{ x, y }` values. Do note that the maximum value for x or y is 255, and the recommended maximum is 224 as this gives animations runoff room before they reset. + +`// LED Index to Flag` is a bitmask, whether or not a certain LEDs is of a certain type. It is recommended that LEDs are set to only 1 type. + +## Flags {#flags} + +|Define |Value |Description | +|----------------------------|------|-------------------------------------------------| +|`HAS_FLAGS(bits, flags)` |*n/a* |Evaluates to `true` if `bits` has all `flags` set| +|`HAS_ANY_FLAGS(bits, flags)`|*n/a* |Evaluates to `true` if `bits` has any `flags` set| +|`LED_FLAG_NONE` |`0x00`|If this LED has no flags | +|`LED_FLAG_ALL` |`0xFF`|If this LED has all flags | +|`LED_FLAG_MODIFIER` |`0x01`|If the LED is on a modifier key | +|`LED_FLAG_UNDERGLOW` |`0x02`|If the LED is for underglow | +|`LED_FLAG_KEYLIGHT` |`0x04`|If the LED is for key backlight | +|`LED_FLAG_INDICATOR` |`0x08`|If the LED is for keyboard state indication | + +## Keycodes {#keycodes} + +All RGB keycodes are currently shared with the RGBLIGHT system: + +|Key |Aliases |Description | +|-------------------|----------|--------------------------------------------------------------------------------------| +|`RGB_TOG` | |Toggle RGB lighting on or off | +|`RGB_MODE_FORWARD` |`RGB_MOD` |Cycle through modes, reverse direction when Shift is held | +|`RGB_MODE_REVERSE` |`RGB_RMOD`|Cycle through modes in reverse, forward direction when Shift is held | +|`RGB_HUI` | |Increase hue, decrease hue when Shift is held | +|`RGB_HUD` | |Decrease hue, increase hue when Shift is held | +|`RGB_SAI` | |Increase saturation, decrease saturation when Shift is held | +|`RGB_SAD` | |Decrease saturation, increase saturation when Shift is held | +|`RGB_VAI` | |Increase value (brightness), decrease value when Shift is held | +|`RGB_VAD` | |Decrease value (brightness), increase value when Shift is held | +|`RGB_SPI` | |Increase effect speed (does not support eeprom yet), decrease speed when Shift is held| +|`RGB_SPD` | |Decrease effect speed (does not support eeprom yet), increase speed when Shift is held| +|`RGB_MODE_PLAIN` |`RGB_M_P` |Static (no animation) mode | +|`RGB_MODE_BREATHE` |`RGB_M_B` |Breathing animation mode | +|`RGB_MODE_RAINBOW` |`RGB_M_R` |Full gradient scrolling left to right (uses the `RGB_MATRIX_CYCLE_LEFT_RIGHT` mode) | +|`RGB_MODE_SWIRL` |`RGB_M_SW`|Full gradient spinning pinwheel around center of keyboard (uses `RGB_MATRIX_CYCLE_PINWHEEL` mode) | + +* `RGB_MODE_*` keycodes will generally work, but not all of the modes are currently mapped to the correct effects for the RGB Matrix system. + +`RGB_MODE_PLAIN`, `RGB_MODE_BREATHE`, `RGB_MODE_RAINBOW`, and `RGB_MODE_SWIRL` are the only ones that are mapped properly. The rest don't have a direct equivalent, and are not mapped. + +::: tip +`RGB_*` keycodes cannot be used with functions like `tap_code16(RGB_HUD)` as they're not USB HID keycodes. If you wish to replicate similar behaviour in custom code within your firmware (e.g. inside `encoder_update_user()` or `process_record_user()`), the equivalent [RGB functions](#functions) should be used instead. +::: + + +::: warning +By default, if you have both the [RGB Light](rgblight) and the RGB Matrix feature enabled, these keycodes will work for both features, at the same time. You can disable the keycode functionality by defining the `*_DISABLE_KEYCODES` option for the specific feature. +::: + +## RGB Matrix Effects {#rgb-matrix-effects} + +All effects have been configured to support current configuration values (Hue, Saturation, Value, & Speed) unless otherwise noted below. These are the effects that are currently available: + +```c +enum rgb_matrix_effects { + RGB_MATRIX_NONE = 0, + RGB_MATRIX_SOLID_COLOR = 1, // Static single hue, no speed support + RGB_MATRIX_ALPHAS_MODS, // Static dual hue, speed is hue for secondary hue + RGB_MATRIX_GRADIENT_UP_DOWN, // Static gradient top to bottom, speed controls how much gradient changes + RGB_MATRIX_GRADIENT_LEFT_RIGHT, // Static gradient left to right, speed controls how much gradient changes + RGB_MATRIX_BREATHING, // Single hue brightness cycling animation + RGB_MATRIX_BAND_SAT, // Single hue band fading saturation scrolling left to right + RGB_MATRIX_BAND_VAL, // Single hue band fading brightness scrolling left to right + RGB_MATRIX_BAND_PINWHEEL_SAT, // Single hue 3 blade spinning pinwheel fades saturation + RGB_MATRIX_BAND_PINWHEEL_VAL, // Single hue 3 blade spinning pinwheel fades brightness + RGB_MATRIX_BAND_SPIRAL_SAT, // Single hue spinning spiral fades saturation + RGB_MATRIX_BAND_SPIRAL_VAL, // Single hue spinning spiral fades brightness + RGB_MATRIX_CYCLE_ALL, // Full keyboard solid hue cycling through full gradient + RGB_MATRIX_CYCLE_LEFT_RIGHT, // Full gradient scrolling left to right + RGB_MATRIX_CYCLE_UP_DOWN, // Full gradient scrolling top to bottom + RGB_MATRIX_CYCLE_OUT_IN, // Full gradient scrolling out to in + RGB_MATRIX_CYCLE_OUT_IN_DUAL, // Full dual gradients scrolling out to in + RGB_MATRIX_RAINBOW_MOVING_CHEVRON, // Full gradient Chevron shapped scrolling left to right + RGB_MATRIX_CYCLE_PINWHEEL, // Full gradient spinning pinwheel around center of keyboard + RGB_MATRIX_CYCLE_SPIRAL, // Full gradient spinning spiral around center of keyboard + RGB_MATRIX_DUAL_BEACON, // Full gradient spinning around center of keyboard + RGB_MATRIX_RAINBOW_BEACON, // Full tighter gradient spinning around center of keyboard + RGB_MATRIX_RAINBOW_PINWHEELS, // Full dual gradients spinning two halfs of keyboard + RGB_MATRIX_FLOWER_BLOOMING, // Full tighter gradient of first half scrolling left to right and second half scrolling right to left + RGB_MATRIX_RAINDROPS, // Randomly changes a single key's hue + RGB_MATRIX_JELLYBEAN_RAINDROPS, // Randomly changes a single key's hue and saturation + RGB_MATRIX_HUE_BREATHING, // Hue shifts up a slight ammount at the same time, then shifts back + RGB_MATRIX_HUE_PENDULUM, // Hue shifts up a slight ammount in a wave to the right, then back to the left + RGB_MATRIX_HUE_WAVE, // Hue shifts up a slight ammount and then back down in a wave to the right + RGB_MATRIX_PIXEL_FRACTAL, // Single hue fractal filled keys pulsing horizontally out to edges + RGB_MATRIX_PIXEL_FLOW, // Pulsing RGB flow along LED wiring with random hues + RGB_MATRIX_PIXEL_RAIN, // Randomly light keys with random hues + RGB_MATRIX_TYPING_HEATMAP, // How hot is your WPM! + RGB_MATRIX_DIGITAL_RAIN, // That famous computer simulation + RGB_MATRIX_SOLID_REACTIVE_SIMPLE, // Pulses keys hit to hue & value then fades value out + RGB_MATRIX_SOLID_REACTIVE, // Static single hue, pulses keys hit to shifted hue then fades to current hue + RGB_MATRIX_SOLID_REACTIVE_WIDE, // Hue & value pulse near a single key hit then fades value out + RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE, // Hue & value pulse near multiple key hits then fades value out + RGB_MATRIX_SOLID_REACTIVE_CROSS, // Hue & value pulse the same column and row of a single key hit then fades value out + RGB_MATRIX_SOLID_REACTIVE_MULTICROSS, // Hue & value pulse the same column and row of multiple key hits then fades value out + RGB_MATRIX_SOLID_REACTIVE_NEXUS, // Hue & value pulse away on the same column and row of a single key hit then fades value out + RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS, // Hue & value pulse away on the same column and row of multiple key hits then fades value out + RGB_MATRIX_SPLASH, // Full gradient & value pulse away from a single key hit then fades value out + RGB_MATRIX_MULTISPLASH, // Full gradient & value pulse away from multiple key hits then fades value out + RGB_MATRIX_SOLID_SPLASH, // Hue & value pulse away from a single key hit then fades value out + RGB_MATRIX_SOLID_MULTISPLASH, // Hue & value pulse away from multiple key hits then fades value out + RGB_MATRIX_STARLIGHT, // LEDs turn on and off at random at varying brightness, maintaining user set color + RGB_MATRIX_STARLIGHT_DUAL_HUE, // LEDs turn on and off at random at varying brightness, modifies user set hue by +- 30 + RGB_MATRIX_STARLIGHT_DUAL_SAT, // LEDs turn on and off at random at varying brightness, modifies user set saturation by +- 30 + RGB_MATRIX_RIVERFLOW, // Modification to breathing animation, offset's animation depending on key location to simulate a river flowing + RGB_MATRIX_EFFECT_MAX +}; +``` + +You can enable a single effect by defining `ENABLE_[EFFECT_NAME]` in your `config.h`: + + +|Define |Description | +|------------------------------------------------------|----------------------------------------------| +|`#define ENABLE_RGB_MATRIX_ALPHAS_MODS` |Enables `RGB_MATRIX_ALPHAS_MODS` | +|`#define ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN` |Enables `RGB_MATRIX_GRADIENT_UP_DOWN` | +|`#define ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT` |Enables `RGB_MATRIX_GRADIENT_LEFT_RIGHT` | +|`#define ENABLE_RGB_MATRIX_BREATHING` |Enables `RGB_MATRIX_BREATHING` | +|`#define ENABLE_RGB_MATRIX_BAND_SAT` |Enables `RGB_MATRIX_BAND_SAT` | +|`#define ENABLE_RGB_MATRIX_BAND_VAL` |Enables `RGB_MATRIX_BAND_VAL` | +|`#define ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT` |Enables `RGB_MATRIX_BAND_PINWHEEL_SAT` | +|`#define ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL` |Enables `RGB_MATRIX_BAND_PINWHEEL_VAL` | +|`#define ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT` |Enables `RGB_MATRIX_BAND_SPIRAL_SAT` | +|`#define ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL` |Enables `RGB_MATRIX_BAND_SPIRAL_VAL` | +|`#define ENABLE_RGB_MATRIX_CYCLE_ALL` |Enables `RGB_MATRIX_CYCLE_ALL` | +|`#define ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT` |Enables `RGB_MATRIX_CYCLE_LEFT_RIGHT` | +|`#define ENABLE_RGB_MATRIX_CYCLE_UP_DOWN` |Enables `RGB_MATRIX_CYCLE_UP_DOWN` | +|`#define ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON` |Enables `RGB_MATRIX_RAINBOW_MOVING_CHEVRON` | +|`#define ENABLE_RGB_MATRIX_CYCLE_OUT_IN` |Enables `RGB_MATRIX_CYCLE_OUT_IN` | +|`#define ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL` |Enables `RGB_MATRIX_CYCLE_OUT_IN_DUAL` | +|`#define ENABLE_RGB_MATRIX_CYCLE_PINWHEEL` |Enables `RGB_MATRIX_CYCLE_PINWHEEL` | +|`#define ENABLE_RGB_MATRIX_CYCLE_SPIRAL` |Enables `RGB_MATRIX_CYCLE_SPIRAL` | +|`#define ENABLE_RGB_MATRIX_DUAL_BEACON` |Enables `RGB_MATRIX_DUAL_BEACON` | +|`#define ENABLE_RGB_MATRIX_RAINBOW_BEACON` |Enables `RGB_MATRIX_RAINBOW_BEACON` | +|`#define ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS` |Enables `RGB_MATRIX_RAINBOW_PINWHEELS` | +|`#define ENABLE_RGB_MATRIX_FLOWER_BLOOMING` |Enables `RGB_MATRIX_FLOWER_BLOOMING` | +|`#define ENABLE_RGB_MATRIX_RAINDROPS` |Enables `RGB_MATRIX_RAINDROPS` | +|`#define ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS` |Enables `RGB_MATRIX_JELLYBEAN_RAINDROPS` | +|`#define ENABLE_RGB_MATRIX_HUE_BREATHING` |Enables `RGB_MATRIX_HUE_BREATHING` | +|`#define ENABLE_RGB_MATRIX_HUE_PENDULUM` |Enables `RGB_MATRIX_HUE_PENDULUM` | +|`#define ENABLE_RGB_MATRIX_HUE_WAVE` |Enables `RGB_MATRIX_HUE_WAVE ` | +|`#define ENABLE_RGB_MATRIX_PIXEL_FRACTAL` |Enables `RGB_MATRIX_PIXEL_FRACTAL` | +|`#define ENABLE_RGB_MATRIX_PIXEL_FLOW` |Enables `RGB_MATRIX_PIXEL_FLOW` | +|`#define ENABLE_RGB_MATRIX_PIXEL_RAIN` |Enables `RGB_MATRIX_PIXEL_RAIN` | +|`#define ENABLE_RGB_MATRIX_STARLIGHT` |Enables `RGB_MATRIX_STARLIGHT` | +|`#define ENABLE_RGB_MATRIX_STARLIGHT_DUAL_HUE` |Enables `RGB_MATRIX_STARLIGHT_DUAL_HUE` | +|`#define ENABLE_RGB_MATRIX_STARLIGHT_DUAL_SAT` |Enables `RGB_MATRIX_STARLIGHT_DUAL_SAT` | +|`#define ENABLE_RGB_MATRIX_RIVERFLOW` |Enables `RGB_MATRIX_RIVERFLOW` | + +|Framebuffer Defines |Description | +|------------------------------------------------------|----------------------------------------------| +|`#define ENABLE_RGB_MATRIX_TYPING_HEATMAP` |Enables `RGB_MATRIX_TYPING_HEATMAP` | +|`#define ENABLE_RGB_MATRIX_DIGITAL_RAIN` |Enables `RGB_MATRIX_DIGITAL_RAIN` | + +::: tip +These modes introduce additional logic that can increase firmware size. +::: + +|Reactive Defines |Description | +|------------------------------------------------------|----------------------------------------------| +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE` |Enables `RGB_MATRIX_SOLID_REACTIVE_SIMPLE` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE` |Enables `RGB_MATRIX_SOLID_REACTIVE` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE` |Enables `RGB_MATRIX_SOLID_REACTIVE_WIDE` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE` |Enables `RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS` |Enables `RGB_MATRIX_SOLID_REACTIVE_CROSS` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS` |Enables `RGB_MATRIX_SOLID_REACTIVE_MULTICROSS`| +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS` |Enables `RGB_MATRIX_SOLID_REACTIVE_NEXUS` | +|`#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS` |Enables `RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS`| +|`#define ENABLE_RGB_MATRIX_SPLASH` |Enables `RGB_MATRIX_SPLASH` | +|`#define ENABLE_RGB_MATRIX_MULTISPLASH` |Enables `RGB_MATRIX_MULTISPLASH` | +|`#define ENABLE_RGB_MATRIX_SOLID_SPLASH` |Enables `RGB_MATRIX_SOLID_SPLASH` | +|`#define ENABLE_RGB_MATRIX_SOLID_MULTISPLASH` |Enables `RGB_MATRIX_SOLID_MULTISPLASH` | + +::: tip +These modes introduce additional logic that can increase firmware size. +::: + + +### RGB Matrix Effect Typing Heatmap {#rgb-matrix-effect-typing-heatmap} + +This effect will color the RGB matrix according to a heatmap of recently pressed keys. Whenever a key is pressed its "temperature" increases as well as that of its neighboring keys. The temperature of each key is then decreased automatically every 25 milliseconds by default. + +In order to change the delay of temperature decrease define `RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS`: + +```c +#define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 50 +``` + +As heatmap uses the physical position of the leds set in the g_led_config, you may need to tweak the following options to get the best effect for your keyboard. Note the size of this grid is `224x64`. + +Limit the distance the effect spreads to surrounding keys. + +```c +#define RGB_MATRIX_TYPING_HEATMAP_SPREAD 40 +``` + +Limit how hot surrounding keys get from each press. + +```c +#define RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16 +``` + +Remove the spread effect entirely. + +```c +#define RGB_MATRIX_TYPING_HEATMAP_SLIM +``` + +It's also possible to adjust the tempo of *heating up*. It's defined as the number of shades that are +increased on the [HSV scale](https://en.wikipedia.org/wiki/HSL_and_HSV). Decreasing this value increases +the number of keystrokes needed to fully heat up the key. + +```c +#define RGB_MATRIX_TYPING_HEATMAP_INCREASE_STEP 32 +``` + +### RGB Matrix Effect Solid Reactive {#rgb-matrix-effect-solid-reactive} + +Solid reactive effects will pulse RGB light on key presses with user configurable hues. To enable gradient mode that will automatically change reactive color, add the following define: + +```c +#define RGB_MATRIX_SOLID_REACTIVE_GRADIENT_MODE +``` + +Gradient mode will loop through the color wheel hues over time and its duration can be controlled with the effect speed keycodes (`RGB_SPI`/`RGB_SPD`). + +## Custom RGB Matrix Effects {#custom-rgb-matrix-effects} + +By setting `RGB_MATRIX_CUSTOM_USER = yes` in `rules.mk`, new effects can be defined directly from your keymap or userspace, without having to edit any QMK core files. To declare new effects, create a `rgb_matrix_user.inc` file in the user keymap directory or userspace folder. + +::: tip +Hardware maintainers who want to limit custom effects to a specific keyboard can create a `rgb_matrix_kb.inc` file in the root of the keyboard directory, and add `RGB_MATRIX_CUSTOM_KB = yes` to the keyboard level `rules.mk`. +::: + +To use custom effects in your code, simply prepend `RGB_MATRIX_CUSTOM_` to the effect name specified in `RGB_MATRIX_EFFECT()`. For example, an effect declared as `RGB_MATRIX_EFFECT(my_cool_effect)` would be referenced with: + +```c +rgb_matrix_mode(RGB_MATRIX_CUSTOM_my_cool_effect); +``` + +```c +// !!! DO NOT ADD #pragma once !!! // + +// Step 1. +// Declare custom effects using the RGB_MATRIX_EFFECT macro +// (note the lack of semicolon after the macro!) +RGB_MATRIX_EFFECT(my_cool_effect) +RGB_MATRIX_EFFECT(my_cool_effect2) + +// Step 2. +// Define effects inside the `RGB_MATRIX_CUSTOM_EFFECT_IMPLS` ifdef block +#ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS + +// e.g: A simple effect, self-contained within a single method +static bool my_cool_effect(effect_params_t* params) { + RGB_MATRIX_USE_LIMITS(led_min, led_max); + for (uint8_t i = led_min; i < led_max; i++) { + rgb_matrix_set_color(i, 0xff, 0xff, 0x00); + } + return rgb_matrix_check_finished_leds(led_max); +} + +// e.g: A more complex effect, relying on external methods and state, with +// dedicated init and run methods +static uint8_t some_global_state; +static void my_cool_effect2_complex_init(effect_params_t* params) { + some_global_state = 1; +} +static bool my_cool_effect2_complex_run(effect_params_t* params) { + RGB_MATRIX_USE_LIMITS(led_min, led_max); + for (uint8_t i = led_min; i < led_max; i++) { + rgb_matrix_set_color(i, 0xff, some_global_state++, 0xff); + } + return rgb_matrix_check_finished_leds(led_max); +} +static bool my_cool_effect2(effect_params_t* params) { + if (params->init) my_cool_effect2_complex_init(params); + return my_cool_effect2_complex_run(params); +} + +#endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS +``` + +For inspiration and examples, check out the built-in effects under `quantum/rgb_matrix/animations/`. + + +## Colors {#colors} + +These are shorthands to popular colors. The `RGB` ones can be passed to the `setrgb` functions, while the `HSV` ones to the `sethsv` functions. + +|RGB |HSV | +|---------------------|---------------------| +|`RGB_AZURE` |`HSV_AZURE` | +|`RGB_BLACK`/`RGB_OFF`|`HSV_BLACK`/`HSV_OFF`| +|`RGB_BLUE` |`HSV_BLUE` | +|`RGB_CHARTREUSE` |`HSV_CHARTREUSE` | +|`RGB_CORAL` |`HSV_CORAL` | +|`RGB_CYAN` |`HSV_CYAN` | +|`RGB_GOLD` |`HSV_GOLD` | +|`RGB_GOLDENROD` |`HSV_GOLDENROD` | +|`RGB_GREEN` |`HSV_GREEN` | +|`RGB_MAGENTA` |`HSV_MAGENTA` | +|`RGB_ORANGE` |`HSV_ORANGE` | +|`RGB_PINK` |`HSV_PINK` | +|`RGB_PURPLE` |`HSV_PURPLE` | +|`RGB_RED` |`HSV_RED` | +|`RGB_SPRINGGREEN` |`HSV_SPRINGGREEN` | +|`RGB_TEAL` |`HSV_TEAL` | +|`RGB_TURQUOISE` |`HSV_TURQUOISE` | +|`RGB_WHITE` |`HSV_WHITE` | +|`RGB_YELLOW` |`HSV_YELLOW` | + +These are defined in [`color.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/color.h). Feel free to add to this list! + + +## Additional `config.h` Options {#additional-configh-options} + +```c +#define RGB_MATRIX_KEYRELEASES // reactive effects respond to keyreleases (instead of keypresses) +#define RGB_MATRIX_TIMEOUT 0 // number of milliseconds to wait until rgb automatically turns off +#define RGB_MATRIX_SLEEP // turn off effects when suspended +#define RGB_MATRIX_LED_PROCESS_LIMIT (RGB_MATRIX_LED_COUNT + 4) / 5 // limits the number of LEDs to process in an animation per task run (increases keyboard responsiveness) +#define RGB_MATRIX_LED_FLUSH_LIMIT 16 // limits in milliseconds how frequently an animation will update the LEDs. 16 (16ms) is equivalent to limiting to 60fps (increases keyboard responsiveness) +#define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200 // limits maximum brightness of LEDs to 200 out of 255. If not defined maximum brightness is set to 255 +#define RGB_MATRIX_DEFAULT_ON true // Sets the default enabled state, if none has been set +#define RGB_MATRIX_DEFAULT_MODE RGB_MATRIX_CYCLE_LEFT_RIGHT // Sets the default mode, if none has been set +#define RGB_MATRIX_DEFAULT_HUE 0 // Sets the default hue value, if none has been set +#define RGB_MATRIX_DEFAULT_SAT 255 // Sets the default saturation value, if none has been set +#define RGB_MATRIX_DEFAULT_VAL RGB_MATRIX_MAXIMUM_BRIGHTNESS // Sets the default brightness value, if none has been set +#define RGB_MATRIX_DEFAULT_SPD 127 // Sets the default animation speed, if none has been set +#define RGB_MATRIX_DEFAULT_FLAGS LED_FLAG_ALL // Sets the default LED flags, if none has been set +#define RGB_MATRIX_DISABLE_KEYCODES // disables control of rgb matrix by keycodes (must use code functions to control the feature) +#define RGB_MATRIX_SPLIT { X, Y } // (Optional) For split keyboards, the number of LEDs connected on each half. X = left, Y = Right. + // If reactive effects are enabled, you also will want to enable SPLIT_TRANSPORT_MIRROR +#define RGB_TRIGGER_ON_KEYDOWN // Triggers RGB keypress events on key down. This makes RGB control feel more responsive. This may cause RGB to not function properly on some boards +``` + +## EEPROM storage {#eeprom-storage} + +The EEPROM for it is currently shared with the LED Matrix system (it's generally assumed only one feature would be used at a time). + +## Functions {#functions} + +### Direct Operation {#direct-operation} +|Function |Description | +|--------------------------------------------|-------------| +|`rgb_matrix_set_color_all(r, g, b)` |Set all of the LEDs to the given RGB value, where `r`/`g`/`b` are between 0 and 255 (not written to EEPROM) | +|`rgb_matrix_set_color(index, r, g, b)` |Set a single LED to the given RGB value, where `r`/`g`/`b` are between 0 and 255, and `index` is between 0 and `RGB_MATRIX_LED_COUNT` (not written to EEPROM) | + +### Disable/Enable Effects {#disable-enable-effects} +|Function |Description | +|--------------------------------------------|-------------| +|`rgb_matrix_toggle()` |Toggle effect range LEDs between on and off | +|`rgb_matrix_toggle_noeeprom()` |Toggle effect range LEDs between on and off (not written to EEPROM) | +|`rgb_matrix_enable()` |Turn effect range LEDs on, based on their previous state | +|`rgb_matrix_enable_noeeprom()` |Turn effect range LEDs on, based on their previous state (not written to EEPROM) | +|`rgb_matrix_disable()` |Turn effect range LEDs off, based on their previous state | +|`rgb_matrix_disable_noeeprom()` |Turn effect range LEDs off, based on their previous state (not written to EEPROM) | + +### Change Effect Mode {#change-effect-mode} +|Function |Description | +|--------------------------------------------|-------------| +|`rgb_matrix_mode(mode)` |Set the mode, if RGB animations are enabled | +|`rgb_matrix_mode_noeeprom(mode)` |Set the mode, if RGB animations are enabled (not written to EEPROM) | +|`rgb_matrix_step()` |Change the mode to the next RGB animation in the list of enabled RGB animations | +|`rgb_matrix_step_noeeprom()` |Change the mode to the next RGB animation in the list of enabled RGB animations (not written to EEPROM) | +|`rgb_matrix_step_reverse()` |Change the mode to the previous RGB animation in the list of enabled RGB animations | +|`rgb_matrix_step_reverse_noeeprom()` |Change the mode to the previous RGB animation in the list of enabled RGB animations (not written to EEPROM) | +|`rgb_matrix_increase_speed()` |Increase the speed of the animations | +|`rgb_matrix_increase_speed_noeeprom()` |Increase the speed of the animations (not written to EEPROM) | +|`rgb_matrix_decrease_speed()` |Decrease the speed of the animations | +|`rgb_matrix_decrease_speed_noeeprom()` |Decrease the speed of the animations (not written to EEPROM) | +|`rgb_matrix_set_speed(speed)` |Set the speed of the animations to the given value where `speed` is between 0 and 255 | +|`rgb_matrix_set_speed_noeeprom(speed)` |Set the speed of the animations to the given value where `speed` is between 0 and 255 (not written to EEPROM) | +|`rgb_matrix_reload_from_eeprom()` |Reload the effect configuration (enabled, mode and color) from EEPROM | + +### Change Color {#change-color} +|Function |Description | +|--------------------------------------------|-------------| +|`rgb_matrix_increase_hue()` |Increase the hue for effect range LEDs. This wraps around at maximum hue | +|`rgb_matrix_increase_hue_noeeprom()` |Increase the hue for effect range LEDs. This wraps around at maximum hue (not written to EEPROM) | +|`rgb_matrix_decrease_hue()` |Decrease the hue for effect range LEDs. This wraps around at minimum hue | +|`rgb_matrix_decrease_hue_noeeprom()` |Decrease the hue for effect range LEDs. This wraps around at minimum hue (not written to EEPROM) | +|`rgb_matrix_increase_sat()` |Increase the saturation for effect range LEDs. This wraps around at maximum saturation | +|`rgb_matrix_increase_sat_noeeprom()` |Increase the saturation for effect range LEDs. This wraps around at maximum saturation (not written to EEPROM) | +|`rgb_matrix_decrease_sat()` |Decrease the saturation for effect range LEDs. This wraps around at minimum saturation | +|`rgb_matrix_decrease_sat_noeeprom()` |Decrease the saturation for effect range LEDs. This wraps around at minimum saturation (not written to EEPROM) | +|`rgb_matrix_increase_val()` |Increase the value for effect range LEDs. This wraps around at maximum value | +|`rgb_matrix_increase_val_noeeprom()` |Increase the value for effect range LEDs. This wraps around at maximum value (not written to EEPROM) | +|`rgb_matrix_decrease_val()` |Decrease the value for effect range LEDs. This wraps around at minimum value | +|`rgb_matrix_decrease_val_noeeprom()` |Decrease the value for effect range LEDs. This wraps around at minimum value (not written to EEPROM) | +|`rgb_matrix_sethsv(h, s, v)` |Set LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 | +|`rgb_matrix_sethsv_noeeprom(h, s, v)` |Set LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) | + +### Query Current Status {#query-current-status} +|Function |Description | +|---------------------------------|---------------------------| +|`rgb_matrix_is_enabled()` |Gets current on/off status | +|`rgb_matrix_get_mode()` |Gets current mode | +|`rgb_matrix_get_hue()` |Gets current hue | +|`rgb_matrix_get_sat()` |Gets current sat | +|`rgb_matrix_get_val()` |Gets current val | +|`rgb_matrix_get_hsv()` |Gets hue, sat, and val and returns a [`HSV` structure](https://github.com/qmk/qmk_firmware/blob/7ba6456c0b2e041bb9f97dbed265c5b8b4b12192/quantum/color.h#L56-L61)| +|`rgb_matrix_get_speed()` |Gets current speed | +|`rgb_matrix_get_suspend_state()` |Gets current suspend state | + +## Callbacks {#callbacks} + +### Indicators {#indicators} + +If you want to set custom indicators, such as an LED for Caps Lock, or layer indication, then you can use the `rgb_matrix_indicators_kb` function on the keyboard level source file, or `rgb_matrix_indicators_user` function in the user `keymap.c`. +```c +bool rgb_matrix_indicators_kb(void) { + if (!rgb_matrix_indicators_user()) { + return false; + } + rgb_matrix_set_color(index, red, green, blue); + return true; +} +``` + +In addition, there are the advanced indicator functions. These are aimed at those with heavily customized displays, where rendering every LED per cycle is expensive. Such as some of the "drashna" layouts. This includes a special macro to help make this easier to use: `RGB_MATRIX_INDICATOR_SET_COLOR(i, r, g, b)`. + +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + RGB_MATRIX_INDICATOR_SET_COLOR(index, red, green, blue); + return false; +} +``` + +### Indicator Examples {#indicator-examples} + +Caps Lock indicator on alphanumeric flagged keys: +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + if (host_keyboard_led_state().caps_lock) { + for (uint8_t i = led_min; i < led_max; i++) { + if (g_led_config.flags[i] & LED_FLAG_KEYLIGHT) { + rgb_matrix_set_color(i, RGB_RED); + } + } + } + return false; +} +``` + +Layer indicator on all keys: +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + for (uint8_t i = led_min; i < led_max; i++) { + switch(get_highest_layer(layer_state|default_layer_state)) { + case 2: + rgb_matrix_set_color(i, RGB_BLUE); + break; + case 1: + rgb_matrix_set_color(i, RGB_YELLOW); + break; + default: + break; + } + } + return false; +} +``` + +Layer indicator only on keys with configured keycodes: +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + if (get_highest_layer(layer_state) > 0) { + uint8_t layer = get_highest_layer(layer_state); + + for (uint8_t row = 0; row < MATRIX_ROWS; ++row) { + for (uint8_t col = 0; col < MATRIX_COLS; ++col) { + uint8_t index = g_led_config.matrix_co[row][col]; + + if (index >= led_min && index < led_max && index != NO_LED && + keymap_key_to_keycode(layer, (keypos_t){col,row}) > KC_TRNS) { + rgb_matrix_set_color(index, RGB_GREEN); + } + } + } + } + return false; +} +``` + +::: tip +Split keyboards will require layer state data syncing with `#define SPLIT_LAYER_STATE_ENABLE`. See [Data Sync Options](split_keyboard#data-sync-options) for more details. +::: + +#### Examples {#indicator-examples-2} + +This example sets the modifiers to be a specific color based on the layer state. You can use a switch case here, instead, if you would like. This uses HSV and then converts to RGB, because this allows the brightness to be limited (important when using the WS2812 driver). + +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + HSV hsv = {0, 255, 255}; + + if (layer_state_is(layer_state, 2)) { + hsv = {130, 255, 255}; + } else { + hsv = {30, 255, 255}; + } + + if (hsv.v > rgb_matrix_get_val()) { + hsv.v = rgb_matrix_get_val(); + } + RGB rgb = hsv_to_rgb(hsv); + + for (uint8_t i = led_min; i < led_max; i++) { + if (HAS_FLAGS(g_led_config.flags[i], 0x01)) { // 0x01 == LED_FLAG_MODIFIER + rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); + } + } + return false; +} +``` + +If you want to indicate a Host LED status (caps lock, num lock, etc), you can use something like this to light up the caps lock key: + +```c +bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { + if (host_keyboard_led_state().caps_lock) { + RGB_MATRIX_INDICATOR_SET_COLOR(5, 255, 255, 255); // assuming caps lock is at led #5 + } else { + RGB_MATRIX_INDICATOR_SET_COLOR(5, 0, 0, 0); + } + return false; +} +``` + +::: tip +RGB indicators on split keyboards will require state information synced to the slave half (e.g. `#define SPLIT_LAYER_STATE_ENABLE`). See [data sync options](split_keyboard#data-sync-options) for more details. +::: + +#### Indicators without RGB Matrix Effect + +If you want to just use RGB indicators without RGB matrix effect, it is not possible to disable the latter because toggling RGB off will disable everything. You can workaround it with solid effect and colors off using this init function: +```c +void keyboard_post_init_user(void) { + rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR); + rgb_matrix_sethsv_noeeprom(HSV_OFF); +} +``` diff --git a/docs/features/rgblight.md b/docs/features/rgblight.md new file mode 100644 index 0000000000..ece1c10467 --- /dev/null +++ b/docs/features/rgblight.md @@ -0,0 +1,581 @@ +# RGB Lighting + +QMK has the ability to control RGB LEDs attached to your keyboard. This is commonly called *underglow*, due to the LEDs often being mounted on the bottom of the keyboard, producing a nice diffused effect when combined with a translucent case. + +![Planck with RGB Underglow](https://raw.githubusercontent.com/qmk/qmk_firmware/3774a7fcdab5544fc787f4c200be05fcd417e31f/keyboards/planck/keymaps/yang/planck-with-rgb-underglow.jpg) + +Some keyboards come with RGB LEDs preinstalled. Others must have them installed after the fact. See the [Hardware Modification](#hardware-modification) section for information on adding RGB lighting to your keyboard. + +Currently QMK supports the following addressable LEDs: + + * WS2811, WS2812, WS2812B, WS2812C, etc. + * SK6812, SK6812MINI, SK6805 + * APA102 + +These LEDs are called "addressable" because instead of using a wire per color, each LED contains a small microchip that understands a special protocol sent over a single wire. The chip passes on the remaining data to the next LED, allowing them to be chained together. In this way, you can easily control the color of the individual LEDs. + +## Usage + +On keyboards with onboard RGB LEDs, it is usually enabled by default. If it is not working for you, check that your `rules.mk` includes the following: + +```make +RGBLIGHT_ENABLE = yes +``` + +::: tip +There are additional configuration options for ARM controllers that offer increased performance over the default WS2812 bitbang driver. Please see [WS2812 Driver](../drivers/ws2812) for more information. +::: + +For APA102 LEDs, add the following to your `rules.mk`: + +```make +RGBLIGHT_ENABLE = yes +RGBLIGHT_DRIVER = apa102 +``` + +At minimum you must define the data pin your LED strip is connected to, and the number of LEDs in the strip, in your `config.h`. For APA102 LEDs, you must also define the clock pin. If your keyboard has onboard RGB LEDs, and you are simply creating a keymap, you usually won't need to modify these. + +|Define |Description | +|--------------------|-------------------------------------------------------------------------| +|`WS2812_DI_PIN` |The pin connected to the data pin of the LEDs (WS2812) | +|`APA102_DI_PIN` |The pin connected to the data pin of the LEDs (APA102) | +|`APA102_CI_PIN` |The pin connected to the clock pin of the LEDs (APA102) | +|`RGBLIGHT_LED_COUNT`|The number of LEDs connected | +|`RGBLED_SPLIT` |(Optional) For split keyboards, the number of LEDs connected on each half| + +Then you should be able to use the keycodes below to change the RGB lighting to your liking. + +### Color Selection + +QMK uses [Hue, Saturation, and Value](https://en.wikipedia.org/wiki/HSL_and_HSV) to select colors rather than RGB. The color wheel below demonstrates how this works. + +<img src="../public/color-wheel.svg" alt="HSV Color Wheel" width="250"/> + +Changing the **Hue** cycles around the circle.<br> +Changing the **Saturation** moves between the inner and outer sections of the wheel, affecting the intensity of the color.<br> +Changing the **Value** sets the overall brightness.<br> + +![QMK Color Wheel with HSV Values](https://i.imgur.com/vkYVo66.jpg) + +## Keycodes + +|Key |Aliases |Description | +|-------------------|----------|--------------------------------------------------------------------| +|`RGB_TOG` | |Toggle RGB lighting on or off | +|`RGB_MODE_FORWARD` |`RGB_MOD` |Cycle through modes, reverse direction when Shift is held | +|`RGB_MODE_REVERSE` |`RGB_RMOD`|Cycle through modes in reverse, forward direction when Shift is held| +|`RGB_HUI` | |Increase hue, decrease hue when Shift is held | +|`RGB_HUD` | |Decrease hue, increase hue when Shift is held | +|`RGB_SAI` | |Increase saturation, decrease saturation when Shift is held | +|`RGB_SAD` | |Decrease saturation, increase saturation when Shift is held | +|`RGB_VAI` | |Increase value (brightness), decrease value when Shift is held | +|`RGB_VAD` | |Decrease value (brightness), increase value when Shift is held | +|`RGB_MODE_PLAIN` |`RGB_M_P `|Static (no animation) mode | +|`RGB_MODE_BREATHE` |`RGB_M_B` |Breathing animation mode | +|`RGB_MODE_RAINBOW` |`RGB_M_R` |Rainbow animation mode | +|`RGB_MODE_SWIRL` |`RGB_M_SW`|Swirl animation mode | +|`RGB_MODE_SNAKE` |`RGB_M_SN`|Snake animation mode | +|`RGB_MODE_KNIGHT` |`RGB_M_K` |"Knight Rider" animation mode | +|`RGB_MODE_XMAS` |`RGB_M_X` |Christmas animation mode | +|`RGB_MODE_GRADIENT`|`RGB_M_G` |Static gradient animation mode | +|`RGB_MODE_RGBTEST` |`RGB_M_T` |Red, Green, Blue test animation mode | +|`RGB_MODE_TWINKLE` |`RGB_M_TW`|Twinkle animation mode | + +::: tip +`RGB_*` keycodes cannot be used with functions like `tap_code16(RGB_HUI)` as they're not USB HID keycodes. If you wish to replicate similar behaviour in custom code within your firmware (e.g. inside `encoder_update_user()` or `process_record_user()`), the equivalent [RGB functions](#functions) should be used instead. +::: + + +::: warning +By default, if you have both the RGB Light and the [RGB Matrix](rgb_matrix) feature enabled, these keycodes will work for both features, at the same time. You can disable the keycode functionality by defining the `*_DISABLE_KEYCODES` option for the specific feature. +::: + +## Configuration + +Your RGB lighting can be configured by placing these `#define`s in your `config.h`: + +|Define |Default |Description | +|---------------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------| +|`RGBLIGHT_HUE_STEP` |`8` |The number of steps to cycle through the hue by | +|`RGBLIGHT_SAT_STEP` |`17` |The number of steps to increment the saturation by | +|`RGBLIGHT_VAL_STEP` |`17` |The number of steps to increment the brightness by | +|`RGBLIGHT_LIMIT_VAL` |`255` |The maximum brightness level | +|`RGBLIGHT_SLEEP` |*Not defined* |If defined, the RGB lighting will be switched off when the host goes to sleep | +|`RGBLIGHT_SPLIT` |*Not defined* |If defined, synchronization functionality for split keyboards is added | +|`RGBLIGHT_DISABLE_KEYCODES`|*Not defined* |If defined, disables the ability to control RGB Light from the keycodes. You must use code functions to control the feature| +|`RGBLIGHT_DEFAULT_MODE` |`RGBLIGHT_MODE_STATIC_LIGHT`|The default mode to use upon clearing the EEPROM | +|`RGBLIGHT_DEFAULT_HUE` |`0` (red) |The default hue to use upon clearing the EEPROM | +|`RGBLIGHT_DEFAULT_SAT` |`UINT8_MAX` (255) |The default saturation to use upon clearing the EEPROM | +|`RGBLIGHT_DEFAULT_VAL` |`RGBLIGHT_LIMIT_VAL` |The default value (brightness) to use upon clearing the EEPROM | +|`RGBLIGHT_DEFAULT_SPD` |`0` |The default speed to use upon clearing the EEPROM | +|`RGBLIGHT_DEFAULT_ON` |`true` |Enable RGB lighting upon clearing the EEPROM | + +## Effects and Animations + +Not only can this lighting be whatever color you want, +if `RGBLIGHT_EFFECT_xxxx` is defined, you also have a number of animation modes at your disposal: + +|Mode number symbol |Additional number |Description | +|-----------------------------|-------------------|---------------------------------------| +|`RGBLIGHT_MODE_STATIC_LIGHT` | *None* |Solid color (this mode is always enabled) | +|`RGBLIGHT_MODE_BREATHING` | 0,1,2,3 |Solid color breathing | +|`RGBLIGHT_MODE_RAINBOW_MOOD` | 0,1,2 |Cycling rainbow | +|`RGBLIGHT_MODE_RAINBOW_SWIRL`| 0,1,2,3,4,5 |Swirling rainbow | +|`RGBLIGHT_MODE_SNAKE` | 0,1,2,3,4,5 |Snake | +|`RGBLIGHT_MODE_KNIGHT` | 0,1,2 |Knight | +|`RGBLIGHT_MODE_CHRISTMAS` | *None* |Christmas | +|`RGBLIGHT_MODE_STATIC_GRADIENT`| 0,1,..,9 |Static gradient | +|`RGBLIGHT_MODE_RGB_TEST` | *None* |RGB Test | +|`RGBLIGHT_MODE_ALTERNATING` | *None* |Alternating | +|`RGBLIGHT_MODE_TWINKLE` | 0,1,2,3,4,5 |Twinkle | + +Check out [this video](https://youtube.com/watch?v=VKrpPAHlisY) for a demonstration. + +Note: For versions older than 0.6.117, The mode numbers were written directly. In `quantum/rgblight/rgblight.h` there is a contrast table between the old mode number and the current symbol. + + +### Effect and Animation Toggles + +Use these defines to add or remove animations from the firmware. When you are running low on flash space, it can be helpful to disable animations you are not using. + +|Define |Default |Description | +|------------------------------------|-------------|-------------------------------------------------------------------------| +|`RGBLIGHT_ANIMATIONS` |*Not defined*|Enable all additional animation modes. (deprecated) | +|`RGBLIGHT_EFFECT_ALTERNATING` |*Not defined*|Enable alternating animation mode. | +|`RGBLIGHT_EFFECT_BREATHING` |*Not defined*|Enable breathing animation mode. | +|`RGBLIGHT_EFFECT_CHRISTMAS` |*Not defined*|Enable christmas animation mode. | +|`RGBLIGHT_EFFECT_KNIGHT` |*Not defined*|Enable knight animation mode. | +|`RGBLIGHT_EFFECT_RAINBOW_MOOD` |*Not defined*|Enable rainbow mood animation mode. | +|`RGBLIGHT_EFFECT_RAINBOW_SWIRL` |*Not defined*|Enable rainbow swirl animation mode. | +|`RGBLIGHT_EFFECT_RGB_TEST` |*Not defined*|Enable RGB test animation mode. | +|`RGBLIGHT_EFFECT_SNAKE` |*Not defined*|Enable snake animation mode. | +|`RGBLIGHT_EFFECT_STATIC_GRADIENT` |*Not defined*|Enable static gradient mode. | +|`RGBLIGHT_EFFECT_TWINKLE` |*Not defined*|Enable twinkle animation mode. | + +::: warning +`RGBLIGHT_ANIMATIONS` is being deprecated and animation modes should be explicitly defined. +::: + +### Effect and Animation Settings + +The following options are used to tweak the various animations: + +|Define |Default |Description | +|------------------------------------|--------------------|-----------------------------------------------------------------------------------------------| +|`RGBLIGHT_EFFECT_BREATHE_CENTER` |*Not defined* |If defined, used to calculate the curve for the breathing animation. Valid values are 1.0 to 2.7 | +|`RGBLIGHT_EFFECT_BREATHE_MAX` |`255` |The maximum brightness for the breathing mode. Valid values are 1 to 255 | +|`RGBLIGHT_EFFECT_CHRISTMAS_INTERVAL`|`40` |How long (in milliseconds) to wait between animation steps for the "Christmas" animation | +|`RGBLIGHT_EFFECT_CHRISTMAS_STEP` |`2` |The number of LEDs to group the red/green colors by for the "Christmas" animation | +|`RGBLIGHT_EFFECT_KNIGHT_LED_NUM` |`RGBLIGHT_LED_COUNT`|The number of LEDs to have the "Knight" animation travel | +|`RGBLIGHT_EFFECT_KNIGHT_LENGTH` |`3` |The number of LEDs to light up for the "Knight" animation | +|`RGBLIGHT_EFFECT_KNIGHT_OFFSET` |`0` |The number of LEDs to start the "Knight" animation from the start of the strip by | +|`RGBLIGHT_RAINBOW_SWIRL_RANGE` |`255` |Range adjustment for the rainbow swirl effect to get different swirls | +|`RGBLIGHT_EFFECT_SNAKE_LENGTH` |`4` |The number of LEDs to light up for the "Snake" animation | +|`RGBLIGHT_EFFECT_TWINKLE_LIFE` |`200` |Adjusts how quickly each LED brightens and dims when twinkling (in animation steps) | +|`RGBLIGHT_EFFECT_TWINKLE_PROBABILITY`|`1/127` |Adjusts how likely each LED is to twinkle (on each animation step) | + +### Example Usage to Reduce Memory Footprint + 1. Use `#undef` to selectively disable animations. The following would disable two animations and save about 4KiB: + +```diff + #undef RGBLIGHT_LED_COUNT ++#undef RGBLIGHT_EFFECT_STATIC_GRADIENT ++#undef RGBLIGHT_EFFECT_RAINBOW_SWIRL + #define RGBLIGHT_LED_COUNT 12 + #define RGBLIGHT_HUE_STEP 8 + #define RGBLIGHT_SAT_STEP 8 +``` + +### Animation Speed + +You can also modify the speeds that the different modes animate at: + +Here is a quick demo on Youtube (with NPKC KC60) (https://www.youtube.com/watch?v=VKrpPAHlisY). + +```c +// How long (in milliseconds) to wait between animation steps for each of the "Solid color breathing" animations +const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5}; + +// How long (in milliseconds) to wait between animation steps for each of the "Cycling rainbow" animations +const uint8_t RGBLED_RAINBOW_MOOD_INTERVALS[] PROGMEM = {120, 60, 30}; + +// How long (in milliseconds) to wait between animation steps for each of the "Swirling rainbow" animations +const uint8_t RGBLED_RAINBOW_SWIRL_INTERVALS[] PROGMEM = {100, 50, 20}; + +// How long (in milliseconds) to wait between animation steps for each of the "Snake" animations +const uint8_t RGBLED_SNAKE_INTERVALS[] PROGMEM = {100, 50, 20}; + +// How long (in milliseconds) to wait between animation steps for each of the "Knight" animations +const uint8_t RGBLED_KNIGHT_INTERVALS[] PROGMEM = {127, 63, 31}; + +// How long (in milliseconds) to wait between animation steps for each of the "Twinkle" animations +const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {50, 25, 10}; + +// These control which hues are selected for each of the "Static gradient" modes +const uint8_t RGBLED_GRADIENT_RANGES[] PROGMEM = {255, 170, 127, 85, 64}; +``` + +## Lighting Layers + +::: tip +**Note:** Lighting Layers is an RGB Light feature, it will not work for RGB Matrix. See [RGB Matrix Indicators](rgb_matrix#indicators) for details on how to do so. +::: + +By including `#define RGBLIGHT_LAYERS` in your `config.h` file you can enable lighting layers. These make +it easy to use your underglow LEDs as status indicators to show which keyboard layer is currently active, or the state of caps lock, all without disrupting any animations. [Here's a video](https://youtu.be/uLGE1epbmdY) showing an example of what you can do. + +### Defining Lighting Layers {#defining-lighting-layers} + +By default, 8 layers are possible. This can be expanded to as many as 32 by overriding the definition of `RGBLIGHT_MAX_LAYERS` in `config.h` (e.g. `#define RGBLIGHT_MAX_LAYERS 32`). Please note, if you use a split keyboard, you will need to flash both sides of the split after changing this. Also, increasing the maximum will increase the firmware size, and will slow sync on split keyboards. + +To define a layer, we modify `keymap.c` to list the LED ranges and the colors we want to overlay on them using an array of `rgblight_segment_t` using the `RGBLIGHT_LAYER_SEGMENTS` macro. We can define multiple layers and enable/disable them independently: + +```c +// Light LEDs 6 to 9 and 12 to 15 red when caps lock is active. Hard to ignore! +const rgblight_segment_t PROGMEM my_capslock_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {6, 4, HSV_RED}, // Light 4 LEDs, starting with LED 6 + {12, 4, HSV_RED} // Light 4 LEDs, starting with LED 12 +); +// Light LEDs 9 & 10 in cyan when keyboard layer 1 is active +const rgblight_segment_t PROGMEM my_layer1_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {9, 2, HSV_CYAN} +); +// Light LEDs 11 & 12 in purple when keyboard layer 2 is active +const rgblight_segment_t PROGMEM my_layer2_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {11, 2, HSV_PURPLE} +); +// Light LEDs 13 & 14 in green when keyboard layer 3 is active +const rgblight_segment_t PROGMEM my_layer3_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {13, 2, HSV_GREEN} +); +// etc.. +``` + +We combine these layers into an array using the `RGBLIGHT_LAYERS_LIST` macro, and assign it to the `rgblight_layers` variable during keyboard setup. Note that you can only define up to 8 lighting layers. Any extra layers will be ignored. Since the different lighting layers overlap, the order matters in the array, with later layers taking precedence: + +```c +// Now define the array of layers. Later layers take precedence +const rgblight_segment_t* const PROGMEM my_rgb_layers[] = RGBLIGHT_LAYERS_LIST( + my_capslock_layer, + my_layer1_layer, // Overrides caps lock layer + my_layer2_layer, // Overrides other layers + my_layer3_layer // Overrides other layers +); + +void keyboard_post_init_user(void) { + // Enable the LED layers + rgblight_layers = my_rgb_layers; +} +``` +Note: For split keyboards with two controllers, both sides need to be flashed when updating the contents of rgblight_layers. + +### Enabling and disabling lighting layers {#enabling-lighting-layers} + +Everything above just configured the definition of each lighting layer. +We can now enable and disable the lighting layers whenever the state of the keyboard changes: + +```c +bool led_update_user(led_t led_state) { + rgblight_set_layer_state(0, led_state.caps_lock); + return true; +} + +layer_state_t default_layer_state_set_user(layer_state_t state) { + rgblight_set_layer_state(1, layer_state_cmp(state, _DVORAK)); + return state; +} + +layer_state_t layer_state_set_user(layer_state_t state) { + rgblight_set_layer_state(2, layer_state_cmp(state, _FN)); + rgblight_set_layer_state(3, layer_state_cmp(state, _ADJUST)); + return state; +} +``` + +### Lighting layer blink {#lighting-layer-blink} + +By including `#define RGBLIGHT_LAYER_BLINK` in your `config.h` file you can turn a lighting +layer on for a specified duration. Once the specified number of milliseconds has elapsed +the layer will be turned off. This is useful, e.g., if you want to acknowledge some +action (e.g. toggling some setting): + +```c +const rgblight_segment_t PROGMEM _yes_layer[] = RGBLIGHT_LAYER_SEGMENTS( {9, 6, HSV_GREEN} ); +const rgblight_segment_t PROGMEM _no_layer[] = RGBLIGHT_LAYER_SEGMENTS( {9, 6, HSV_RED} ); + +const rgblight_segment_t* const PROGMEM _rgb_layers[] = + RGBLIGHT_LAYERS_LIST( _yes_layer, _no_layer ); + +void keyboard_post_init_user(void) { + rgblight_layers = _rgb_layers; +} + +// Note we user post_process_record_user because we want the state +// after the flag has been flipped... +void post_process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case QK_DEBUG_TOGGLE: + rgblight_blink_layer(debug_enable ? 0 : 1, 500); + break; + + case NK_TOGG: + case NK_ON: + case NK_OFF: + rgblight_blink_layer(keymap_config.nkro ? 0 : 1, 500); + break; + } +} +``` + +You can also use `rgblight_blink_layer_repeat` to specify the amount of times the layer is supposed to blink. Using the layers from above, +```c +void post_process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case QK_DEBUG_TOGGLE: + rgblight_blink_layer_repeat(debug_enable ? 0 : 1, 200, 3); + break; + } +} +``` +would turn the layer 0 (or 1) on and off again three times when `DB_TOGG` is pressed. + +Blinking accumulates layers so if multiple layers are set blinking at the same time they will all blink for the duration and repeat times of the last layer to be blinked. +To stop these other layers from blinking use `rgblight_unblink_layer` or `rgblight_unblink_all_but_layer`: + +```c +rgblight_blink_layer(1, 500); +rgblight_unblink_all_but_layer(1); +``` + +```c +rgblight_unblink_layer(3); +rgblight_blink_layer(2, 500); +``` + +::: warning +Lighting layers on split keyboards will require layer state synced to the slave half (e.g. `#define SPLIT_LAYER_STATE_ENABLE`). See [data sync options](split_keyboard#data-sync-options) for more details. +::: + +### Overriding RGB Lighting on/off status + +Normally lighting layers are not shown when RGB Lighting is disabled (e.g. with `RGB_TOG` keycode). If you would like lighting layers to work even when the RGB Lighting is otherwise off, add `#define RGBLIGHT_LAYERS_OVERRIDE_RGB_OFF` to your `config.h`. + +### Retain brightness + +Usually lighting layers apply their configured brightness once activated. If you would like lighting layers to retain the currently used brightness (as returned by `rgblight_get_val()`), add `#define RGBLIGHT_LAYERS_RETAIN_VAL` to your `config.h`. + +## Functions + +If you need to change your RGB lighting in code, for example in a macro to change the color whenever you switch layers, QMK provides a set of functions to assist you. See [`rgblight.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/rgblight/rgblight.h) for the full list, but the most commonly used functions include: + +### Low level Functions +|Function |Description | +|--------------------------------------------|-------------------------------------------| +|`rgblight_set()` |Flush out led buffers to LEDs | +|`rgblight_set_clipping_range(pos, num)` |Set clipping Range. see [Clipping Range](#clipping-range) | + +### Effects and Animations Functions +#### effect range setting +|Function |Description | +|--------------------------------------------|------------------| +|`rgblight_set_effect_range(pos, num)` |Set Effects Range | + +#### direct operation +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_setrgb_at(r, g, b, index)` |Set a single LED to the given RGB value, where `r`/`g`/`b` are between 0 and 255 and `index` is between 0 and `RGBLIGHT_LED_COUNT` (not written to EEPROM) | +|`rgblight_sethsv_at(h, s, v, index)` |Set a single LED to the given HSV value, where `h`/`s`/`v` are between 0 and 255, and `index` is between 0 and `RGBLIGHT_LED_COUNT` (not written to EEPROM) | +|`rgblight_setrgb_range(r, g, b, start, end)`|Set a continuous range of LEDs to the given RGB value, where `r`/`g`/`b` are between 0 and 255 and `start`(included) and `stop`(excluded) are between 0 and `RGBLIGHT_LED_COUNT` (not written to EEPROM)| +|`rgblight_sethsv_range(h, s, v, start, end)`|Set a continuous range of LEDs to the given HSV value, where `h`/`s`/`v` are between 0 and 255, and `start`(included) and `stop`(excluded) are between 0 and `RGBLIGHT_LED_COUNT` (not written to EEPROM)| +|`rgblight_setrgb(r, g, b)` |Set effect range LEDs to the given RGB value where `r`/`g`/`b` are between 0 and 255 (not written to EEPROM) | +|`rgblight_setrgb_master(r, g, b)` |Set the LEDs on the master side to the given RGB value, where `r`/`g`/`b` are between 0 and 255 (not written to EEPROM) | +|`rgblight_setrgb_slave(r, g, b)` |Set the LEDs on the slave side to the given RGB value, where `r`/`g`/`b` are between 0 and 255 (not written to EEPROM) | +|`rgblight_sethsv_master(h, s, v)` |Set the LEDs on the master side to the given HSV value, where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) | +|`rgblight_sethsv_slave(h, s, v)` |Set the LEDs on the slave side to the given HSV value, where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) | + +Example: +```c +rgblight_sethsv_at(HSV_WHITE, 0); // led 0 +rgblight_sethsv_at(HSV_RED, 1); // led 1 +rgblight_sethsv_at(HSV_GREEN, 2); // led 2 +// The above functions automatically calls rgblight_set(), so there is no need to call it explicitly. +// Note that it is inefficient to call repeatedly. +``` + +#### effect mode change +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_mode(x)` |Set the mode, if RGB animations are enabled | +|`rgblight_mode_noeeprom(x)` |Set the mode, if RGB animations are enabled (not written to EEPROM) | +|`rgblight_step()` |Change the mode to the next RGB animation in the list of enabled RGB animations | +|`rgblight_step_noeeprom()` |Change the mode to the next RGB animation in the list of enabled RGB animations (not written to EEPROM) | +|`rgblight_step_reverse()` |Change the mode to the previous RGB animation in the list of enabled RGB animations | +|`rgblight_step_reverse_noeeprom()` |Change the mode to the previous RGB animation in the list of enabled RGB animations (not written to EEPROM) | +|`rgblight_reload_from_eeprom()` |Reload the effect configuration (enabled, mode and color) from EEPROM | + +#### effects mode disable/enable +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_toggle()` |Toggle effect range LEDs between on and off | +|`rgblight_toggle_noeeprom()` |Toggle effect range LEDs between on and off (not written to EEPROM) | +|`rgblight_enable()` |Turn effect range LEDs on, based on their previous state | +|`rgblight_enable_noeeprom()` |Turn effect range LEDs on, based on their previous state (not written to EEPROM) | +|`rgblight_disable()` |Turn effect range LEDs off | +|`rgblight_disable_noeeprom()` |Turn effect range LEDs off (not written to EEPROM) | + +#### hue, sat, val change +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_increase_hue()` |Increase the hue for effect range LEDs. This wraps around at maximum hue | +|`rgblight_increase_hue_noeeprom()` |Increase the hue for effect range LEDs. This wraps around at maximum hue (not written to EEPROM) | +|`rgblight_decrease_hue()` |Decrease the hue for effect range LEDs. This wraps around at minimum hue | +|`rgblight_decrease_hue_noeeprom()` |Decrease the hue for effect range LEDs. This wraps around at minimum hue (not written to EEPROM) | +|`rgblight_increase_sat()` |Increase the saturation for effect range LEDs. This stops at maximum saturation | +|`rgblight_increase_sat_noeeprom()` |Increase the saturation for effect range LEDs. This stops at maximum saturation (not written to EEPROM) | +|`rgblight_decrease_sat()` |Decrease the saturation for effect range LEDs. This stops at minimum saturation | +|`rgblight_decrease_sat_noeeprom()` |Decrease the saturation for effect range LEDs. This stops at minimum saturation (not written to EEPROM) | +|`rgblight_increase_val()` |Increase the value for effect range LEDs. This stops at maximum value | +|`rgblight_increase_val_noeeprom()` |Increase the value for effect range LEDs. This stops at maximum value (not written to EEPROM) | +|`rgblight_decrease_val()` |Decrease the value for effect range LEDs. This stops at minimum value | +|`rgblight_decrease_val_noeeprom()` |Decrease the value for effect range LEDs. This stops at minimum value (not written to EEPROM) | +|`rgblight_sethsv(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 | +|`rgblight_sethsv_noeeprom(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) | + +#### Speed functions +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_increase_speed()` |Increases the animation speed | +|`rgblight_increase_speed_noeeprom()` |Increases the animation speed (not written to EEPROM) | +|`rgblight_decrease_speed()` |Decreases the animation speed | +|`rgblight_decrease_speed_noeeprom()` |Decreases the animation speed (not written to EEPROM) | +|`rgblight_set_speed()` |Sets the speed. Value is between 0 and 255 | +|`rgblight_set_speed_noeeprom()` |Sets the speed. Value is between 0 and 255 (not written to EEPROM) | + + +#### layer functions +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_get_layer_state(i)` |Returns `true` if lighting layer `i` is enabled | +|`rgblight_set_layer_state(i, is_on)` |Enable or disable lighting layer `i` based on value of `bool is_on` | + +#### query +|Function |Description | +|-----------------------|---------------------------| +|`rgblight_is_enabled()`|Gets current on/off status | +|`rgblight_get_mode()` |Gets current mode | +|`rgblight_get_hue()` |Gets current hue | +|`rgblight_get_sat()` |Gets current sat | +|`rgblight_get_val()` |Gets current val | +|`rgblight_get_speed()` |Gets current speed | + +## Colors + +These are shorthands to popular colors. The `RGB` ones can be passed to the `setrgb` functions, while the `HSV` ones to the `sethsv` functions. + +|RGB |HSV | +|---------------------|---------------------| +|`RGB_AZURE` |`HSV_AZURE` | +|`RGB_BLACK`/`RGB_OFF`|`HSV_BLACK`/`HSV_OFF`| +|`RGB_BLUE` |`HSV_BLUE` | +|`RGB_CHARTREUSE` |`HSV_CHARTREUSE` | +|`RGB_CORAL` |`HSV_CORAL` | +|`RGB_CYAN` |`HSV_CYAN` | +|`RGB_GOLD` |`HSV_GOLD` | +|`RGB_GOLDENROD` |`HSV_GOLDENROD` | +|`RGB_GREEN` |`HSV_GREEN` | +|`RGB_MAGENTA` |`HSV_MAGENTA` | +|`RGB_ORANGE` |`HSV_ORANGE` | +|`RGB_PINK` |`HSV_PINK` | +|`RGB_PURPLE` |`HSV_PURPLE` | +|`RGB_RED` |`HSV_RED` | +|`RGB_SPRINGGREEN` |`HSV_SPRINGGREEN` | +|`RGB_TEAL` |`HSV_TEAL` | +|`RGB_TURQUOISE` |`HSV_TURQUOISE` | +|`RGB_WHITE` |`HSV_WHITE` | +|`RGB_YELLOW` |`HSV_YELLOW` | + +```c +rgblight_setrgb(RGB_ORANGE); +rgblight_sethsv_noeeprom(HSV_GREEN); +rgblight_setrgb_at(RGB_GOLD, 3); +rgblight_sethsv_range(HSV_WHITE, 0, 6); +``` + +These are defined in [`color.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/color.h). Feel free to add to this list! + + +## Changing the order of the LEDs + +If you want to make the logical order of LEDs different from the electrical connection order, you can do this by defining the `RGBLIGHT_LED_MAP` macro in your `config.h`. + +Normally, the contents of the LED buffer are output to the LEDs in the same order. +<img src="https://user-images.githubusercontent.com/2170248/55743718-01866c80-5a6e-11e9-8134-25419928327a.JPG" alt="simple dicrect" width="50%"/> + +By defining `RGBLIGHT_LED_MAP` as in the example below, you can specify the LED with addressing in reverse order of the electrical connection order. + +```c +// config.h + +#define RGBLIGHT_LED_COUNT 4 +#define RGBLIGHT_LED_MAP { 3, 2, 1, 0 } + +``` +<img src="https://user-images.githubusercontent.com/2170248/55743725-08ad7a80-5a6e-11e9-83ed-126a2b0209fc.JPG" alt="simple mapped" width="50%"/> + +## Clipping Range + +Using the `rgblight_set_clipping_range()` function, you can prepare more buffers than the actual number of LEDs, and output some of the buffers to the LEDs. This is useful if you want the split keyboard to treat left and right LEDs as logically contiguous. + +You can set the Clipping Range by executing the following code. + +```c +// some source +rgblight_set_clipping_range(3, 4); +``` +<img src="https://user-images.githubusercontent.com/2170248/55743785-2bd82a00-5a6e-11e9-9d4b-1b4ffaf4932b.JPG" alt="clip direct" width="70%"/> + +In addition to setting the Clipping Range, you can use `RGBLIGHT_LED_MAP` together. + +```c +// config.h +#define RGBLIGHT_LED_COUNT 8 +#define RGBLIGHT_LED_MAP { 7, 6, 5, 4, 3, 2, 1, 0 } + +// some source +rgblight_set_clipping_range(3, 4); +``` +<img src="https://user-images.githubusercontent.com/2170248/55743747-119e4c00-5a6e-11e9-91e5-013203ffae8a.JPG" alt="clip mapped" width="70%"/> + +## Hardware Modification + +If your keyboard lacks onboard underglow LEDs, you may often be able to solder on an RGB LED strip yourself. You will need to find an unused pin to wire to the data pin of your LED strip. Some keyboards may break out unused pins from the MCU to make soldering easier. The other two pins, VCC and GND, must also be connected to the appropriate power pins. + +## Velocikey + +Velocikey is a feature that lets you control the speed of lighting effects (like the Rainbow Swirl effect) with the speed of your typing. The faster you type, the faster the lights will go! + +### Usage +For Velocikey to take effect, there are two steps. First, when compiling your keyboard, you'll need to set `VELOCIKEY_ENABLE=yes` in `rules.mk`, e.g.: + +``` +MOUSEKEY_ENABLE = no +STENO_ENABLE = no +EXTRAKEY_ENABLE = yes +VELOCIKEY_ENABLE = yes +``` + +Then, while using your keyboard, you need to also turn it on with the `VK_TOGG` keycode, which toggles the feature on and off. + +The following light effects will all be controlled by Velocikey when it is enabled: + - RGB Breathing + - RGB Rainbow Mood + - RGB Rainbow Swirl + - RGB Snake + - RGB Knight + +Support for LED breathing effects is planned but not available yet. + + As long as Velocikey is enabled, it will control the speed regardless of any other speed setting that your RGB lights are currently on. + + ### Configuration + Velocikey doesn't currently support any configuration via keyboard settings. If you want to adjust something like the speed increase or decay rate, you would need to edit `velocikey.c` and adjust the values there to achieve the kinds of speeds that you like. diff --git a/docs/features/secure.md b/docs/features/secure.md new file mode 100644 index 0000000000..02ed20a470 --- /dev/null +++ b/docs/features/secure.md @@ -0,0 +1,56 @@ +# Secure + +The secure feature aims to prevent unwanted interaction without user intervention. + +::: tip +Secure does **not** currently implement encryption/decryption/etc and should not be a replacement where a strong hardware/software based solution is required. +::: + +### Unlock sequence + +To unlock, the user must perform a set of actions. This can optionally be configured to be multiple keys. + +* While unlocking all keyboard input is ignored +* Incorrect attempts will revert back to the previously locked state + +### Automatic Locking + +Once unlocked, the keyboard will revert back to a locked state after the configured timeout. +The timeout can be refreshed by using the `secure_activity_event` function, for example from one of the various [hooks](../custom_quantum_functions). + +## Usage + +Add the following to your `rules.mk`: + +```make +SECURE_ENABLE = yes +``` + +## Keycodes + +| Key |Aliases | Description | +|---------------------|---------|--------------------------------------------------------------------------------| +| `QK_SECURE_LOCK` |`SE_LOCK`| Revert back to a locked state | +| `QK_SECURE_UNLOCK` |`SE_UNLK`| Forces unlock without performing a unlock sequence | +| `QK_SECURE_TOGGLE` |`SE_TOGG`| Toggle directly between locked and unlock without performing a unlock sequence | +| `QK_SECURE_REQUEST` |`SE_REQ` | Request that user perform the unlock sequence | + +## Configuration + +| Define | Default | Description | +|-------------------------|----------------|---------------------------------------------------------------------------------| +|`SECURE_UNLOCK_TIMEOUT` | `5000` | Timeout for the user to perform the configured unlock sequence - `0` to disable | +|`SECURE_IDLE_TIMEOUT` | `60000` | Timeout while unlocked before returning to locked - `0` to disable | +|`SECURE_UNLOCK_SEQUENCE` | `{ { 0, 0 } }` | Array of matrix locations describing a sequential sequence of keypresses | + +## Functions + +| Function | Description | +|---------------------------|----------------------------------------------------------------------------| +| `secure_is_locked()` | Check if the device is currently locked | +| `secure_is_unlocking()` | Check if an unlock sequence is currently in progress | +| `secure_is_unlocked()` | Check if the device is currently unlocked | +| `secure_lock()` | Lock down the device | +| `secure_unlock()` | Force unlock the device - bypasses user unlock sequence | +| `secure_request_unlock()` | Begin listening for an unlock sequence | +| `secure_activity_event()` | Flag that user activity has happened and the device should remain unlocked | diff --git a/docs/features/send_string.md b/docs/features/send_string.md new file mode 100644 index 0000000000..ed9311e20a --- /dev/null +++ b/docs/features/send_string.md @@ -0,0 +1,226 @@ +# Send String {#send-string} + +The Send String API is part of QMK's macro system. It allows for sequences of keystrokes to be sent automatically. + +The full ASCII character set is supported, along with all of the keycodes in the Basic Keycode range (as these are the only ones that will actually be sent to the host). + +::: tip +Unicode characters are **not** supported with this API -- see the [Unicode](unicode) feature instead. +::: + +## Usage {#usage} + +Send String is enabled by default, so there is usually no need for any special setup. However, if it is disabled, add the following to your `rules.mk`: + +```make +SEND_STRING_ENABLE = yes +``` + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|-----------------|----------------|------------------------------------------------------------------------------------------------------------| +|`SENDSTRING_BELL`|*Not defined* |If the [Audio](audio) feature is enabled, the `\a` character (ASCII `BEL`) will beep the speaker.| +|`BELL_SOUND` |`TERMINAL_SOUND`|The song to play when the `\a` character is encountered. By default, this is an eighth note of C5. | + +## Keycodes {#keycodes} + +The Send String functions accept C string literals, but specific keycodes can be injected with the below macros. All of the keycodes in the [Basic Keycode range](../keycodes_basic) are supported (as these are the only ones that will actually be sent to the host), but with an `X_` prefix instead of `KC_`. + +|Macro |Description | +|--------------|-------------------------------------------------------------------| +|`SS_TAP(x)` |Send a keydown, then keyup, event for the given Send String keycode| +|`SS_DOWN(x)` |Send a keydown event for the given Send String keycode | +|`SS_UP(x)` |Send a keyup event for the given Send String keycode | +|`SS_DELAY(ms)`|Wait for `ms` milliseconds | + +The following characters are also mapped to their respective keycodes for convenience: + +|Character|Hex |ASCII|Keycode | +|---------|------|-----|--------------| +|`\b` |`\x08`|`BS` |`KC_BACKSPACE`| +|`\e` |`\x09`|`ESC`|`KC_ESCAPE` | +|`\n` |`\x0A`|`LF` |`KC_ENTER` | +|`\t` |`\x1B`|`TAB`|`KC_TAB` | +| |`\x7F`|`DEL`|`KC_DELETE` | + +### Language Support {#language-support} + +By default, Send String assumes your OS keyboard layout is set to US ANSI. If you are using a different keyboard layout, you can [override the lookup tables used to convert ASCII characters to keystrokes](../reference_keymap_extras#sendstring-support). + +## Examples {#examples} + +### Hello World {#example-hello-world} + +A simple custom keycode which types out "Hello, world!" and the Enter key when pressed. + +Add the following to your `keymap.c`: + +```c +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case SS_HELLO: + if (record->event.pressed) { + SEND_STRING("Hello, world!\n"); + } + return false; + } + + return true; +} +``` + +### Keycode Injection {#example-keycode-injection} + +This example types out opening and closing curly braces, then taps the left arrow key to move the cursor between the two. + +```c +SEND_STRING("{}" SS_TAP(X_LEFT)); +``` + +This example types Ctrl+A, then Ctrl+C, without releasing Ctrl. + +```c +SEND_STRING(SS_LCTL("ac")); +``` + +## API {#api} + +### `void send_string(const char *string)` {#api-send-string} + +Type out a string of ASCII characters. + +This function simply calls `send_string_with_delay(string, 0)`. + +#### Arguments {#api-send-string-arguments} + + - `const char *string` + The string to type out. + +--- + +### `void send_string_with_delay(const char *string, uint8_t interval)` {#api-send-string-with-delay} + +Type out a string of ASCII characters, with a delay between each character. + +#### Arguments {#api-send-string-with-delay-arguments} + + - `const char *string` + The string to type out. + - `uint8_t interval` + The amount of time, in milliseconds, to wait before typing the next character. + +--- + +### `void send_string_P(const char *string)` {#api-send-string-p} + +Type out a PROGMEM string of ASCII characters. + +On ARM devices, this function is simply an alias for `send_string_with_delay(string, 0)`. + +#### Arguments {#api-send-string-p-arguments} + + - `const char *string` + The string to type out. + +--- + +### `void send_string_with_delay_P(const char *string, uint8_t interval)` {#api-send-string-with-delay-p} + +Type out a PROGMEM string of ASCII characters, with a delay between each character. + +On ARM devices, this function is simply an alias for `send_string_with_delay(string, interval)`. + +#### Arguments {#api-send-string-with-delay-p-arguments} + + - `const char *string` + The string to type out. + - `uint8_t interval` + The amount of time, in milliseconds, to wait before typing the next character. + +--- + +### `void send_char(char ascii_code)` {#api-send-char} + +Type out an ASCII character. + +#### Arguments {#api-send-char-arguments} + + - `char ascii_code` + The character to type. + +--- + +### `void send_dword(uint32_t number)` {#api-send-dword} + +Type out an eight digit (unsigned 32-bit) hexadecimal value. + +The format is `[0-9a-f]{8}`, eg. `00000000` through `ffffffff`. + +#### Arguments {#api-send-dword-arguments} + + - `uint32_t number` + The value to type, from 0 to 4,294,967,295. + +--- + +### `void send_word(uint16_t number)` {#api-send-word} + +Type out a four digit (unsigned 16-bit) hexadecimal value. + +The format is `[0-9a-f]{4}`, eg. `0000` through `ffff`. + +#### Arguments {#api-send-word-arguments} + + - `uint16_t number` + The value to type, from 0 to 65,535. + +--- + +### `void send_byte(uint8_t number)` {#api-send-bytes} + +Type out a two digit (8-bit) hexadecimal value. + +The format is `[0-9a-f]{2}`, eg. `00` through `ff`. + +#### Arguments {#api-send-byte-arguments} + + - `uint8_t number` + The value to type, from 0 to 255. + +--- + +### `void send_nibble(uint8_t number)` {#api-send-nibble} + +Type out a single hexadecimal digit. + +The format is `[0-9a-f]{1}`, eg. `0` through `f`. + +#### Arguments {#api-send-nibble-arguments} + + - `uint8_t number` + The value to type, from 0 to 15. + +--- + +### `void tap_random_base64(void)` {#api-tap-random-base64} + +Type a pseudorandom character from the set `A-Z`, `a-z`, `0-9`, `+` and `/`. + +--- + +### `SEND_STRING(string)` {#api-send-string-macro} + +Shortcut macro for `send_string_with_delay_P(PSTR(string), 0)`. + +On ARM devices, this define evaluates to `send_string_with_delay(string, 0)`. + +--- + +### `SEND_STRING_DELAY(string, interval)` {#api-send-string-delay-macro} + +Shortcut macro for `send_string_with_delay_P(PSTR(string), interval)`. + +On ARM devices, this define evaluates to `send_string_with_delay(string, interval)`. diff --git a/docs/features/sequencer.md b/docs/features/sequencer.md new file mode 100644 index 0000000000..f5f1f549af --- /dev/null +++ b/docs/features/sequencer.md @@ -0,0 +1,89 @@ +# Sequencer + +Since QMK has experimental support for MIDI, you can now turn your keyboard into a [step sequencer](https://en.wikipedia.org/wiki/Music_sequencer#Step_sequencers)! + +::: warning +This feature is highly experimental, it has only been tested on a Planck EZ so far. Also, the scope will be limited to support the drum machine use-case to start with. +::: + +## Enable the step sequencer + +Add the following line to your `rules.mk`: + +```make +SEQUENCER_ENABLE = yes +``` + +By default the sequencer has 16 steps, but you can override this setting in your `config.h`: + +```c +#define SEQUENCER_STEPS 32 +``` + +## Tracks + +You can program up to 8 independent tracks with the step sequencer. Select the tracks you want to edit, enable or disable some steps, and start the sequence! + +## Resolutions + +While the tempo defines the absolute speed at which the sequencer goes through the steps, the resolution defines the granularity of these steps (from coarser to finer). + +|Resolution |Description | +|---------- |----------- | +|`SQ_RES_2` |Every other beat | +|`SQ_RES_2T` |Every 1.5 beats | +|`SQ_RES_4` |Every beat | +|`SQ_RES_4T` |Three times per 2 beats| +|`SQ_RES_8` |Twice per beat | +|`SQ_RES_8T` |Three times per beat | +|`SQ_RES_16` |Four times per beat | +|`SQ_RES_16T` |Six times per beat | +|`SQ_RES_32` |Eight times per beat | + +## Keycodes + +|Key |Aliases |Description | +|-------------------------------|---------|---------------------------------------------------| +|`QK_SEQUENCER_ON` |`SQ_ON` |Start the step sequencer | +|`QK_SEQUENCER_OFF` |`SQ_OFF` |Stop the step sequencer | +|`QK_SEQUENCER_TOGGLE` |`SQ_TOGG`|Toggle the step sequencer playback | +|`QK_SEQUENCER_STEPS_ALL` |`SQ_SALL`|Enable all the steps | +|`QK_SEQUENCER_STEPS_CLEAR` |`SQ_SCLR`|Disable all the steps | +|`QK_SEQUENCER_TEMPO_DOWN` |`SQ_TMPD`|Decrease the tempo | +|`QK_SEQUENCER_TEMPO_UP` |`SQ_TMPU`|Increase the tempo | +|`QK_SEQUENCER_RESOLUTION_DOWN` |`SQ_RESD`|Change to the slower resolution | +|`QK_SEQUENCER_RESOLUTION_UP` |`SQ_RESU`|Change to the faster resolution | +|`SQ_S(n)` | |Toggle the step `n` | +|`SQ_R(n)` | |Set the resolution to n | +|`SQ_T(n)` | |Set `n` as the only active track or deactivate all | + +## Functions + +|Function |Description | +|-------- |----------- | +|`bool is_sequencer_on(void);` |Return whether the sequencer is playing | +|`void sequencer_toggle(void);` |Toggle the step sequencer playback | +|`void sequencer_on(void);` |Start the step sequencer | +|`void sequencer_off(void);` |Stop the step sequencer | +|`bool is_sequencer_step_on(uint8_t step);` |Return whether the step is currently enabled | +|`void sequencer_set_step(uint8_t step, bool value);` |Enable or disable the step | +|`void sequencer_set_step_on();` |Enable the step | +|`void sequencer_set_step_off();` |Disable the step | +|`void sequencer_toggle_step(uint8_t step);` |Toggle the step | +|`void sequencer_set_all_steps(bool value);` |Enable or disable all the steps | +|`void sequencer_set_all_steps_on();` |Enable all the steps | +|`void sequencer_set_all_steps_off();` |Disable all the steps | +|`uint8_t sequencer_get_tempo(void);` |Return the current tempo | +|`void sequencer_set_tempo(uint8_t tempo);` |Set the tempo to `tempo` (between 1 and 255) | +|`void sequencer_increase_tempo(void);` |Increase the tempo | +|`void sequencer_decrease_tempo(void);` |Decrease the tempo | +|`sequencer_resolution_t sequencer_get_resolution(void);` |Return the current resolution | +|`void sequencer_set_resolution(sequencer_resolution_t resolution);` |Set the resolution to `resolution` | +|`void sequencer_increase_resolution(void);` |Change to the faster resolution | +|`void sequencer_decrease_resolution(void);` |Change to the slower resolution | +|`bool is_sequencer_track_active(uint8_t track);` |Return whether the track is active | +|`void sequencer_set_track_activation(uint8_t track, bool value);` |Activate or deactivate the `track` | +|`void sequencer_toggle_track_activation(uint8_t track);` |Toggle the `track` | +|`void sequencer_activate_track(uint8_t track);` |Activate the `track` | +|`void sequencer_deactivate_track(uint8_t track);` |Deactivate the `track` | +|`void sequencer_toggle_single_active_track(uint8_t track);` |Set `track` as the only active track or deactivate all | diff --git a/docs/features/space_cadet.md b/docs/features/space_cadet.md new file mode 100644 index 0000000000..0abdaebf33 --- /dev/null +++ b/docs/features/space_cadet.md @@ -0,0 +1,60 @@ +# Space Cadet: The Future, Built In + +Steve Losh described the [Space Cadet Shift](https://web.archive.org/web/20230330090938/https://stevelosh.com/blog/2012/10/a-modern-space-cadet/) quite well. Essentially, when you tap Left Shift on its own, you get an opening parenthesis; tap Right Shift on its own and you get the closing one. When held, the Shift keys function as normal. Yes, it's as cool as it sounds, and now even cooler supporting Control and Alt as well! + +## Usage + +Firstly, in your keymap, do one of the following: +- Replace the Left Shift key with `SC_LSPO` (Left Shift, Parenthesis Open), and Right Shift with `SC_RSPC` (Right Shift, Parenthesis Close). +- Replace the Left Control key with `SC_LCPO` (Left Control, Parenthesis Open), and Right Control with `SC_RCPC` (Right Control, Parenthesis Close). +- Replace the Left Alt key with `SC_LAPO` (Left Alt, Parenthesis Open), and Right Alt with `SC_RAPC` (Right Alt, Parenthesis Close). +- Replace any Shift key in your keymap with `SC_SENT` (Right Shift, Enter). + +## Keycodes + +|Keycode |Aliases |Description | +|----------------------------------------------|---------|----------------------------------------| +|`QK_SPACE_CADET_LEFT_CTRL_PARENTHESIS_OPEN` |`SC_LCPO`|Left Control when held, `(` when tapped | +|`QK_SPACE_CADET_RIGHT_CTRL_PARENTHESIS_CLOSE` |`SC_RCPC`|Right Control when held, `)` when tapped| +|`QK_SPACE_CADET_LEFT_SHIFT_PARENTHESIS_OPEN` |`SC_LSPO`|Left Shift when held, `(` when tapped | +|`QK_SPACE_CADET_RIGHT_SHIFT_PARENTHESIS_CLOSE`|`SC_RSPC`|Right Shift when held, `)` when tapped | +|`QK_SPACE_CADET_LEFT_ALT_PARENTHESIS_OPEN` |`SC_LAPO`|Left Alt when held, `(` when tapped | +|`QK_SPACE_CADET_RIGHT_ALT_PARENTHESIS_CLOSE` |`SC_RAPC`|Right Alt when held, `)` when tapped | +|`QK_SPACE_CADET_RIGHT_SHIFT_ENTER` |`SC_SENT`|Right Shift when held, Enter when tapped| + +## Caveats + +Space Cadet's functionality can conflict with the default Command functionality when both Shift keys are held at the same time. See the [Command feature](command) for info on how to change it, or make sure that Command is disabled in your `rules.mk` with: + +```make +COMMAND_ENABLE = no +``` + +## Configuration + +By default Space Cadet assumes a US ANSI layout, but if your layout uses different keys for parentheses, you can redefine them in your `config.h`. In addition, you can redefine the modifier to send on tap, or even send no modifier at all. The new configuration defines bundle all options up into a single define of 3 key codes in this order: the `Modifier` when held or when used with other keys, the `Tap Modifer` sent when tapped (no modifier if `KC_TRNS`), finally the `Keycode` sent when tapped. Now keep in mind, mods from other keys will still apply to the `Keycode` if say `KC_RSFT` is held while tapping `SC_LSPO` key with `KC_TRNS` as the `Tap Modifer`. + +|Define |Default |Description | +|----------------|-------------------------------|---------------------------------------------------------------------------------| +|`LSPO_KEYS` |`KC_LSFT, LSPO_MOD, LSPO_KEY` |Send `KC_LSFT` when held, the mod and key defined by `LSPO_MOD` and `LSPO_KEY`. | +|`RSPC_KEYS` |`KC_RSFT, RSPC_MOD, RSPC_KEY` |Send `KC_RSFT` when held, the mod and key defined by `RSPC_MOD` and `RSPC_KEY`. | +|`LCPO_KEYS` |`KC_LCTL, KC_LSFT, KC_9` |Send `KC_LCTL` when held, the mod `KC_LSFT` with the key `KC_9` when tapped. | +|`RCPC_KEYS` |`KC_RCTL, KC_RSFT, KC_0` |Send `KC_RCTL` when held, the mod `KC_RSFT` with the key `KC_0` when tapped. | +|`LAPO_KEYS` |`KC_LALT, KC_LSFT, KC_9` |Send `KC_LALT` when held, the mod `KC_LSFT` with the key `KC_9` when tapped. | +|`RAPC_KEYS` |`KC_RALT, KC_RSFT, KC_0` |Send `KC_RALT` when held, the mod `KC_RSFT` with the key `KC_0` when tapped. | +|`SFTENT_KEYS` |`KC_RSFT, KC_TRNS, SFTENT_KEY` |Send `KC_RSFT` when held, no mod with the key `SFTENT_KEY` when tapped. | +|`SPACE_CADET_MODIFIER_CARRYOVER` |*Not defined* |Store current modifiers before the hold mod is pressed and use them with the tap mod and keycode. Useful for when you frequently release a modifier before triggering Space Cadet. | + + +## Obsolete Configuration + +These defines are used in the above defines internally to support backwards compatibility, so you may continue to use them, however the above defines open up a larger range of flexibility than before. As an example, say you want to not send any modifier when you tap just `SC_LSPO`, with the old defines you had an all or nothing choice of using the `DISABLE_SPACE_CADET_MODIFIER` define. Now you can define that key as: `#define LSPO_KEYS KC_LSFT, KC_TRNS, KC_9`. This tells the system to set Left Shift if held or used with other keys, then on tap send no modifier (transparent) with the `KC_9`. + +|Define |Default |Description | +|------------------------------|-------------|------------------------------------------------------------------| +|`LSPO_KEY` |`KC_9` |The keycode to send when Left Shift is tapped | +|`RSPC_KEY` |`KC_0` |The keycode to send when Right Shift is tapped | +|`LSPO_MOD` |`KC_LSFT` |The modifier to apply to `LSPO_KEY` | +|`RSPC_MOD` |`KC_RSFT` |The modifier to apply to `RSPC_KEY` | +|`SFTENT_KEY` |`KC_ENT` |The keycode to send when the Shift key is tapped | +|`DISABLE_SPACE_CADET_MODIFIER`|*Not defined*|If defined, prevent the Space Cadet from applying a modifier | diff --git a/docs/features/split_keyboard.md b/docs/features/split_keyboard.md new file mode 100644 index 0000000000..6efa1c2a35 --- /dev/null +++ b/docs/features/split_keyboard.md @@ -0,0 +1,508 @@ +# Split Keyboard + +Many keyboards in the QMK Firmware repo are "split" keyboards. They use two controllers—one plugging into USB, and the second connected by a serial or an I<sup>2</sup>C connection over a TRRS or similar cable. + +Split keyboards can have a lot of benefits, but there is some additional work needed to get them enabled. + +QMK Firmware has a generic implementation that is usable by any board, as well as numerous board specific implementations. + +For this, we will mostly be talking about the generic implementation used by the Let's Split and other keyboards. + +::: warning +ARM split supports most QMK subsystems when using the 'serial' and 'serial_usart' drivers. I2C slave is currently unsupported. +::: + +::: warning +Both sides must use the same MCU family, for eg two Pro Micro-compatible controllers or two Blackpills. Currently, mixing AVR and ARM is not possible as ARM vs AVR uses different method for serial communication, and are not compatible. Moreover Blackpill's uses 3.3v logic, and atmega32u4 uses 5v logic. +::: + +## Compatibility Overview + +| Transport | AVR | ARM | +|------------------------------|--------------------|--------------------| +| ['serial'](../drivers/serial) | :heavy_check_mark: | :white_check_mark: <sup>1</sup> | +| I2C | :heavy_check_mark: | | + +Notes: + +1. Both hardware and software limitations are detailed within the [driver documentation](../drivers/serial). + +## Hardware Configuration + +This assumes that you're using two Pro Micro-compatible controllers, and are using TRRS jacks to connect to two halves. + +### Required Hardware + +Apart from diodes and key switches for the keyboard matrix in each half, you will need 2x TRRS sockets and 1x TRRS cable. + +Alternatively, you can use any sort of cable and socket that has at least 3 wires. + +If you want to use I<sup>2</sup>C to communicate between halves, you will need a cable with at least 4 wires and 2x 4.7kΩ pull-up resistors. + +#### Considerations + +The most commonly used connection is a TRRS cable and jacks. These provide 4 wires, making them very useful for split keyboards, and are easy to find. + +However, since one of the wires carries VCC, this means that the boards are not hot pluggable. You should always disconnect the board from USB before unplugging and plugging in TRRS cables, or you can short the controller, or worse. + +Another option is to use phone cables (as in, old school RJ-11/RJ-14 cables). Make sure that you use one that actually supports 4 wires/lanes. + +However, USB cables, SATA cables, and even just 4 wires have been known to be used for communication between the controllers. + +::: warning +Using USB cables for communication between the controllers works just fine, but the connector could be mistaken for a normal USB connection and potentially short out the keyboard, depending on how it's wired. For this reason, they are not recommended for connecting split keyboards. +::: + +### Serial Wiring + +The 3 wires of the TRS/TRRS cable need to connect GND, VCC, and D0/D1/D2/D3 (aka PD0/PD1/PD2/PD3) between the two Pro Micros. + +::: tip +Note that the pin used here is actually set by `SOFT_SERIAL_PIN` below. +::: + +<img alt="sk-pd0-connection-mono" src="https://user-images.githubusercontent.com/2170248/92296488-28e9ad80-ef70-11ea-98be-c40cb48a0319.JPG" width="48%"/> +<img alt="sk-pd2-connection-mono" src="https://user-images.githubusercontent.com/2170248/92296490-2d15cb00-ef70-11ea-801f-5ace313013e6.JPG" width="48%"/> + +### I<sup>2</sup>C Wiring + +The 4 wires of the TRRS cable need to connect GND, VCC, and SCL and SDA (aka PD0/pin 3 and PD1/pin 2, respectively) between the two Pro Micros. + +The pull-up resistors may be placed on either half. If you wish to use the halves independently, it is also possible to use 4 resistors and have the pull-ups in both halves. +Note that the total resistance for the connected system should be within spec at 2.2k-10kOhm, with an 'ideal' at 4.7kOhm, regardless of the placement and number. + +<img alt="sk-i2c-connection-mono" src="https://user-images.githubusercontent.com/2170248/92297182-92b98580-ef77-11ea-9d7d-d6033914af43.JPG" width="50%"/> + +## Firmware Configuration + +To enable the split keyboard feature, add the following to your `rules.mk`: + +```make +SPLIT_KEYBOARD = yes +``` + +If you're using a custom transport (communication method), then you will also need to add: + +```make +SPLIT_TRANSPORT = custom +``` + +### Layout Macro + +Configuring your layout in a split keyboard works slightly differently to a non-split keyboard. Take for example the following layout. The top left numbers refer to the matrix row and column, and the bottom right are the order of the keys in the layout: + +![Physical layout](https://i.imgur.com/QeY6kMQ.png) + +Since the matrix scanning procedure operates on entire rows, it first populates the left half's rows, then the right half's. Thus, the matrix as QMK views it has double the rows instead of double the columns: + +![Matrix](https://i.imgur.com/4wjJzBU.png) + +### Setting Handedness + +By default, the firmware does not know which side is which; it needs some help to determine that. There are several ways to do this, listed in order of precedence. + +#### Handedness by Pin + +You can configure the firmware to read a pin on the controller to determine handedness. To do this, add the following to your `config.h` file: + +```c +#define SPLIT_HAND_PIN B7 +``` + +This will read the specified pin. By default, if it's high, then the controller assumes it is the left hand, and if it's low, it's assumed to be the right side. + +This behaviour can be flipped by adding this to you `config.h` file: + +```c +#define SPLIT_HAND_PIN_LOW_IS_LEFT +``` + +#### Handedness by Matrix Pin + +You can configure the firmware to read key matrix pins on the controller to determine handedness. To do this, add the following to your `config.h` file: + +```c +#define SPLIT_HAND_MATRIX_GRID D0, F1 +``` + +The first pin is the output pin and the second is the input pin. + +Some keyboards have unused intersections in the key matrix. This setting uses one of these unused intersections to determine the handedness. + +Normally, when a diode is connected to an intersection, it is judged to be right. If you add the following definition, it will be judged to be left. + +```c +#define SPLIT_HAND_MATRIX_GRID_LOW_IS_LEFT +``` + +Note that adding a diode at a previously unused intersection will effectively tell the firmware that there is a key held down at that point. You can instruct qmk to ignore that intersection by defining `MATRIX_MASKED` and then defining a `matrix_row_t matrix_mask[MATRIX_ROWS]` array in your keyboard config. Each bit of a single value (starting form the least-significant bit) is used to tell qmk whether or not to pay attention to key presses at that intersection. + +While `MATRIX_MASKED` isn't necessary to use `SPLIT_HAND_MATRIX_GRID` successfully, without it you may experience issues trying to suspend your computer with your keyboard attached as the matrix will always report at least one key-press. + +#### Handedness by EEPROM + +This method sets the keyboard's handedness by setting a flag in the persistent storage (`EEPROM`). This is checked when the controller first starts up, and determines what half the keyboard is, and how to orient the keyboard layout. + + +To enable this method, add the following to your `config.h` file: + +```c +#define EE_HANDS +``` + +Next, you will have to flash the correct handedness option to the controller on each halve. You can do this manually with the following bootloader targets using `qmk flash -kb <keyboard> -km <keymap> -bl <bootloader>` command to flash: + +|Microcontroller Type|Bootloader Parameter| +|--------------------|--------------------| +|AVR controllers with Caterina bootloader<br>(e.g. Pro Micro)|`avrdude-split-left`<br>`avrdude-split-right`| +|AVR controllers with the stock Amtel DFU or DFU compatible bootloader<br>(e.g. Elite-C)|`dfu-split-left`<br>`dfu-split-right`| +|ARM controllers with a DFU compatible bootloader<br>(e.g. Proton-C)|`dfu-util-split-left`<br>`dfu-util-split-right`| +|ARM controllers with a UF2 compatible bootloader<br>(e.g. RP2040)|`uf2-split-left`<br>`uf2-split-right`| + +Example for `crkbd/rev1` keyboard with normal AVR Pro Micro MCUs, reset the left controller and run: +``` +qmk flash -kb crkbd/rev1 -km default -bl avrdude-split-left +``` +Reset the right controller and run: +``` +qmk flash -kb crkbd/rev1 -km default -bl avrdude-split-right +``` + +::: tip +Some controllers (e.g. Blackpill with DFU compatible bootloader) will need to be flashed with handedness bootloader parameter every time because it is not retained between flashes. +::: + +::: tip +[QMK Toolbox](https://github.com/qmk/qmk_toolbox/releases/) can also be used to flash EEPROM handedness files. Place the controller in bootloader mode and select menu option Tools -> EEPROM -> Set Left/Right Hand +::: + +This setting is not changed when re-initializing the EEPROM using the `EE_CLR` key, or using the `eeconfig_init()` function. However, if you reset the EEPROM outside of the firmware's built in options (such as flashing a file that overwrites the `EEPROM`, like how the [QMK Toolbox](https://github.com/qmk/qmk_toolbox/releases/)'s "Reset EEPROM" button works), you'll need to re-flash the controller with the `EEPROM` files. + +You can find the `EEPROM` files in the QMK firmware repo, [here](https://github.com/qmk/qmk_firmware/tree/master/quantum/split_common). + + +#### Handedness by `#define` + +You can use this option when USB cable is always connected to just one side of the split keyboard. + +If the USB cable is always connected to the right side, add the following to your `config.h` file and flash both sides with this option: +```c +#define MASTER_RIGHT +``` + +If the USB cable is always connected to the left side, add the following to your `config.h` file and flash both sides with this option: +```c +#define MASTER_LEFT +``` + +::: tip +If neither options are defined, the handedness defaults to `MASTER_LEFT`. +::: + + +### Communication Options + +Because not every split keyboard is identical, there are a number of additional options that can be configured in your `config.h` file. + +```c +#define USE_I2C +``` + +This configures the use of I<sup>2</sup>C support for split keyboard transport (AVR only). + +```c +#define SOFT_SERIAL_PIN D0 +``` + +This sets the pin to be used for serial communication. If you're not using serial, you shouldn't need to define this. + +However, if you are using serial and I<sup>2</sup>C on the board, you will need to set this, and to something other than D0 and D1 (as these are used for I<sup>2</sup>C communication). + +```c +#define SELECT_SOFT_SERIAL_SPEED {#} +``` + +If you're having issues with serial communication, you can change this value, as it controls the communication speed for serial. The default is 1, and the possible values are: + +* **`0`**: about 189kbps (Experimental only) +* **`1`**: about 137kbps (default) +* **`2`**: about 75kbps +* **`3`**: about 39kbps +* **`4`**: about 26kbps +* **`5`**: about 20kbps + +```c +#define FORCED_SYNC_THROTTLE_MS 100 +``` + +This sets the maximum number of milliseconds before forcing a synchronization of data from master to slave. Under normal circumstances this sync occurs whenever the data _changes_, for safety a data transfer occurs after this number of milliseconds if no change has been detected since the last sync. + +```c +#define SPLIT_MAX_CONNECTION_ERRORS 10 +``` +This sets the maximum number of failed communication attempts (one per scan cycle) from the master part before it assumes that no slave part is connected. This makes it possible to use a master part without the slave part connected. + +Set to 0 to disable the disconnection check altogether. + +```c +#define SPLIT_CONNECTION_CHECK_TIMEOUT 500 +``` +How long (in milliseconds) the master part should block all connection attempts to the slave after the communication has been flagged as disconnected (see `SPLIT_MAX_CONNECTION_ERRORS` above). + +One communication attempt will be allowed everytime this amount of time has passed since the last attempt. If that attempt succeeds, the communication is seen as working again. + +Set to 0 to disable this throttling of communications while disconnected. This can save you a couple of bytes of firmware size. + + +### Data Sync Options + +The following sync options add overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. These can be enabled by adding the chosen option(s) to your `config.h` file. + +```c +#define SPLIT_TRANSPORT_MIRROR +``` + +This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to keypresses). + +```c +#define SPLIT_LAYER_STATE_ENABLE +``` + +This enables syncing of the layer state between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the currently active layer. + +```c +#define SPLIT_LED_STATE_ENABLE +``` + +This enables syncing of the Host LED status (caps lock, num lock, etc) between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the Host LED status. + +```c +#define SPLIT_MODS_ENABLE +``` + +This enables transmitting modifier state (normal, weak, oneshot and oneshot locked) to the non primary side of the split keyboard. The purpose of this feature is to support cosmetic use of modifer state (e.g. displaying status on an OLED screen). + +```c +#define SPLIT_WPM_ENABLE +``` + +This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). + +```c +#define SPLIT_OLED_ENABLE +``` + +This enables transmitting the current OLED on/off status to the slave side of the split keyboard. The purpose of this feature is to support state (on/off state only) syncing. + +```c +#define SPLIT_ST7565_ENABLE +``` + +This enables transmitting the current ST7565 on/off status to the slave side of the split keyboard. The purpose of this feature is to support state (on/off state only) syncing. + +```c +#define SPLIT_POINTING_ENABLE +``` + +This enables transmitting the pointing device status to the master side of the split keyboard. The purpose of this feature is to enable use pointing devices on the slave side. + +::: warning +There is additional required configuration for `SPLIT_POINTING_ENABLE` outlined in the [pointing device documentation](pointing_device#split-keyboard-configuration). +::: + +```c +#define SPLIT_HAPTIC_ENABLE +``` + +This enables the triggering of haptic feedback on the slave side of the split keyboard. This will send information to the slave side such as the mode, dwell, and whether buzz is enabled. + +```c +#define SPLIT_ACTIVITY_ENABLE +``` + +This synchronizes the activity timestamps between sides of the split keyboard, allowing for activity timeouts to occur. + +### Custom data sync between sides {#custom-data-sync} + +QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master. + +To leverage this, a keyboard or user/keymap can define a comma-separated list of _transaction IDs_: + +```c +// for keyboard-level data sync: +#define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A, KEYBOARD_SYNC_B +// or, for user: +#define SPLIT_TRANSACTION_IDS_USER USER_SYNC_A, USER_SYNC_B, USER_SYNC_C +``` + +These _transaction IDs_ then need a slave-side handler function to be registered with the split transport, for example: + +```c +typedef struct _master_to_slave_t { + int m2s_data; +} master_to_slave_t; + +typedef struct _slave_to_master_t { + int s2m_data; +} slave_to_master_t; + +void user_sync_a_slave_handler(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) { + const master_to_slave_t *m2s = (const master_to_slave_t*)in_data; + slave_to_master_t *s2m = (slave_to_master_t*)out_data; + s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back +} + +void keyboard_post_init_user(void) { + transaction_register_rpc(USER_SYNC_A, user_sync_a_slave_handler); +} +``` + +The master side can then invoke the slave-side handler - for normal keyboard functionality to be minimally affected, any keyboard- or user-level code attempting to sync data should be throttled: + +```c +void housekeeping_task_user(void) { + if (is_keyboard_master()) { + // Interact with slave every 500ms + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 500) { + master_to_slave_t m2s = {6}; + slave_to_master_t s2m = {0}; + if(transaction_rpc_exec(USER_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) { + last_sync = timer_read32(); + dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5 + } else { + dprint("Slave sync failed!\n"); + } + } + } +} +``` + +::: warning +It is recommended that any data sync between halves happens during the master side's _housekeeping task_. This ensures timely retries should failures occur. +::: + +If only one-way data transfer is needed, helper methods are provided: + +```c +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +bool transaction_rpc_send(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer); +bool transaction_rpc_recv(int8_t transaction_id, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +``` + +By default, the inbound and outbound data is limited to a maximum of 32 bytes each. The sizes can be altered if required: + +```c +// Master to slave: +#define RPC_M2S_BUFFER_SIZE 48 +// Slave to master: +#define RPC_S2M_BUFFER_SIZE 48 +``` + +### Hardware Configuration Options + +There are some settings that you may need to configure, based on how the hardware is set up. + +```c +#define MATRIX_ROW_PINS_RIGHT { <row pins> } +#define MATRIX_COL_PINS_RIGHT { <col pins> } +``` + +This allows you to specify a different set of pins for the matrix on the right side. This is useful if you have a board with differently-shaped halves that requires a different configuration (such as Keebio's Quefrency). The number of pins in the right and left matrices must be the same, if you have a board with a different number of rows or columns on one side, pad out the extra spaces with `NO_PIN` and make sure you add the unused rows or columns to your matrix. + +```c +#define DIRECT_PINS_RIGHT { { F1, F0, B0, C7 }, { F4, F5, F6, F7 } } +``` + +This allows you to specify a different set of direct pins for the right side. + +```c +#define ENCODERS_PAD_A_RIGHT { encoder1a, encoder2a } +#define ENCODERS_PAD_B_RIGHT { encoder1b, encoder2b } +``` + +This allows you to specify a different set of encoder pins for the right side. + +```c +#define RGBLIGHT_SPLIT +``` + +This option enables synchronization of the RGB Light modes between the controllers of the split keyboard. This is for keyboards that have RGB LEDs that are directly wired to the controller (that is, they are not using the "extra data" option on the TRRS cable). + +```c +#define RGBLED_SPLIT { 6, 6 } +``` + +This sets how many LEDs are directly connected to each controller. The first number is the left side, and the second number is the right side. + +::: tip +This setting implies that `RGBLIGHT_SPLIT` is enabled, and will forcibly enable it, if it's not. +::: + + +```c +#define SPLIT_USB_DETECT +``` + +Enabling this option changes the startup behavior to listen for an active USB communication to delegate which part is master and which is slave. With this option enabled and theres's USB communication, then that half assumes it is the master, otherwise it assumes it is the slave. + +Without this option, the master is the half that can detect voltage on the physical USB connection (VBUS detection). + +Enabled by default on ChibiOS/ARM. + +::: tip +This setting will stop the ability to demo using battery packs. +::: + +```c +#define SPLIT_USB_TIMEOUT 2000 +``` +This sets the maximum timeout when detecting master/slave when using `SPLIT_USB_DETECT`. + +```c +#define SPLIT_USB_TIMEOUT_POLL 10 +``` +This sets the poll frequency when detecting master/slave when using `SPLIT_USB_DETECT` + +```c +#define SPLIT_WATCHDOG_ENABLE +``` + +This will enable a software watchdog on any side delegated as slave and will reboot the keyboard if no successful communication occurs within `SPLIT_WATCHDOG_TIMEOUT`. This can be particularly helpful when `SPLIT_USB_DETECT` delegates both sides as slave in some circumstances. + +```c +#define SPLIT_WATCHDOG_TIMEOUT 3000 +``` +This set the maximum slave timeout when waiting for communication from master when using `SPLIT_WATCHDOG_ENABLE` + +## Hardware Considerations and Mods + +Master/slave delegation is made either by detecting voltage on VBUS connection or waiting for USB communication (`SPLIT_USB_DETECT`). Pro Micro boards can use VBUS detection out of the box and be used with or without `SPLIT_USB_DETECT`. + +Many ARM boards, but not all, do not support VBUS detection. Because it is common that ARM boards lack VBUS detection, `SPLIT_USB_DETECT` is automatically defined on ARM targets (technically when ChibiOS is targetted). + +### Teensy boards + +Teensy boards lack VBUS detection out of the box and must have `SPLIT_USB_DETECT` defined. With the Teensy 2.0 and Teensy++ 2.0, there is a simple hardware mod that you can perform to add VBUS detection, so you don't need the `SPLIT_USB_DETECT` option. + +You'll only need a few things: + +* A knife (x-acto knife, ideally) +* A solder station or hot air station +* An appropriate Schottky diode, such as the [PMEG2005EH](https://www.digikey.com/en/products/detail/nexperia-usa-inc/PMEG2005EH,115/1589924) + +You'll need to cut the small trace between the 5V and center pads on the back of the Teensy. + +Once you have done that, you will want to solder the diode from the 5V pad to the center pad. + +You may need to use the 5V pad from the regulator block above as the pads were too small and placed too closely together to place the Schottky diode properly. + +![Teensy++ 2.0](https://i.imgur.com/BPEC5n5.png) + +## Additional Resources + +Nicinabox has a [very nice and detailed guide](https://github.com/nicinabox/lets-split-guide) for the Let's Split keyboard, that covers most everything you need to know, including troubleshooting information. + +However, the RGB Light section is out of date, as it was written long before the RGB Split code was added to QMK Firmware. Instead, wire each strip up directly to the controller. + +<!-- I may port this information later, but for now ... it's very nice, and covers everything --> diff --git a/docs/features/st7565.md b/docs/features/st7565.md new file mode 100644 index 0000000000..de3e44d8e9 --- /dev/null +++ b/docs/features/st7565.md @@ -0,0 +1,274 @@ +# ST7565 LCD Driver + +## Supported Hardware + +LCD modules using ST7565 driver IC, communicating over SPI. + +|Module |IC |Size |Notes | +|------------------------------|-------|------|----------------------------------------------------------| +|Newhaven Display NHD-C12832A1Z|ST7565R|128x32|Used by Ergodox Infinity; primary consumer of this feature| +|Zolentech ZLE12864B |ST7565P|128x64|Requires contrast adjustment | + +## Usage + +To enable the feature, there are three steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`: + +```make +ST7565_ENABLE = yes +``` + +Then in your `keymap.c` file, implement the ST7565 task call. This example assumes your keymap has three layers named `_QWERTY`, `_FN` and `_ADJ`: + +```c +#ifdef ST7565_ENABLE +void st7565_task_user(void) { + // Host Keyboard Layer Status + st7565_write_P(PSTR("Layer: "), false); + + switch (get_highest_layer(layer_state)) { + case _QWERTY: + st7565_write_P(PSTR("Default\n"), false); + break; + case _FN: + st7565_write_P(PSTR("FN\n"), false); + break; + case _ADJ: + st7565_write_P(PSTR("ADJ\n"), false); + break; + default: + // Or use the write_ln shortcut over adding '\n' to the end of your string + st7565_write_ln_P(PSTR("Undefined"), false); + } + + // Host Keyboard LED Status + led_t led_state = host_keyboard_led_state(); + st7565_write_P(led_state.num_lock ? PSTR("NUM ") : PSTR(" "), false); + st7565_write_P(led_state.caps_lock ? PSTR("CAP ") : PSTR(" "), false); + st7565_write_P(led_state.scroll_lock ? PSTR("SCR ") : PSTR(" "), false); +} +#endif +``` + +## Logo Example + +In the default font, certain ranges of characters are reserved for a QMK logo. To render this logo to the screen, use the following code example: + +```c +static void render_logo(void) { + static const char PROGMEM qmk_logo[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x00 + }; + + st7565_write_P(qmk_logo, false); +} +``` + +## Buffer Read Example +For some purposes, you may need to read the current state of the display buffer. The `st7565_read_raw` function can be used to safely read bytes from the buffer. + +In this example, calling `fade_display` in the `st7565_task_user` function will slowly fade away whatever is on the screen by turning random pixels off over time. +```c +//Setup some mask which can be or'd with bytes to turn off pixels +const uint8_t single_bit_masks[8] = {127, 191, 223, 239, 247, 251, 253, 254}; + +static void fade_display(void) { + //Define the reader structure + display_buffer_reader_t reader; + uint8_t buff_char; + if (random() % 30 == 0) { + srand(timer_read()); + // Fetch a pointer for the buffer byte at index 0. The return structure + // will have the pointer and the number of bytes remaining from this + // index position if we want to perform a sequential read by + // incrementing the buffer pointer + reader = st7565_read_raw(0); + //Loop over the remaining buffer and erase pixels as we go + for (uint16_t i = 0; i < reader.remaining_element_count; i++) { + //Get the actual byte in the buffer by dereferencing the pointer + buff_char = *reader.current_element; + if (buff_char != 0) { + st7565_write_raw_byte(buff_char & single_bit_masks[rand() % 8], i); + } + //increment the pointer to fetch a new byte during the next loop + reader.current_element++; + } + } +} +``` + +## Other Examples + +In split keyboards, it is very common to have two displays that each render different content and are oriented or flipped differently. You can do this by switching which content to render by using the return value from `is_keyboard_master()` or `is_keyboard_left()` found in `split_util.h`, e.g: + +```c +#ifdef ST7565_ENABLE +display_rotation_t st7565_init_user(display_rotation_t rotation) { + if (!is_keyboard_master()) { + return DISPLAY_ROTATION_180; // flips the display 180 degrees if offhand + } + + return rotation; +} + +void st7565_task_user(void) { + if (is_keyboard_master()) { + render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc) + } else { + render_logo(); // Renders a static logo + } +} +#endif +``` + +## Basic Configuration + +|Define |Default |Description | +|------------------------|--------------|-----------------------------------------------------------------------------------------------------| +|`ST7565_A0_PIN` |*Not defined* |(Required) The GPIO connected to the display's A0 (data/command) pin | +|`ST7565_RST_PIN` |*Not defined* |(Required) The GPIO connected to the display's reset pin | +|`ST7565_SS_PIN` |*Not defined* |(Required) The GPIO connected to the display's slave select pin | +|`ST7565_SPI_CLK_DIVISOR`|`4` |The SPI clock divisor to use | +|`ST7565_FONT_H` |`"glcdfont.c"`|The font code file to use for custom fonts | +|`ST7565_FONT_START` |`0` |The starting character index for custom fonts | +|`ST7565_FONT_END` |`223` |The ending character index for custom fonts | +|`ST7565_FONT_WIDTH` |`6` |The font width | +|`ST7565_FONT_HEIGHT` |`8` |The font height (untested) | +|`ST7565_TIMEOUT` |`60000` |Turns off the screen after 60000ms of keyboard inactivity. Helps reduce burn-in. Set to 0 to disable.| +|`ST7565_COLUMN_OFFSET` |`0` |Shift output to the right this many pixels. | +|`ST7565_CONTRAST` |`32` |The default contrast level of the display, from 0 to 255. | +|`ST7565_UPDATE_INTERVAL`|`0` |Set the time interval for updating the display in ms. This will improve the matrix scan rate. | + +## Custom sized displays + +The default display size for this feature is 128x32 and all necessary defines are precalculated with that in mind. + +|Define |Default |Description | +|-----------------------|----------|-----------------------------------------------------------------------------------------------------------| +|`ST7565_DISPLAY_WIDTH` |`128` |The width of the display. | +|`ST7565_DISPLAY_HEIGHT`|`32` |The height of the display. | +|`ST7565_MATRIX_SIZE` |`512` |The local buffer size to allocate.<br>`(ST7565_DISPLAY_HEIGHT / 8 * ST7565_DISPLAY_WIDTH)`. | +|`ST7565_BLOCK_TYPE` |`uint16_t`|The unsigned integer type to use for dirty rendering. | +|`ST7565_BLOCK_COUNT` |`16` |The number of blocks the display is divided into for dirty rendering.<br>`(sizeof(ST7565_BLOCK_TYPE) * 8)`.| +|`ST7565_BLOCK_SIZE` |`32` |The size of each block for dirty rendering<br>`(ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)`. | + +## API + +```c +// Rotation enum values are flags +typedef enum { + DISPLAY_ROTATION_0, + DISPLAY_ROTATION_180 +} display_rotation_t; + +// Initialize the display, rotating the rendered output based on the define passed in. +// Returns true if the was initialized successfully +bool st7565_init(display_rotation_t rotation); + +// Called at the start of st7565_init, weak function overridable by the user +// rotation - the value passed into st7565_init +// Return new display_rotation_t if you want to override default rotation +display_rotation_t st7565_init_user(display_rotation_t rotation); + +// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering +void st7565_clear(void); + +// Renders the dirty chunks of the buffer to display +void st7565_render(void); + +// Moves cursor to character position indicated by column and line, wraps if out of bounds +// Max column denoted by 'st7565_max_chars()' and max lines by 'st7565_max_lines()' functions +void st7565_set_cursor(uint8_t col, uint8_t line); + +// Advances the cursor to the next page, writing ' ' if true +// Wraps to the begining when out of bounds +void st7565_advance_page(bool clearPageRemainder); + +// Moves the cursor forward 1 character length +// Advance page if there is not enough room for the next character +// Wraps to the begining when out of bounds +void st7565_advance_char(void); + +// Writes a single character to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Main handler that writes character data to the display buffer +void st7565_write_char(const char data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +void st7565_write(const char *data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +void st7565_write_ln(const char *data, bool invert); + +// Pans the buffer to the right (or left by passing true) by moving contents of the buffer +// Useful for moving the screen in preparation for new drawing +void st7565_pan(bool left); + +// Returns a pointer to the requested start index in the buffer plus remaining +// buffer length as struct +display_buffer_reader_t st7565_read_raw(uint16_t start_index); + +// Writes a string to the buffer at current cursor position +void st7565_write_raw(const char *data, uint16_t size); + +// Writes a single byte into the buffer at the specified index +void st7565_write_raw_byte(const char data, uint16_t index); + +// Sets a specific pixel on or off +// Coordinates start at top-left and go right and down for positive x and y +void st7565_write_pixel(uint8_t x, uint8_t y, bool on); + +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Remapped to call 'void st7565_write(const char *data, bool invert);' on ARM +void st7565_write_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +// Remapped to call 'void st7565_write_ln(const char *data, bool invert);' on ARM +void st7565_write_ln_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +void st7565_write_raw_P(const char *data, uint16_t size); + +// Can be used to manually turn on the screen if it is off +// Returns true if the screen was on or turns on +bool st7565_on(void); + +// Called when st7565_on() turns on the screen, weak function overridable by the user +// Not called if the screen is already on +void st7565_on_user(void); + +// Can be used to manually turn off the screen if it is on +// Returns true if the screen was off or turns off +bool st7565_off(void); + +// Called when st7565_off() turns off the screen, weak function overridable by the user +// Not called if the screen is already off +void st7565_off_user(void); + +// Returns true if the screen is currently on, false if it is +// not +bool st7565_is_on(void); + +// Basically it's st7565_render, but with timeout management and st7565_task_user calling! +void st7565_task(void); + +// Called at the start of st7565_task, weak function overridable by the user +void st7565_task_user(void); + +// Inverts the display +// Returns true if the screen was or is inverted +bool st7565_invert(bool invert); + +// Returns the maximum number of characters that will fit on a line +uint8_t st7565_max_chars(void); + +// Returns the maximum number of lines that will fit on the display +uint8_t st7565_max_lines(void); +``` diff --git a/docs/features/stenography.md b/docs/features/stenography.md new file mode 100644 index 0000000000..c6c2155a9a --- /dev/null +++ b/docs/features/stenography.md @@ -0,0 +1,221 @@ +# Stenography in QMK {#stenography-in-qmk} + +[Stenography](https://en.wikipedia.org/wiki/Stenotype) is a method of writing most often used by court reports, closed-captioning, and real-time transcription for the deaf. In stenography words are chorded syllable by syllable with a mixture of spelling, phonetic, and shortcut (briefs) strokes. Professional stenographers can reach 200-300 WPM without any of the strain usually found in standard typing and with far fewer errors (>99.9% accuracy). + +The [Open Steno Project](https://www.openstenoproject.org/) has built an open-source program called Plover that provides real-time translation of steno strokes into words and commands. It has an established dictionary and supports + +## Plover with QWERTY Keyboard {#plover-with-qwerty-keyboard} + +Plover can work with any standard QWERTY keyboard, although it is more efficient if the keyboard supports NKRO (n-key rollover) to allow Plover to see all the pressed keys at once. An example keymap for Plover can be found in `planck/keymaps/default`. Switching to the `PLOVER` layer adjusts the position of the keyboard to support the number bar. + +To enable NKRO, add `NKRO_ENABLE = yes` in your `rules.mk` and make sure to press `NK_ON` to turn it on because `NKRO_ENABLE = yes` merely adds the possibility of switching to NKRO mode but it doesn't automatically switch to it. If you want to automatically switch, add `#define FORCE_NKRO` in your `config.h`. + +You may also need to adjust your layout, either in QMK or in Plover, if you have anything other than a standard layout. You may also want to purchase some steno-friendly keycaps to make it easier to hit multiple keys. + +## Plover with Steno Protocol {#plover-with-steno-protocol} + +Plover also understands the language of several steno machines. QMK can speak a couple of these languages: TX Bolt and GeminiPR. An example layout can be found in `planck/keymaps/steno`. + +When QMK speaks to Plover over a steno protocol, Plover will not use the keyboard as input. This means that you can switch back and forth between a standard keyboard and your steno keyboard, or even switch layers from Plover to standard and back without needing to activate/deactivate Plover. + +In this mode, Plover expects to speak with a steno machine over a serial port so QMK will present itself to the operating system as a virtual serial port in addition to a keyboard. + +::: info +Note: Due to hardware limitations, you might not be able to run both a virtual serial port and mouse emulation at the same time. +::: + +::: warning +Serial stenography protocols are not supported on [V-USB keyboards](../compatible_microcontrollers#atmel-avr). +::: + +To enable stenography protocols, add the following lines to your `rules.mk`: +```make +STENO_ENABLE = yes +``` + +### TX Bolt {#tx-bolt} + +TX Bolt communicates the status of 24 keys over a simple protocol in variable-sized (1–4 bytes) packets. + +To select TX Bolt, add the following lines to your `rules.mk`: +```make +STENO_ENABLE = yes +STENO_PROTOCOL = txbolt +``` + +Each byte of the packet represents a different group of steno keys. Determining the group of a certain byte of the packet is done by checking the first two bits, the remaining bits are set if the corresponding steno key was pressed for the stroke. The last set of keys (as indicated by leading `11`) needs to keep track of less keys than there are bits so one of the bits is constantly 0. + +The start of a new packet can be detected by comparing the group “ID” (the two MSBs) of the current byte to that of the previously received byte. If the group “ID” of the current byte is smaller or equal to that of the previous byte, it means that the current byte is the beginning of a new packet. + +The format of TX Bolt packets is shown below. +``` +00HWPKTS 01UE*OAR 10GLBPRF 110#ZDST +``` + +Examples of steno strokes and the associated packet: +- `EUBG` = `01110000 10101000` +- `WAZ` = `00010000 01000010 11001000` +- `PHAPBGS` = `00101000 01000010 10101100 11000010` + +### GeminiPR {#geminipr} + +GeminiPR encodes 42 keys into a 6-byte packet. While TX Bolt contains everything that is necessary for standard stenography, GeminiPR opens up many more options, including differentiating between top and bottom `S-`, and supporting non-English theories. + +To select GeminiPR, add the following lines to your `rules.mk`: +```make +STENO_ENABLE = yes +STENO_PROTOCOL = geminipr +``` + +All packets in the GeminiPR protocol consist of exactly six bytes, used as bit-arrays for different groups of keys. The beginning of a packet is indicated by setting the most significant bit (MSB) to 1 while setting the MSB of the remaining five bytes to 0. + +The format of GeminiPR packets is shown below. +``` +1 Fn #1 #2 #3 #4 #5 #6 +0 S1- S2- T- K- P- W- H- +0 R- A- O- *1 *2 res1 res2 +0 pwr *3 *4 -E -U -F -R +0 -P -B -L -G -T -S -D +0 #7 #8 #9 #A #B #C -Z +``` + +Examples of steno strokes and the associated packet: +- `EUBG` = `10000000 00000000 00000000 00001100 00101000 00000000` +- `WAZ` = `10000000 00000010 00100000 00000000 00000000 00000001` +- `PHAPBGS` = `10000000 00000101 00100000 00000000 01101010 00000000` + +### Switching protocols on the fly {#switching-protocols-on-the-fly} + +If you wish to switch the serial protocol used to transfer the steno chords without having to recompile your keyboard firmware every time, you can press the `QK_STENO_BOLT` and `QK_STENO_GEMINI` keycodes in order to switch protocols on the fly. + +To enable these special keycodes, add the following lines to your `rules.mk`: +```make +STENO_ENABLE = yes +STENO_PROTOCOL = all +``` + +If you want to switch protocols programatically, as part of a custom macro for example, don't use `tap_code(QK_STENO_*)`, as `tap_code` only supports [basic keycodes](../keycodes_basic). Instead, you should use `steno_set_mode(STENO_MODE_*)`, whose valid arguments are `STENO_MODE_BOLT` and `STENO_MODE_GEMINI`. + +The default protocol is Gemini PR but the last protocol used is stored in non-volatile memory so QMK will remember your choice between reboots of your keyboard — assuming that your keyboard features (emulated) EEPROM. + +Naturally, this option takes the most amount of firmware space as it needs to compile the code for all the available stenography protocols. In most cases, compiling a single stenography protocol is sufficient. + +The default value for `STENO_PROTOCOL` is `all`. + +## Configuring QMK for Steno {#configuring-qmk-for-steno} + +After enabling stenography and optionally selecting a protocol, you may also need disable mouse keys, extra keys, or another USB endpoint to prevent conflicts. The builtin USB stack for some processors only supports a certain number of USB endpoints and the virtual serial port needed for steno fills 3 of them. + +::: warning +If you had *explicitly* set `VIRSTER_ENABLE = no`, none of the serial stenography protocols (GeminiPR, TX Bolt) will work properly. You are expected to either set it to `yes`, remove the line from your `rules.mk` or send the steno chords yourself in an alternative way using the [provided interceptable hooks](#interfacing-with-the-code). +::: + +In your keymap, create a new layer for Plover, that you can fill in with the [steno keycodes](#keycode-reference). Remember to create a key to switch to the layer as well as a key for exiting the layer. + +Once you have your keyboard flashed, launch Plover. Click the 'Configure...' button. In the 'Machine' tab, select the Stenotype Machine that corresponds to your desired protocol. Click the 'Configure...' button on this tab and enter the serial port or click 'Scan'. Baud rate is fine at 9600 (although you should be able to set as high as 115200 with no issues). Use the default settings for everything else (Data Bits: 8, Stop Bits: 1, Parity: N, no flow control). + +To test your keymap, you can chord keys on your keyboard and either look at the output of the 'paper tape' (Tools > Paper Tape) or that of the 'layout display' (Tools > Layout Display). If your strokes correctly show up, you are now ready to steno! + +## Learning Stenography {#learning-stenography} + +* [Learn Plover!](https://sites.google.com/site/learnplover/) +* [Steno Jig](https://joshuagrams.github.io/steno-jig/) +* More resources at the Plover [Learning Stenography](https://github.com/openstenoproject/plover/wiki/Learning-Stenography) wiki + +## Interfacing with the code {#interfacing-with-the-code} + +The steno code has three interceptable hooks. If you define these functions, they will be called at certain points in processing; if they return true, processing continues, otherwise it's assumed you handled things. + +```c +bool send_steno_chord_user(steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE]); +``` + +This function is called when a chord is about to be sent. Mode will be one of `STENO_MODE_BOLT` or `STENO_MODE_GEMINI`. This represents the actual chord that would be sent via whichever protocol. You can modify the chord provided to alter what gets sent. Remember to return true if you want the regular sending process to happen. + +```c +bool process_steno_user(uint16_t keycode, keyrecord_t *record) { return true; } +``` + +This function is called when a keypress has come in, before it is processed. The keycode should be one of `QK_STENO_BOLT`, `QK_STENO_GEMINI`, or one of the `STN_*` key values. + +```c +bool post_process_steno_user(uint16_t keycode, keyrecord_t *record, steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE], int8_t n_pressed_keys); +``` + +This function is called after a key has been processed, but before any decision about whether or not to send a chord. This is where to put hooks for things like, say, live displays of steno chords or keys. + +If `record->event.pressed` is false, and `n_pressed_keys` is 0 or 1, the chord will be sent shortly, but has not yet been sent. This relieves you of the need of keeping track of where a packet ends and another begins. + +The `chord` argument contains the packet of the current chord as specified by the protocol in use. This is *NOT* simply a list of chorded steno keys of the form `[STN_E, STN_U, STN_BR, STN_GR]`. Refer to the appropriate protocol section of this document to learn more about the format of the packets in your steno protocol/mode of choice. + +The `n_pressed_keys` argument is the number of physical keys actually being held down. +This is not always equal to the number of bits set to 1 (aka the [Hamming weight](https://en.wikipedia.org/wiki/Hamming_weight)) in `chord` because it is possible to simultaneously press down four keys, then release three of those four keys and then press yet another key while the fourth finger is still holding down its key. +At the end of this scenario given as an example, `chord` would have five bits set to 1 but +`n_pressed_keys` would be set to 2 because there are only two keys currently being pressed down. + +## Keycode Reference {#keycode-reference} + +::: info +Note: TX Bolt does not support the full set of keys. The TX Bolt implementation in QMK will map the GeminiPR keys to the nearest TX Bolt key so that one key map will work for both. +::: + +|GeminiPR|TX Bolt|Steno Key| +|--------|-------|-----------| +|`STN_N1`|`STN_NUM`|Number bar #1| +|`STN_N2`|`STN_NUM`|Number bar #2| +|`STN_N3`|`STN_NUM`|Number bar #3| +|`STN_N4`|`STN_NUM`|Number bar #4| +|`STN_N5`|`STN_NUM`|Number bar #5| +|`STN_N6`|`STN_NUM`|Number bar #6| +|`STN_N7`|`STN_NUM`|Number bar #7| +|`STN_N8`|`STN_NUM`|Number bar #8| +|`STN_N9`|`STN_NUM`|Number bar #9| +|`STN_NA`|`STN_NUM`|Number bar #A| +|`STN_NB`|`STN_NUM`|Number bar #B| +|`STN_NC`|`STN_NUM`|Number bar #C| +|`STN_S1`|`STN_SL`| `S-` upper| +|`STN_S2`|`STN_SL`| `S-` lower| +|`STN_TL`|`STN_TL`| `T-`| +|`STN_KL`|`STN_KL`| `K-`| +|`STN_PL`|`STN_PL`| `P-`| +|`STN_WL`|`STN_WL`| `W-`| +|`STN_HL`|`STN_HL`| `H-`| +|`STN_RL`|`STN_RL`| `R-`| +|`STN_A`|`STN_A`| `A` vowel| +|`STN_O`|`STN_O`| `O` vowel| +|`STN_ST1`|`STN_STR`| `*` upper-left | +|`STN_ST2`|`STN_STR`| `*` lower-left| +|`STN_ST3`|`STN_STR`| `*` upper-right| +|`STN_ST4`|`STN_STR`| `*` lower-right| +|`STN_E`|`STN_E`| `E` vowel| +|`STN_U`|`STN_U`| `U` vowel| +|`STN_FR`|`STN_FR`| `-F`| +|`STN_RR`|`STN_RR`| `-R`| +|`STN_PR`|`STN_PR`| `-P`| +|`STN_BR`|`STN_BR`| `-B`| +|`STN_LR`|`STN_LR`| `-L`| +|`STN_GR`|`STN_GR`| `-G`| +|`STN_TR`|`STN_TR`| `-T`| +|`STN_SR`|`STN_SR`| `-S`| +|`STN_DR`|`STN_DR`| `-D`| +|`STN_ZR`|`STN_ZR`| `-Z`| +|`STN_FN`|| (Function)| +|`STN_RES1`||(Reset 1)| +|`STN_RES2`||(Reset 2)| +|`STN_PWR`||(Power)| + +If you do not want to hit two keys with one finger combined keycodes can be used. These cause both keys to be reported as pressed or released. To use these keycodes define `STENO_COMBINEDMAP` in your `config.h` file. + +|Combined key | Key1 | Key 2 | +|---------------|--------|----------| +|STN_S3 | STN_S1 | STN_S2 | +|STN_TKL | STN_TL | STN_KL | +|STN_PWL | STN_PL | STN_WL | +|STN_HRL | STN_HL | STN_RL | +|STN_FRR | STN_FR | STN_RR | +|STN_PBR | STN_PR | STN_BR | +|STN_LGR | STN_LR | STN_GR | +|STN_TSR | STN_TR | STN_SR | +|STN_DZR | STN_DR | STN_ZR | +|STN_AO | STN_A | STN_O | +|STN_EU | STN_E | STN_U | diff --git a/docs/features/swap_hands.md b/docs/features/swap_hands.md new file mode 100644 index 0000000000..3560fe22f0 --- /dev/null +++ b/docs/features/swap_hands.md @@ -0,0 +1,57 @@ +# Swap-Hands Action + +The swap-hands action allows support for one-handed typing without requiring a separate layer. Set `SWAP_HANDS_ENABLE` in the Makefile and define a `hand_swap_config` entry in your keymap. Now whenever the `ACTION_SWAP_HANDS` command key is pressed the keyboard is mirrored. For instance, to type "Hello, World" on QWERTY you would type `^Ge^s^s^w^c W^wr^sd` + +## Configuration + +The configuration table is a simple 2-dimensional array to map from column/row to new column/row. Example `hand_swap_config` for Planck: + +```c +const keypos_t PROGMEM hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = { + {{11, 0}, {10, 0}, {9, 0}, {8, 0}, {7, 0}, {6, 0}, {5, 0}, {4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}}, + {{11, 1}, {10, 1}, {9, 1}, {8, 1}, {7, 1}, {6, 1}, {5, 1}, {4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}}, + {{11, 2}, {10, 2}, {9, 2}, {8, 2}, {7, 2}, {6, 2}, {5, 2}, {4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}}, + {{11, 3}, {10, 3}, {9, 3}, {8, 3}, {7, 3}, {6, 3}, {5, 3}, {4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}}, +}; +``` + +Note that the array indices are reversed same as the matrix and the values are of type `keypos_t` which is `{col, row}` and all values are zero-based. In the example above, `hand_swap_config[2][4]` (third row, fifth column) would return `{7, 2}` (third row, eighth column). Yes, this is confusing. + +## Swap Keycodes + +|Key |Aliases |Description | +|-----------------------------|---------|----------------------------------------------------| +|`SH_T(kc)` | |Momentary swap when held, `kc` when tapped | +|`QK_SWAP_HANDS_ON` |`SH_ON` |Turn on hand swap | +|`QK_SWAP_HANDS_OFF` |`SH_OFF` |Turn off hand swap | +|`QK_SWAP_HANDS_MOMENTARY_ON` |`SH_MON` |Turn on hand swap while held | +|`QK_SWAP_HANDS_MOMENTARY_OFF`|`SH_MOFF`|Turn off hand swap while held | +|`QK_SWAP_HANDS_TOGGLE` |`SH_TOGG`|Toggle hand swap | +|`QK_SWAP_HANDS_TAP_TOGGLE` |`SH_TT` |Momentary swap when held, toggle when tapped | +|`QK_SWAP_HANDS_ONE_SHOT` |`SH_OS` |Turn on hand swap while held or until next key press| + +`SH_TT` swap-hands tap-toggle key is similar to [layer tap-toggle](../feature_layers#switching-and-toggling-layers). Tapping repeatedly (5 taps by default) will toggle swap-hands on or off, like `SH_TOGG`. Tap-toggle count can be changed by defining a value for `TAPPING_TOGGLE`. + +## Encoder Mapping + +When using an encoder mapping, it's also able to handle swapping encoders between sides, too. + +Encoder indexes are defined as left-to-right, and the extent of the array needs to match the number of encoders on the keyboard. + +As an example, if a split keyboard has a single encoder per side, you can swap the order by using the following code in your keymap: +```c +#if defined(SWAP_HANDS_ENABLE) && defined(ENCODER_MAP_ENABLE) +const uint8_t PROGMEM encoder_hand_swap_config[NUM_ENCODERS] = { 1, 0 }; +#endif +``` + +### Functions {#functions} + +User callback functions to manipulate Swap-Hands: + +| Function | Description | +|-----------------------|---------------------------------------------| +| `swap_hands_on()` | Turns Swap-Hands on. | +| `swap_hands_off()` | Turns Swap-Hands off. | +| `swap_hands_toggle()` | Toggles Swap-Hands. | +| `is_swap_hands_on()` | Returns true if Swap-Hands is currently on. | diff --git a/docs/features/tap_dance.md b/docs/features/tap_dance.md new file mode 100644 index 0000000000..3c4040db22 --- /dev/null +++ b/docs/features/tap_dance.md @@ -0,0 +1,567 @@ +# Tap Dance: A Single Key Can Do 3, 5, or 100 Different Things + +## Introduction {#introduction} + +Hit the semicolon key once, send a semicolon. Hit it twice, rapidly -- send a colon. Hit it three times, and your keyboard's LEDs do a wild dance. That's just one example of what Tap Dance can do. It's one of the nicest community-contributed features in the firmware, conceived and created by [algernon](https://github.com/algernon) in [#451](https://github.com/qmk/qmk_firmware/pull/451). Here's how algernon describes the feature: + +With this feature one can specify keys that behave differently, based on the amount of times they have been tapped, and when interrupted, they get handled before the interrupter. + +## How to Use Tap Dance {#how-to-use} + +First, you will need `TAP_DANCE_ENABLE = yes` in your `rules.mk`, because the feature is disabled by default. This adds a little less than 1k to the firmware size. + +Optionally, you might want to set a custom `TAPPING_TERM` time by adding something like this in your `config.h` file: + +```c +#define TAPPING_TERM 175 +#define TAPPING_TERM_PER_KEY +``` + +The `TAPPING_TERM` time is the maximum time allowed between taps of your Tap Dance key, and is measured in milliseconds. For example, if you used the above `#define` statement and set up a Tap Dance key that sends `Space` on single-tap and `Enter` on double-tap, then this key will send `ENT` only if you tap this key twice in less than 175ms. If you tap the key, wait more than 175ms, and tap the key again you'll end up sending `SPC SPC` instead. The `TAPPING_TERM_PER_KEY` definition is only needed if you control the tapping term through a [custom `get_tapping_term` function](../tap_hold#tapping_term), which may be needed because `TAPPING_TERM` affects not just tap-dance keys. + +Next, you will want to define some tap-dance keys, which is easiest to do with the `TD()` macro. That macro takes a number which will later be used as an index into the `tap_dance_actions` array and turns it into a tap-dance keycode. + +After this, you'll want to use the `tap_dance_actions` array to specify what actions shall be taken when a tap-dance key is in action. Currently, there are five possible options: + +* `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. When the key is held, the appropriate keycode is registered: `kc1` when pressed and held, `kc2` when tapped once, then pressed and held. +* `ACTION_TAP_DANCE_LAYER_MOVE(kc, layer)`: Sends the `kc` keycode when tapped once, or moves to `layer`. (this functions like the `TO` layer keycode). +* `ACTION_TAP_DANCE_LAYER_TOGGLE(kc, layer)`: Sends the `kc` keycode when tapped once, or toggles the state of `layer`. (this functions like the `TG` layer keycode). +* `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the final tap count of the tap dance action. +* `ACTION_TAP_DANCE_FN_ADVANCED(on_each_tap_fn, on_dance_finished_fn, on_dance_reset_fn)`: Calls the first specified function - defined in the user keymap - on every tap, the second function when the dance action finishes (like the previous option), and the last function when the tap dance action resets. +* `ACTION_TAP_DANCE_FN_ADVANCED_WITH_RELEASE(on_each_tap_fn, on_each_release_fn, on_dance_finished_fn, on_dance_reset_fn)`: This macro is identical to `ACTION_TAP_DANCE_FN_ADVANCED` with the addition of `on_each_release_fn` which is invoked every time the key for the tap dance is released. It is worth noting that `on_each_release_fn` will still be called even when the key is released after the dance finishes (e.g. if the key is released after being pressed and held for longer than the `TAPPING_TERM`). + +The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise. + +::: warning +Keep in mind that only [basic keycodes](../keycodes_basic) are supported here. Custom keycodes are not supported. +::: + +Similar to the first option, the second and third option are good for simple layer-switching cases. + +For more complicated cases, like blink the LEDs, fiddle with the backlighting, and so on, use the fourth or fifth option. Examples of each are listed below. + +## Implementation Details {#implementation} + +Well, that's the bulk of it! You should now be able to work through the examples below, and to develop your own Tap Dance functionality. But if you want a deeper understanding of what's going on behind the scenes, then read on for the explanation of how it all works! + +Let's go over the three functions mentioned in `ACTION_TAP_DANCE_FN_ADVANCED` in a little more detail. They all receive the same two arguments: a pointer to a structure that holds all dance related state information, and a pointer to a use case specific state variable. The three functions differ in when they are called. The first, `on_each_tap_fn()`, is called every time the tap dance key is *pressed*. Before it is called, the counter is incremented and the timer is reset. The second function, `on_dance_finished_fn()`, is called when the tap dance is interrupted or ends because `TAPPING_TERM` milliseconds have passed since the last tap. When the `finished` field of the dance state structure is set to `true`, the `on_dance_finished_fn()` is skipped. After `on_dance_finished_fn()` was called or would have been called, but no sooner than when the tap dance key is *released*, `on_dance_reset_fn()` is called. It is possible to end a tap dance immediately, skipping `on_dance_finished_fn()`, but not `on_dance_reset_fn`, by calling `reset_tap_dance(state)`. + +To accomplish this logic, the tap dance mechanics use three entry points. The main entry point is `process_tap_dance()`, called from `process_record_quantum()` *after* `process_record_kb()` and `process_record_user()`. This function is responsible for calling `on_each_tap_fn()` and `on_dance_reset_fn()`. In order to handle interruptions of a tap dance, another entry point, `preprocess_tap_dance()` is run right at the beginning of `process_record_quantum()`. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. Finally, `tap_dance_task()` periodically checks whether `TAPPING_TERM` has passed since the last key press and finishes a tap dance if that is the case. + +This means that you have `TAPPING_TERM` time to tap the key again; you do not have to input all the taps within a single `TAPPING_TERM` timeframe. This allows for longer tap counts, with minimal impact on responsiveness. + +## Examples {#examples} + +### Simple Example: Send `ESC` on Single Tap, `CAPS_LOCK` on Double Tap {#simple-example} + +Here's a simple example for a single definition: + +1. In your `rules.mk`, add `TAP_DANCE_ENABLE = yes` +2. In your `keymap.c` file, define the variables and definitions, then add to your keymap: + +```c +// Tap Dance declarations +enum { + TD_ESC_CAPS, +}; + +// Tap Dance definitions +tap_dance_action_t tap_dance_actions[] = { + // Tap once for Escape, twice for Caps Lock + [TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS), +}; + +// Add tap dance item to your keymap in place of a keycode +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + // ... + TD(TD_ESC_CAPS) + // ... +}; +``` + +### Complex Examples {#complex-examples} + +This section details several complex tap dance examples. +All the enums used in the examples are declared like this: + +```c +// Enums defined for all examples: +enum { + TD_ESC_CAPS, + CT_EGG, + CT_FLSH, + CT_CLN, + X_CTL, +}; +``` + +#### Example 1: Send "Safety Dance!" After 100 Taps {#example-1} + +```c +void dance_egg(tap_dance_state_t *state, void *user_data) { + if (state->count >= 100) { + SEND_STRING("Safety dance!"); + reset_tap_dance(state); + } +} + +tap_dance_action_t tap_dance_actions[] = { + [CT_EGG] = ACTION_TAP_DANCE_FN(dance_egg), +}; +``` + +#### Example 2: Turn LED Lights On Then Off, One at a Time {#example-2} + +```c +// On each tap, light up one LED, from right to left +// On the fourth tap, turn them off from right to left +void dance_flsh_each(tap_dance_state_t *state, void *user_data) { + switch (state->count) { + case 1: + ergodox_right_led_3_on(); + break; + case 2: + ergodox_right_led_2_on(); + break; + case 3: + ergodox_right_led_1_on(); + break; + case 4: + ergodox_right_led_3_off(); + wait_ms(50); + ergodox_right_led_2_off(); + wait_ms(50); + ergodox_right_led_1_off(); + } +} + +// On the fourth tap, set the keyboard on flash state +void dance_flsh_finished(tap_dance_state_t *state, void *user_data) { + if (state->count >= 4) { + reset_keyboard(); + } +} + +// If the flash state didn't happen, then turn off LEDs, left to right +void dance_flsh_reset(tap_dance_state_t *state, void *user_data) { + ergodox_right_led_1_off(); + wait_ms(50); + ergodox_right_led_2_off(); + wait_ms(50); + ergodox_right_led_3_off(); +} + +// All tap dances now put together. Example 2 is "CT_FLSH" +tap_dance_action_t tap_dance_actions[] = { + [TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS), + [CT_EGG] = ACTION_TAP_DANCE_FN(dance_egg), + [CT_FLSH] = ACTION_TAP_DANCE_FN_ADVANCED(dance_flsh_each, dance_flsh_finished, dance_flsh_reset) +}; +``` + +#### Example 3: Send `:` on Tap, `;` on Hold {#example-3} + +With a little effort, powerful tap-hold configurations can be implemented as tap dances. To emit taps as early as possible, we need to act on releases of the tap dance key. There is no callback for this in the tap dance framework, so we use `process_record_user()`. + +```c +typedef struct { + uint16_t tap; + uint16_t hold; + uint16_t held; +} tap_dance_tap_hold_t; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + tap_dance_action_t *action; + + switch (keycode) { + case TD(CT_CLN): // list all tap dance keycodes with tap-hold configurations + action = &tap_dance_actions[QK_TAP_DANCE_GET_INDEX(keycode)]; + if (!record->event.pressed && action->state.count && !action->state.finished) { + tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; + tap_code16(tap_hold->tap); + } + } + return true; +} + +void tap_dance_tap_hold_finished(tap_dance_state_t *state, void *user_data) { + tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data; + + if (state->pressed) { + if (state->count == 1 +#ifndef PERMISSIVE_HOLD + && !state->interrupted +#endif + ) { + register_code16(tap_hold->hold); + tap_hold->held = tap_hold->hold; + } else { + register_code16(tap_hold->tap); + tap_hold->held = tap_hold->tap; + } + } +} + +void tap_dance_tap_hold_reset(tap_dance_state_t *state, void *user_data) { + tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data; + + if (tap_hold->held) { + unregister_code16(tap_hold->held); + tap_hold->held = 0; + } +} + +#define ACTION_TAP_DANCE_TAP_HOLD(tap, hold) \ + { .fn = {NULL, tap_dance_tap_hold_finished, tap_dance_tap_hold_reset}, .user_data = (void *)&((tap_dance_tap_hold_t){tap, hold, 0}), } + +tap_dance_action_t tap_dance_actions[] = { + [CT_CLN] = ACTION_TAP_DANCE_TAP_HOLD(KC_COLN, KC_SCLN), +}; +``` + +#### Example 4: 'Quad Function Tap-Dance' {#example-4} + +By [DanielGGordon](https://github.com/danielggordon) + +Allow one key to have 4 (or more) functions, depending on number of presses, and if the key is held or tapped. +Below is a specific example: +* Tap = Send `x` +* Hold = Send `Control` +* Double Tap = Send `Escape` +* Double Tap and Hold = Send `Alt` + +You will need a few things that can be used for 'Quad Function Tap-Dance'. + +You'll need to add these to the top of your `keymap.c` file, before your keymap. + +```c +typedef enum { + TD_NONE, + TD_UNKNOWN, + TD_SINGLE_TAP, + TD_SINGLE_HOLD, + TD_DOUBLE_TAP, + TD_DOUBLE_HOLD, + TD_DOUBLE_SINGLE_TAP, // Send two single taps + TD_TRIPLE_TAP, + TD_TRIPLE_HOLD +} td_state_t; + +typedef struct { + bool is_press_action; + td_state_t state; +} td_tap_t; + +// Tap dance enums +enum { + X_CTL, + SOME_OTHER_DANCE +}; + +td_state_t cur_dance(tap_dance_state_t *state); + +// For the x tap dance. Put it here so it can be used in any keymap +void x_finished(tap_dance_state_t *state, void *user_data); +void x_reset(tap_dance_state_t *state, void *user_data); +``` + +Now, at the bottom of your `keymap.c` file, you'll need to add the following: + +```c +/* Return an integer that corresponds to what kind of tap dance should be executed. + * + * How to figure out tap dance state: interrupted and pressed. + * + * Interrupted: If the state of a dance is "interrupted", that means that another key has been hit + * under the tapping term. This is typically indicitive that you are trying to "tap" the key. + * + * Pressed: Whether or not the key is still being pressed. If this value is true, that means the tapping term + * has ended, but the key is still being pressed down. This generally means the key is being "held". + * + * One thing that is currenlty not possible with qmk software in regards to tap dance is to mimic the "permissive hold" + * feature. In general, advanced tap dances do not work well if they are used with commonly typed letters. + * For example "A". Tap dances are best used on non-letter keys that are not hit while typing letters. + * + * Good places to put an advanced tap dance: + * z,q,x,j,k,v,b, any function key, home/end, comma, semi-colon + * + * Criteria for "good placement" of a tap dance key: + * Not a key that is hit frequently in a sentence + * Not a key that is used frequently to double tap, for example 'tab' is often double tapped in a terminal, or + * in a web form. So 'tab' would be a poor choice for a tap dance. + * Letters used in common words as a double. For example 'p' in 'pepper'. If a tap dance function existed on the + * letter 'p', the word 'pepper' would be quite frustating to type. + * + * For the third point, there does exist the 'TD_DOUBLE_SINGLE_TAP', however this is not fully tested + * + */ +td_state_t cur_dance(tap_dance_state_t *state) { + if (state->count == 1) { + if (state->interrupted || !state->pressed) return TD_SINGLE_TAP; + // Key has not been interrupted, but the key is still held. Means you want to send a 'HOLD'. + else return TD_SINGLE_HOLD; + } else if (state->count == 2) { + // TD_DOUBLE_SINGLE_TAP is to distinguish between typing "pepper", and actually wanting a double tap + // action when hitting 'pp'. Suggested use case for this return value is when you want to send two + // keystrokes of the key, and not the 'double tap' action/macro. + if (state->interrupted) return TD_DOUBLE_SINGLE_TAP; + else if (state->pressed) return TD_DOUBLE_HOLD; + else return TD_DOUBLE_TAP; + } + + // Assumes no one is trying to type the same letter three times (at least not quickly). + // If your tap dance key is 'KC_W', and you want to type "www." quickly - then you will need to add + // an exception here to return a 'TD_TRIPLE_SINGLE_TAP', and define that enum just like 'TD_DOUBLE_SINGLE_TAP' + if (state->count == 3) { + if (state->interrupted || !state->pressed) return TD_TRIPLE_TAP; + else return TD_TRIPLE_HOLD; + } else return TD_UNKNOWN; +} + +// Create an instance of 'td_tap_t' for the 'x' tap dance. +static td_tap_t xtap_state = { + .is_press_action = true, + .state = TD_NONE +}; + +void x_finished(tap_dance_state_t *state, void *user_data) { + xtap_state.state = cur_dance(state); + switch (xtap_state.state) { + case TD_SINGLE_TAP: register_code(KC_X); break; + case TD_SINGLE_HOLD: register_code(KC_LCTL); break; + case TD_DOUBLE_TAP: register_code(KC_ESC); break; + case TD_DOUBLE_HOLD: register_code(KC_LALT); break; + // Last case is for fast typing. Assuming your key is `f`: + // For example, when typing the word `buffer`, and you want to make sure that you send `ff` and not `Esc`. + // In order to type `ff` when typing fast, the next character will have to be hit within the `TAPPING_TERM`, which by default is 200ms. + case TD_DOUBLE_SINGLE_TAP: tap_code(KC_X); register_code(KC_X); break; + default: break; + } +} + +void x_reset(tap_dance_state_t *state, void *user_data) { + switch (xtap_state.state) { + case TD_SINGLE_TAP: unregister_code(KC_X); break; + case TD_SINGLE_HOLD: unregister_code(KC_LCTL); break; + case TD_DOUBLE_TAP: unregister_code(KC_ESC); break; + case TD_DOUBLE_HOLD: unregister_code(KC_LALT); break; + case TD_DOUBLE_SINGLE_TAP: unregister_code(KC_X); break; + default: break; + } + xtap_state.state = TD_NONE; +} + +tap_dance_action_t tap_dance_actions[] = { + [X_CTL] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished, x_reset) +}; +``` + +And then simply use `TD(X_CTL)` anywhere in your keymap. + +::: info +In this configuration "hold" takes place **after** tap dance timeout. To achieve instant hold, remove `state->interrupted` checks in conditions. As a result you may use comfortable longer tapping periods to have more time for taps and not to wait too long for holds (try starting with doubled `TAPPING_TERM`). +::: + +#### Example 5: Using tap dance for advanced mod-tap and layer-tap keys {#example-5} + +Tap dance can be used to emulate `MT()` and `LT()` behavior when the tapped code is not a basic keycode. This is useful to send tapped keycodes that normally require `Shift`, such as parentheses or curly braces—or other modified keycodes, such as `Control + X`. + +Below your layers and custom keycodes, add the following: + +```c +// Tap Dance keycodes +enum td_keycodes { + ALT_LP // Our example key: `LALT` when held, `(` when tapped. Add additional keycodes for each tapdance. +}; + +// Define a type containing as many tapdance states as you need +typedef enum { + TD_NONE, + TD_UNKNOWN, + TD_SINGLE_TAP, + TD_SINGLE_HOLD, + TD_DOUBLE_SINGLE_TAP +} td_state_t; + +// Create a global instance of the tapdance state type +static td_state_t td_state; + +// Declare your tapdance functions: + +// Function to determine the current tapdance state +td_state_t cur_dance(tap_dance_state_t *state); + +// `finished` and `reset` functions for each tapdance keycode +void altlp_finished(tap_dance_state_t *state, void *user_data); +void altlp_reset(tap_dance_state_t *state, void *user_data); +``` + +Below your `LAYOUT`, define each of the tapdance functions: + +```c +// Determine the tapdance state to return +td_state_t cur_dance(tap_dance_state_t *state) { + if (state->count == 1) { + if (state->interrupted || !state->pressed) return TD_SINGLE_TAP; + else return TD_SINGLE_HOLD; + } + + if (state->count == 2) return TD_DOUBLE_SINGLE_TAP; + else return TD_UNKNOWN; // Any number higher than the maximum state value you return above +} + +// Handle the possible states for each tapdance keycode you define: + +void altlp_finished(tap_dance_state_t *state, void *user_data) { + td_state = cur_dance(state); + switch (td_state) { + case TD_SINGLE_TAP: + register_code16(KC_LPRN); + break; + case TD_SINGLE_HOLD: + register_mods(MOD_BIT(KC_LALT)); // For a layer-tap key, use `layer_on(_MY_LAYER)` here + break; + case TD_DOUBLE_SINGLE_TAP: // Allow nesting of 2 parens `((` within tapping term + tap_code16(KC_LPRN); + register_code16(KC_LPRN); + break; + default: + break; + } +} + +void altlp_reset(tap_dance_state_t *state, void *user_data) { + switch (td_state) { + case TD_SINGLE_TAP: + unregister_code16(KC_LPRN); + break; + case TD_SINGLE_HOLD: + unregister_mods(MOD_BIT(KC_LALT)); // For a layer-tap key, use `layer_off(_MY_LAYER)` here + break; + case TD_DOUBLE_SINGLE_TAP: + unregister_code16(KC_LPRN); + break; + default: + break; + } +} + +// Define `ACTION_TAP_DANCE_FN_ADVANCED()` for each tapdance keycode, passing in `finished` and `reset` functions +tap_dance_action_t tap_dance_actions[] = { + [ALT_LP] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, altlp_finished, altlp_reset) +}; +``` + +Wrap each tapdance keycode in `TD()` when including it in your keymap, e.g. `TD(ALT_LP)`. + +#### Example 6: Using tap dance for momentary-layer-switch and layer-toggle keys {#example-6} + +Tap Dance can be used to mimic MO(layer) and TG(layer) functionality. For this example, we will set up a key to function as `KC_QUOT` on single-tap, as `MO(_MY_LAYER)` on single-hold, and `TG(_MY_LAYER)` on double-tap. + +The first step is to include the following code towards the beginning of your `keymap.c`: + +```c +// Define a type for as many tap dance states as you need +typedef enum { + TD_NONE, + TD_UNKNOWN, + TD_SINGLE_TAP, + TD_SINGLE_HOLD, + TD_DOUBLE_TAP +} td_state_t; + +typedef struct { + bool is_press_action; + td_state_t state; +} td_tap_t; + +enum { + QUOT_LAYR, // Our custom tap dance key; add any other tap dance keys to this enum +}; + +// Declare the functions to be used with your tap dance key(s) + +// Function associated with all tap dances +td_state_t cur_dance(tap_dance_state_t *state); + +// Functions associated with individual tap dances +void ql_finished(tap_dance_state_t *state, void *user_data); +void ql_reset(tap_dance_state_t *state, void *user_data); +``` + +Towards the bottom of your `keymap.c`, include the following code: + +```c +// Determine the current tap dance state +td_state_t cur_dance(tap_dance_state_t *state) { + if (state->count == 1) { + if (!state->pressed) return TD_SINGLE_TAP; + else return TD_SINGLE_HOLD; + } else if (state->count == 2) return TD_DOUBLE_TAP; + else return TD_UNKNOWN; +} + +// Initialize tap structure associated with example tap dance key +static td_tap_t ql_tap_state = { + .is_press_action = true, + .state = TD_NONE +}; + +// Functions that control what our tap dance key does +void ql_finished(tap_dance_state_t *state, void *user_data) { + ql_tap_state.state = cur_dance(state); + switch (ql_tap_state.state) { + case TD_SINGLE_TAP: + tap_code(KC_QUOT); + break; + case TD_SINGLE_HOLD: + layer_on(_MY_LAYER); + break; + case TD_DOUBLE_TAP: + // Check to see if the layer is already set + if (layer_state_is(_MY_LAYER)) { + // If already set, then switch it off + layer_off(_MY_LAYER); + } else { + // If not already set, then switch the layer on + layer_on(_MY_LAYER); + } + break; + default: + break; + } +} + +void ql_reset(tap_dance_state_t *state, void *user_data) { + // If the key was held down and now is released then switch off the layer + if (ql_tap_state.state == TD_SINGLE_HOLD) { + layer_off(_MY_LAYER); + } + ql_tap_state.state = TD_NONE; +} + +// Associate our tap dance key with its functionality +tap_dance_action_t tap_dance_actions[] = { + [QUOT_LAYR] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, ql_finished, ql_reset) +}; + +// Set a long-ish tapping term for tap-dance keys +uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: + return 275; + default: + return TAPPING_TERM; + } +} +``` + +The above code is similar to that used in previous examples. The one point to note is that we need to be able to check which layers are active at any time so we can toggle them if needed. To do this we use the `layer_state_is(layer)` function which returns `true` if the given `layer` is active. + +The use of `cur_dance()` and `ql_tap_state` mirrors the above examples. + +The `case: TD_SINGLE_TAP` in `ql_finished` is similar to the above examples. The `TD_SINGLE_HOLD` case works in conjunction with `ql_reset()` to switch to `_MY_LAYER` while the tap dance key is held, and to switch away from `_MY_LAYER` when the key is released. This mirrors the use of `MO(_MY_LAYER)`. The `TD_DOUBLE_TAP` case works by checking whether `_MY_LAYER` is the active layer, and toggling it on or off accordingly. This mirrors the use of `TG(_MY_LAYER)`. + +`tap_dance_actions[]` works similar to the above examples. Note that, additionally, I set a longer tapping term for the tap dance keys. This is because I like my `TAPPING_TERM` to be short (\~175ms) for my non-tap-dance keys but find that this is too quick for me to reliably complete tap dance actions - thus the increased time of 275ms here. In order for the per-key tapping terms to take effect, `TAPPING_TERM_PER_KEY` must be defined in your `config.h`. + +Finally, to get this tap dance key working, be sure to include `TD(QUOT_LAYR)` in your `keymaps[]`. diff --git a/docs/features/tri_layer.md b/docs/features/tri_layer.md new file mode 100644 index 0000000000..c32e65caed --- /dev/null +++ b/docs/features/tri_layer.md @@ -0,0 +1,50 @@ +# Tri Layers {#tri-layers} + +This enables support for the OLKB style "Tri Layer" keycodes. These function similar to the `MO` (momentary) function key, but if both the "Lower" and "Upper" keys are pressed, it activates a third "Adjust" layer. To enable this functionality, add this line to your `rules.mk`: + +```make +TRI_LAYER_ENABLE = yes +``` + +Note that the "upper", "lower" and "adjust" names don't have a particular significance, they are just used to identify and clarify the behavior. Layers are processed from highest numeric value to lowest, however the values are not required to be consecutive. + +For a detailed explanation of how the layer stack works, check out [Keymap Overview](../keymap#keymap-and-layers). + +## Keycodes {#keycodes} + +| Keycode | Alias | Description | +|----------------------|-----------|---------------------------------------------------------------------------------------------------------| +| `QK_TRI_LAYER_LOWER` | `TL_LOWR` | Momentarily enables the "lower" layer. Enables the "adjust" layer if the "upper" layer is also enabled" | +| `QK_TRI_LAYER_UPPER` | `TL_UPPR` | Momentarily enables the "upper" layer. Enables the "adjust" layer if the "lower" layer is also enabled" | + +## Configuration + +To change the default values for the layers, you can change these defines, in your `config.h` + +| Config name | Default | Description | +|--------------------------|---------|------------------------------------------| +| `TRI_LAYER_LOWER_LAYER` | `1` | Sets the default for the "lower" layer. | +| `TRI_LAYER_UPPER_LAYER` | `2` | Sets the default for the "upper" layer. | +| `TRI_LAYER_ADJUST_LAYER` | `3` | Sets the default for the "adjust" layer. | + +Eg, if you wanted to set the "Adjust" layer to be layer 5, you'd add this to your `config.h`: + +```c +#define TRI_LAYER_ADJUST_LAYER 5 +``` + +## Functions + +| Function name | Description | +|----------------------------------------------|-------------------------------------------------| +| `set_tri_layer_lower_layer(layer)` | Changes the "lower" layer*. | +| `set_tri_layer_upper_layer(layer)` | Changes the "upper" layer*. | +| `set_tri_layer_adjust_layer(layer)` | Changes the "adjust" layer*. | +| `set_tri_layer_layers(lower, upper, adjust)` | Sets the "lower", "upper" and "adjust" layers*. | +| `get_tri_layer_lower_layer()` | Gets the current "lower" layer. | +| `get_tri_layer_upper_layer()` | Gets the current "upper" layer. | +| `get_tri_layer_adjust_layer()` | Gets the current "adjust" layer. | + +::: warning +Note: these settings are not persistent, and will be reset to the default on power loss or power cycling of the controller. +::: diff --git a/docs/features/unicode.md b/docs/features/unicode.md new file mode 100644 index 0000000000..7ed178c30d --- /dev/null +++ b/docs/features/unicode.md @@ -0,0 +1,454 @@ +# Unicode {#unicode} + +With a little help from your OS, practically any Unicode character can be input using your keyboard. + +## Caveats {#caveats} + +There are some limitations to this feature. Because there is no "standard" method of Unicode input across all operating systems, each of them require their own setup process on both the host *and* in the firmware, which may involve installation of additional software. This also means Unicode input will not "just work" when the keyboard is plugged into another device. + +## Usage {#usage} + +The core Unicode API can be used purely programmatically. However, there are also additional subsystems which build on top of it and come with keycodes to make things easier. See below for more details. + +Add the following to your keymap's `rules.mk`: + +```make +UNICODE_COMMON = yes +``` + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|------------------------|------------------|--------------------------------------------------------------------------------| +|`UNICODE_KEY_MAC` |`KC_LEFT_ALT` |The key to hold when beginning a Unicode sequence with the macOS input mode | +|`UNICODE_KEY_LNX` |`LCTL(LSFT(KC_U))`|The key to tap when beginning a Unicode sequence with the Linux input mode | +|`UNICODE_KEY_WINC` |`KC_RIGHT_ALT` |The key to hold when beginning a Unicode sequence with the WinCompose input mode| +|`UNICODE_SELECTED_MODES`|`-1` |A comma separated list of input modes for cycling through | +|`UNICODE_CYCLE_PERSIST` |`true` |Whether to persist the current Unicode input mode to EEPROM | +|`UNICODE_TYPE_DELAY` |`10` |The amount of time to wait, in milliseconds, between Unicode sequence keystrokes| + +### Audio Feedback {#audio-feedback} + +If you have the [Audio](audio) feature enabled on your board, you can configure it to play sounds when the input mode is changed. + +Add the following to your `config.h`: + +|Define |Default|Description | +|-------------------|-------|-----------------------------------------------------------| +|`UNICODE_SONG_MAC` |*n/a* |The song to play when the macOS input mode is selected | +|`UNICODE_SONG_LNX` |*n/a* |The song to play when the Linux input mode is selected | +|`UNICODE_SONG_BSD` |*n/a* |The song to play when the BSD input mode is selected | +|`UNICODE_SONG_WIN` |*n/a* |The song to play when the Windows input mode is selected | +|`UNICODE_SONG_WINC`|*n/a* |The song to play when the WinCompose input mode is selected| + +## Input Subsystems {#input-subsystems} + +Each of these subsystems have their own pros and cons in terms of flexibility and ease of use. Choose the one that best fits your needs. + +::::tabs + +=== Basic + +This is the easiest to use, albeit somewhat limited. It supports code points up to `U+7FFF`, which covers characters for most modern languages (including East Asian), as well as many symbols, but does not include emoji. + +To enable Basic Unicode, add the following to your `rules.mk`: + +```make +UNICODE_ENABLE = yes +``` + +You can then add `UC(c)` keycodes to your keymap, where *c* is the code point of the desired character (in hexadecimal - the `U+` prefix will not work). For example, `UC(0x40B)` will output [Ћ](https://unicode-table.com/en/040B/), and `UC(0x30C4)` will output [ツ](https://unicode-table.com/en/30C4). + +=== Unicode Map + +Unicode Map supports all possible code points (up to `U+10FFFF`). Here, the code points are stored in a separate mapping table (which may contain at most 16,384 entries), instead of directly in the keymap. + +To enable Unicode Map, add the following to your `rules.mk`: + +```make +UNICODEMAP_ENABLE = yes +``` + +Then, you will need to create a mapping table in your `keymap.c`, and (optionally) an enum for naming the array indices, like so: + +```c +enum unicode_names { + BANG, + IRONY, + SNEK +}; + +const uint32_t PROGMEM unicode_map[] = { + [BANG] = 0x203D, // ‽ + [IRONY] = 0x2E2E, // ⸮ + [SNEK] = 0x1F40D, // 🐍 +}; +``` + +Finally, add `UM(i)` keycodes to your keymap, where *i* is an index into the `unicode_map[]` array. If you defined the enum above, you can use those names instead, for example `UM(BANG)` or `UM(SNEK)`. + +#### Lower and Upper Case Pairs {#unicodemap-pairs} + +Some writing systems have lowercase and uppercase variants of each character, such as å and Å. To make inputting these characters easier, you can use the `UP(i, j)` keycode in your keymap, where *i* and *j* are the mapping table indices of the lowercase and uppercase characters, respectively. If you're holding down Shift or have Caps Lock turned on when you press the key, the uppercase character will be inserted; otherwise, the lowercase character will be inserted. + +```c +const uint32_t PROGMEM unicode_map[] = { + [AE_LOWER] = 0x00E6, // æ + [AE_UPPER] = 0x00C6, // Æ +}; +``` + +This is most useful when creating a keymap for an international layout with special characters. Instead of having to put the lower and upper case versions of a character on separate keys, you can have them both on the same key. This helps blend Unicode keys in with regular keycodes. + +Due to keycode size constraints, *i* and *j* can each only refer to one of the first 128 characters in your `unicode_map`. In other words, 0 ≤ *i* ≤ 127 and 0 ≤ *j* ≤ 127. + +=== UCIS + +As with Unicode Map, the UCIS method also supports all possible code points, and requires the use of a mapping table. However, it works much differently - Unicode characters are input by replacing a typed mnemonic. + +To enable UCIS, add the following to your keymap's `rules.mk`: + +```make +UCIS_ENABLE = yes +``` + +Then, create a mapping table in your `keymap.c`: + +```c +const ucis_symbol_t ucis_symbol_table[] = UCIS_TABLE( + UCIS_SYM("poop", 0x1F4A9), // 💩 + UCIS_SYM("rofl", 0x1F923), // 🤣 + UCIS_SYM("ukr", 0x1F1FA, 0x1F1E6), // 🇺🇦 + UCIS_SYM("look", 0x0CA0, 0x005F, 0x0CA0) // ಠ_ಠ +); +``` + +By default, each table entry may be up to three code points long. This can be changed by adding `#define UCIS_MAX_CODE_POINTS n` to your keymap's `config.h`. + +To invoke UCIS input, the `ucis_start()` function must first be called (for example, in a custom "Unicode" keycode). Then, type the mnemonic for the mapping table entry (such as "rofl"), and hit Space or Enter. The "rofl" text will be backspaced and the emoji inserted. + +:::: + +## Input Modes {#input-modes} + +Unicode input works by typing a sequence of characters, similar to a macro. However, since this sequence depends on your OS, you will need to prepare both your host machine and QMK to recognise and send the correct Unicode input sequences respectively. + +To set the list of enabled input modes, add the `UNICODE_SELECTED_MODES` define to your keymap's `config.h`, for example: + +```c +#define UNICODE_SELECTED_MODES UNICODE_MODE_LINUX +// or +#define UNICODE_SELECTED_MODES UNICODE_MODE_MACOS, UNICODE_MODE_WINCOMPOSE +``` + +These modes can then be cycled through using the `UC_NEXT` and `UC_PREV` keycodes. You can also switch to any input mode, even if it is not specified in `UNICODE_SELECTED_MODES`, using their respective keycodes. + +If your keyboard has working EEPROM, it will remember the last used input mode and continue using it on the next power up. This can be disabled by defining `UNICODE_CYCLE_PERSIST` to `false`. + +:::::tabs + +==== macOS + +**Mode Name:** `UNICODE_MODE_MACOS` + +macOS has built-in support for Unicode input as its own input source. It supports all possible code points by way of surrogate pairs for code points above `U+FFFF`. + +To enable, go to **System Preferences → Keyboard → Input Sources**, then add Unicode Hex Input to the list (under Other), and activate it from the input dropdown in the menu bar. Note that this may disable some Option-based shortcuts such as Option+Left and Option+Right. + +==== Linux (IBus) + +**Mode Name:** `UNICODE_MODE_LINUX` + +For Linux distros with IBus, Unicode input is enabled by default, supports all possible code points, and works almost anywhere. Without IBus, it works under GTK apps, but rarely anywhere else. + +Users who would like support in non-GTK apps without IBus may need to resort to a more indirect method, such as creating a custom keyboard layout. + +==== Windows (WinCompose) + +**Mode Name:** `UNICODE_MODE_WINCOMPOSE` + +This mode requires a third-party tool called [WinCompose](https://github.com/samhocevar/wincompose). It supports all possible code points, and is the recommended input mode for Windows. + +To enable, install the [latest release from GitHub](https://github.com/samhocevar/wincompose/releases/latest). Once installed, it will automatically run on startup. This works reliably under all versions of Windows supported by WinCompose. + +==== Windows (HexNumpad) + +**Mode Name:** `UNICODE_MODE_WINDOWS` + +::: warning +This input mode is *not* the "Alt code" system. Alt codes are not Unicode; they instead follow [the Windows-1252 character set](https://en.wikipedia.org/wiki/Alt_code). +::: + +This is Windows' built-in hex numpad Unicode input mode. It only supports code points up to `U+FFFF`, and is not recommended due to reliability and compatibility issues. + +To enable, run the following as an administrator, then reboot: + +``` +reg add "HKCU\Control Panel\Input Method" -v EnableHexNumpad -t REG_SZ -d 1 +``` + +==== Emacs + +**Mode Name:** `UNICODE_MODE_EMACS` + +Emacs supports code point input with the `insert-char` command. + +==== BSD + +**Mode Name:** `UNICODE_MODE_BSD` + +Not currently implemented. If you're a BSD user and want to contribute support for this input mode, please [feel free](../contributing)! + +::::: + +## Keycodes {#keycodes} + +|Key |Aliases |Description | +|----------------------------|---------|----------------------------------------------------------------| +|`UC(c)` | |Send Unicode code point `c`, up to `0x7FFF` | +|`UM(i)` | |Send Unicode code point at index `i` in `unicode_map` | +|`UP(i, j)` | |Send Unicode code point at index `i`, or `j` if Shift/Caps is on| +|`QK_UNICODE_MODE_NEXT` |`UC_NEXT`|Cycle through selected input modes | +|`QK_UNICODE_MODE_PREVIOUS` |`UC_PREV`|Cycle through selected input modes in reverse | +|`QK_UNICODE_MODE_MACOS` |`UC_MAC` |Switch to macOS input | +|`QK_UNICODE_MODE_LINUX` |`UC_LINX`|Switch to Linux input | +|`QK_UNICODE_MODE_WINDOWS` |`UC_WIN` |Switch to Windows input | +|`QK_UNICODE_MODE_BSD` |`UC_BSD` |Switch to BSD input (not implemented) | +|`QK_UNICODE_MODE_WINCOMPOSE`|`UC_WINC`|Switch to Windows input using WinCompose | +|`QK_UNICODE_MODE_EMACS` |`UC_EMAC`|Switch to emacs (`C-x-8 RET`) | + +## API {#api} + +### `uint8_t get_unicode_input_mode(void)` {#api-get-unicode-input-mode} + +Get the current Unicode input mode. + +#### Return Value {#api-get-unicode-input-mode-return-value} + +The currently active Unicode input mode. + +--- + +### `void set_unicode_input_mode(uint8_t mode)` {#api-set-unicode-input-mode} + +Set the Unicode input mode. + +#### Arguments {#api-set-unicode-input-mode-arguments} + + - `uint8_t mode` + The input mode to set. + +--- + +### `void unicode_input_mode_step(void)` {#api-unicode-input-mode-step} + +Change to the next Unicode input mode. + +--- + +### `void unicode_input_mode_step_reverse(void)` {#api-unicode-input-mode-step-reverse} + +Change to the previous Unicode input mode. + +--- + +### `void unicode_input_mode_set_user(uint8_t input_mode)` {#api-unicode-input-mode-set-user} + +User-level callback, invoked when the input mode is changed. + +#### Arguments {#api-unicode-input-mode-set-user-arguments} + + - `uint8_t input_mode` + The new input mode. + +--- + +### `void unicode_input_mode_set_kb(uint8_t input_mode)` {#api-unicode-input-mode-set-kb} + +Keyboard-level callback, invoked when the input mode is changed. + +#### Arguments {#api-unicode-input-mode-set-kb-arguments} + + - `uint8_t input_mode` + The new input mode. + +--- + +### `void unicode_input_start(void)` {#api-unicode-input-start} + +Begin the Unicode input sequence. The exact behavior depends on the currently selected input mode: + + - **macOS**: Hold `UNICODE_KEY_MAC` + - **Linux**: Tap `UNICODE_KEY_LNX` + - **WinCompose**: Tap `UNICODE_KEY_WINC`, then U + - **HexNumpad**: Hold Left Alt, then tap Numpad + + - **Emacs**: Tap Ctrl+X, then 8, then Enter + +This function is weakly defined, and can be overridden in user code. + +--- + +### `void unicode_input_finish(void)` {#api-unicode-input-finish} + +Complete the Unicode input sequence. The exact behavior depends on the currently selected input mode: + + - **macOS**: Release `UNICODE_KEY_MAC` + - **Linux**: Tap Space + - **WinCompose**: Tap Enter + - **HexNumpad**: Release Left Alt + - **Emacs**: Tap Enter + +This function is weakly defined, and can be overridden in user code. + +--- + +### `void unicode_input_cancel(void)` {#api-unicode-input-cancel} + +Cancel the Unicode input sequence. The exact behavior depends on the currently selected input mode: + + - **macOS**: Release `UNICODE_KEY_MAC` + - **Linux**: Tap Escape + - **WinCompose**: Tap Escape + - **HexNumpad**: Release Left Alt + - **Emacs**: Tap Ctrl+G + +This function is weakly defined, and can be overridden in user code. + +--- + +### `void register_unicode(uint32_t code_point)` {#api-register-unicode} + +Input a single Unicode character. A surrogate pair will be sent if required by the input mode. + +#### Arguments {#api-register-unicode-arguments} + + - `uint32_t code_point` + The code point of the character to send. + +--- + +### `void send_unicode_string(const char *str)` {#api-send-unicode-string} + +Send a string containing Unicode characters. + +#### Arguments {#api-send-unicode-string-arguments} + + - `const char *str` + The string to send. + +--- + +### `uint8_t unicodemap_index(uint16_t keycode)` {#api-unicodemap-index} + +Get the index into the `unicode_map` array for the given keycode, respecting shift state for pair keycodes. + +#### Arguments {#api-unicodemap-index-arguments} + + - `uint16_t keycode` + The Unicode Map keycode to get the index of. + +#### Return Value {#api-unicodemap-index-return-value} + +An index into the `unicode_map` array. + +--- + +### `uint32_t unicodemap_get_code_point(uint8_t index)` {#api-unicodemap-get-code-point} + +Get the code point for the given index in the `unicode_map` array. + +#### Arguments {#unicodemap-get-code-point-arguments} + + - `uint8_t index` + The index into the `unicode_map` array. + +#### Return Value {#unicodemap-get-code-point-return-value} + +A Unicode code point value. + +--- + +### `void register_unicodemap(uint8_t index)` {#api-register-unicodemap} + +Send the code point for the given index in the `unicode_map` array. + +#### Arguments {#api-register-unicodemap-arguments} + + - `uint8_t index` + The index into the `unicode_map` array. + +--- + +### `void ucis_start(void)` {#api-ucis-start} + +Begin the input sequence. + +--- + +### `bool ucis_active(void)` {#api-ucis-active} + +Whether UCIS is currently active. + +#### Return Value {#api-ucis-active-return-value} + +`true` if UCIS is active. + +--- + +### `uint8_t ucis_count(void)` {#api-ucis-count} + +Get the number of characters in the input sequence buffer. + +#### Return Value {#api-ucis-count-return-value} + +The current input sequence buffer length. + +--- + +### `bool ucis_add(uint16_t keycode)` {#api-ucis-add} + +Add the given keycode to the input sequence buffer. + +#### Arguments {#api-ucis-add-arguments} + + - `uint16_t keycode` + The keycode to add. Must be between `KC_A` and `KC_Z`, or `KC_1` and `KC_0`. + +#### Return Value {#api-ucis-add-return-value} + +`true` if the keycode was added. + +--- + +### `bool ucis_remove_last(void)` {#api-ucis-remove-last} + +Remove the last character from the input sequence buffer. + +#### Return Value {#api-ucis-remove-last-return-value} + +`true` if the sequence was not empty. + +--- + +### `void ucis_finish(void)` {#api-ucis-finish} + +Mark the input sequence as complete, and attempt to match. + +--- + +### `void ucis_cancel(void)` {#api-ucis-cancel} + +Cancel the input sequence. + +--- + +### `void register_ucis(void)` {#api-register-ucis} + +Send the code point(s) for the given UCIS index. + +#### Arguments {#api-register-ucis-arguments} + + - `uint8_t index` + The index into the UCIS symbol table. diff --git a/docs/features/wpm.md b/docs/features/wpm.md new file mode 100644 index 0000000000..4f4ed43739 --- /dev/null +++ b/docs/features/wpm.md @@ -0,0 +1,76 @@ +# Word Per Minute (WPM) Calculation + +The WPM feature uses time between keystrokes to compute a rolling average words per minute rate and makes this available for various uses. + +Enable the WPM system by adding this to your `rules.mk`: + + WPM_ENABLE = yes + +For split keyboards using soft serial, the computed WPM score will be available on the master AND slave half. + +## Configuration + +| Define | Default | Description | +|------------------------------|---------------|------------------------------------------------------------------------------------------| +| `WPM_ESTIMATED_WORD_SIZE` | `5` | This is the value used when estimating average word size (for regression and normal use) | +| `WPM_ALLOW_COUNT_REGRESSION` | _Not defined_ | If defined allows the WPM to be decreased when hitting Delete or Backspace | +| `WPM_UNFILTERED` | _Not defined_ | If undefined (the default), WPM values will be smoothed to avoid sudden changes in value | +| `WPM_SAMPLE_SECONDS` | `5` | This defines how many seconds of typing to average, when calculating WPM | +| `WPM_SAMPLE_PERIODS` | `25` | This defines how many sampling periods to use when calculating WPM | +| `WPM_LAUNCH_CONTROL` | _Not defined_ | If defined, WPM values will be calculated using partial buffers when typing begins | + +'WPM_UNFILTERED' is potentially useful if you're filtering data in some other way (and also because it reduces the code required for the WPM feature), or if reducing measurement latency to a minimum is important for you. + +Increasing 'WPM_SAMPLE_SECONDS' will give more smoothly changing WPM values at the expense of slightly more latency to the WPM calculation. + +Increasing 'WPM_SAMPLE_PERIODS' will improve the smoothness at which WPM decays once typing stops, at a cost of approximately this many bytes of firmware space. + +If 'WPM_LAUNCH_CONTROL' is defined, whenever WPM drops to zero, the next time typing begins WPM will be calculated based only on the time since that typing began, instead of the whole period of time specified by WPM_SAMPLE_SECONDS. This results in reaching an accurate WPM value much faster, even when filtering is enabled and a large WPM_SAMPLE_SECONDS value is specified. + +## Public Functions + +|Function |Description | +|--------------------------|--------------------------------------------------| +|`get_current_wpm(void)` | Returns the current WPM as a value between 0-255 | +|`set_current_wpm(x)` | Sets the current WPM to `x` (between 0-255) | + +## Callbacks + +By default, the WPM score only includes letters, numbers, space and some punctuation. If you want to change the set of characters considered as part of the WPM calculation, you can implement your own `bool wpm_keycode_user(uint16_t keycode)` and return true for any characters you would like included in the calculation, or false to not count that particular keycode. + +For instance, the default is: + +```c +bool wpm_keycode_user(uint16_t keycode) { + if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX) || (keycode >= QK_MODS && keycode <= QK_MODS_MAX)) { + keycode = keycode & 0xFF; + } else if (keycode > 0xFF) { + keycode = 0; + } + if ((keycode >= KC_A && keycode <= KC_0) || (keycode >= KC_TAB && keycode <= KC_SLSH)) { + return true; + } + + return false; +} +``` + +Additionally, if `WPM_ALLOW_COUNT_REGRESSION` is defined, there is the `uint8_t wpm_regress_count(uint16_t keycode)` function that allows you to decrease the WPM. This is useful if you want to be able to penalize certain keycodes (or even combinations). + +```c +__attribute__((weak)) uint8_t wpm_regress_count(uint16_t keycode) { + bool weak_modded = (keycode >= QK_LCTL && keycode < QK_LSFT) || (keycode >= QK_RCTL && keycode < QK_RSFT); + + if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX) || (keycode >= QK_MODS && keycode <= QK_MODS_MAX)) { + keycode = keycode & 0xFF; + } else if (keycode > 0xFF) { + keycode = 0; + } + if (((get_mods() | get_oneshot_mods()) & MOD_MASK_CTRL} || weak_modded) && (keycode == KC_DEL || keycode == KC_BSPC)) { + return WPM_ESTIMATED_WORD_SIZE; + } + if (keycode == KC_DEL || keycode == KC_BSPC) { + return 1; + } +} +``` |