qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

spleeb.c (18078B)


      1 // Copyright 2022 Chris Hoage (@chrishoage)
      2 // SPDX-License-Identifier: GPL-2.0-or-later
      3 
      4 #include "spleeb.h"
      5 #include "transactions.h"
      6 
      7 #ifdef CONSOLE_ENABLE
      8 #    include "print.h"
      9 #endif // CONSOLE_ENABLE
     10 
     11 #ifdef POINTING_DEVICE_ENABLE
     12 #    include "drivers/sensors/cirque_pinnacle_gestures.h"
     13 #endif // POINTING_DEVICE_ENABLE
     14 
     15 #if defined(POINTING_DEVICE_ENABLE) || defined(SPLEEB_ENCODER_MODE_MAP_ENABLE)
     16 typedef union {
     17     uint16_t raw;
     18     struct {
     19         uint8_t pointer_default_dpi : 4; // 16 steps available.
     20         uint8_t pointer_sniping_dpi : 2; // 4 steps available.
     21         uint8_t enc_modes[NUM_ENCODERS];
     22         bool    is_dragscroll_enabled : 1;
     23         bool    is_sniping_enabled : 1;
     24     } __attribute__((packed));
     25 } spleeb_config_t;
     26 
     27 static spleeb_config_t g_spleeb_config = {0};
     28 
     29 /**
     30  * \brief Set the value of `config` from EEPROM.
     31  *
     32  * Note that `is_dragscroll_enabled` and `is_sniping_enabled` are purposefully
     33  * ignored since we do not want to persist this state to memory.  In practice,
     34  * this state is always written to maximize write-performances.  Therefore, we
     35  * explicitly set them to `false` in this function.
     36  */
     37 static void read_spleeb_config_from_eeprom(spleeb_config_t* config) {
     38     config->raw                   = eeconfig_read_kb() & 0xffff;
     39     config->is_dragscroll_enabled = false;
     40     config->is_sniping_enabled    = false;
     41 }
     42 
     43 /**
     44  * \brief Save the value of `config` to eeprom.
     45  *
     46  * Note that all values are written verbatim, including whether drag-scroll
     47  * and/or sniper mode are enabled.  `read_spleeb_config_from_eeprom(…)`
     48  * resets these 2 values to `false` since it does not make sense to persist
     49  * these across reboots of the board.
     50  */
     51 static void write_spleeb_config_to_eeprom(spleeb_config_t* config) {
     52     eeconfig_update_kb(config->raw);
     53 }
     54 
     55 void eeconfig_init_kb(void) {
     56     g_spleeb_config.raw                 = 0;
     57     g_spleeb_config.pointer_default_dpi = 4;
     58 
     59 #    ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
     60     for (size_t i = 0; i < NUM_ENCODERS; i++) {
     61         if (spleeb_encoder_mode_map[i][0].initalized) {
     62             spleeb_enc_mode_t* first_enc_mode = &spleeb_encoder_mode_map[i][0];
     63             g_spleeb_config.enc_modes[i]      = first_enc_mode->mode;
     64         }
     65     }
     66 #    endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
     67 
     68     write_spleeb_config_to_eeprom(&g_spleeb_config);
     69     eeconfig_init_user();
     70 }
     71 
     72 void matrix_init_kb(void) {
     73     read_spleeb_config_from_eeprom(&g_spleeb_config);
     74     matrix_init_user();
     75 }
     76 
     77 void spleeb_config_sync_handler(uint8_t initiator2target_buffer_size, const void* initiator2target_buffer, uint8_t target2initiator_buffer_size, void* target2initiator_buffer) {
     78     if (initiator2target_buffer_size == sizeof(g_spleeb_config)) {
     79         memcpy(&g_spleeb_config, initiator2target_buffer, sizeof(g_spleeb_config));
     80     }
     81 }
     82 
     83 void keyboard_post_init_kb(void) {
     84     transaction_register_rpc(RPC_ID_KB_CONFIG_SYNC, spleeb_config_sync_handler);
     85     keyboard_post_init_user();
     86 }
     87 
     88 void housekeeping_task_kb(void) {
     89     if (is_keyboard_master()) {
     90         // Keep track of the last state, so that we can tell if we need to propagate to slave.
     91         static spleeb_config_t last_spleeb_config = {0};
     92         static uint32_t        last_sync          = 0;
     93         bool                   needs_sync         = false;
     94 
     95         // Check if the state values are different.
     96         if (memcmp(&g_spleeb_config, &last_spleeb_config, sizeof(g_spleeb_config))) {
     97             needs_sync = true;
     98             memcpy(&last_spleeb_config, &g_spleeb_config, sizeof(g_spleeb_config));
     99         }
    100         // Send to slave every 500ms regardless of state change.
    101         if (timer_elapsed32(last_sync) > 500) {
    102             needs_sync = true;
    103         }
    104 
    105         // Perform the sync if requested.
    106         if (needs_sync) {
    107             if (transaction_rpc_send(RPC_ID_KB_CONFIG_SYNC, sizeof(g_spleeb_config), &g_spleeb_config)) {
    108                 last_sync = timer_read32();
    109             }
    110         }
    111     }
    112     // No need to invoke the user-specific callback, as it's been called
    113     // already.
    114 }
    115 #endif // defined(POINTING_DEVICE_ENABLE) || defined(SPLEEB_ENCODER_MODE_MAP_ENABLE)
    116 
    117 #ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
    118 
    119 /**
    120  * \brief Handle the encoder mode action when triggered by encoder_update_kb
    121  *
    122  * Weakly defined fuction intended to be overridden in a users keymap
    123  */
    124 __attribute__((weak)) void spleeb_encoder_mode_trigger(uint8_t mode, bool clockwise) {}
    125 
    126 typedef struct {
    127     uint8_t            index;
    128     spleeb_enc_mode_t* enc_mode;
    129 } spleeb_found_enc_mode_t;
    130 
    131 static spleeb_found_enc_mode_t spleeb_get_found_encoder_mode(spleeb_config_t* config, uint8_t index) {
    132     spleeb_found_enc_mode_t found_enc_mode;
    133 
    134     for (size_t i = 0; i < SPLEEB_ENCODER_MODE_COUNT; i++) {
    135         spleeb_enc_mode_t* cur_enc_mode = &spleeb_encoder_mode_map[index][i];
    136         if (cur_enc_mode->mode == config->enc_modes[index]) {
    137             found_enc_mode.index    = i;
    138             found_enc_mode.enc_mode = cur_enc_mode;
    139             break;
    140         }
    141     }
    142 
    143     return found_enc_mode;
    144 }
    145 
    146 /**
    147  * \brief Step through the defined encoder modes for the encoder at the given
    148  * index
    149  *
    150  * Step though the modes defined in spleeb_encoder_mode_map at the users keymap.
    151  * Use a null terminator at the first character on the name property for the
    152  * enc_mode struct to determine if we've reached the end of the defined encoder
    153  * modes. When this happens loop back to the beginning.
    154  */
    155 static void spleeb_step_encoder_mode(spleeb_config_t* config, uint8_t index) {
    156     spleeb_found_enc_mode_t cur_enc_mode  = spleeb_get_found_encoder_mode(config, index);
    157     spleeb_enc_mode_t*      next_enc_mode = &spleeb_encoder_mode_map[index][(cur_enc_mode.index + 1) % SPLEEB_ENCODER_MODE_COUNT];
    158 
    159     if (!next_enc_mode->initalized) {
    160         next_enc_mode = &spleeb_encoder_mode_map[index][0];
    161     }
    162 
    163     if (next_enc_mode->initalized) {
    164         config->enc_modes[index] = next_enc_mode->mode;
    165         write_spleeb_config_to_eeprom(config);
    166     }
    167 }
    168 
    169 bool encoder_update_kb(uint8_t index, bool clockwise) {
    170     if (!encoder_update_user(index, clockwise)) {
    171         return false;
    172     }
    173 
    174     spleeb_encoder_mode_trigger(g_spleeb_config.enc_modes[index], clockwise);
    175 
    176     return true;
    177 }
    178 #endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
    179 
    180 #ifdef POINTING_DEVICE_ENABLE
    181 
    182 /** \brief Return the current value of the pointer's default DPI. */
    183 static uint16_t get_pointer_default_dpi(spleeb_config_t* config) {
    184     return (uint16_t)config->pointer_default_dpi * SPLEEB_DEFAULT_DPI_CONFIG_STEP + SPLEEB_MINIMUM_DEFAULT_DPI;
    185 }
    186 
    187 /** \brief Return the current value of the pointer's sniper-mode DPI. */
    188 static uint16_t get_pointer_sniping_dpi(spleeb_config_t* config) {
    189     return (uint16_t)config->pointer_sniping_dpi * SPLEEB_SNIPING_DPI_CONFIG_STEP + SPLEEB_MINIMUM_SNIPING_DPI;
    190 }
    191 
    192 /** \brief Return the current value of the pointer's default DPI. */
    193 static uint16_t get_pointer_current_dpi(spleeb_config_t* config) {
    194     if (config->is_sniping_enabled) {
    195         return get_pointer_sniping_dpi(config);
    196     } else {
    197         return get_pointer_default_dpi(config);
    198     }
    199 }
    200 
    201 /** \brief Set the appropriate DPI for the input config. */
    202 static void maybe_update_pointing_device_cpi(spleeb_config_t* config) {
    203     if (config->is_sniping_enabled) {
    204         pointing_device_set_cpi(get_pointer_sniping_dpi(config));
    205     } else {
    206         pointing_device_set_cpi(get_pointer_default_dpi(config));
    207     }
    208 }
    209 
    210 /**
    211  * \brief Update the pointer's default DPI to the next or previous step.
    212  *
    213  * Increases the DPI value if `forward` is `true`, decreases it otherwise.
    214  * The increment/decrement steps are equal to SPLEEB_DEFAULT_DPI_CONFIG_STEP.
    215  */
    216 static void step_pointer_default_dpi(spleeb_config_t* config, bool forward) {
    217     config->pointer_default_dpi += forward ? 1 : -1;
    218     maybe_update_pointing_device_cpi(config);
    219 }
    220 
    221 /**
    222  * \brief Update the pointer's sniper-mode DPI to the next or previous step.
    223  *
    224  * Increases the DPI value if `forward` is `true`, decreases it otherwise.
    225  * The increment/decrement steps are equal to SPLEEB_SNIPING_DPI_CONFIG_STEP.
    226  */
    227 static void step_pointer_sniping_dpi(spleeb_config_t* config, bool forward) {
    228     config->pointer_sniping_dpi += forward ? 1 : -1;
    229     maybe_update_pointing_device_cpi(config);
    230 }
    231 
    232 uint16_t spleeb_get_pointer_default_dpi(void) {
    233     return get_pointer_default_dpi(&g_spleeb_config);
    234 }
    235 
    236 uint16_t spleeb_get_pointer_sniping_dpi(void) {
    237     return get_pointer_sniping_dpi(&g_spleeb_config);
    238 }
    239 
    240 void spleeb_cycle_pointer_default_dpi_noeeprom(bool forward) {
    241     step_pointer_default_dpi(&g_spleeb_config, forward);
    242 }
    243 
    244 void spleeb_cycle_pointer_default_dpi(bool forward) {
    245     step_pointer_default_dpi(&g_spleeb_config, forward);
    246     write_spleeb_config_to_eeprom(&g_spleeb_config);
    247 }
    248 
    249 void spleeb_cycle_pointer_sniping_dpi_noeeprom(bool forward) {
    250     step_pointer_sniping_dpi(&g_spleeb_config, forward);
    251 }
    252 
    253 void spleeb_cycle_pointer_sniping_dpi(bool forward) {
    254     step_pointer_sniping_dpi(&g_spleeb_config, forward);
    255     write_spleeb_config_to_eeprom(&g_spleeb_config);
    256 }
    257 
    258 bool spleeb_get_pointer_sniping_enabled(void) {
    259     return g_spleeb_config.is_sniping_enabled;
    260 }
    261 
    262 void spleeb_set_pointer_sniping_enabled(bool enable) {
    263     g_spleeb_config.is_sniping_enabled = enable;
    264     maybe_update_pointing_device_cpi(&g_spleeb_config);
    265 }
    266 
    267 bool spleeb_get_pointer_dragscroll_enabled(void) {
    268     return g_spleeb_config.is_dragscroll_enabled;
    269 }
    270 
    271 void spleeb_set_pointer_dragscroll_enabled(bool enable) {
    272     g_spleeb_config.is_dragscroll_enabled = enable;
    273     cirque_pinnacle_enable_cursor_glide(enable);
    274     maybe_update_pointing_device_cpi(&g_spleeb_config);
    275 }
    276 #endif // POINTING_DEVICE_ENABLE
    277 
    278 #ifdef POINTING_DEVICE_ENABLE
    279 void pointing_device_init_kb(void) {
    280     maybe_update_pointing_device_cpi(&g_spleeb_config);
    281 
    282     // only glide on drag scroll
    283     cirque_pinnacle_enable_cursor_glide(false);
    284 
    285     set_auto_mouse_enable(true);
    286 }
    287 
    288 /**
    289  * \brief Augment the pointing device behavior.
    290  *
    291  * Drag-scroll implementation borrowed from https://github.com/qmk/qmk_firmware/pull/18218
    292  */
    293 static void pointing_device_task_spleeb(report_mouse_t* mouse_report) {
    294     static int16_t scroll_x = 0;
    295     static int16_t scroll_y = 0;
    296     if (g_spleeb_config.is_dragscroll_enabled) {
    297         scroll_x -= mouse_report->x;
    298         scroll_y += mouse_report->y;
    299         mouse_report->h = scroll_x / SPLEEB_DRAGSCROLL_DIVISOR;
    300         mouse_report->v = scroll_y / SPLEEB_DRAGSCROLL_DIVISOR;
    301         mouse_report->x = 0;
    302         mouse_report->y = 0;
    303         scroll_x -= (int16_t)mouse_report->h * SPLEEB_DRAGSCROLL_DIVISOR;
    304         scroll_y -= (int16_t)mouse_report->v * SPLEEB_DRAGSCROLL_DIVISOR;
    305     }
    306 }
    307 
    308 report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
    309     if (is_keyboard_master()) {
    310         pointing_device_task_spleeb(&mouse_report);
    311 
    312         mouse_report = pointing_device_task_user(mouse_report);
    313     }
    314 
    315     return mouse_report;
    316 }
    317 
    318 /**
    319  * \brief Outputs the Spleeb configuration to console.
    320  *
    321  * Prints the in-memory configuration structure to console, for debugging.
    322  * Includes:
    323  *   - raw value
    324  *   - drag-scroll: on/off
    325  *   - sniping: on/off
    326  *   - default DPI: internal table index/actual DPI
    327  *   - sniping DPI: internal table index/actual DPI
    328  */
    329 static void debug_spleeb_config_to_console(spleeb_config_t* config) {
    330 #    ifdef CONSOLE_ENABLE
    331     pd_dprintf("(spleeb) process_record_kb: config = {\n"
    332                "\traw = 0x%u,\n"
    333                "\t{\n"
    334                "\t\tis_dragscroll_enabled=%u\n"
    335                "\t\tis_sniping_enabled=%u\n"
    336                "\t\tdefault_dpi=0x%X (%u)\n"
    337                "\t\tsniping_dpi=0x%X (%u)\n"
    338                "\t}\n"
    339                "}\n",
    340                config->raw, config->is_dragscroll_enabled, config->is_sniping_enabled, config->pointer_default_dpi, get_pointer_default_dpi(config), config->pointer_sniping_dpi, get_pointer_sniping_dpi(config));
    341 #    endif // CONSOLE_ENABLE
    342 }
    343 #endif // POINTING_DEVICE_ENABLE
    344 
    345 bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
    346     if (!process_record_user(keycode, record)) {
    347 #ifdef POINTING_DEVICE_ENABLE
    348 
    349         debug_spleeb_config_to_console(&g_spleeb_config);
    350 #endif // POINTING_DEVICE_ENABLE
    351         return false;
    352     }
    353 #ifdef POINTING_DEVICE_ENABLE
    354     switch (keycode) {
    355         case POINTER_DEFAULT_DPI_FORWARD:
    356             if (record->event.pressed) {
    357                 spleeb_cycle_pointer_default_dpi(true);
    358             }
    359             break;
    360         case POINTER_DEFAULT_DPI_REVERSE:
    361             if (record->event.pressed) {
    362                 spleeb_cycle_pointer_default_dpi(false);
    363             }
    364             break;
    365         case POINTER_SNIPING_DPI_FORWARD:
    366             if (record->event.pressed) {
    367                 spleeb_cycle_pointer_sniping_dpi(true);
    368             }
    369             break;
    370         case POINTER_SNIPING_DPI_REVERSE:
    371             if (record->event.pressed) {
    372                 spleeb_cycle_pointer_sniping_dpi(false);
    373             }
    374             break;
    375         case SNIPING_MODE:
    376             spleeb_set_pointer_sniping_enabled(record->event.pressed);
    377             break;
    378         case SNIPING_MODE_TOGGLE:
    379             if (record->event.pressed) {
    380                 spleeb_set_pointer_sniping_enabled(!spleeb_get_pointer_sniping_enabled());
    381             }
    382             break;
    383         case DRAGSCROLL_MODE:
    384             spleeb_set_pointer_dragscroll_enabled(record->event.pressed);
    385             break;
    386         case DRAGSCROLL_MODE_TOGGLE:
    387             if (record->event.pressed) {
    388                 spleeb_set_pointer_dragscroll_enabled(!spleeb_get_pointer_dragscroll_enabled());
    389             }
    390             break;
    391     }
    392 #endif // POINTING_DEVICE_ENABLE
    393 
    394 #ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
    395     switch (keycode) {
    396         case ENC_MODE_STEP_LEFT:
    397             if (record->event.pressed) {
    398                 spleeb_step_encoder_mode(&g_spleeb_config, 0);
    399             }
    400             break;
    401         case ENC_MODE_STEP_RIGHT:
    402             if (record->event.pressed) {
    403                 spleeb_step_encoder_mode(&g_spleeb_config, 1);
    404             }
    405             break;
    406     }
    407 #endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
    408 
    409 #ifdef POINTING_DEVICE_ENABLE
    410     if ((keycode >= POINTER_DEFAULT_DPI_FORWARD && keycode <= ENC_MODE_STEP_RIGHT) || IS_MOUSEKEY(keycode)) {
    411         debug_spleeb_config_to_console(&g_spleeb_config);
    412     }
    413 #endif // POINTING_DEVICE_ENABLE
    414 
    415     return true;
    416 }
    417 
    418 #ifdef POINTING_DEVICE_ENABLE
    419 
    420 bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
    421     switch (keycode) {
    422         case DRAGSCROLL_MODE:
    423         case SNIPING_MODE:
    424             return true;
    425         default:
    426             return false;
    427     }
    428 
    429     return is_mouse_record_user(keycode, record);
    430 }
    431 
    432 #endif // POINTING_DEVICE_ENABLE
    433 
    434 #ifdef OLED_ENABLE
    435 
    436 static void render_layer(void) {
    437     oled_write_P(PSTR("LAYER: "), false);
    438 
    439     switch (get_highest_layer(layer_state)) {
    440         case 0:
    441             oled_write_ln_P(PSTR("\xC0\xC1"), false);
    442             break;
    443         case 1:
    444             oled_write_ln_P(PSTR("\xC2\xC3"), false);
    445             break;
    446         case 2:
    447             oled_write_ln_P(PSTR("\xC4\xC5"), false);
    448             break;
    449         case 3:
    450             oled_write_ln_P(PSTR("\xC6\xC7"), false);
    451             break;
    452         case 4:
    453             oled_write_ln_P(PSTR("\xC8\xC9"), false);
    454             break;
    455         case 5:
    456             oled_write_ln_P(PSTR("\xCA\xCB"), false);
    457             break;
    458         default:
    459             oled_write_ln_P(get_u8_str(get_highest_layer(layer_state) + 0x30, ' '), true);
    460     }
    461 
    462     oled_write_ln_P("", false);
    463 }
    464 
    465 static void render_mods(void) {
    466     uint8_t modifiers = get_mods();
    467 
    468     oled_write_ln_P(PSTR("MODS:"), false);
    469     oled_write_ln_P("", false);
    470     oled_write_P(PSTR("\325\326"), (modifiers & MOD_MASK_SHIFT));
    471     oled_write_P(PSTR("\327\330"), (modifiers & MOD_MASK_CTRL));
    472     oled_write_P(PSTR("\331\332"), (modifiers & MOD_MASK_ALT));
    473     oled_write_ln_P(PSTR("\333\334"), (modifiers & MOD_MASK_GUI));
    474     oled_write_ln_P("", false);
    475 }
    476 
    477 static void render_lock(void) {
    478     led_t led_state = host_keyboard_led_state();
    479 
    480     oled_write_P(PSTR("LOCK: "), false);
    481     oled_write_P(PSTR("\235\236"), led_state.caps_lock);
    482     oled_write_ln_P(PSTR("\275\276"), led_state.num_lock);
    483 }
    484 
    485 static void render_pointer(void) {
    486 #    ifdef POINTING_DEVICE_ENABLE
    487     oled_write_ln_P(PSTR("POINTER:"), false);
    488     oled_write_ln_P("", false);
    489     oled_write_P(PSTR("dpi:"), false);
    490     oled_write_ln_P(get_u16_str(get_pointer_current_dpi(&g_spleeb_config), ' '), false);
    491     oled_write_ln_P("", false);
    492 #    endif // POINTING_DEVICE_ENABLE
    493 }
    494 
    495 #    ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
    496 static uint8_t spleeb_get_encoder_mode(spleeb_config_t* config, uint8_t index) {
    497     spleeb_found_enc_mode_t found_enc_mode = spleeb_get_found_encoder_mode(config, index);
    498     return found_enc_mode.enc_mode->mode;
    499 }
    500 
    501 /**
    502  * \brief Map an encoder mode to a string to be displayed on the OLED
    503  *
    504  * Weakly defined fuction intended to be overridden in a users keymap. My be
    505  * omitted if no OLED is used.
    506  */
    507 __attribute__((weak)) const char* spleeb_encoder_mode_string(uint8_t mode) {
    508     return get_u8_str(mode, ' ');
    509 }
    510 #    endif //  SPLEEB_ENCODER_MODE_MAP_ENABLE
    511 
    512 static void render_encoder(void) {
    513 #    ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
    514     oled_write_ln_P(PSTR("ENCODER:"), false);
    515     oled_write_ln_P("", false);
    516     oled_write_P(PSTR("R: "), false);
    517     oled_write_ln_P(spleeb_encoder_mode_string(spleeb_get_encoder_mode(&g_spleeb_config, 1)), false);
    518     oled_write_P(PSTR("L: "), false);
    519     oled_write_ln_P(spleeb_encoder_mode_string(spleeb_get_encoder_mode(&g_spleeb_config, 0)), false);
    520 #    endif //  SPLEEB_ENCODER_MODE_MAP_ENABLE
    521 }
    522 
    523 static void render_status(void) {
    524     render_layer();
    525     render_mods();
    526     render_lock();
    527     render_pointer();
    528     render_encoder();
    529 }
    530 
    531 oled_rotation_t oled_init_kb(oled_rotation_t rotation) {
    532     return OLED_ROTATION_90;
    533 }
    534 
    535 bool oled_task_kb(void) {
    536     if (is_keyboard_master()) {
    537         return false;
    538     }
    539 
    540     if (!oled_task_user()) {
    541         return false;
    542     }
    543 
    544     render_status();
    545     return false;
    546 }
    547 #endif // OLED_ENABLE