diff options
| -rw-r--r-- | keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c | 132 | ||||
| -rw-r--r-- | users/pcewing/README.md | 85 | ||||
| -rw-r--r-- | users/pcewing/key_repeater.c | 168 | ||||
| -rw-r--r-- | users/pcewing/key_repeater.h | 68 | ||||
| -rw-r--r-- | users/pcewing/rules.mk | 1 | 
5 files changed, 454 insertions, 0 deletions
| diff --git a/keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c b/keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c new file mode 100644 index 0000000000..4bb9554677 --- /dev/null +++ b/keyboards/cozykeys/speedo/v3/keymaps/pcewing/keymap.c @@ -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); +} diff --git a/users/pcewing/README.md b/users/pcewing/README.md new file mode 100644 index 0000000000..0073b4f492 --- /dev/null +++ b/users/pcewing/README.md @@ -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) diff --git a/users/pcewing/key_repeater.c b/users/pcewing/key_repeater.c new file mode 100644 index 0000000000..6c77d45b5f --- /dev/null +++ b/users/pcewing/key_repeater.c @@ -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; +} diff --git a/users/pcewing/key_repeater.h b/users/pcewing/key_repeater.h new file mode 100644 index 0000000000..30b7f748ff --- /dev/null +++ b/users/pcewing/key_repeater.h @@ -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); diff --git a/users/pcewing/rules.mk b/users/pcewing/rules.mk new file mode 100644 index 0000000000..35d0bc3ab7 --- /dev/null +++ b/users/pcewing/rules.mk @@ -0,0 +1 @@ +SRC += key_repeater.c | 
