Browse Source

Add pcewing Speedo v3 keymap (#18118)

* Add pcewing Speedo v3 keymap

* Add readme and code quality improvements
pull/18132/head
Paul Ewing 1 year ago
committed by GitHub
parent
commit
3c0806a489
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 454 additions and 0 deletions
  1. +132
    -0
      keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c
  2. +85
    -0
      users/pcewing/README.md
  3. +168
    -0
      users/pcewing/key_repeater.c
  4. +68
    -0
      users/pcewing/key_repeater.h
  5. +1
    -0
      users/pcewing/rules.mk

+ 132
- 0
keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c View File

@ -0,0 +1,132 @@
/*
* Copyright 2022 Paul Ewing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include QMK_KEYBOARD_H
#include "key_repeater.h"
#include <stdlib.h>
enum {
LAYER_DEFAULT,
LAYER_FN,
LAYER_MACRO,
__LAYER_COUNT,
};
#define TO_MACRO TO(LAYER_MACRO)
#define TO_DFLT TO(LAYER_DEFAULT)
#define MO_FN MO(LAYER_FN)
#define RGB_N RGB_MOD // Rotate to next RGB mode
#define RGB_P RGB_RMOD // Rotate to next RGB mode
#define KC_YANK LCTL(KC_INS) // Copy shortcut in most terminal emulators
#define KC_PUT LSFT(KC_INS) // Paste shortcut in most terminal emulators
// Custom keycodes
enum {
SH_TOG = SAFE_RANGE, // Toggle shift
SH_BTN1, // Shift left click
RP_BTN1, // Click repeatedly while key is held
};
const uint16_t PROGMEM keymaps[__LAYER_COUNT][MATRIX_ROWS][MATRIX_COLS] = {
[LAYER_DEFAULT] = LAYOUT(
KC_EQL, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_UP, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSLS,
KC_ESC, KC_A, KC_S, KC_D, KC_F, KC_G, KC_LEFT, KC_RGHT, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_DOWN, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT,
KC_LCTL, KC_LGUI, KC_LALT, KC_GRV, MO_FN, KC_BSPC, KC_DEL, KC_ENT, KC_SPC, KC_LBRC, KC_RBRC, KC_RALT, KC_RGUI, KC_RCTL
),
[LAYER_FN] = LAYOUT(
RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, _______, _______, _______, _______, _______, RESET,
_______, KC_F9, KC_F10, KC_F11, KC_F12, KC_PSCR, RGB_TOG, KC_YANK, KC_GRV, KC_LBRC, KC_RBRC, KC_PUT, _______,
KC_CAPS, KC_F5, KC_F6, KC_F7, KC_F8, KC_INS, RGB_N, RGB_P, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, _______, _______,
_______, KC_F1, KC_F2, KC_F3, KC_F4, KC_PAUS, RGB_M_P, KC_HOME, KC_PGDN, KC_PGUP, KC_END, _______, _______,
_______, _______, _______, _______, _______, _______, _______, TO_MACRO, _______, _______, _______, _______, _______, _______
),
[LAYER_MACRO] = LAYOUT(
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, RP_BTN1, SH_TOG, _______, _______, _______, _______, _______, _______, _______,
TO_DFLT, _______, _______, KC_BTN1, SH_BTN1, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, KC_1, KC_6, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, KC_BTN1, _______, _______, _______, _______, _______, _______, _______, _______
),
};
static bool shift_enabled = false;
static struct key_repeater_t* click_repeater = NULL;
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case SH_TOG:
if (record->event.pressed) {
if (shift_enabled) {
unregister_code(KC_LSFT);
} else {
register_code(KC_LSFT);
}
shift_enabled = !shift_enabled;
}
return false; // Skip all further processing of this key
case SH_BTN1:
if (record->event.pressed) {
register_code(KC_LSFT);
register_code(KC_BTN1);
} else {
unregister_code(KC_BTN1);
unregister_code(KC_LSFT);
}
return false;
case RP_BTN1:
if (record->event.pressed) {
kr_enable(click_repeater);
} else {
kr_disable(click_repeater);
}
return false;
default:
return true; // Process all other keycodes normally
}
}
void keyboard_post_init_user(void) {
// Seed the random number generator which is used by the key repeater
srand(timer_read32());
// Configure and instantiate a key repeater for mouse button 1 "rapid fire"
struct key_repeater_config_t cfg = {
.key = KC_BTN1,
.key_duration_min = 20,
.key_duration_max = 50,
.wait_duration_min = 90,
.wait_duration_max = 140,
};
click_repeater = kr_new(&cfg);
}
void matrix_scan_user(void) {
kr_poll(click_repeater);
}

+ 85
- 0
users/pcewing/README.md View File

@ -0,0 +1,85 @@
# User Space Code for Paul Ewing
This folder contains my user space code.
## Key Repeater
I've implemented a key repeater utility in [./key_repeater.h](./key_repeater.h)
and [./key_repeater.c](./key_repeater.c) that is similar in concept to the
"rapid fire" feature many game controllers come with. The intent behind this is
that the user can hold down a key and while it is pressed, a keycode will be
repeatedly sent. I have found this useful in certain games and during game
development.
The duration of the key press as well as the time between key presses is
slightly randomized by design. This is to simulate more realistic human
behavior. By setting the minimum and maximum duration fields to the same value
in the configuration, this randomization can be disabled.
**Note:** Please be aware that this might be against the terms of service in
certain games so use your own discretion before using this feature.
### How to Use
Define the repeater and then configure and allocate it in your keymap's
initialization process:
```c
static struct key_repeater_t* click_repeater = NULL;
void keyboard_post_init_user(void) {
// Seed the random number generator which is used by the key repeater
srand(timer_read32());
// Configure and instantiate a key repeater for mouse button 1 "rapid fire"
struct key_repeater_config_t cfg = {
.key = KC_BTN1, // Press mouse button 1 (Left click)
.key_duration_min = 20, // Press key for 20 to 50 milliseconds
.key_duration_max = 50,
.wait_duration_min = 90, // Wait for 90 to 140 milliseconds before pressing again
.wait_duration_max = 140,
};
click_repeater = kr_new(&cfg);
}
```
Make sure the key repeater is polled during matrix scanning:
```c
void matrix_scan_user(void) {
kr_poll(click_repeater);
}
```
Define a custom keycode that will enable/disable the repeater:
```c
enum {
RP_BTN1 = SAFE_RANGE, // Click repeatedly while key is held
};
```
Assign the keycode to a key in your `LAYOUT(...)` macro.
Define the logic to enable/disable the repeater when the custom keycode is
pressed or released:
```c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case RP_BTN1:
if (record->event.pressed) {
kr_enable(click_repeater);
} else {
kr_disable(click_repeater);
}
return false;
default:
return true;
}
}
```
For a full working example in own of my own keymaps, see:
[keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c](../../keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c)

+ 168
- 0
users/pcewing/key_repeater.c View File

@ -0,0 +1,168 @@
/*
* Copyright 2022 Paul Ewing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "key_repeater.h"
#include <stdlib.h>
#include QMK_KEYBOARD_H
enum key_repeater_state {
KR_DISABLED,
KR_WAITING,
KR_BUTTON_DOWN,
__KR_STATE_COUNT
};
struct key_repeater_t {
int key;
uint32_t key_duration_min;
uint32_t key_duration_max;
uint32_t wait_duration_min;
uint32_t wait_duration_max;
enum key_repeater_state state;
uint32_t previous_button_down;
uint32_t previous_button_up;
uint32_t key_duration;
uint32_t wait_duration;
};
// Utility functions
uint32_t get_rand(uint32_t min, uint32_t max);
// State handler function table
void kr_waiting(struct key_repeater_t *kr);
void kr_button_down(struct key_repeater_t *kr);
typedef void (*kr_state_handler)(struct key_repeater_t *kr);
static kr_state_handler kr_state_handlers[] = {
[KR_DISABLED] = NULL,
[KR_WAITING] = kr_waiting,
[KR_BUTTON_DOWN] = kr_button_down,
};
struct key_repeater_t* kr_new(struct key_repeater_config_t* cfg) {
struct key_repeater_t* kr = (struct key_repeater_t*)malloc(sizeof(struct key_repeater_t));
if (cfg) {
kr->key = cfg->key;
kr->key_duration_min = cfg->key_duration_min;
kr->key_duration_max = cfg->key_duration_max;
kr->wait_duration_min = cfg->wait_duration_min;
kr->wait_duration_max = cfg->wait_duration_max;
} else {
kr->key = KC_NO;
kr->key_duration_min = 0;
kr->key_duration_max = 0;
kr->wait_duration_min = 0;
kr->wait_duration_max = 0;
}
kr->state = KR_DISABLED;
kr->previous_button_down = 0;
kr->previous_button_up = 0;
kr->key_duration = 0;
kr->wait_duration = 0;
return kr;
}
void kr_free(struct key_repeater_t **kr) {
if (kr && *kr) {
free(*kr);
*kr = NULL;
}
}
void kr_enable(struct key_repeater_t *kr) {
if (!kr || kr->key == KC_NO) {
return;
}
if (kr->state != KR_DISABLED) {
return;
}
kr->state = KR_WAITING;
kr->previous_button_down = 0;
kr->previous_button_up = 0;
kr->key_duration = 0;
kr->wait_duration = 0;
}
void kr_disable(struct key_repeater_t *kr) {
if (!kr || kr->key == KC_NO) {
return;
}
if (kr->state == KR_BUTTON_DOWN) {
unregister_code(kr->key);
}
kr->state = KR_DISABLED;
}
void kr_poll(struct key_repeater_t *kr) {
if (!kr || kr->key == KC_NO) {
return;
}
kr_state_handler handler = kr_state_handlers[kr->state];
if (handler) {
(handler)(kr);
}
}
void kr_waiting(struct key_repeater_t *kr) {
if (!kr || kr->key == KC_NO) {
return;
}
uint32_t now = timer_read32();
if (now > (kr->previous_button_up + kr->wait_duration)) {
kr->state = KR_BUTTON_DOWN;
kr->previous_button_down = now;
if (kr->key_duration_min == kr->key_duration_max) {
kr->key_duration = kr->key_duration_min;
} else {
kr->key_duration = get_rand(kr->key_duration_min, kr->key_duration_max);
}
register_code(kr->key);
}
}
void kr_button_down(struct key_repeater_t *kr) {
if (!kr || kr->key == KC_NO) {
return;
}
uint32_t now = timer_read32();
if (now > (kr->previous_button_down + kr->key_duration)) {
kr->state = KR_WAITING;
kr->previous_button_up = now;
if (kr->wait_duration_min == kr->wait_duration_max) {
kr->wait_duration = kr->wait_duration_min;
} else {
kr->wait_duration = get_rand(kr->wait_duration_min, kr->wait_duration_max);
}
unregister_code(kr->key);
}
}
// Return a random number between min and max; assumes that the random number
// generator has already been seeded
uint32_t get_rand(uint32_t min, uint32_t max) {
return (rand() % (max - min)) + min;
}

+ 68
- 0
users/pcewing/key_repeater.h View File

@ -0,0 +1,68 @@
/*
* Copyright 2022 Paul Ewing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// The key_repeater_config_t type holds user configurable options set when
// allocating a new key repeater.
struct key_repeater_config_t {
// The key code that will be repeatedly registered
int key;
// The minimum amount of time to press down the key when registered
const uint32_t key_duration_min;
// The maximum amount of time to press down the key when registered
const uint32_t key_duration_max;
// The minimum amount of time to wait between registering key presses
const uint32_t wait_duration_min;
// The maximum amount of time to wait between registering key presses
const uint32_t wait_duration_max;
};
// The key_repeater_t type represents a key repeater. This is similar to the
// "Rapid fire" feature on many game controllers. The intention behind this is
// to periodically send a key code while the user is pressing a key.
//
// The duration of the key press as well as the time between key presses is
// slightly randomized by design. This is to simulate more realistic human
// behavior. By setting the minimum and maximum duration fields to the same
// value in the configuration, this randomization can be disabled.
//
// This type is intentionally opaque to avoid the user setting internal fields
// directly. It must be allocated and destroyed using the kr_new() and
// kr_free() functions respectively.
struct key_repeater_t;
// Allocate a new key repeater.
struct key_repeater_t* kr_new(struct key_repeater_config_t* cfg);
// Release an allocated key repeater.
void kr_free(struct key_repeater_t** kr);
// Enable the key repeater such that it will start periodically registering the
// configured key code.
void kr_enable(struct key_repeater_t* kr);
// Disable the key repeater such that it will stop periodically registering the
// configured key code.
void kr_disable(struct key_repeater_t* kr);
// Poll the key repeater to execute, tyically called from matrix_scan_user().
void kr_poll(struct key_repeater_t* kr);

+ 1
- 0
users/pcewing/rules.mk View File

@ -0,0 +1 @@
SRC += key_repeater.c

Loading…
Cancel
Save