qmk/quantum/pointing_device/pointing_device_auto_mouse.c
Sergey Vlasov a7b2f4233c
Fix keycode parameter extraction to match the new DD keycodes (#18977)
* Add macros to extract parameters from keycode values

Implement both encoding and decoding for keycodes like TO(layer) or
LM(layer, mod) in one place, so that the decoding won't get out of sync
with the encoding.

While at it, fix some macros for creating keycode values that did not
apply the appropriate masks to parameters (and therefore could allow the
result to be out of range if a wrong parameter was passed).

* keymap_common: Use extraction macros for keycodes

* pointing_device_auto_mouse: Use extraction macros for keycodes

Fixes #18970.

* process_autocorrect: Use extraction macros for keycodes

* process_caps_word: Use extraction macros for keycodes

(Also fix a minor bug - SH_TG was not handled properly)

* process_leader: Use extraction macros for keycodes

(Technically the code is not 100% correct, because it always assumes
that the LT() or MT() action was a tap, but it's a separate issue that
already existed before the keycode changes.)

* process_unicode: Use extraction macros for keycodes

* process_unicodemap: Use extraction macros for keycodes
2022-11-06 21:39:05 +00:00

389 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:
if (QK_TO_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
if (!(record->event.pressed)) auto_mouse_toggle();
}
break;
// TG((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
if (QK_TOGGLE_LAYER_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
if (!(record->event.pressed)) auto_mouse_toggle();
}
break;
// MO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
if (QK_MOMENTARY_GET_LAYER(keycode) == (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 (QK_LAYER_MOD_GET_LAYER(keycode) == (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 (QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode) == (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 (QK_LAYER_TAP_GET_LAYER(keycode) == (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