spleeb.c 18 KB

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