qmk_firmware

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

pointing_device_auto_mouse.c (16887B)


      1 /* Copyright 2021 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
      2  * Copyright 2022 Alabastard
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 2 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
     19 
     20 #    include <stdlib.h>
     21 #    include <string.h>
     22 #    include "pointing_device_auto_mouse.h"
     23 #    include "debug.h"
     24 #    include "action_util.h"
     25 #    include "quantum_keycodes.h"
     26 
     27 /* local data structure for tracking auto mouse */
     28 static auto_mouse_context_t auto_mouse_context = {
     29     .config.layer    = (uint8_t)(AUTO_MOUSE_DEFAULT_LAYER),
     30     .config.timeout  = (uint16_t)(AUTO_MOUSE_TIME),
     31     .config.debounce = (uint8_t)(AUTO_MOUSE_DEBOUNCE),
     32 };
     33 
     34 /* local functions */
     35 static bool is_mouse_record(uint16_t keycode, keyrecord_t* record);
     36 static void auto_mouse_reset(void);
     37 
     38 /* check for target layer deactivation overrides */
     39 static inline bool layer_hold_check(void) {
     40     return get_auto_mouse_toggle() ||
     41 #    ifndef NO_ACTION_ONESHOT
     42            get_oneshot_layer() == (AUTO_MOUSE_TARGET_LAYER) ||
     43 #    endif
     44            false;
     45 }
     46 
     47 /* check all layer activation criteria */
     48 bool is_auto_mouse_active(void) {
     49     return auto_mouse_context.status.is_activated || auto_mouse_context.status.mouse_key_tracker || layer_hold_check();
     50 }
     51 
     52 /**
     53  * @brief Get auto mouse enable state
     54  *
     55  * Return is_enabled value
     56  *
     57  * @return bool true: auto mouse enabled false: auto mouse disabled
     58  */
     59 bool get_auto_mouse_enable(void) {
     60     return auto_mouse_context.config.is_enabled;
     61 }
     62 
     63 /**
     64  * @brief get current target layer index
     65  *
     66  * NOTE: (AUTO_MOUSE_TARGET_LAYER) is an alias for this function
     67  *
     68  * @return uint8_t target layer index
     69  */
     70 uint8_t get_auto_mouse_layer(void) {
     71     return auto_mouse_context.config.layer;
     72 }
     73 
     74 /**
     75  * @brief Get the current timeout to turn off mouse layer
     76  *
     77  * @return uint16_t timeout in ms
     78  */
     79 uint16_t get_auto_mouse_timeout(void) {
     80     return auto_mouse_context.config.timeout;
     81 }
     82 
     83 /**
     84  * @brief Get the auto mouse debouncing timeout
     85  *
     86  * @return uint8_t
     87  */
     88 uint8_t get_auto_mouse_debounce(void) {
     89     return auto_mouse_context.config.debounce;
     90 }
     91 
     92 /**
     93  * @brief get layer_toggled value
     94  *
     95  * @return bool of current layer_toggled state
     96  */
     97 bool get_auto_mouse_toggle(void) {
     98     return auto_mouse_context.status.is_toggled;
     99 }
    100 
    101 /**
    102  * @brief get key tracker value
    103  *
    104  * @return bool of current layer_toggled state
    105  */
    106 int8_t get_auto_mouse_key_tracker(void) {
    107     return auto_mouse_context.status.mouse_key_tracker;
    108 }
    109 
    110 /**
    111  * @brief Reset auto mouse context
    112  *
    113  * Clear timers and status
    114  *
    115  * NOTE: this will set is_toggled to false so careful when using it
    116  */
    117 static void auto_mouse_reset(void) {
    118     memset(&auto_mouse_context.status, 0, sizeof(auto_mouse_context.status));
    119     memset(&auto_mouse_context.timer, 0, sizeof(auto_mouse_context.timer));
    120 }
    121 
    122 /**
    123  * @brief Set auto mouse enable state
    124  *
    125  * Set local auto mouse enabled state
    126  *
    127  * @param[in] state bool
    128  */
    129 void set_auto_mouse_enable(bool enable) {
    130     // skip if unchanged
    131     if (auto_mouse_context.config.is_enabled == enable) return;
    132     auto_mouse_context.config.is_enabled = enable;
    133     auto_mouse_reset();
    134 }
    135 
    136 /**
    137  * @brief Change target layer for auto mouse
    138  *
    139  * Sets input as the new target layer if different from current and resets auto mouse
    140  *
    141  * NOTE: remove_auto_mouse_layer(state, false) or auto_mouse_layer_off should be called
    142  *       before this function to avoid issues with layers getting stuck
    143  *
    144  * @param[in] layer uint8_t
    145  */
    146 void set_auto_mouse_layer(uint8_t layer) {
    147     // skip if unchanged
    148     if (auto_mouse_context.config.layer == layer) return;
    149     auto_mouse_context.config.layer = layer;
    150     auto_mouse_reset();
    151 }
    152 
    153 /**
    154  * @brief Changes the timeout for the mouse auto layer to be disabled
    155  *
    156  * @param timeout
    157  */
    158 void set_auto_mouse_timeout(uint16_t timeout) {
    159     if (auto_mouse_context.config.timeout == timeout) return;
    160     auto_mouse_context.config.timeout = timeout;
    161     auto_mouse_reset();
    162 }
    163 
    164 /**
    165  * @brief Set the auto mouse key debounce
    166  *
    167  * @param debounce
    168  */
    169 void set_auto_mouse_debounce(uint8_t debounce) {
    170     if (auto_mouse_context.config.debounce == debounce) return;
    171     auto_mouse_context.config.debounce = debounce;
    172     auto_mouse_reset();
    173 }
    174 
    175 /**
    176  * @brief Changes the timeout for the mouse auto layer to be disabled
    177  *
    178  * @param key_tracker
    179  */
    180 void set_auto_mouse_key_tracker(int8_t key_tracker) {
    181     auto_mouse_context.status.mouse_key_tracker = key_tracker;
    182 }
    183 
    184 /**
    185  * @brief toggle mouse layer setting
    186  *
    187  * Change state of local layer_toggled bool meant to track when the mouse layer is toggled on by other means
    188  *
    189  * NOTE: While is_toggled is true it will prevent deactiving target layer (but not activation)
    190  */
    191 void auto_mouse_toggle(void) {
    192     auto_mouse_context.status.is_toggled ^= 1;
    193     auto_mouse_context.timer.delay = 0;
    194 }
    195 
    196 /**
    197  * @brief Remove current auto mouse target layer from layer state
    198  *
    199  * Will remove auto mouse target layer from given layer state if appropriate.
    200  *
    201  * NOTE: Removal can be forced, ignoring appropriate critera
    202  *
    203  * @params state[in] layer_state_t original layer state
    204  * @params force[in] bool force removal
    205  *
    206  * @return layer_state_t modified layer state
    207  */
    208 layer_state_t remove_auto_mouse_layer(layer_state_t state, bool force) {
    209     if (force || ((AUTO_MOUSE_ENABLED) && !layer_hold_check())) {
    210         state &= ~((layer_state_t)1 << (AUTO_MOUSE_TARGET_LAYER));
    211     }
    212     return state;
    213 }
    214 
    215 /**
    216  * @brief Disable target layer
    217  *
    218  * Will disable target layer if appropriate.
    219  * NOTE: NOT TO BE USED in layer_state_set stack!!!
    220  */
    221 void auto_mouse_layer_off(void) {
    222     if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && (AUTO_MOUSE_ENABLED) && !layer_hold_check()) {
    223         layer_off((AUTO_MOUSE_TARGET_LAYER));
    224     }
    225 }
    226 
    227 /**
    228  * @brief Weak function to handel testing if pointing_device is active
    229  *
    230  * Will trigger target layer activation(if delay timer has expired) and prevent deactivation when true.
    231  * May be replaced by bool in report_mouse_t in future
    232  *
    233  * NOTE: defined weakly to allow for changing and adding conditions for specific hardware/customization
    234  *
    235  * @param[in] mouse_report report_mouse_t
    236  * @return bool of pointing_device activation
    237  */
    238 __attribute__((weak)) bool auto_mouse_activation(report_mouse_t mouse_report) {
    239     auto_mouse_context.total_mouse_movement.x += mouse_report.x;
    240     auto_mouse_context.total_mouse_movement.y += mouse_report.y;
    241     auto_mouse_context.total_mouse_movement.h += mouse_report.h;
    242     auto_mouse_context.total_mouse_movement.v += mouse_report.v;
    243     return abs(auto_mouse_context.total_mouse_movement.x) > AUTO_MOUSE_THRESHOLD || abs(auto_mouse_context.total_mouse_movement.y) > AUTO_MOUSE_THRESHOLD || abs(auto_mouse_context.total_mouse_movement.h) > AUTO_MOUSE_THRESHOLD || abs(auto_mouse_context.total_mouse_movement.v) > AUTO_MOUSE_THRESHOLD || mouse_report.buttons;
    244 }
    245 
    246 /**
    247  * @brief Update the auto mouse based on mouse_report
    248  *
    249  * Handel activation/deactivation of target layer based on auto_mouse_activation and state timers and local key/layer tracking data
    250  *
    251  * @param[in] mouse_report report_mouse_t
    252  */
    253 void pointing_device_task_auto_mouse(report_mouse_t mouse_report) {
    254     // skip if disabled, delay timer running, or debounce
    255     if (!(AUTO_MOUSE_ENABLED) || timer_elapsed(auto_mouse_context.timer.active) <= auto_mouse_context.config.debounce || timer_elapsed(auto_mouse_context.timer.delay) <= AUTO_MOUSE_DELAY) {
    256         return;
    257     }
    258     // update activation and reset debounce
    259     auto_mouse_context.status.is_activated = auto_mouse_activation(mouse_report);
    260     if (is_auto_mouse_active()) {
    261         auto_mouse_context.total_mouse_movement = (total_mouse_movement_t){.x = 0, .y = 0, .h = 0, .v = 0};
    262         auto_mouse_context.timer.active         = timer_read();
    263         auto_mouse_context.timer.delay          = 0;
    264         if (!layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
    265             layer_on((AUTO_MOUSE_TARGET_LAYER));
    266         }
    267     } else if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && timer_elapsed(auto_mouse_context.timer.active) > auto_mouse_context.config.timeout) {
    268         layer_off((AUTO_MOUSE_TARGET_LAYER));
    269         auto_mouse_context.timer.active         = 0;
    270         auto_mouse_context.total_mouse_movement = (total_mouse_movement_t){.x = 0, .y = 0, .h = 0, .v = 0};
    271     }
    272 }
    273 
    274 /**
    275  * @brief Handle mouskey event
    276  *
    277  * Increments/decrements mouse_key_tracker and restart active timer
    278  *
    279  * @param[in] pressed bool
    280  */
    281 void auto_mouse_keyevent(bool pressed) {
    282     if (pressed) {
    283         auto_mouse_context.status.mouse_key_tracker++;
    284     } else {
    285         auto_mouse_context.status.mouse_key_tracker--;
    286     }
    287     auto_mouse_context.timer.delay = 0;
    288 }
    289 
    290 /**
    291  * @brief Handle auto mouse non mousekey reset
    292  *
    293  * Start/restart delay timer and reset auto mouse on keydown as well as turn the
    294  * target layer off if on and reset toggle status
    295  *
    296  * NOTE: NOT TO BE USED in layer_state_set stack!!!
    297  *
    298  * @param[in] pressed bool
    299  */
    300 void auto_mouse_reset_trigger(bool pressed) {
    301     if (pressed) {
    302         if (layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
    303             layer_off((AUTO_MOUSE_TARGET_LAYER));
    304         };
    305         auto_mouse_reset();
    306     }
    307     auto_mouse_context.timer.delay = timer_read();
    308 }
    309 
    310 /**
    311  * @brief handle key events processing for auto mouse
    312  *
    313  * Will process keys differently depending on if key is defined as mousekey or not.
    314  * Some keys have built in behaviour(not overwritable):
    315  * mouse buttons        : auto_mouse_keyevent()
    316  * non-mouse keys       : auto_mouse_reset_trigger()
    317  * mod keys             : skip auto mouse key processing
    318  * mod tap              : skip on hold (mod keys)
    319  * QK mods e.g. LCTL(kc): default to non-mouse key, add at kb/user level as needed
    320  * non target layer keys: skip auto mouse key processing (same as mod keys)
    321  * MO(target layer)     : auto_mouse_keyevent()
    322  * target layer toggles : auto_mouse_toggle() (on both key up and keydown)
    323  * target layer tap     : default processing on tap mouse key on hold
    324  * all other keycodes   : default to non-mouse key, add at kb/user level as needed
    325  *
    326  * Will deactivate target layer once a non mouse key is pressed if nothing is holding the layer active
    327  * such as held mousekey, toggled current target layer, or auto_mouse_activation is true
    328  *
    329  * @params keycode[in] uint16_t
    330  * @params record[in] keyrecord_t pointer
    331  */
    332 bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
    333     // skip if not enabled or mouse_layer not set
    334     if (!(AUTO_MOUSE_ENABLED)) return true;
    335 
    336     switch (keycode) {
    337         // Skip Mod keys to avoid layer reset
    338         case KC_LEFT_CTRL ... KC_RIGHT_GUI:
    339         case QK_MODS ... QK_MODS_MAX:
    340             break;
    341         // TO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
    342         case QK_TO ... QK_TO_MAX:
    343             if (QK_TO_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    344                 if (!(record->event.pressed)) auto_mouse_toggle();
    345             }
    346             break;
    347         // TG((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
    348         case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
    349             if (QK_TOGGLE_LAYER_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    350                 if (!(record->event.pressed)) auto_mouse_toggle();
    351             }
    352             break;
    353         // MO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
    354         case QK_MOMENTARY ... QK_MOMENTARY_MAX:
    355             if (QK_MOMENTARY_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    356                 auto_mouse_keyevent(record->event.pressed);
    357             }
    358         // DF ---------------------------------------------------------------------------------------------------------
    359         case QK_DEF_LAYER ... QK_DEF_LAYER_MAX:
    360         // PDF --------------------------------------------------------------------------------------------------------
    361         case QK_PERSISTENT_DEF_LAYER ... QK_PERSISTENT_DEF_LAYER_MAX:
    362 #    ifndef NO_ACTION_ONESHOT
    363         // OSL((AUTO_MOUSE_TARGET_LAYER))------------------------------------------------------------------------------
    364         case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
    365         case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
    366 #    endif
    367             break;
    368         // LM((AUTO_MOUSE_TARGET_LAYER), mod)--------------------------------------------------------------------------
    369         case QK_LAYER_MOD ... QK_LAYER_MOD_MAX:
    370             if (QK_LAYER_MOD_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    371                 auto_mouse_keyevent(record->event.pressed);
    372             }
    373             break;
    374             // TT((AUTO_MOUSE_TARGET_LAYER))---------------------------------------------------------------------------
    375 #    ifndef NO_ACTION_TAPPING
    376         case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
    377             if (QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    378                 auto_mouse_keyevent(record->event.pressed);
    379 #        if TAPPING_TOGGLE != 0
    380                 if (record->tap.count == TAPPING_TOGGLE) {
    381                     if (record->event.pressed) {
    382                         auto_mouse_context.status.mouse_key_tracker--;
    383                     } else {
    384                         auto_mouse_toggle();
    385                         auto_mouse_context.status.mouse_key_tracker++;
    386                     }
    387                 }
    388 #        endif
    389             }
    390             break;
    391         // LT((AUTO_MOUSE_TARGET_LAYER), kc)---------------------------------------------------------------------------
    392         case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
    393             if (!record->tap.count) {
    394                 if (QK_LAYER_TAP_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
    395                     auto_mouse_keyevent(record->event.pressed);
    396                 }
    397                 break;
    398             }
    399         // MT(kc) only skip on hold
    400         case QK_MOD_TAP ... QK_MOD_TAP_MAX:
    401             if (!record->tap.count) break;
    402 #    endif
    403         // QK_MODS goes to default
    404         default:
    405             // skip on no event
    406             if (IS_NOEVENT(record->event)) break;
    407             // check if keyrecord is mousekey
    408             if (is_mouse_record(keycode, record)) {
    409                 auto_mouse_keyevent(record->event.pressed);
    410             } else if (!is_auto_mouse_active()) {
    411                 // all non-mousekey presses restart delay timer and reset status
    412                 auto_mouse_reset_trigger(record->event.pressed);
    413             }
    414     }
    415     if (auto_mouse_context.status.mouse_key_tracker < 0) {
    416         auto_mouse_context.status.mouse_key_tracker = 0;
    417         dprintf("key tracker error (<0) \n");
    418     }
    419     return true;
    420 }
    421 
    422 /**
    423  * @brief Local function to handle checking if a keycode is a mouse button
    424  *
    425  * Starts code stack for checking keyrecord if defined as mousekey
    426  *
    427  * @params keycode[in] uint16_t
    428  * @params record[in]  keyrecord_t pointer
    429  * @return bool true: keyrecord is mousekey false: keyrecord is not mousekey
    430  */
    431 static bool is_mouse_record(uint16_t keycode, keyrecord_t* record) {
    432     // allow for keyboard to hook in and override if need be
    433     if (is_mouse_record_kb(keycode, record) || IS_MOUSEKEY(keycode)) return true;
    434     return false;
    435 }
    436 
    437 /**
    438  * @brief Weakly defined keyboard level callback for adding keyrecords as mouse keys
    439  *
    440  * Meant for redefinition at keyboard level and should return is_mouse_record_user by default at end of function
    441  *
    442  * @params keycode[in] uint16_t
    443  * @params record[in] keyrecord_t pointer
    444  * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
    445  */
    446 __attribute__((weak)) bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
    447     return is_mouse_record_user(keycode, record);
    448 }
    449 
    450 /**
    451  * @brief Weakly defined keymap/user level callback for adding keyrecords as mouse keys
    452  *
    453  * Meant for redefinition at keymap/user level and should return false by default at end of function
    454  *
    455  * @params keycode[in] uint16_t
    456  * @params record[in] keyrecord_t pointer
    457  * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
    458  */
    459 __attribute__((weak)) bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record) {
    460     return false;
    461 }
    462 
    463 #endif // POINTING_DEVICE_AUTO_MOUSE_ENABLE