[Core] Pointing Device Automatic Mouse Layer (#17962)

Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Stefan Kerkmann <karlk90@pm.me>
This commit is contained in:
Alabastard-64 2022-09-24 00:43:55 -06:00 committed by GitHub
parent 2318ae0433
commit 7c1797f52f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 727 additions and 1 deletions

View file

@ -136,6 +136,7 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
VPATH += $(QUANTUM_DIR)/pointing_device VPATH += $(QUANTUM_DIR)/pointing_device
SRC += $(QUANTUM_DIR)/pointing_device/pointing_device.c SRC += $(QUANTUM_DIR)/pointing_device/pointing_device.c
SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_drivers.c SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_drivers.c
SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_auto_mouse.c
ifneq ($(strip $(POINTING_DEVICE_DRIVER)), custom) ifneq ($(strip $(POINTING_DEVICE_DRIVER)), custom)
SRC += drivers/sensors/$(strip $(POINTING_DEVICE_DRIVER)).c SRC += drivers/sensors/$(strip $(POINTING_DEVICE_DRIVER)).c
OPT_DEFS += -DPOINTING_DEVICE_DRIVER_$(strip $(shell echo $(POINTING_DEVICE_DRIVER) | tr '[:lower:]' '[:upper:]')) OPT_DEFS += -DPOINTING_DEVICE_DRIVER_$(strip $(shell echo $(POINTING_DEVICE_DRIVER) | tr '[:lower:]' '[:upper:]'))

View file

@ -345,7 +345,7 @@ The combined functions below are only available when using `SPLIT_POINTING_ENABL
| Function | Description | | Function | Description |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `pointing_device_set_shared_report(mouse_report)` | Sets the shared mouse report to the assigned `mouse_report_t` data structured passed to the function. | | `pointing_device_set_shared_report(mouse_report)` | Sets the shared mouse report to the assigned `mouse_report_t` data structured passed to the function. |
| `pointing_device_set_cpi_on_side(bool, uint16_t)` | Sets the CPI/DPI of one side, if supported. Passing `true` will set the left and `false` the right` | | `pointing_device_set_cpi_on_side(bool, uint16_t)` | Sets the CPI/DPI of one side, if supported. Passing `true` will set the left and `false` the right |
| `pointing_device_combine_reports(left_report, right_report)` | Returns a combined mouse_report of left_report and right_report (as a `mouse_report_t` data structure) | | `pointing_device_combine_reports(left_report, right_report)` | Returns a combined mouse_report of left_report and right_report (as a `mouse_report_t` data structure) |
| `pointing_device_task_combined_kb(left_report, right_report)` | Callback, so keyboard code can intercept and modify the data. Returns a combined mouse report. | | `pointing_device_task_combined_kb(left_report, right_report)` | Callback, so keyboard code can intercept and modify the data. Returns a combined mouse report. |
| `pointing_device_task_combined_user(left_report, right_report)` | Callback, so user code can intercept and modify. Returns a combined mouse report using `pointing_device_combine_reports` | | `pointing_device_task_combined_user(left_report, right_report)` | Callback, so user code can intercept and modify. Returns a combined mouse report using `pointing_device_combine_reports` |
@ -497,3 +497,233 @@ If you are having issues with pointing device drivers debug messages can be enab
``` ```
?> The messages will be printed out to the `CONSOLE` output. For additional information, refer to [Debugging/Troubleshooting QMK](faq_debug.md). ?> The messages will be printed out to the `CONSOLE` output. For additional information, refer to [Debugging/Troubleshooting QMK](faq_debug.md).
---
# Automatic Mouse Layer :id=pointing-device-auto-mouse
When using a pointing device combined with a keyboard the mouse buttons are often kept on a separate layer from the default keyboard layer, which requires pressing or holding a key to change layers before using the mouse. To make this easier and more efficient an additional pointing device feature may be enabled that will automatically activate a target layer as soon as the pointing device is active _(in motion, mouse button pressed etc.)_ and deactivate the target layer after a set time.
Additionally if any key that is defined as a mouse key is pressed then the layer will be held as long as the key is pressed and the timer will be reset on key release. When a non-mouse key is pressed then the layer is deactivated early _(with some exceptions see below)_. Mod, mod tap, and one shot mod keys are ignored _(i.e. don't hold or activate layer but do not deactivate the layer either)_ when sending a modifier keycode _(e.g. hold for mod tap)_ allowing for mod keys to be used with the mouse without activating the target layer when typing.
All of the standard layer keys (tap toggling, toggle, toggle on, one_shot, layer tap, layer mod) that activate the current target layer are uniquely handled to ensure they behave as expected _(see layer key table below)_. The target layer that can be changed at any point during by calling the `set_auto_mouse_layer(<new_target_layer>);` function.
### Behaviour of Layer keys that activate the target layer
| Layer key as in `keymap.c` | Auto Mouse specific behaviour |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `MO(<target_layer>)` | Treated as a mouse key holding the layer while pressed |
| `LT(<target_layer>)` | When tapped will be treated as non mouse key and mouse key when held |
| `LM(<target_layer>)` | Treated as a mouse key |
| `TG(<target_layer>)` | Will set flag preventing target layer deactivation or removal until pressed again |
| `TO(<target_layer>)` | Same as `TG(<target_layer>)` |
| `TT(<target_layer>)` | Treated as a mouse key when `tap.count < TAPPING_TOGGLE` and as `TG` when `tap.count == TAPPING_TOGGLE` |
| `DF(<target_layer>)` | Skips auto mouse key processing similar to mod keys |
| `OSL(<target_layer>)` | Skips, but if current one shot layer is the target layer then it will prevent target layer deactivation or removal |
## How to enable:
```c
// in config.h:
#define POINTING_DEVICE_AUTO_MOUSE_ENABLE
// only required if not setting mouse layer elsewhere
#define AUTO_MOUSE_DEFAULT_LAYER <index of your mouse layer>
// in keymap.c:
void pointing_device_init_user(void) {
set_auto_mouse_layer(<mouse_layer>); // only required if AUTO_MOUSE_DEFAULT_LAYER is not set to index of <mouse_layer>
set_auto_mouse_enable(true); // always required before the auto mouse feature will work
}
```
Because the auto mouse feature can be disabled/enabled during runtime and starts as disabled by default it must be enabled by calling `set_auto_mouse_enable(true);` somewhere in firmware before the feature will work.
_Note: for setting the target layer during initialization either setting `AUTO_MOUSE_DEFAULT_LAYER` in `config.h` or calling `set_auto_mouse_layer(<mouse_layer>)` can be used._
## How to Customize:
There are a few ways to control the auto mouse feature with both `config.h` options and functions for controlling it during runtime.
### `config.h` Options:
| Define | Description | Range | Units | Default |
| ----------------------------------- | --------------------------------------------------------------------- | :------------------: | :---------: | -------------------------: |
| `POINTING_DEVICE_AUTO_MOUSE_ENABLE` | (Required) Enables auto mouse layer feature | | _None_ | _Not defined_ |
| `AUTO_MOUSE_DEFAULT_LAYER` | (Optional) Index of layer to use as default target layer | 0 - `LAYER_MAX` | _`uint8_t`_ | `1` |
| `AUTO_MOUSE_TIME` | (Optional) Time layer remains active after activation | _ideally_ (250-1000) | _ms_ | `650 ms` |
| `AUTO_MOUSE_DELAY` | (Optional) Lockout time after non-mouse key is pressed | _ideally_ (100-1000) | _ms_ | `TAPPING_TERM` or `200 ms` |
| `AUTO_MOUSE_DEBOUNCE` | (Optional) Time delay from last activation to next update | _ideally_ (10 - 100) | _ms_ | `25 ms` |
### Adding mouse keys
While all default mouse keys and layer keys(for current mouse layer) are treated as mouse keys, additional Keyrecords can be added to mouse keys by adding them to the is_mouse_record_* stack.
#### Callbacks for setting up additional key codes as mouse keys:
| Callback | Description |
| -------------------------------------------------------------------- | -------------------------------------------------- |
| `bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record)` | keyboard level callback for adding mouse keys |
| `bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record)` | user/keymap level callback for adding mouse keys |
##### To use the callback function to add mouse keys:
The following code will cause the enter key and all of the arrow keys to be treated as mouse keys (hold target layer while they are pressed and reset active layer timer).
```c
// in <keyboard>.c:
bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
switch(keycode) {
case KC_ENT:
return true;
case KC_RIGHT ... KC_UP:
return true;
default:
return false;
}
return is_mouse_record_user(keycode, record);
}
```
## Advanced control
There are several functions that allow for more advanced interaction with the auto mouse feature allowing for greater control.
### Functions to control auto mouse enable and target layer:
| Function | Description | Aliases | Return type |
| :--------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------- | --------------: |
| `set_auto_mouse_enable(bool enable)` | Enable or disable auto mouse (true:enable, false:disable) | | `void`(None) |
| `get_auto_mouse_enable(void)` | Return auto mouse enable state (true:enabled, false:disabled) | `AUTO_MOUSE_ENABLED` | `bool` |
| `set_auto_mouse_layer(uint8_t LAYER)` | Change/set the target layer for auto mouse | | `void`(None) |
| `get_auto_mouse_layer(void)` | Return auto mouse target layer index | `AUTO_MOUSE_TARGET_LAYER` | `uint8_t` |
| `remove_auto_mouse_layer(layer_state_t state, bool force)` | Return `state` with target layer removed if appropriate (ignore criteria if `force`) | | `layer_state_t` |
| `auto_mouse_layer_off(void)` | Disable target layer if appropriate will call (makes call to `layer_state_set`) | | `void`(None) |
| `auto_mouse_toggle(void)` | Toggle on/off target toggle state (disables layer deactivation when true) | | `void`(None) |
| `get_auto_mouse_toggle(void)` | Return value of toggling state variable | | `bool` |
_NOTES:_
- _Due to the nature of how some functions work, the `auto_mouse_trigger_reset`, and `auto_mouse_layer_off` functions should never be called in the `layer_state_set_*` stack as this can cause indefinite loops._
- _It is recommended that `remove_auto_mouse_layer` is used in the `layer_state_set_*` stack of functions and `auto_mouse_layer_off` is used everywhere else_
- _`remove_auto_mouse_layer(state, false)` or `auto_mouse_layer_off()` should be called before any instance of `set_auto_mouse_enabled(false)` or `set_auto_mouse_layer(layer)` to ensure that the target layer will be removed appropriately before disabling auto mouse or changing target to avoid a stuck layer_
### Functions for handling custom key events:
| Function | Description | Return type |
| :--------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------: |
| `auto_mouse_keyevent(bool pressed)` | Auto mouse mouse key event (true: key down, false: key up) | `void`(None) |
| `auto_mouse_trigger_reset(bool pressed)` | Reset auto mouse status on key down and start delay timer (non-mouse key event) | `void`(None) |
| `auto_mouse_toggle(void)` | Toggle on/off target toggle state (disables layer deactivation when true) | `void`(None) |
| `get_auto_mouse_toggle(void)` | Return value of toggling state variable | `bool` |
_NOTE: Generally it would be preferable to use the `is_mouse_record_*` functions to add any additional keys that should act as mouse keys rather than adding `auto_mouse_keyevent(record.event->pressed)` to `process_records_*`_
### Advanced control examples
#### Disable auto mouse on certain layers:
The auto mouse feature can be disabled any time and this can be helpful if you want to disable the auto mouse feature under certain circumstances such as when particular layers are active. One issue however is the handling of the target layer, it needs to be removed appropriately **before** disabling auto mouse _(see notes under control functions above)_. The following function would disable the auto_mouse feature whenever the layers `_LAYER5` through `_LAYER7` are active as the top most layer _(ignoring target layer)_.
```c
// in keymap.c:
layer_state_t layer_state_set_user(layer_state_t state) {
// checks highest layer other than target layer
switch(get_highest_layer(remove_auto_mouse_layer(state, true))) {
case _LAYER5 ... _LAYER7:
// remove_auto_mouse_target must be called to adjust state *before* setting enable
state = remove_auto_mouse_layer(state, false);
set_auto_mouse_enable(false);
break;
default:
set_auto_mouse_enable(true);
break;
}
// recommend that any code that makes adjustment based on auto mouse layer state would go here
return state;
}
```
#### Set different target layer when a particular layer is active:
The below code will change the auto mouse layer target to `_MOUSE_LAYER_2` when `_DEFAULT_LAYER_2` is highest default layer state.
*NOTE: that `auto_mouse_layer_off` is used here instead of `remove_auto_mouse_layer` as `default_layer_state_set_*` stack is separate from the `layer_state_set_*` stack* if something similar was to be done in `layer_state_set_user `state = remove_auto_mouse_layer(state, false)` should be used instead
*ADDITIONAL NOTE: `AUTO_MOUSE_TARGET_LAYER` is checked if already set to avoid deactivating the target layer unless needed*
```c
// in keymap.c
layer_state_t default_layer_state_set_user(layer_state_t state) {
// switch on change in default layer need to check if target layer already set to avoid turning off layer needlessly
switch(get_highest_layer(state)) {
case _DEFAULT_LAYER_2:
if ((AUTO_MOUSE_TARGET_LAYER) == _MOUSE_LAYER_2) break;
auto_mouse_layer_off();
set_auto_mouse_layer(_MOUSE_LAYER_2);
break;
default:
if((AUTO_MOUSE_TARGET_LAYER) == _MOUSE_LAYER_1) break;
auto_mouse_layer_off();
set_auto_mouse_layer(_MOUSE_LAYER_1);
}
return state;
}
```
### Use custom keys to control auto mouse:
Custom key records could also be created that control the auto mouse feature.
The code example below would create a custom key that would toggle the auto mouse feature on and off when pressed while also setting a bool that could be used to disable other code that may turn it on such as the layer code above.
```c
// in config.h:
enum user_custom_keycodes {
AM_Toggle = SAFE_RANGE
};
// in keymap.c:
// set up global bool to adjust other user code
bool auto_mouse_tg_off = !AUTO_MOUSE_ENABLED;
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
// toggle auto mouse enable key
case AM_Toggle:
if(record->event.pressed) { // key down
auto_mouse_layer_off(); // disable target layer if needed
set_auto_mouse_enabled((AUTO_MOUSE_ENABLED) ^ 1);
auto_mouse_tg_off = !get_auto_mouse_enabled();
} // do nothing on key up
return false; // prevent further processing of keycode
}
}
```
## Customize Target Layer Activation
Layer activation can be customized by overwriting the `auto_mouse_activation` function. This function is checked every time `pointing_device_task` is called when inactive and every `AUTO_MOUSE_DEBOUNCE` ms when active, and will evaluate pointing device level conditions that trigger target layer activation. When it returns true, the target layer will be activated barring the usual exceptions _(e.g. delay time has not expired)_.
By default it will return true if any of the `mouse_report` axes `x`,`y`,`h`,`v` are non zero, or if there is any mouse buttons active in `mouse_report`.
_Note: The Cirque pinnacle track pad already implements a custom activation function that will activate on touchdown as well as movement all of the default conditions, currently this only works for the master side of split keyboards._
| Function | Description | Return type |
| :--------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------: |
| `auto_mouse_activation(report_mouse_t mouse_report)` | Overwritable function that controls target layer activation (when true) | `bool` |
## Auto Mouse for Custom Pointing Device Task
When using a custom pointing device (overwriting `pointing_device_task`) the following code should be somewhere in the `pointing_device_task_*` stack:
```c
void pointing_device_task(void) {
//...Custom pointing device task code
// handle automatic mouse layer (needs report_mouse_t as input)
pointing_device_task_auto_mouse(local_mouse_report);
//...More custom pointing device task code
pointing_device_send();
}
```
In general the following two functions must be implemented in appropriate locations for auto mouse to function:
| Function | Description | Suggested location |
| -------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------: |
| `pointing_device_task_auto_mouse(report_mouse_t mouse_report)` | handles target layer activation and is_active status updates | `pointing_device_task` stack |
| `process_auto_mouse(uint16_t keycode, keyrecord_t* record)` | Keycode processing for auto mouse | `process_record` stack |

View file

@ -267,6 +267,10 @@ __attribute__((weak)) void pointing_device_task(void) {
#else #else
local_mouse_report = pointing_device_adjust_by_defines(local_mouse_report); local_mouse_report = pointing_device_adjust_by_defines(local_mouse_report);
local_mouse_report = pointing_device_task_kb(local_mouse_report); local_mouse_report = pointing_device_task_kb(local_mouse_report);
#endif
// automatic mouse layer function
#ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
pointing_device_task_auto_mouse(local_mouse_report);
#endif #endif
// combine with mouse report to ensure that the combined is sent correctly // combine with mouse report to ensure that the combined is sent correctly
#ifdef MOUSEKEY_ENABLE #ifdef MOUSEKEY_ENABLE

View file

@ -21,6 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "host.h" #include "host.h"
#include "report.h" #include "report.h"
#ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
# include "pointing_device_auto_mouse.h"
#endif
#if defined(POINTING_DEVICE_DRIVER_adns5050) #if defined(POINTING_DEVICE_DRIVER_adns5050)
# include "drivers/sensors/adns5050.h" # include "drivers/sensors/adns5050.h"
#elif defined(POINTING_DEVICE_DRIVER_adns9800) #elif defined(POINTING_DEVICE_DRIVER_adns9800)

View file

@ -0,0 +1,384 @@
/* 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

View file

@ -0,0 +1,87 @@
/* 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/>.
*/
#pragma once
#include <string.h>
#include "quantum.h"
#include "pointing_device.h"
#include "print.h"
/* check settings and set defaults */
#ifndef POINTING_DEVICE_AUTO_MOUSE_ENABLE
# error "POINTING_DEVICE_AUTO_MOUSE_ENABLE not defined! check config settings"
#endif
#ifndef AUTO_MOUSE_DEFAULT_LAYER
# define AUTO_MOUSE_DEFAULT_LAYER 1
#endif
#ifndef AUTO_MOUSE_TIME
# define AUTO_MOUSE_TIME 650
#endif
#ifndef AUTO_MOUSE_DELAY
#define AUTO_MOUSE_DELAY GET_TAPPING_TERM(KC_MS_BTN1, &(keyrecord_t){})
#endif
#ifndef AUTO_MOUSE_DEBOUNCE
# define AUTO_MOUSE_DEBOUNCE 25
#endif
/* data structure */
typedef struct {
struct {
bool is_enabled;
uint8_t layer;
} config;
struct {
uint16_t active;
uint16_t delay;
} timer;
struct {
bool is_activated;
bool is_toggled;
int8_t mouse_key_tracker;
} status;
} auto_mouse_context_t;
/* ----------Set up and control------------------------------------------------------------------------------ */
void set_auto_mouse_enable(bool enable); // enable/disable auto mouse feature
bool get_auto_mouse_enable(void); // get auto_mouse_enable
void set_auto_mouse_layer(uint8_t layer); // set target layer by index
uint8_t get_auto_mouse_layer(void); // get target layer index
void auto_mouse_layer_off(void); // disable target layer if appropriate (DO NOT USE in layer_state_set stack!!)
layer_state_t remove_auto_mouse_layer(layer_state_t state, bool force); // remove auto mouse target layer from state if appropriate (can be forced)
/* ----------For custom pointing device activation----------------------------------------------------------- */
bool auto_mouse_activation(report_mouse_t mouse_report); // handles pointing device trigger conditions for target layer activation (overwritable)
/* ----------Handling keyevents------------------------------------------------------------------------------ */
void auto_mouse_keyevent(bool pressed); // trigger auto mouse keyevent: mouse_keytracker increment/decrement on press/release
void auto_mouse_reset_trigger(bool pressed); // trigger non mouse keyevent: reset and start delay timer (DO NOT USE in layer_state_set stack!!)
void auto_mouse_toggle(void); // toggle mouse layer flag disables mouse layer deactivation while on (meant for tap toggle or toggle of target)
bool get_auto_mouse_toggle(void); // get toggle mouse layer flag value
/* ----------Callbacks for adding keycodes to mouse record checking------------------------------------------ */
bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record);
bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record);
/* ----------Core functions (only used in custom pointing devices or key processing)------------------------- */
void pointing_device_task_auto_mouse(report_mouse_t mouse_report); // add to pointing_device_task_*
bool process_auto_mouse(uint16_t keycode, keyrecord_t* record); // add to process_record_*
/* ----------Macros/Aliases---------------------------------------------------------------------------------- */
#define AUTO_MOUSE_TARGET_LAYER get_auto_mouse_layer()
#define AUTO_MOUSE_ENABLED get_auto_mouse_enable()

View file

@ -113,6 +113,15 @@ void cirque_pinnacle_configure_cursor_glide(float trigger_px) {
# endif # endif
# if CIRQUE_PINNACLE_POSITION_MODE # if CIRQUE_PINNACLE_POSITION_MODE
# ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
static bool is_touch_down;
bool auto_mouse_activation(report_mouse_t mouse_report) {
return is_touch_down || mouse_report.x != 0 || mouse_report.y != 0 || mouse_report.h != 0 || mouse_report.v != 0 || mouse_report.buttons;
}
# endif
report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) { report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
pinnacle_data_t touchData = cirque_pinnacle_read_data(); pinnacle_data_t touchData = cirque_pinnacle_read_data();
mouse_xy_report_t report_x = 0, report_y = 0; mouse_xy_report_t report_x = 0, report_y = 0;
@ -143,6 +152,10 @@ report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
pd_dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue); pd_dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue);
} }
# ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
is_touch_down = touchData.touchDown;
# endif
// Scale coordinates to arbitrary X, Y resolution // Scale coordinates to arbitrary X, Y resolution
cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale()); cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale());

View file

@ -271,6 +271,9 @@ bool process_record_quantum(keyrecord_t *record) {
#endif #endif
#if defined(VIA_ENABLE) #if defined(VIA_ENABLE)
process_record_via(keycode, record) && process_record_via(keycode, record) &&
#endif
#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE)
process_auto_mouse(keycode, record) &&
#endif #endif
process_record_kb(keycode, record) && process_record_kb(keycode, record) &&
#if defined(SECURE_ENABLE) #if defined(SECURE_ENABLE)