Co-authored-by: Drashna Jaelre <drashna@live.com> Co-authored-by: Stefan Kerkmann <karlk90@pm.me>
		
			
				
	
	
		
			385 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			385 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Copyright 2021 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 | |
|  * Copyright 2022 Alabastard
 | |
|  *
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
 | |
| 
 | |
| #    include "pointing_device_auto_mouse.h"
 | |
| 
 | |
| /* local data structure for tracking auto mouse */
 | |
| static auto_mouse_context_t auto_mouse_context = {.config.layer = (uint8_t)AUTO_MOUSE_DEFAULT_LAYER};
 | |
| 
 | |
| /* local functions */
 | |
| static bool is_mouse_record(uint16_t keycode, keyrecord_t* record);
 | |
| static void auto_mouse_reset(void);
 | |
| 
 | |
| /* check for target layer deactivation overrides */
 | |
| static inline bool layer_hold_check(void) {
 | |
|     return get_auto_mouse_toggle() ||
 | |
| #    ifndef NO_ACTION_ONESHOT
 | |
|            get_oneshot_layer() == (AUTO_MOUSE_TARGET_LAYER) ||
 | |
| #    endif
 | |
|            false;
 | |
| }
 | |
| 
 | |
| /* check all layer activation criteria */
 | |
| static inline bool is_auto_mouse_active(void) {
 | |
|     return auto_mouse_context.status.is_activated || auto_mouse_context.status.mouse_key_tracker || layer_hold_check();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Get auto mouse enable state
 | |
|  *
 | |
|  * Return is_enabled value
 | |
|  *
 | |
|  * @return bool true: auto mouse enabled false: auto mouse disabled
 | |
|  */
 | |
| bool get_auto_mouse_enable(void) {
 | |
|     return auto_mouse_context.config.is_enabled;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief get current target layer index
 | |
|  *
 | |
|  * NOTE: (AUTO_MOUSE_TARGET_LAYER) is an alias for this function
 | |
|  *
 | |
|  * @return uint8_t target layer index
 | |
|  */
 | |
| uint8_t get_auto_mouse_layer(void) {
 | |
|     return auto_mouse_context.config.layer;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief get layer_toggled value
 | |
|  *
 | |
|  * @return bool of current layer_toggled state
 | |
|  */
 | |
| bool get_auto_mouse_toggle(void) {
 | |
|     return auto_mouse_context.status.is_toggled;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Reset auto mouse context
 | |
|  *
 | |
|  * Clear timers and status
 | |
|  *
 | |
|  * NOTE: this will set is_toggled to false so careful when using it
 | |
|  */
 | |
| static void auto_mouse_reset(void) {
 | |
|     memset(&auto_mouse_context.status, 0, sizeof(auto_mouse_context.status));
 | |
|     memset(&auto_mouse_context.timer, 0, sizeof(auto_mouse_context.timer));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Set auto mouse enable state
 | |
|  *
 | |
|  * Set local auto mouse enabled state
 | |
|  *
 | |
|  * @param[in] state bool
 | |
|  */
 | |
| void set_auto_mouse_enable(bool enable) {
 | |
|     // skip if unchanged
 | |
|     if (auto_mouse_context.config.is_enabled == enable) return;
 | |
|     auto_mouse_context.config.is_enabled = enable;
 | |
|     auto_mouse_reset();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Change target layer for auto mouse
 | |
|  *
 | |
|  * Sets input as the new target layer if different from current and resets auto mouse
 | |
|  *
 | |
|  * NOTE: remove_auto_mouse_layer(state, false) or auto_mouse_layer_off should be called
 | |
|  *       before this function to avoid issues with layers getting stuck
 | |
|  *
 | |
|  * @param[in] layer uint8_t
 | |
|  */
 | |
| void set_auto_mouse_layer(uint8_t layer) {
 | |
|     // skip if unchanged
 | |
|     if (auto_mouse_context.config.layer == layer) return;
 | |
|     auto_mouse_context.config.layer = layer;
 | |
|     auto_mouse_reset();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief toggle mouse layer setting
 | |
|  *
 | |
|  * Change state of local layer_toggled bool meant to track when the mouse layer is toggled on by other means
 | |
|  *
 | |
|  * NOTE: While is_toggled is true it will prevent deactiving target layer (but not activation)
 | |
|  */
 | |
| void auto_mouse_toggle(void) {
 | |
|     auto_mouse_context.status.is_toggled ^= 1;
 | |
|     auto_mouse_context.timer.delay = 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Remove current auto mouse target layer from layer state
 | |
|  *
 | |
|  * Will remove auto mouse target layer from given layer state if appropriate.
 | |
|  *
 | |
|  * NOTE: Removal can be forced, ignoring appropriate critera
 | |
|  *
 | |
|  * @params state[in] layer_state_t original layer state
 | |
|  * @params force[in] bool force removal
 | |
|  *
 | |
|  * @return layer_state_t modified layer state
 | |
|  */
 | |
| layer_state_t remove_auto_mouse_layer(layer_state_t state, bool force) {
 | |
|     if (force || ((AUTO_MOUSE_ENABLED) && !layer_hold_check())) {
 | |
|         state &= ~((layer_state_t)1 << (AUTO_MOUSE_TARGET_LAYER));
 | |
|     }
 | |
|     return state;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Disable target layer
 | |
|  *
 | |
|  * Will disable target layer if appropriate.
 | |
|  * NOTE: NOT TO BE USED in layer_state_set stack!!!
 | |
|  */
 | |
| void auto_mouse_layer_off(void) {
 | |
|     if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && (AUTO_MOUSE_ENABLED) && !layer_hold_check()) {
 | |
|         layer_off((AUTO_MOUSE_TARGET_LAYER));
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Weak function to handel testing if pointing_device is active
 | |
|  *
 | |
|  * Will trigger target layer activation(if delay timer has expired) and prevent deactivation when true.
 | |
|  * May be replaced by bool in report_mouse_t in future
 | |
|  *
 | |
|  * NOTE: defined weakly to allow for changing and adding conditions for specific hardware/customization
 | |
|  *
 | |
|  * @param[in] mouse_report report_mouse_t
 | |
|  * @return bool of pointing_device activation
 | |
|  */
 | |
| __attribute__((weak)) bool auto_mouse_activation(report_mouse_t mouse_report) {
 | |
|     return mouse_report.x != 0 || mouse_report.y != 0 || mouse_report.h != 0 || mouse_report.v != 0 || mouse_report.buttons;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Update the auto mouse based on mouse_report
 | |
|  *
 | |
|  * Handel activation/deactivation of target layer based on auto_mouse_activation and state timers and local key/layer tracking data
 | |
|  *
 | |
|  * @param[in] mouse_report report_mouse_t
 | |
|  */
 | |
| void pointing_device_task_auto_mouse(report_mouse_t mouse_report) {
 | |
|     // skip if disabled, delay timer running, or debounce
 | |
|     if (!(AUTO_MOUSE_ENABLED) || timer_elapsed(auto_mouse_context.timer.active) <= AUTO_MOUSE_DEBOUNCE || timer_elapsed(auto_mouse_context.timer.delay) <= AUTO_MOUSE_DELAY) {
 | |
|         return;
 | |
|     }
 | |
|     // update activation and reset debounce
 | |
|     auto_mouse_context.status.is_activated = auto_mouse_activation(mouse_report);
 | |
|     if (is_auto_mouse_active()) {
 | |
|         auto_mouse_context.timer.active = timer_read();
 | |
|         auto_mouse_context.timer.delay  = 0;
 | |
|         if (!layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
 | |
|             layer_on((AUTO_MOUSE_TARGET_LAYER));
 | |
|         }
 | |
|     } else if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && timer_elapsed(auto_mouse_context.timer.active) > AUTO_MOUSE_TIME) {
 | |
|         layer_off((AUTO_MOUSE_TARGET_LAYER));
 | |
|         auto_mouse_context.timer.active = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Handle mouskey event
 | |
|  *
 | |
|  * Increments/decrements mouse_key_tracker and restart active timer
 | |
|  *
 | |
|  * @param[in] pressed bool
 | |
|  */
 | |
| void auto_mouse_keyevent(bool pressed) {
 | |
|     if (pressed) {
 | |
|         auto_mouse_context.status.mouse_key_tracker++;
 | |
|     } else {
 | |
|         auto_mouse_context.status.mouse_key_tracker--;
 | |
|     }
 | |
|     auto_mouse_context.timer.delay = 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Handle auto mouse non mousekey reset
 | |
|  *
 | |
|  * Start/restart delay timer and reset auto mouse on keydown as well as turn the
 | |
|  * target layer off if on and reset toggle status
 | |
|  *
 | |
|  * NOTE: NOT TO BE USED in layer_state_set stack!!!
 | |
|  *
 | |
|  * @param[in] pressed bool
 | |
|  */
 | |
| void auto_mouse_reset_trigger(bool pressed) {
 | |
|     if (pressed) {
 | |
|         if (layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
 | |
|             layer_off((AUTO_MOUSE_TARGET_LAYER));
 | |
|         };
 | |
|         auto_mouse_reset();
 | |
|     }
 | |
|     auto_mouse_context.timer.delay = timer_read();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief handle key events processing for auto mouse
 | |
|  *
 | |
|  * Will process keys differently depending on if key is defined as mousekey or not.
 | |
|  * Some keys have built in behaviour(not overwritable):
 | |
|  * mouse buttons        : auto_mouse_keyevent()
 | |
|  * non-mouse keys       : auto_mouse_reset_trigger()
 | |
|  * mod keys             : skip auto mouse key processing
 | |
|  * mod tap              : skip on hold (mod keys)
 | |
|  * QK mods e.g. LCTL(kc): default to non-mouse key, add at kb/user level as needed
 | |
|  * non target layer keys: skip auto mouse key processing (same as mod keys)
 | |
|  * MO(target layer)     : auto_mouse_keyevent()
 | |
|  * target layer toggles : auto_mouse_toggle() (on both key up and keydown)
 | |
|  * target layer tap     : default processing on tap mouse key on hold
 | |
|  * all other keycodes   : default to non-mouse key, add at kb/user level as needed
 | |
|  *
 | |
|  * Will deactivate target layer once a non mouse key is pressed if nothing is holding the layer active
 | |
|  * such as held mousekey, toggled current target layer, or auto_mouse_activation is true
 | |
|  *
 | |
|  * @params keycode[in] uint16_t
 | |
|  * @params record[in] keyrecord_t pointer
 | |
|  */
 | |
| bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
 | |
|     // skip if not enabled or mouse_layer not set
 | |
|     if (!(AUTO_MOUSE_ENABLED)) return true;
 | |
| 
 | |
|     switch (keycode) {
 | |
|         // Skip Mod keys to avoid layer reset
 | |
|         case KC_LEFT_CTRL ... KC_RIGHT_GUI:
 | |
|         case QK_MODS ... QK_MODS_MAX:
 | |
|             break;
 | |
|         // TO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
 | |
|         case QK_TO ... QK_TO_MAX: // same proccessing as next
 | |
|         // TG((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
 | |
|         case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
 | |
|             if ((keycode & 0xff) == (AUTO_MOUSE_TARGET_LAYER)) {
 | |
|                 if (!(record->event.pressed)) auto_mouse_toggle();
 | |
|             }
 | |
|             break;
 | |
|         // MO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
 | |
|         case QK_MOMENTARY ... QK_MOMENTARY_MAX:
 | |
|             if ((keycode & 0xff) == (AUTO_MOUSE_TARGET_LAYER)) {
 | |
|                 auto_mouse_keyevent(record->event.pressed);
 | |
|             }
 | |
|         // DF ---------------------------------------------------------------------------------------------------------
 | |
|         case QK_DEF_LAYER ... QK_DEF_LAYER_MAX:
 | |
| #    ifndef NO_ACTION_ONESHOT
 | |
|         // OSL((AUTO_MOUSE_TARGET_LAYER))------------------------------------------------------------------------------
 | |
|         case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
 | |
|         case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
 | |
| #    endif
 | |
|             break;
 | |
|         // LM((AUTO_MOUSE_TARGET_LAYER), mod)--------------------------------------------------------------------------
 | |
|         case QK_LAYER_MOD ... QK_LAYER_MOD_MAX:
 | |
|             if (((keycode >> 8) & 0x0f) == (AUTO_MOUSE_TARGET_LAYER)) {
 | |
|                 auto_mouse_keyevent(record->event.pressed);
 | |
|             }
 | |
|             break;
 | |
|             // TT((AUTO_MOUSE_TARGET_LAYER))---------------------------------------------------------------------------
 | |
| #    ifndef NO_ACTION_TAPPING
 | |
|         case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
 | |
|             if ((keycode & 0xff) == (AUTO_MOUSE_TARGET_LAYER)) {
 | |
|                 auto_mouse_keyevent(record->event.pressed);
 | |
| #        if TAPPING_TOGGLE != 0
 | |
|                 if (record->tap.count == TAPPING_TOGGLE) {
 | |
|                     if (record->event.pressed) {
 | |
|                         auto_mouse_context.status.mouse_key_tracker--;
 | |
|                     } else {
 | |
|                         auto_mouse_toggle();
 | |
|                         auto_mouse_context.status.mouse_key_tracker++;
 | |
|                     }
 | |
|                 }
 | |
| #        endif
 | |
|             }
 | |
|             break;
 | |
|         // LT((AUTO_MOUSE_TARGET_LAYER), kc)---------------------------------------------------------------------------
 | |
|         case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
 | |
|             if (!record->tap.count) {
 | |
|                 if (((keycode >> 8) & 0x0f) == (AUTO_MOUSE_TARGET_LAYER)) {
 | |
|                     auto_mouse_keyevent(record->event.pressed);
 | |
|                 }
 | |
|                 break;
 | |
|             }
 | |
|         // MT(kc) only skip on hold
 | |
|         case QK_MOD_TAP ... QK_MOD_TAP_MAX:
 | |
|             if (!record->tap.count) break;
 | |
| #    endif
 | |
|         // QK_MODS goes to default
 | |
|         default:
 | |
|             // skip on no event
 | |
|             if (IS_NOEVENT(record->event)) break;
 | |
|             // check if keyrecord is mousekey
 | |
|             if (is_mouse_record(keycode, record)) {
 | |
|                 auto_mouse_keyevent(record->event.pressed);
 | |
|             } else if (!is_auto_mouse_active()) {
 | |
|                 // all non-mousekey presses restart delay timer and reset status
 | |
|                 auto_mouse_reset_trigger(record->event.pressed);
 | |
|             }
 | |
|     }
 | |
|     if (auto_mouse_context.status.mouse_key_tracker < 0) {
 | |
|         auto_mouse_context.status.mouse_key_tracker = 0;
 | |
|         dprintf("key tracker error (<0) \n");
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Local function to handle checking if a keycode is a mouse button
 | |
|  *
 | |
|  * Starts code stack for checking keyrecord if defined as mousekey
 | |
|  *
 | |
|  * @params keycode[in] uint16_t
 | |
|  * @params record[in]  keyrecord_t pointer
 | |
|  * @return bool true: keyrecord is mousekey false: keyrecord is not mousekey
 | |
|  */
 | |
| static bool is_mouse_record(uint16_t keycode, keyrecord_t* record) {
 | |
|     // allow for keyboard to hook in and override if need be
 | |
|     if (is_mouse_record_kb(keycode, record) || IS_MOUSEKEY(keycode)) return true;
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Weakly defined keyboard level callback for adding keyrecords as mouse keys
 | |
|  *
 | |
|  * Meant for redefinition at keyboard level and should return is_mouse_record_user by default at end of function
 | |
|  *
 | |
|  * @params keycode[in] uint16_t
 | |
|  * @params record[in] keyrecord_t pointer
 | |
|  * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
 | |
|  */
 | |
| __attribute__((weak)) bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
 | |
|     return is_mouse_record_user(keycode, record);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Weakly defined keymap/user level callback for adding keyrecords as mouse keys
 | |
|  *
 | |
|  * Meant for redefinition at keymap/user level and should return false by default at end of function
 | |
|  *
 | |
|  * @params keycode[in] uint16_t
 | |
|  * @params record[in] keyrecord_t pointer
 | |
|  * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
 | |
|  */
 | |
| __attribute__((weak)) bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record) {
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| #endif // POINTING_DEVICE_AUTO_MOUSE_ENABLE
 |