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