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