diff --git a/qmk_firmware/keyboards/keyball/keyball61/config.h b/qmk_firmware/keyboards/keyball/keyball61/config.h index 2e7d6965f..6573594eb 100644 --- a/qmk_firmware/keyboards/keyball/keyball61/config.h +++ b/qmk_firmware/keyboards/keyball/keyball61/config.h @@ -22,7 +22,7 @@ along with this program. If not, see . // USB Device descriptor parameters #define VENDOR_ID 0x5957 // "YW" = Yowkees -#define PRODUCT_ID 0x0100 // dummy ID +#define PRODUCT_ID 0x0100 #define DEVICE_VER 0x0001 #define MANUFACTURER Yowkees #define PRODUCT Keyball61 diff --git a/qmk_firmware/keyboards/keyball/keyball61/keyball61.c b/qmk_firmware/keyboards/keyball/keyball61/keyball61.c index a2ac770d4..1c90f5945 100644 --- a/qmk_firmware/keyboards/keyball/keyball61/keyball61.c +++ b/qmk_firmware/keyboards/keyball/keyball61/keyball61.c @@ -18,68 +18,7 @@ along with this program. If not, see . #include QMK_KEYBOARD_H -#include - -#include "quantum.h" - -#include "transactions.h" -#include "drivers/pmw3360/pmw3360.h" - -////////////////////////////////////////////////////////////////////////////// - -#define KEYBALL_CPI_DEFAULT 4 // 4: 500 CPI -#define KEYBALL_SCROLL_DIV_DEFAULT 4 - -////////////////////////////////////////////////////////////////////////////// - -#define TX_GETINFO_INTERVAL 500 -#define TX_GETINFO_MAXTRY 10 -#define TX_GETMOTION_INTERVAL 5 - -////////////////////////////////////////////////////////////////////////////// - -typedef union { - uint32_t raw; - struct { - uint8_t cpi : 7; - uint8_t sdiv : 3; // scroll divider - }; -} keyball_config_t; - -typedef struct { - uint16_t vid; - uint16_t pid; - uint8_t ballcnt; // count of balls: support only 0 or 1, for now -} keyball_info_t; - -typedef uint8_t keyball_motion_id_t; - -typedef struct { - int16_t x; - int16_t y; -} keyball_motion_t; - -typedef uint8_t keyball_cpi_t; - -////////////////////////////////////////////////////////////////////////////// - -static bool this_have_ball = false; -static bool that_enable = false; -static bool that_have_ball = false; - -static uint8_t cpi_value = KEYBALL_CPI_DEFAULT; -static bool cpi_changed = false; - -static keyball_motion_t this_motion = {0}; -static keyball_motion_t that_motion = {0}; - -static bool scroll_mode = false; -static uint8_t scroll_div = 0; - -static uint16_t last_keycode; -static uint8_t last_row; -static uint8_t last_col; -static report_mouse_t last_mouse = {}; +#include "lib/keyball/keyball.h" ////////////////////////////////////////////////////////////////////////////// @@ -98,476 +37,12 @@ matrix_row_t matrix_mask[MATRIX_ROWS] = { }; // clang-format on -static void adjust_rgblight_ranges(void) { +void keyball_on_adjust_layout(keyball_adjust_t v) { #ifdef RGBLIGHT_ENABLE // adjust RGBLIGHT's clipping and effect ranges - uint8_t lednum_this = this_have_ball ? 34 : 37; - uint8_t lednum_that = !that_enable ? 0 : that_have_ball ? 34 : 37; + uint8_t lednum_this = keyball.this_have_ball ? 34 : 37; + uint8_t lednum_that = !keyball.that_enable ? 0 : keyball.that_have_ball ? 34 : 37; rgblight_set_clipping_range(is_keyboard_left() ? 0 : lednum_that, lednum_this); rgblight_set_effect_range(0, lednum_this + lednum_that); #endif } - -static void adjust_board_as_this(void) { - adjust_rgblight_ranges(); - - keyball_config_t c; - c.raw = eeconfig_read_kb(); - if (c.cpi != 0) { - pointing_device_set_cpi(c.cpi); - } - scroll_div = c.sdiv; -} - -static void adjust_board_on_primary(void) { - adjust_rgblight_ranges(); - -#ifdef VIA_ENABLE - // adjust layout options value according to current combination. - bool left = is_keyboard_left(); - uint8_t layouts = (this_have_ball ? (left ? 0x02 : 0x01) : 0x00) | (that_have_ball ? (left ? 0x01 : 0x02) : 0x00); - uint32_t curr = via_get_layout_options(); - uint32_t next = (curr & ~0x3) | layouts; - if (next != curr) { - via_set_layout_options(next); - } -#endif -} - -static void adjust_board_on_secondary(void) { adjust_rgblight_ranges(); } - -////////////////////////////////////////////////////////////////////////////// - -// add16 adds two int16_t with clipping. -static int16_t add16(int16_t a, int16_t b) { - int16_t r = a + b; - if (a >= 0 && b >= 0 && r < 0) { - r = 32767; - } else if (a < 0 && b < 0 && r >= 0) { - r = -32768; - } - return r; -} - -// incU8 increments a uint8_t with clipping. -static inline uint8_t incU8(uint8_t a) { return a < 0xff ? a + 1 : 0xff; } - -// clip2int8 clips an integer fit into int8_t. -static inline int8_t clip2int8(int16_t v) { return (v) < -127 ? -127 : (v) > 127 ? 127 : (int8_t)v; } - -static void motion_to_mouse_move(keyball_motion_t *m, report_mouse_t *r, bool is_left) { - r->x = clip2int8(m->y); - r->y = clip2int8(m->x); - if (is_left) { - r->x = -r->x; - r->y = -r->y; - } - // clear motion - m->x = 0; - m->y = 0; -} - -static void motion_to_mouse_scroll(keyball_motion_t *m, report_mouse_t *r, bool is_left) { - int16_t x = m->x >> scroll_div; - m->x -= x << scroll_div; - int16_t y = m->y >> scroll_div; - m->y -= y << scroll_div; - r->h = clip2int8(y); - r->v = clip2int8(x); - if (!is_left) { - r->h = -r->h; - r->v = -r->v; - } -} - -static void motion_to_mouse(keyball_motion_t *m, report_mouse_t *r, bool is_left, bool as_scroll) { - if (as_scroll) { - motion_to_mouse_scroll(m, r, is_left); - } else { - motion_to_mouse_move(m, r, is_left); - } -} - -void pointing_device_driver_init(void) { - this_have_ball = pmw3360_init(); - if (this_have_ball) { - pmw3360_reg_write(pmw3360_Config1, KEYBALL_CPI_DEFAULT); - pmw3360_reg_write(pmw3360_Motion_Burst, 0); - } -} - -report_mouse_t pointing_device_driver_get_report(report_mouse_t rep) { - // fetch from optical sensor. - if (this_have_ball) { - pmw3360_motion_t d = {0}; - if (pmw3360_motion_burst(&d)) { - ATOMIC_BLOCK_FORCEON { - this_motion.x = add16(this_motion.x, d.x); - this_motion.y = add16(this_motion.y, d.y); - } - } - } - // report mouse event, if keyboard is primary. - if (is_keyboard_master()) { - bool is_left = is_keyboard_left(); - if (this_have_ball) { - motion_to_mouse(&this_motion, &rep, is_left, scroll_mode); - } - if (that_have_ball) { - motion_to_mouse(&that_motion, &rep, !is_left, scroll_mode ^ this_have_ball); - } - } - return rep; -} - -uint16_t pointing_device_driver_get_cpi(void) { return cpi_value; } - -void pointing_device_driver_set_cpi(uint16_t cpi) { - if (this_have_ball) { - pmw3360_cpi_set(cpi); - pmw3360_reg_write(pmw3360_Motion_Burst, 0); - } - cpi_value = cpi; - cpi_changed = true; -} - -static void add_cpi(int8_t delta) { - int16_t v = cpi_value + delta; - if (v < 0) { - v = 0; - } else if (v > pmw3360_MAXCPI) { - v = pmw3360_MAXCPI; - } - pointing_device_set_cpi(v); -} - -////////////////////////////////////////////////////////////////////////////// - -static void keyball_get_info_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { - keyball_info_t *that = (keyball_info_t *)in_data; - if (that->vid == VENDOR_ID && that->pid == PRODUCT_ID) { - that_enable = true; - that_have_ball = that->ballcnt > 0; - } - keyball_info_t info = { - .vid = VENDOR_ID, - .pid = PRODUCT_ID, - .ballcnt = this_have_ball ? 1 : 0, - }; - memcpy(out_data, &info, sizeof(info)); - adjust_board_on_secondary(); -} - -static void keyball_get_info_invoke(void) { - static bool negotiated = false; - static uint32_t last_sync = 0; - static int round = 0; - if (negotiated || timer_elapsed32(last_sync) < TX_GETINFO_INTERVAL) { - return; - } - last_sync = timer_read32(); - round++; - keyball_info_t req = { - .vid = VENDOR_ID, - .pid = PRODUCT_ID, - .ballcnt = this_have_ball ? 1 : 0, - }; - keyball_info_t recv = {0}; - if (!transaction_rpc_exec(KEYBALL_GET_INFO, sizeof(req), &req, sizeof(recv), &recv)) { - if (round < TX_GETINFO_MAXTRY) { - dprintf("keyball_get_info_invoke: missed #%d\n", round); - return; - } - } - negotiated = true; - if (recv.vid == VENDOR_ID && recv.pid == PRODUCT_ID) { - that_enable = true; - that_have_ball = recv.ballcnt > 0; - } - dprintf("keyball_get_info_invoke: negotiated #%d %d\n", round, that_have_ball); - if (is_keyboard_master()) { - adjust_board_on_primary(); - } else { - adjust_board_on_secondary(); - } -} - -static void keyball_get_motion_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { - if (this_have_ball && *((keyball_motion_id_t *)in_data) == 0) { - *(keyball_motion_t *)out_data = this_motion; - // clear motion - this_motion.x = 0; - this_motion.y = 0; - } -} - -static void keyball_get_motion_invoke(void) { - static uint32_t last_sync = 0; - if (!that_have_ball || timer_elapsed32(last_sync) < TX_GETMOTION_INTERVAL) { - return; - } - keyball_motion_id_t req = 0; - keyball_motion_t recv = {0}; - if (transaction_rpc_exec(KEYBALL_GET_MOTION, sizeof(req), &req, sizeof(recv), &recv)) { - ATOMIC_BLOCK_FORCEON { - that_motion.x = add16(that_motion.x, recv.x); - that_motion.y = add16(that_motion.y, recv.y); - } - } else { - dprintf("keyball_get_motion_invoke: failed"); - } - last_sync = timer_read32(); - return; -} - -static void keyball_set_cpi_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { - if (this_have_ball) { - pmw3360_cpi_set(*(keyball_cpi_t *)in_data); - pmw3360_reg_write(pmw3360_Motion_Burst, 0); - } -} - -static void keyball_set_cpi_invoke(void) { - if (!that_have_ball || !cpi_changed) { - return; - } - keyball_cpi_t req = cpi_value; - if (!transaction_rpc_send(KEYBALL_SET_CPI, sizeof(req), &req)) { - return; - } - cpi_changed = false; -} - -////////////////////////////////////////////////////////////////////////////// - -bool keyball_get_scroll_mode(void) { return scroll_mode; } - -void keyball_set_scroll_mode(bool mode) { - scroll_mode = mode; -} - -#ifdef OLED_ENABLE - -static const char *format_4d(int8_t d) { - static char buf[5] = {0}; // max width (4) + NUL (1) - char lead = ' '; - if (d < 0) { - d = -d; - lead = '-'; - } - buf[3] = (d % 10) + '0'; - d /= 10; - if (d == 0) { - buf[2] = lead; - lead = ' '; - } else { - buf[2] = (d % 10) + '0'; - d /= 10; - } - if (d == 0) { - buf[1] = lead; - lead = ' '; - } else { - buf[1] = (d % 10) + '0'; - d /= 10; - } - buf[0] = lead; - return buf; -} - -static char to_1x(uint8_t x) { - x &= 0x0f; - return x < 10 ? x + '0' : x + 'a' - 10; -} - -#endif - -void keyball_oled_render_ballinfo(void) { -#ifdef OLED_ENABLE - // Format: `Ball:{mouse x}{mouse y}{mouse h}{mouse v}` - // ` CPI{CPI} S{SCROLL_MODE} D{SCROLL_DIV}` - // - // Output example: - // - // Ball: -12 34 0 0 - // - oled_write_P(PSTR("Ball:"), false); - oled_write(format_4d(last_mouse.x), false); - oled_write(format_4d(last_mouse.y), false); - oled_write(format_4d(last_mouse.h), false); - oled_write(format_4d(last_mouse.v), false); - // CPI - oled_write_P(PSTR(" CPI"), false); - oled_write(format_4d(cpi_value + 1) + 1, false); - oled_write_P(PSTR("00 S"), false); - oled_write_char(scroll_mode ? '1' : '0', false); - oled_write_P(PSTR(" D"), false); - oled_write_char('0' + scroll_div, false); -#endif -} - -#ifdef OLED_ENABLE -// clang-format off -const char PROGMEM code_to_name[] = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', - '5', '6', '7', '8', '9', '0', 'R', 'E', 'B', 'T', - '_', '-', '=', '[', ']', '\\', '#', ';', '\'', '`', - ',', '.', '/', -}; -// clang-format on -#endif - -void keyball_oled_render_keyinfo(void) { -#ifdef OLED_ENABLE - // Format: `Key : R{row} C{col} K{kc} '{name}` - // - // Where `kc` is lower 8 bit of keycode. - // Where `name` is readable label for `kc`, valid between 4 and 56. - // - // It is aligned to fit with output of keyball_oled_render_ballinfo(). - // For example: - // - // Key : R2 C3 K06 'c - // Ball: 0 0 0 0 - // - uint8_t keycode = last_keycode; - - oled_write_P(PSTR("Key : R"), false); - oled_write_char(to_1x(last_row), false); - oled_write_P(PSTR(" C"), false); - oled_write_char(to_1x(last_col), false); - if (keycode) { - oled_write_P(PSTR(" K"), false); - oled_write_char(to_1x(keycode >> 4), false); - oled_write_char(to_1x(keycode), false); - } - if (keycode >= 4 && keycode < 57) { - oled_write_P(PSTR(" '"), false); - char name = pgm_read_byte(code_to_name + keycode - 4); - oled_write_char(name, false); - } else { - oled_advance_page(true); - } -#endif -} - -////////////////////////////////////////////////////////////////////////////// - -void keyboard_post_init_kb(void) { - // register transaction handlers on secondary. - if (!is_keyboard_master()) { - transaction_register_rpc(KEYBALL_GET_INFO, keyball_get_info_handler); - transaction_register_rpc(KEYBALL_GET_MOTION, keyball_get_motion_handler); - transaction_register_rpc(KEYBALL_SET_CPI, keyball_set_cpi_handler); - } - adjust_board_as_this(); - keyboard_post_init_user(); -} - -void housekeeping_task_kb(void) { - if (is_keyboard_master()) { - keyball_get_info_invoke(); - keyball_get_motion_invoke(); - keyball_set_cpi_invoke(); - } -} - -bool process_record_kb(uint16_t keycode, keyrecord_t *record) { - // store last keycode, row, and col for OLED - last_keycode = keycode; - last_row = record->event.key.row; - last_col = record->event.key.col; - - if (!process_record_user(keycode, record)) { - return false; - } - - switch (keycode) { - // process KC_MS_BTN1~8 by myself - // See process_action() in quantum/action.c for details. -#ifndef MOUSEKEY_ENABLE - case KC_MS_BTN1 ... KC_MS_BTN8: { - extern void register_button(bool, enum mouse_buttons); - register_button(record->event.pressed, MOUSE_BTN_MASK(keycode - KC_MS_BTN1)); - break; - } -#endif - - case KBC_RST: - if (record->event.pressed) { - pointing_device_set_cpi(KEYBALL_CPI_DEFAULT); - scroll_div = KEYBALL_SCROLL_DIV_DEFAULT; - } - break; - case KBC_SAVE: - if (record->event.pressed) { - keyball_config_t c = { - .cpi = cpi_value, - .sdiv = scroll_div, - }; - eeconfig_update_kb(c.raw); - } - break; - - case CPI_I100: - if (record->event.pressed) { - add_cpi(1); - } - break; - case CPI_D100: - if (record->event.pressed) { - add_cpi(-1); - } - break; - case CPI_I1K: - if (record->event.pressed) { - add_cpi(10); - } - break; - case CPI_D1K: - if (record->event.pressed) { - add_cpi(-10); - } - break; - - case SCRL_TO: - if (record->event.pressed) { - scroll_mode = !scroll_mode; - } - break; - case SCRL_MO: - scroll_mode = record->event.pressed; - break; - case SCRL_DVI: - if (record->event.pressed && scroll_div < 7) { - scroll_div++; - } - break; - case SCRL_DVD: - if (record->event.pressed && scroll_div > 0) { - scroll_div--; - } - break; - - default: - return true; - } - return false; -} - -report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { - // store mouse report for OLED. - last_mouse = pointing_device_task_user(mouse_report); - return last_mouse; -} - -void eeconfig_init_kb(void) { - keyball_config_t c = { - .cpi = 0, - .sdiv = KEYBALL_SCROLL_DIV_DEFAULT, - }; - eeconfig_update_kb(c.raw); - eeconfig_init_user(); -} diff --git a/qmk_firmware/keyboards/keyball/keyball61/keyball61.h b/qmk_firmware/keyboards/keyball/keyball61/keyball61.h index ceb7c5906..857d83fda 100644 --- a/qmk_firmware/keyboards/keyball/keyball61/keyball61.h +++ b/qmk_firmware/keyboards/keyball/keyball61/keyball61.h @@ -19,6 +19,7 @@ along with this program. If not, see . #pragma once #include "quantum.h" +#include "lib/keyball/keyball.h" // clang-format off @@ -106,40 +107,3 @@ along with this program. If not, see . #define LAYOUT LAYOUT_right_ball #define LAYOUT_universal LAYOUT_no_ball - -////////////////////////////////////////////////////////////////////////////// - -enum keyball_keycodes { - KBC_RST = SAFE_RANGE, // Keyball configuration: reset to default - KBC_SAVE, // Keyball configuration: save to EEPROM - - CPI_I100, // CPI +100 CPI - CPI_D100, // CPI -100 CPI - CPI_I1K, // CPI +1000 CPI - CPI_D1K, // CPI -1000 CPI - - // In scroll mode, motion from primary trackball is treated as scroll - // wheel. - SCRL_TO, // Toggle scroll mode - SCRL_MO, // Momentary scroll mode - SCRL_DVI, // Increment scroll divider - SCRL_DVD, // Decrement scroll divider -}; - -////////////////////////////////////////////////////////////////////////////// -// Experimental API - -// keyball_get_scroll_mode returns current scroll ode of trackball. -bool keyball_get_scroll_mode(void); - -// keyball_set_scroll_mode enables/disables scroll mode of trackball. -// When scroll mode enabled, rotating trackball reports scrolling events. -void keyball_set_scroll_mode(bool mode); - -// keyball_oled_render_ballinfo renders ball information to OLED. -// It uses just 21 columns to show the info. -void keyball_oled_render_ballinfo(void); - -// keyball_oled_render_keyinfo renders last processed key information to OLED. -// It shows column, row, key code, and key name (if available). -void keyball_oled_render_keyinfo(void); diff --git a/qmk_firmware/keyboards/keyball/keyball61/rules.mk b/qmk_firmware/keyboards/keyball/keyball61/rules.mk index 78f904752..a6d44e8c1 100644 --- a/qmk_firmware/keyboards/keyball/keyball61/rules.mk +++ b/qmk_firmware/keyboards/keyball/keyball61/rules.mk @@ -46,6 +46,9 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend OLED_ENABLE = no # Please Enable this in each keymaps. SRC += lib/oledkit/oledkit.c # OLED utility for Keyball series. +# Include common library +SRC += lib/keyball/keyball.c + # Disable other features to squeeze firmware size SPACE_CADET_ENABLE = no MAGIC_ENABLE = no diff --git a/qmk_firmware/keyboards/keyball/lib/keyball/keyball.c b/qmk_firmware/keyboards/keyball/lib/keyball/keyball.c new file mode 100644 index 000000000..66d5e0d6e --- /dev/null +++ b/qmk_firmware/keyboards/keyball/lib/keyball/keyball.c @@ -0,0 +1,500 @@ +/* +Copyright 2022 MURAOKA Taro (aka KoRoN, @kaoriya) + +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 . +*/ + +#include "quantum.h" +#include "transactions.h" + +#include "keyball.h" +#include "drivers/pmw3360/pmw3360.h" + +const uint8_t CPI_DEFAULT = KEYBALL_CPI_DEFAULT / 100; +const uint8_t CPI_MAX = pmw3360_MAXCPI + 1; +const uint8_t SCROLL_DIV_MAX = 7; + +keyball_t keyball = { + .this_have_ball = false, + .that_enable = false, + .that_have_ball = false, + + .this_motion = {0}, + .that_motion = {0}, + + .cpi_value = 0, + .cpi_changed = false, + + .scroll_mode = false, + .scroll_div = 0, +}; + +////////////////////////////////////////////////////////////////////////////// +// Hook points + +__attribute__((weak)) void keyball_on_adjust_layout(keyball_adjust_t v) {} + +////////////////////////////////////////////////////////////////////////////// +// Static utilities + +// add16 adds two int16_t with clipping. +static int16_t add16(int16_t a, int16_t b) { + int16_t r = a + b; + if (a >= 0 && b >= 0 && r < 0) { + r = 32767; + } else if (a < 0 && b < 0 && r >= 0) { + r = -32768; + } + return r; +} + +// clip2int8 clips an integer fit into int8_t. +static inline int8_t clip2int8(int16_t v) { return (v) < -127 ? -127 : (v) > 127 ? 127 : (int8_t)v; } + +static const char *format_4d(int8_t d) { + static char buf[5] = {0}; // max width (4) + NUL (1) + char lead = ' '; + if (d < 0) { + d = -d; + lead = '-'; + } + buf[3] = (d % 10) + '0'; + d /= 10; + if (d == 0) { + buf[2] = lead; + lead = ' '; + } else { + buf[2] = (d % 10) + '0'; + d /= 10; + } + if (d == 0) { + buf[1] = lead; + lead = ' '; + } else { + buf[1] = (d % 10) + '0'; + d /= 10; + } + buf[0] = lead; + return buf; +} + +static char to_1x(uint8_t x) { + x &= 0x0f; + return x < 10 ? x + '0' : x + 'a' - 10; +} + +static void add_cpi(int8_t delta) { + int16_t v = keyball_get_cpi() + delta; + keyball_set_cpi(v < 1 ? 1 : v); +} + +static void add_scroll_div(int8_t delta) { + int8_t v = keyball_get_scroll_div() + delta; + keyball_set_scroll_div(v < 1 ? 1 : v); +} + +////////////////////////////////////////////////////////////////////////////// +// Pointing device driver + +void pointing_device_driver_init(void) { + keyball.this_have_ball = pmw3360_init(); + if (keyball.this_have_ball) { + pmw3360_cpi_set(CPI_DEFAULT - 1); + pmw3360_reg_write(pmw3360_Motion_Burst, 0); + } +} + +uint16_t pointing_device_driver_get_cpi(void) { return keyball_get_cpi(); } + +void pointing_device_driver_set_cpi(uint16_t cpi) { keyball_set_cpi(cpi); } + +static void motion_to_mouse_move(keyball_motion_t *m, report_mouse_t *r, bool is_left) { + r->x = clip2int8(m->y); + r->y = clip2int8(m->x); + if (is_left) { + r->x = -r->x; + r->y = -r->y; + } + // clear motion + m->x = 0; + m->y = 0; +} + +static void motion_to_mouse_scroll(keyball_motion_t *m, report_mouse_t *r, bool is_left) { + uint8_t div = keyball_get_scroll_div() - 1; + int16_t x = m->x >> div; + m->x -= x << div; + int16_t y = m->y >> div; + m->y -= y << div; + r->h = clip2int8(y); + r->v = clip2int8(x); + if (!is_left) { + r->h = -r->h; + r->v = -r->v; + } +} + +static void motion_to_mouse(keyball_motion_t *m, report_mouse_t *r, bool is_left, bool as_scroll) { + if (as_scroll) { + motion_to_mouse_scroll(m, r, is_left); + } else { + motion_to_mouse_move(m, r, is_left); + } +} + +report_mouse_t pointing_device_driver_get_report(report_mouse_t rep) { + // fetch from optical sensor. + if (keyball.this_have_ball) { + pmw3360_motion_t d = {0}; + if (pmw3360_motion_burst(&d)) { + ATOMIC_BLOCK_FORCEON { + keyball.this_motion.x = add16(keyball.this_motion.x, d.x); + keyball.this_motion.y = add16(keyball.this_motion.y, d.y); + } + } + } + // report mouse event, if keyboard is primary. + if (is_keyboard_master()) { + if (keyball.this_have_ball) { + motion_to_mouse(&keyball.this_motion, &rep, is_keyboard_left(), keyball.scroll_mode); + } + if (keyball.that_have_ball) { + motion_to_mouse(&keyball.that_motion, &rep, !is_keyboard_left(), keyball.scroll_mode ^ keyball.this_have_ball); + } + } + return rep; +} + +////////////////////////////////////////////////////////////////////////////// +// Split RPC + +static void rpc_get_info_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { + keyball_info_t *that = (keyball_info_t *)in_data; + if (that->vid == VENDOR_ID && that->pid == PRODUCT_ID) { + keyball.that_enable = true; + keyball.that_have_ball = that->ballcnt > 0; + } + keyball_info_t info = { + .vid = VENDOR_ID, + .pid = PRODUCT_ID, + .ballcnt = keyball.this_have_ball ? 1 : 0, + }; + *(keyball_info_t *)out_data = info; + keyball_on_adjust_layout(KEYBALL_ADJUST_SECONDARY); +} + +static void rpc_get_info_invoke(void) { + static bool negotiated = false; + static uint32_t last_sync = 0; + static int round = 0; + if (negotiated || timer_elapsed32(last_sync) < KEYBALL_TX_GETINFO_INTERVAL) { + return; + } + last_sync = timer_read32(); + round++; + keyball_info_t req = { + .vid = VENDOR_ID, + .pid = PRODUCT_ID, + .ballcnt = keyball.this_have_ball ? 1 : 0, + }; + keyball_info_t recv = {0}; + if (!transaction_rpc_exec(KEYBALL_GET_INFO, sizeof(req), &req, sizeof(recv), &recv)) { + if (round < KEYBALL_TX_GETINFO_MAXTRY) { + dprintf("keyball:rpc_get_info_invoke: missed #%d\n", round); + return; + } + } + negotiated = true; + if (recv.vid == VENDOR_ID && recv.pid == PRODUCT_ID) { + keyball.that_enable = true; + keyball.that_have_ball = recv.ballcnt > 0; + } + dprintf("keyball:rpc_get_info_invoke: negotiated #%d %d\n", round, keyball.that_have_ball); + + // split keyboard negotiation completed. + +#ifdef VIA_ENABLE + // adjust VIA layout options according to current combination. + uint8_t layouts = (keyball.this_have_ball ? (is_keyboard_left() ? 0x02 : 0x01) : 0x00) | (keyball.that_have_ball ? (is_keyboard_left() ? 0x01 : 0x02) : 0x00); + uint32_t curr = via_get_layout_options(); + uint32_t next = (curr & ~0x3) | layouts; + if (next != curr) { + via_set_layout_options(next); + } +#endif + + keyball_on_adjust_layout(KEYBALL_ADJUST_PRIMARY); +} + +static void rpc_get_motion_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { + if (keyball.this_have_ball && *((keyball_motion_id_t *)in_data) == 0) { + *(keyball_motion_t *)out_data = keyball.this_motion; + // clear motion + keyball.this_motion.x = 0; + keyball.this_motion.y = 0; + } +} + +static void rpc_get_motion_invoke(void) { + static uint32_t last_sync = 0; + if (!keyball.that_have_ball || timer_elapsed32(last_sync) < KEYBALL_TX_GETMOTION_INTERVAL) { + return; + } + keyball_motion_id_t req = 0; + keyball_motion_t recv = {0}; + if (transaction_rpc_exec(KEYBALL_GET_MOTION, sizeof(req), &req, sizeof(recv), &recv)) { + ATOMIC_BLOCK_FORCEON { + keyball.that_motion.x = add16(keyball.that_motion.x, recv.x); + keyball.that_motion.y = add16(keyball.that_motion.y, recv.y); + } + } else { + dprintf("keyball:rpc_get_motion_invoke: failed"); + } + last_sync = timer_read32(); + return; +} + +static void rpc_set_cpi_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { + if (keyball.this_have_ball) { + keyball_set_cpi(*(keyball_cpi_t *)in_data); + } +} + +static void rpc_set_cpi_invoke(void) { + if (!keyball.that_have_ball || !keyball.cpi_changed) { + return; + } + keyball_cpi_t req = keyball.cpi_value; + if (!transaction_rpc_send(KEYBALL_SET_CPI, sizeof(req), &req)) { + return; + } + keyball.cpi_changed = false; +} + +////////////////////////////////////////////////////////////////////////////// +// OLED utility + +#ifdef OLED_ENABLE +// clang-format off +const char PROGMEM code_to_name[] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '0', 'R', 'E', 'B', 'T', + '_', '-', '=', '[', ']', '\\', '#', ';', '\'', '`', + ',', '.', '/', +}; +// clang-format on +#endif + +void keyball_oled_render_ballinfo(void) { +#ifdef OLED_ENABLE + // Format: `Ball:{mouse x}{mouse y}{mouse h}{mouse v}` + // ` CPI{CPI} S{SCROLL_MODE} D{SCROLL_DIV}` + // + // Output example: + // + // Ball: -12 34 0 0 + // + oled_write_P(PSTR("Ball:"), false); + oled_write(format_4d(keyball.last_mouse.x), false); + oled_write(format_4d(keyball.last_mouse.y), false); + oled_write(format_4d(keyball.last_mouse.h), false); + oled_write(format_4d(keyball.last_mouse.v), false); + // CPI + oled_write_P(PSTR(" CPI"), false); + oled_write(format_4d(keyball_get_cpi()) + 1, false); + oled_write_P(PSTR("00 S"), false); + oled_write_char(keyball.scroll_mode ? '1' : '0', false); + oled_write_P(PSTR(" D"), false); + oled_write_char('0' + keyball_get_scroll_div(), false); +#endif +} + +void keyball_oled_render_keyinfo(void) { +#ifdef OLED_ENABLE + // Format: `Key : R{row} C{col} K{kc} '{name}` + // + // Where `kc` is lower 8 bit of keycode. + // Where `name` is readable label for `kc`, valid between 4 and 56. + // + // It is aligned to fit with output of keyball_oled_render_ballinfo(). + // For example: + // + // Key : R2 C3 K06 'c + // Ball: 0 0 0 0 + // + uint8_t keycode = keyball.last_kc; + + oled_write_P(PSTR("Key : R"), false); + oled_write_char(to_1x(keyball.last_pos.row), false); + oled_write_P(PSTR(" C"), false); + oled_write_char(to_1x(keyball.last_pos.col), false); + if (keycode) { + oled_write_P(PSTR(" K"), false); + oled_write_char(to_1x(keycode >> 4), false); + oled_write_char(to_1x(keycode), false); + } + if (keycode >= 4 && keycode < 57) { + oled_write_P(PSTR(" '"), false); + char name = pgm_read_byte(code_to_name + keycode - 4); + oled_write_char(name, false); + } else { + oled_advance_page(true); + } +#endif +} + +////////////////////////////////////////////////////////////////////////////// + +bool keyball_get_scroll_mode(void) { return keyball.scroll_mode; } + +void keyball_set_scroll_mode(bool mode) { keyball.scroll_mode = mode; } + +uint8_t keyball_get_scroll_div(void) { return keyball.scroll_div == 0 ? KEYBALL_SCROLL_DIV_DEFAULT : keyball.scroll_div; } + +void keyball_set_scroll_div(uint8_t div) { keyball.scroll_div = div > SCROLL_DIV_MAX ? SCROLL_DIV_MAX : div; } + +uint8_t keyball_get_cpi(void) { return keyball.cpi_value == 0 ? CPI_DEFAULT : keyball.cpi_value; } + +void keyball_set_cpi(uint8_t cpi) { + if (cpi > CPI_MAX) { + cpi = CPI_MAX; + } + keyball.cpi_value = cpi; + keyball.cpi_changed = true; + if (keyball.this_have_ball) { + pmw3360_cpi_set(cpi == 0 ? CPI_DEFAULT - 1 : cpi - 1); + pmw3360_reg_write(pmw3360_Motion_Burst, 0); + } +} + +////////////////////////////////////////////////////////////////////////////// + +void keyboard_post_init_kb(void) { + // register transaction handlers on secondary. + if (!is_keyboard_master()) { + transaction_register_rpc(KEYBALL_GET_INFO, rpc_get_info_handler); + transaction_register_rpc(KEYBALL_GET_MOTION, rpc_get_motion_handler); + transaction_register_rpc(KEYBALL_SET_CPI, rpc_set_cpi_handler); + } + + // read keyball configuration from EEPROM + if (eeconfig_is_enabled()) { + keyball_config_t c = {.raw = eeconfig_read_kb()}; + if (c.cpi != 0) { + keyball_set_cpi(c.cpi); + } + keyball_set_scroll_div(c.sdiv); + } + + keyball_on_adjust_layout(KEYBALL_ADJUST_PENDING); + keyboard_post_init_user(); +} + +void housekeeping_task_kb(void) { + if (is_keyboard_master()) { + rpc_get_info_invoke(); + rpc_get_motion_invoke(); + rpc_set_cpi_invoke(); + } +} + +report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { + // store mouse report for OLED. + keyball.last_mouse = pointing_device_task_user(mouse_report); + return keyball.last_mouse; +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + // store last keycode, row, and col for OLED + keyball.last_kc = keycode; + keyball.last_pos = record->event.key; + + if (!process_record_user(keycode, record)) { + return false; + } + + switch (keycode) { + // process KC_MS_BTN1~8 by myself + // See process_action() in quantum/action.c for details. +#ifndef MOUSEKEY_ENABLE + case KC_MS_BTN1 ... KC_MS_BTN8: { + extern void register_button(bool, enum mouse_buttons); + register_button(record->event.pressed, MOUSE_BTN_MASK(keycode - KC_MS_BTN1)); + break; + } +#endif + + case KBC_RST: + if (record->event.pressed) { + keyball_set_cpi(0); + keyball_set_scroll_div(0); + } + break; + case KBC_SAVE: + if (record->event.pressed) { + keyball_config_t c = { + .cpi = keyball.cpi_value, + .sdiv = keyball.scroll_div, + }; + eeconfig_update_kb(c.raw); + } + break; + + case CPI_I100: + if (record->event.pressed) { + add_cpi(1); + } + break; + case CPI_D100: + if (record->event.pressed) { + add_cpi(-1); + } + break; + case CPI_I1K: + if (record->event.pressed) { + add_cpi(10); + } + break; + case CPI_D1K: + if (record->event.pressed) { + add_cpi(-10); + } + break; + + case SCRL_TO: + if (record->event.pressed) { + keyball.scroll_mode = !keyball.scroll_mode; + } + break; + case SCRL_MO: + keyball.scroll_mode = record->event.pressed; + break; + case SCRL_DVI: + if (record->event.pressed) { + add_scroll_div(1); + } + break; + case SCRL_DVD: + if (record->event.pressed) { + add_scroll_div(-1); + } + break; + + default: + return true; + } + return false; +} diff --git a/qmk_firmware/keyboards/keyball/lib/keyball/keyball.h b/qmk_firmware/keyboards/keyball/lib/keyball/keyball.h new file mode 100644 index 000000000..bf3479f08 --- /dev/null +++ b/qmk_firmware/keyboards/keyball/lib/keyball/keyball.h @@ -0,0 +1,135 @@ +/* +Copyright 2022 MURAOKA Taro (aka KoRoN, @kaoriya) + +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 . +*/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////// + +#ifndef KEYBALL_CPI_DEFAULT +# define KEYBALL_CPI_DEFAULT 500 +#endif + +#ifndef KEYBALL_SCROLL_DIV_DEFAULT +# define KEYBALL_SCROLL_DIV_DEFAULT 4 // 4: 1/8 (1/2^(n-1)) +#endif + +////////////////////////////////////////////////////////////////////////////// + +#define KEYBALL_TX_GETINFO_INTERVAL 500 +#define KEYBALL_TX_GETINFO_MAXTRY 10 +#define KEYBALL_TX_GETMOTION_INTERVAL 5 + +////////////////////////////////////////////////////////////////////////////// + +enum keyball_keycodes { + KBC_RST = SAFE_RANGE, // Keyball configuration: reset to default + KBC_SAVE, // Keyball configuration: save to EEPROM + + CPI_I100, // CPI +100 CPI + CPI_D100, // CPI -100 CPI + CPI_I1K, // CPI +1000 CPI + CPI_D1K, // CPI -1000 CPI + + // In scroll mode, motion from primary trackball is treated as scroll + // wheel. + SCRL_TO, // Toggle scroll mode + SCRL_MO, // Momentary scroll mode + SCRL_DVI, // Increment scroll divider + SCRL_DVD, // Decrement scroll divider + + KEYBALL_SAFE_RANGE, +}; + +typedef union { + uint32_t raw; + struct { + uint8_t cpi : 7; + uint8_t sdiv : 3; // scroll divider + }; +} keyball_config_t; + +typedef struct { + uint16_t vid; + uint16_t pid; + uint8_t ballcnt; // count of balls: support only 0 or 1, for now +} keyball_info_t; + +typedef uint8_t keyball_motion_id_t; + +typedef struct { + int16_t x; + int16_t y; +} keyball_motion_t; + +typedef uint8_t keyball_cpi_t; + +typedef struct { + bool this_have_ball; + bool that_enable; + bool that_have_ball; + + keyball_motion_t this_motion; + keyball_motion_t that_motion; + + uint8_t cpi_value; + bool cpi_changed; + + bool scroll_mode; + uint8_t scroll_div; + + uint16_t last_kc; + keypos_t last_pos; + report_mouse_t last_mouse; +} keyball_t; + +typedef enum { + KEYBALL_ADJUST_PENDING = 0, + KEYBALL_ADJUST_PRIMARY = 1, + KEYBALL_ADJUST_SECONDARY = 2, +} keyball_adjust_t; + +////////////////////////////////////////////////////////////////////////////// + +extern keyball_t keyball; + +////////////////////////////////////////////////////////////////////////////// + +// keyball_oled_render_ballinfo renders ball information to OLED. +// It uses just 21 columns to show the info. +void keyball_oled_render_ballinfo(void); + +// keyball_oled_render_keyinfo renders last processed key information to OLED. +// It shows column, row, key code, and key name (if available). +void keyball_oled_render_keyinfo(void); + +// TODO: document +bool keyball_get_scroll_mode(void); + +// TODO: document +void keyball_set_scroll_mode(bool mode); + +// TODO: document +uint8_t keyball_get_scroll_div(void); + +// TODO: document +void keyball_set_scroll_div(uint8_t div); + +// TODO: document +uint8_t keyball_get_cpi(void); + +// TODO: document +void keyball_set_cpi(uint8_t cpi);