/* Copyright 2022 @Yowkees 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 QMK_KEYBOARD_H #include "quantum.h" #include "os_detection.h" #include "raw_hid.h" // Home row mods #define HR_A KC_A #define HR_S MT(MOD_LALT, KC_S) #define HR_D MT(MOD_LSFT, KC_D) #define HR_F MT(MOD_LCTL, KC_F) #define HR_J MT(MOD_LCTL, KC_J) #define HR_K MT(MOD_RSFT, KC_K) #define HR_L MT(MOD_RALT, KC_L) #define HR_SCLN KC_SCLN // Custom shortcuts #define KC_EUNDO LCTL(LSFT(KC_MINUS)) #define KC_AENTER LCTL(LALT(KC_ENT)) #define WAYFULL LGUI(LSFT(KC_F)) #define WAYHALF LGUI(LSFT(KC_D)) #define KC_CAD LCTL(LALT(KC_DEL)) enum { TD_UNDO, TD_NAV, }; // Combo for caps word const uint16_t PROGMEM capsword_combo[] = {HR_D, HR_K, COMBO_END}; combo_t key_combos[] = { COMBO(capsword_combo, QK_CAPS_WORD_TOGGLE), }; // Advanced tap dance for bottom-left thumb key: hold = momentary layer 5, double tap = toggle layer 1 typedef enum { TD_NAV_NONE, TD_NAV_SINGLE_HOLD, TD_NAV_DOUBLE_TAP, } td_nav_state_t; static td_nav_state_t nav_tap_state = TD_NAV_NONE; td_nav_state_t nav_cur_dance(tap_dance_state_t *state) { if (state->count == 1) { return state->pressed ? TD_NAV_SINGLE_HOLD : TD_NAV_NONE; } else if (state->count == 2) { return TD_NAV_DOUBLE_TAP; } return TD_NAV_NONE; } void nav_finished(tap_dance_state_t *state, void *user_data) { nav_tap_state = nav_cur_dance(state); switch (nav_tap_state) { case TD_NAV_SINGLE_HOLD: layer_on(5); break; case TD_NAV_DOUBLE_TAP: layer_invert(1); break; default: break; } } void nav_reset(tap_dance_state_t *state, void *user_data) { if (nav_tap_state == TD_NAV_SINGLE_HOLD) { layer_off(5); } nav_tap_state = TD_NAV_NONE; } tap_dance_action_t tap_dance_actions[] = { [TD_UNDO] = ACTION_TAP_DANCE_DOUBLE(KC_U, KC_EUNDO), [TD_NAV] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, nav_finished, nav_reset), }; // clang-format off const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { // Base layer [0] = LAYOUT_universal( KC_Q , KC_W , KC_E , KC_R , KC_T , KC_Y , TD(TD_UNDO), KC_I , KC_O , KC_P , HR_A , HR_S , HR_D , HR_F , KC_G , KC_H , HR_J , HR_K , HR_L , HR_SCLN , KC_Z , KC_X , KC_C , KC_V , KC_B , KC_N , KC_M , KC_COMM , KC_DOT , KC_SLSH , TD(TD_NAV), KC_TAB , KC_ESC , LT(3,KC_TAB), LT(4,KC_BSPC), KC_LGUI, MT(MOD_RGUI, KC_ENT), LT(4,KC_SPC), _______, _______, _______, KC_QUOT ), // Transparent layer for HR_L GUI+N behavior; GUI thumb keys become RCTL, RCTL home row mod becomes LCTL [1] = LAYOUT_universal( _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , MT(MOD_LCTL, KC_J), _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , KC_RCTL, MT(MOD_RCTL, KC_ENT), _______ , _______ , _______ , _______ , _______ ), // Gaming layer (removes home row mods via layer toggle) [2] = LAYOUT_universal( _______ , _______ , KC_E , _______ , _______ , _______ , KC_U , _______ , _______ , _______ , KC_A , KC_S , KC_D , KC_F , _______ , _______ , KC_J , KC_K , KC_L , KC_SCLN , _______ , _______ , _______ , _______ , KC_B , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ ), // Mouse + Numpad layer (via left thumb) [3] = LAYOUT_universal( _______ , _______ , _______ , _______ , _______ , KC_SLSH , KC_7 , KC_8 , KC_9 , KC_ASTR , _______ , _______ , _______ , _______ , _______ , KC_MINS , KC_4 , KC_5 , KC_6 , KC_PLUS , SCRL_TO , SCRL_MO , KC_BTN2 , KC_BTN1 , _______ , KC_0 , KC_1 , KC_2 , KC_3 , KC_DOT , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ ), // Symbols layer (via thumb keys) [4] = LAYOUT_universal( KC_GRV , KC_TILDE , KC_HASH , KC_AMPR , KC_PIPE, KC_CIRC , KC_LCBR , KC_RCBR , KC_LBRC , KC_RBRC , KC_EXLM , KC_UNDS , KC_COLN , KC_EQL , KC_DLR , KC_AT , KC_LPRN , KC_RPRN , KC_UNDS , KC_SCLN , KC_PERC , KC_QUES , KC_ASTR , KC_PLUS , KC_BSLS , KC_SLSH , KC_MINUS , KC_LT , KC_GT , KC_DQT , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ ), // Nav/Settings layer // F13/F14/F15 (below left/down/right arrows) are otherwise-unused // keysyms bound in dwl to office shade open/stop/close -- picked so // they don't need a modifier chord, since there's no home row mod // available while this layer is held. [5] = LAYOUT_universal( RGB_TOG , AML_TO , AML_I50 , AML_D50 , TG(1) , _______ , SSNP_VRT , KC_UP , SSNP_FRE , QK_BOOT , WAYHALF , WAYFULL , KC_CAD , KC_AENTER, SCRL_DVI, CPI_I100, KC_LEFT , KC_DOWN , KC_RGHT , _______ , RGB_MOD , RGB_RMOD , RGB_VAI , RGB_VAD , SCRL_DVD, CPI_D100, KC_F13 , KC_F14 , KC_F15 , _______ , QK_BOOT , KBC_RST , _______ , _______ , _______ , _______ , _______ , SSNP_VRT, SSNP_HOR , SSNP_FRE , KC_PGDN , KBC_SAVE ), }; // clang-format on // waybar qmk/qmk-layers/qmk-trackball modules: raw HID protocol. Fixed-size // unnumbered reports (QMK_RAW_REPORT_SIZE bytes); byte 0 is a command. 0x01 // (host->keyboard) asks for the current state; 0x02 (keyboard->host) carries // it: bytes 1-4 are layer_state as a little-endian uint32_t, bytes 5-6 are // the trackball's CPI as a little-endian uint16_t, byte 7 is the scroll // divisor, byte 8 is the scroll-mode flag (0/1). See man/waybar-qmk.5.scd // and man/waybar-qmk-trackball.5.scd in the waybar repo for the host-side // half of this. enum { QMK_RAW_CMD_GET_LAYER = 0x01, QMK_RAW_CMD_LAYER_STATE = 0x02, }; // Fixed at 32 (QMK's RAW_EPSIZE for USB LUFA/ChibiOS targets) rather than // referencing RAW_EPSIZE directly: that macro lives in usb_descriptor.h, // which isn't visible from this translation unit (e.g. when this file is // pulled into quantum/keymap_introspection.c for keymap introspection). #define QMK_RAW_REPORT_SIZE 32 static void send_layer_state(void) { uint8_t response[QMK_RAW_REPORT_SIZE] = {0}; response[0] = QMK_RAW_CMD_LAYER_STATE; uint32_t state = layer_state; memcpy(&response[1], &state, sizeof(state)); uint16_t cpi = keyball_get_cpi(); memcpy(&response[5], &cpi, sizeof(cpi)); response[7] = keyball_get_scroll_div(); response[8] = keyball_get_scroll_mode() ? 1 : 0; raw_hid_send(response, sizeof(response)); } void raw_hid_receive(uint8_t *data, uint8_t length) { if (length > 0 && data[0] == QMK_RAW_CMD_GET_LAYER) { send_layer_state(); } } layer_state_t layer_state_set_user(layer_state_t state) { // Auto enable scroll mode on layer 5 (nav layer) // Only force scroll ON for layer 5; let SCRL_TO handle toggling otherwise if (get_highest_layer(state) == 5) { keyball_set_scroll_mode(true); } send_layer_state(); return state; } bool process_record_user(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case TD(TD_NAV): // Backstop: QMK's tap-dance reset() isn't reliably tied to physical // key release (it can be deferred past the next tap-dance timeout // if other keys are pressed/released while this key is still held, // leaving layer 5 stuck on with nothing visibly held). Force it off // the instant this key physically releases, regardless of what the // tap-dance state machine thinks it's doing. if (!record->event.pressed && nav_tap_state == TD_NAV_SINGLE_HOLD) { layer_off(5); nav_tap_state = TD_NAV_NONE; } break; case HR_L: if (record->event.pressed && record->tap.count && layer_state_is(1)) { uint8_t mods = get_mods(); uint8_t weak = get_weak_mods(); uint8_t oneshot = get_oneshot_mods(); if ((mods | weak | oneshot) & MOD_MASK_GUI) { // GUI is already held; just tap N so the host sees GUI+N. tap_code(KC_N); return false; } } break; } return true; } bool process_detected_host_os_user(os_variant_t detected_os) { if (detected_os == OS_WINDOWS) layer_on(1); else layer_off(1); return true; } bool caps_word_press_user(uint16_t keycode) { switch (keycode) { // Keycodes that continue Caps Word, with shift applied. case KC_A ... KC_Z: case TD(TD_UNDO): // Treat tap dance U like a letter add_weak_mods(MOD_BIT(KC_LSFT)); return true; // Keycodes that continue Caps Word, without shifting. case KC_1 ... KC_0: case KC_BSPC: case KC_DEL: case KC_UNDS: case KC_MINS: return true; default: return false; // Deactivate Caps Word. } } // Override scroll direction (invert vertical scroll) with accumulator // Adjust SCROLL_DIVISOR to control speed (higher = slower) #define SCROLL_DIVISOR 4 void keyball_on_apply_motion_to_mouse_scroll(report_mouse_t *report, report_mouse_t *output, bool is_left) { static int16_t acc_h = 0; static int16_t acc_v = 0; // Accumulate movement acc_h += report->x; acc_v += report->y; // Consume input report->x = 0; report->y = 0; // Calculate scroll output int8_t h = acc_h / SCROLL_DIVISOR; int8_t v = acc_v / SCROLL_DIVISOR; // Keep remainder for next time acc_h -= h * SCROLL_DIVISOR; acc_v -= v * SCROLL_DIVISOR; // Apply to output (inverted) output->h = is_left ? h : -h; output->v = is_left ? v : -v; // scroll snapping switch (keyball_get_scrollsnap_mode()) { case KEYBALL_SCROLLSNAP_MODE_VERTICAL: output->h = 0; break; case KEYBALL_SCROLLSNAP_MODE_HORIZONTAL: output->v = 0; break; default: // pass by without doing anything break; } } uint16_t get_quick_tap_term(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case LT(4, KC_BSPC): return TAPPING_TERM; // Enable quick-tap for backspace case HR_F: return TAPPING_TERM; // Enable quick-tap for Ctrl+F repeating default: return 0; // Disabled for everything else (home row mods) } } #ifdef OLED_ENABLE # include "lib/oledkit/oledkit.h" void oledkit_render_info_user(void) { keyball_oled_render_keyinfo(); keyball_oled_render_ballinfo(); keyball_oled_render_layerinfo(); } #endif