Alabastard 1 week ago
committed by GitHub
parent
commit
cf0fb35b87
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
21 changed files with 1939 additions and 17 deletions
  1. +2
    -0
      builddefs/common_features.mk
  2. +1
    -2
      data/constants/keycodes/keycodes_0.0.1.hjson
  3. +13
    -0
      data/constants/keycodes/keycodes_0.0.4.hjson
  4. +28
    -0
      data/constants/keycodes/keycodes_0.0.4_pointing_mode_util.hjson
  5. +715
    -9
      docs/feature_pointing_device.md
  6. +10
    -1
      quantum/keyboard.h
  7. +17
    -0
      quantum/keycodes.h
  8. +7
    -0
      quantum/keymap_common.c
  9. +55
    -0
      quantum/keymap_introspection.c
  10. +17
    -0
      quantum/keymap_introspection.h
  11. +46
    -3
      quantum/pointing_device/pointing_device.c
  12. +4
    -0
      quantum/pointing_device/pointing_device.h
  13. +7
    -2
      quantum/pointing_device/pointing_device_auto_mouse.c
  14. +678
    -0
      quantum/pointing_device/pointing_device_modes.c
  15. +166
    -0
      quantum/pointing_device/pointing_device_modes.h
  16. +108
    -0
      quantum/process_keycode/process_pointing_mode_records.c
  17. +28
    -0
      quantum/process_keycode/process_pointing_mode_records.h
  18. +4
    -0
      quantum/quantum.c
  19. +3
    -0
      quantum/quantum.h
  20. +27
    -0
      quantum/quantum_keycodes.h
  21. +3
    -0
      tests/test_common/keycode_table.cpp

+ 2
- 0
builddefs/common_features.mk View File

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


+ 1
- 2
data/constants/keycodes/keycodes_0.0.1.hjson View File

@ -36,8 +36,7 @@
"0x52C0/0x001F": {
"define": "QK_LAYER_TAP_TOGGLE"
},
// 0x52E0/0x001F - UNUSED
// 0x5300/0x02FF - UNUSED
// 0x5303/0x02FC - UNUSED
"0x5600/0x00FF": {
"define": "QK_SWAP_HANDS"
},


+ 13
- 0
data/constants/keycodes/keycodes_0.0.4.hjson View File

@ -0,0 +1,13 @@
{
"ranges": {
"0x52E0/0x000F": {
"define": "QK_POINTING_MODE_MO"
},
"0x52F0/0x000F": {
"define": "QK_POINTING_MODE_TG"
},
"0x5300/0x0002": {
"define": "QK_POINTING_MODE_UTIL"
}
}
}

+ 28
- 0
data/constants/keycodes/keycodes_0.0.4_pointing_mode_util.hjson View File

@ -0,0 +1,28 @@
{
"keycodes": {
"0x5300": {
"group": "pointing_mode_util",
"key": "QK_PM_CYCLE_DEVICES",
"label": "Cycle pointing mode device",
"aliases": [
"PMR_CYD"
]
},
"0x5301": {
"group": "pointing_mode_util",
"key": "QK_PM_DEVICE_RIGHT",
"label": "Set pointing mode device to PM_RIGHT_SIDE",
"aliases": [
"PMR_RGHT"
]
},
"0x5302": {
"group": "pointing_mode_util",
"key": "QK_PM_DEVICE_LEFT",
"label": "Set pointing mode device to PM_LEFT_SIDE",
"aliases": [
"PMR_LEFT"
]
}
}
}

+ 715
- 9
docs/feature_pointing_device.md View File

@ -509,7 +509,9 @@ Recall that the mouse report is set to zero (except the buttons) whenever it is
### Drag Scroll or Mouse Scroll
A very common implementation is to use the mouse movement to scroll instead of moving the cursor on the system. This uses the `pointing_device_task_user` callback to intercept and modify the mouse report before it's sent to the host system.
A very common implementation is to use the mouse movement to scroll instead of moving the cursor on the system. This uses the `pointing_device_task_user` callback to intercept and modify the mouse report before it's sent to the host system.
!> Note that the pointing device modes feature implements this along with other popular pointing device features automatically
```c
enum custom_keycodes {
@ -741,7 +743,7 @@ While all default mouse keys and layer keys(for current mouse layer) are treated
| `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:
##### 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
@ -800,7 +802,7 @@ _NOTE: Generally it would be preferable to use the `is_mouse_record_*` functions
### Advanced control examples
#### Disable auto mouse on certain layers:
#### 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)_.
@ -825,11 +827,9 @@ layer_state_t layer_state_set_user(layer_state_t 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*.
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
@ -851,7 +851,8 @@ layer_state_t default_layer_state_set_user(layer_state_t state) {
}
```
### Use custom keys to control auto mouse:
### 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.
@ -914,3 +915,708 @@ In general the following two functions must be implemented in appropriate locati
| -------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------: |
| `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 |
---
# Pointing Device Modes :id=pointing-device-modes
Inspired by the work of previous trackball users that added features such as drag scroll, caret scroll, and sniping modes to their keyboards, this framework allows for easy setup and inclusion of different pointing device modes that when active will change the behaviour of a pointing device by taking it's x/y outputs and changing them into something else such as h/v for drag scrolling, key presses such as arrow keys for caret scrolling, and even just adjusting the x/y values before output. When a pointing device mode is active it accumulates x and y outputs from a pointing device and stores it into internal x & y values, halting normal mouse x and y output (_modes can re-enable and/or modify mouse output_), these internally stored x and y values are then divided by a defined divisor resulting the modified output (_key taps, h/v, modified mouse x/y etc._). The dividing factors can be used to control sensitivity in each mode as adjusting cpi may not always be desired/possible.
The framework has keycode support for up to **15** *(16 total modes including `PM_NONE`, 10 not including built in modes)* custom modes natively through the `PM_MO(<pointing mode>)` and `PM_TG(<pointing mode>)` keycode macros which act as momentary and toggle keys for `<pointing mode>` respectively, similarly to the layer keys of the same type (up to 256). 5 of the 15 modes are already used by built in modes, however these can easily be overwritten if needed. There is an additional Null mode `PM_NONE` (_Default pointing device output_) that cannot be overwritten. More modes beyond this (_mode id's > 16_) can be added but they will require the addition of custom keycodes to activate the modes as the `PM_MO(<pm>)` and `PM_TG(<pm>)` macros only support up to mode id 15. New custom modes can be added through either adding keycode maps to the `pointing_device_mode_maps` array or through the through user/kb callbacks functions (_see advanced use below_).
## Pointing Modes Basic Use
### How To Enable
On a keyboard that has a pointing device (_i.e._ `POINTING_DEVICE_ENABLE` _is defined_) pointing modes can be enabled by defining `POITNING_DEVICE_MODES_ENABLE` in `config.h`. If only built in modes are being used then simply adding keycodes to the keymap to enable is all that is needed (_see advanced use for activating modes outside of key presses_).
```c
// in config.h:
#define POINTING_DEVICE_MODES_ENABLE
```
### Activating Pointing Device Modes
The first 15 pointing device modes can easily be activated by keypress through adding the following keycode macros to a keymap:
#### Keycode Macros (_for _`PM_NONE`_ and the first 16 modes only_)
| Keycode Macro | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `PM_MO(<pm>)` | Momentary key for pointing mode `<pm>` (i.e active while key pressed deactivate on release) |
| `PM_TG(<pm>)` | Toggle key for pointing mode `<pm>` (toggle on release, remain until pressed and released again) |
!> For pointing device modes above mode id 15 a custom keycode would need to be added unless the mode is being activated through some other means (such as on specific layers see (advanced use)[#pointing-modes-advanced-use] below)
#### Toggled Pointing Device Modes vs Momentary Pointing Modes
Pointing device modes activated by toggle type functions/macros have their mode id saved until toggled off or a different mode is activated by toggle overwriting the last toggle mode. When a Momentary type function or key is used while another mode is toggled the toggled mode will be reactivated once the momentary mode is released. Toggling a mode on will overwrite both the saved toggled mode id (_if different than current_) as well as the current mode id while using a momentary type key will only overwrite the current mode.
#### Built-in Pointing Device Modes
| Pointing Device Mode | Alias | Mode Id | Description |
| :------------------------ | --------- | :-----: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PM_NONE` | _None_ | 0 | Null pointing mode that will will pass through normal x and y output of pointing device (Cannot be overwritten) |
| `PM_PRECISION` | `PM_PRE` | 1 | Reduce x and y movement output of pointing device by the divisor which can affect other modes when toggled (_see notes_) |
| `PM_DRAG` | `PM_DRG` | 2 | Change x and y movement of pointing device into h and v axis values `x->h` `y->v` |
| `PM_CARET` | `PM_CRT` | 3 | Taps arrow keys based on pointing input `x->(<-, ->)` `y->(^, v)` |
| `PM_HISTORY` | `PM_HST` | 4 | x movement of pointing device to undo and redo macros `x->(C(KC_Z)), C(KC_Y)` `y->ignored` |
| `PM_VOLUME` | `PM_VOL` | 5 | y movement of pointing device to media volume up/down (requires `EXTRAKEY_ENABLED`) `x->ignored` `y->(KC_VOLU, KC_VOLD)` |
| `PM_SAFE_RANGE` | _None_ | 5-6 | Start of free mode id range supported by the `PM_MO()` and `PM_TG()` key macros (_default start of mode maps_) |
| `PM_ADVANCED_RANGE_START` | _None_ | 16 | Start of mode id range that will require the addition of custom keycode to activate them (_new keycodes, on layers etc._) |
***Notes:***
-***These modes can all be overwritten with the exception of `PM_NONE`.***
-***Mode ids 6-15 are free to be used for custom modes without overwriting a mode and will be supported by the built in keycode macros `PM_MO(<pm>)` and `PM_TG(<pm>)` (see adding custom modes below)***
_***`PM_PRECISION` is intended to slow cursor movment when desired on devices without CPI settings or when changing CPI settings is unreliable or undesirable***
-***`PM_PRECISION` has additional behaviour when it is toggled, all modes activated as momentary modes will have their divisors multiplied by `POINTING_PRECISION_DIVISOR` while `PM_PRECISION` is the current toggle mode (see adding divisors for more detail)***
#### Use In A keymap:
```c
// in keymap.c
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_TEST] = LAYOUT(
PM_MO(PM_DRG), PM_TG(PM_PRE), PM_MO(PM_VOL)
)
};
```
### Settings
| Define | Description | Range | Units | Default |
| -------------------------------- | ----------------------------------------------------------------------------------------- | :-----: | :----------: | -----------------------: |
| `POINTING_DEVICE_MODES_ENABLE` | (Required) Enables pointing device pointing device modes feature | `NA` | `None` | _Not defined_ |
| `POINTING_DEVICE_MODES_INVERT_X` | (optional) Inverts stored y axis accumulation (affects all modes) | `NA` | `None` | _Not defined_ |
| `POINTING_DEVICE_MODES_INVERT_Y` | (optional) Inverts stored x axis accumulation (affects all modes) | `NA` | `None` | _Not defined_ |
| `POINTING_MODE_DEFAULT` | (optional) Default pointing device mode | `0-255` | `None` | `PM_NONE` |
| `POINTING_TAP_DELAY` | (optional) Delay between key presses in `pointing_tap_codes` in ms | `0-255` | `ms` | `TAP_CODE_DELAY` |
| `POINTING_MODE_MAP_ENABLE` | (optional) Enable use of pointing mode maps | `NA` | `None` | _Not defined_ |
| `POINTING_MODE_MAP_START` | (optional) Starting mode id of `pointing_device_mode_maps` | `0-255` | `None` | `PM_SAFE_RANGE` |
| `POINTING_DEFAULT_DIVISOR` | (optional) Default divisor for all modes that do not have a defined divisor | `1-255` | `Varies` | `64` |
| `POINTING_HISTORY_DIVISOR` | (optional) Accumulated stored x/y per key tap in `PM_HISTORY` mode | `1-255` | `(x\|y)/tap` | `64` |
| `POINTING_VOLUME_DIVISOR` | (optional) Accumulated stored x/y per key tap in `PM_VOLUME` mode | `1-255` | `(x\|y)/tap` | `64` |
| `POINTING_CARET_DIVISOR` | (optional) Accumulated stored x/y per key tap in `PM_CARET` mode | `1-255` | `(x\|y)/tap` | `32` |
| `POINTING_CARET_DIVISOR_V` | (optional) x input per left/right tap in `PM_CARET`(_overrides_ `POINTING_CARET_DIVISOR`) | `1-255` | `(y)/tap` | `POINTING_CARET_DIVISOR` |
| `POINTING_CARET_DIVISOR_H` | (optional) y input per up/down tap in `PM_CARET`(_overrides_ `POINTING_CARET_DIVISOR`) | `1-255` | `(x)/tap` | `POINTING_CARET_DIVISOR` |
| `POINTING_PRECISION_DIVISOR` | (optional) Pointing device x/y movement per output x/y in `PM_PRECISION` mode | `1-255` | `(x\|y)/dot` | `2` |
| `POINTING_DRAG_DIVISOR` | (optional) Pointing device x/y movement per h/v axis tick in `PM_DRAG` mode | `1-255` | `(x\|y)/dot` | `4` |
!> For processors without hardware supported integer division it is generally recommended that powers of 2 are used for divisors (_i.e. 1, 2, 4, 8, 16, 32, 64, 128_) as it will usually optimize better (both faster and less code space), however **any positive integer of 255 or less** can work.
Speed and sensitivity of any mode will be impacted by the pointing device CPI setting so divisors may need to be adjusted to personal preference and CPI settings typically used (_note dynamic divisors adjustment based on cpi could be used for testing_).
!> Drag scroll speed will additionally be effected by OS mouse scoll settings, there are usually separate settings for scroll "wheel" and "wheel tilt" which is Vertical and Horzontal scroll respectively. The `POINTING_DRAG_DIVISOR` default value of 16 is based on having mouse settings in the OS set to three lines per tick of "mouse wheel" or "wheel tilt" (_Windows Default_).
`POINTING_PRECISION_DIVISOR` default will half cursor speed when active (_divisor of 2_) but a divisor of 4 is fine to use as well but the cursor will be twice as slow as the default, however divisors of 8 or greater will likely only work well for very high cpi settings.
Recommended settings for `POINTING_CARET_DIVISOR_V` and `POINTING_CARET_DIVISOR_H` are `16` and `32` respectively which will give preference to horizontal caret movement (`KC_LEFT`, `KC_RIGHT`) over vertical (`KC_UP`, `KC_DOWN`) giving even more stability to horizontal movement (_reducing errant up and down movement_).
!> `POINTING_TAP_DELAY` defaults to `TAP_CODE_DELAY` so if `TAP_CODE_DELAY` is set high it can cause some noticable latency on some modes such as `PM_CARET`
### Basic Custom Modes
There are a couple of ways to add new pointing device modes, using the pointing device mode maps will be covered here under basic use where the other method of using the `process_pointing_mode_*` callbacks will be covered under advance use.
#### Pointing Device Mode Maps
The easiest way to add pointing device modes that are only using keycode taps (similar to `PM_CARET`, `PM_VOLUME`, and `PM_HISTORY`) are through creating pointing device mode maps. Additionally Pointing Device Mode Maps support all QMK keycodes similar to encoder maps.
```c
// Pointing Device Mode Maps Format
const uint16_t PROGMEM pointing_device_mode_maps[][POINTING_NUM_DIRECTIONS] = {
[0] = POINTING_MODE_LAYOUT(
<keycode up>,
<keycode left>, <keycode right>,
<keycode down>
),
//... all other pointing mode maps ...
[<POINTING_MODE_MAP_COUNT - 1>] = POINTING_MODE_LAYOUT(
<keycode up>,
<keycode left>, <keycode right>,
<keycode down>
)
}
```
#### Example Mode Maps:
```c
// in config.h:
#define POINTING_DEVICE_MODES_ENABLE // (Required)
#define POINTING_MODE_MAP_ENABLE // (Required)
#define EXTRAKEY_ENABLE // (optional)
// pointing mode map start is left at default value in this example
// in keymap.c
// required enum to set mode id's
// Note the use of POINTING_MODE_MAP_START here is essential
enum keymap_pointing_mode_ids {
PM_BROW = POINTING_MODE_MAP_START, // BROWSER TAB Manipulation [mode id 6]
PM_APP, // Open App browsing [mode id 7]
PM_RGB_MB, // Change RGB Mode brightness [mode id 8]
PM_RGB_HS, // Change RGB Hue and Saturation [mode id 9]
PM_RGB_MSP, // Change RGB Mode and Speed [madi id 10]
POSTMAP_PM_SAFE_RANGE // To avoid overlap when adding additional modes [mode id 11]
};
// (optional) enum to make things easier to read (index numbers can be used directly)
// Must be in the same order as the above mode ids
enum keymap_pointing_mode_maps_index {
_PM_BROW, // first mode map [index 0]
_PM_APP, // second mode map [index 1]
_PM_RGB_MB, // third mode map [index 2]
_PM_RGB_HS, // fourth mode map [index 3]
_PM_RGB_MSP // fifth mode map [index 5]
};
const uint16_t PROGMEM pointing_device_mode_maps[][POINTING_NUM_DIRECTIONS] = {
[_PM_BROW] = POINTING_MODE_LAYOUT(
C(S(KC_PGUP)),
C(S(KC_TAB)), C(KC_TAB),
C(S(KC_PGDN))
),
[_PM_APP] = POINTING_MODE_LAYOUT(
KC_NO,
A(S(KC_TAB)), A(KC_TAB),
KC_NO
),
[_PM_RGB_MB] = POINTING_MODE_LAYOUT(
RGB_VAI,
RGB_RMOD, RGB_MOD,
RGB_VAD
),
[_PM_RGB_HS] = POINTING_MODE_LAYOUT(
RGB_HUI,
RGB_SAD, RGB_SAI,n
RGB_HUD
),
[_PM_RGB_MSP] = POINTING_MODE_LAYOUT(
RGB_SPI,
RGB_RMOD, RGB_MOD,
RGB_SPD
)
};
```
!> use `KC_NO` when no keycode is desired, use of `KC_TRNS` or `_______` is unsupported as these maps do not act like layers (only one can be active at a time).
The mode map array starts at index 0 and **must** be in the **same order** as the maps mode_ids (i.e `<mode map array index> + POINTING_MODE_MAP_START = <mode map mode_id>`), so use of `POINTING_MODE_MAP_START` and somethin mode maps and pointing mode processing callbacks are being used together to define multiple modes care must be taken to ensure that there is no overlap between mode ids to avoid unexpected behaviour (_The reason for the definition of `POSTMAP_PM_SAFE_RANGE` at the end of enum in the above example_)
### Adding & Customizing Divisors
All Newly added modes will use `POINTING_DEFAULT_DIVISOR` unless a divisor is defined for the modes in the `get_pointing_mode_divisor` callback functions.
For most keycode tapping modes a divisor of `64` works well, which is the default divisor for `POINTING_DEFAULT_DIVISOR`. The `get_pointing_mode_divisor_*` callbacks have two variables available, `mode_id` which is the current pointing device mode id and `direction` which indicates the primary direction of the stored accumulated h/v values with the largest magnitude (_see table below_). This makes it simple to have a unique divisor for each direction or axis for a particular mode.
| Direction code | Value |Description |
| :------------- | ----- | -------------------------------------------------------------------------------------------------- |
| `PD_DOWN` | 0 | Stored y axis is negative and the largest value (also default if both x and y are the same value) |
| `PD_UP` | 1 | Stored y axis is positive and the largest value |
| `PD_LEFT` | 2 | Stored x axis is negative and the largest value |
| `PD_RIGHT` | 3 | Stored x axis is positive and the largest value |
!> if checking `pointing_mode.direction` or `direction` is on either the x or y axis using `direction < PD_LEFT` could be used to return true for the y axis and false for the x axis respectively (_see example below_)
#### Callbacks to set pointing device mode divisors
The following callbacks can be used to overwrite built in mode divisors or to set divisors for new modes. The `get_pointing_mode_divisor` stacks works by checking the functions until a non zero value is reached in order of `user`->`kb`->`built in`->`default_value`. Returning a divisor of `0` will allow processing to continue on to the next stage, However this means that if any of the get divisor callback functions return a default value other than 0 then that will overwrite all subsequent divisors(such as built in modes). These functions allows for overriding and modifying built in divisors by users/keymaps and keyboards and overriding keyboard level divisors by users/keymaps so it is possible to give built in modes the same level of divisor customization as new custom modes.
| Callback | Description |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `uint8_t get_pointing_mode_divisor_kb(uint8_t mode_id, uint8_t direction);` | Keyboard level callback for setting divisor based on mode id an direction |
| `uint8_t get_pointing_mode_divisor_user(uint8_t mode_id, uint8_t direction);` | Keymap/user level callback for setting divisor |
| `bool pointing_mode_divisor_postprocess_kb(uint8_t mode_id, uint8_t direction);` | keyboard level callback for modifying all divisors after above callbacks (_return `false` to skip subsequent post_processing_) |
| `bool pointing_mode_divisor_postprocess_user(uint8_t mode_id, uint8_t direction);` | Keymap/user level callback for modifying all divisors after above callbacks (_return `false` to skip subsequent post_processing_) |
#### Example code of assigning divisors for new modes
```c
// added to keymap.c
// assuming poinding device enum and maps from example above
uint8_t get_pointing_mode_divisor_user(uint8_t mode_id, uint8_t direction) {
switch(mode_id) {
case PM_HALF_V:
// half speed for vertical axis
return direction < PD_LEFT ? 128 : 64;
case PM_HALF_H:
// half speed for horizontal axis
return direction < PD_LEFT ? 64 : 128;
case PM_ALL_DIFF:
// example of unique divisor for each mode (not actually recommended for this mode (64 would be a good divisor here))
switch(direction) {
case PD_DOWN:
return 32;
case PD_UP:
return 64;
case PD_LEFT:
return 16;
case PD_RIGHT:
return 128;
}
case PM_SLOW:
return 64; // could skip adding this if default if POINTING_DEFAULT_DIVISOR is 64
}
return 0; // returning 0 to let processing of divisors continue
}
```
This code assigns some divisors for some of the modes added in a previous code example showing some of the things that are possible with defining divisors. It could also be possible to make divisors adjust based on global variables such as a dynamic divisor adjustment term (_note that if a divisor of zero is returned it will default to_ `POINTING_DIVISOR_DEFAULT`).
### Using `pointing_mode_divisor_postprocess` stack
The post process stack is intended to allow for modification of divisors after they have already been assigned by `get_pointing_mode_divisor` stack or `pointing_mode_divisor_override`. This allows for additional precision modes or modification of certain modes devisors or different default divisors depending on conditions.
#### example code:
```c
//in keymap.c
static bool super_precision;
static uint8_t dynamic_default_divisor;
#define constrain_div(amt) (amt > UINT8_MAX ? UINT8_MAX : amt)
bool pointing_mode_divisor_postprocess_user(uint8_t* divisor) {
if(super_precision) {
*divisor = constrain_div(*divisor * 4);
}
if(!(*divisor)) {
*divisor = dynamic_default_divisor;
return false; // force divisor processing to stop here
}
return true; // allow divisor processing to continue
}
```
### Creating Custom pointing mode keycodes
There are built in functions to simplify the creation of custom keycodes and it is generally recommended to use these in combination with other functions rather than using mode changing functions as they do handle some edge cases to ensure consistent behaviour.
| Function | Description | Return type |
| :----------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :---------: |
| `pointing_mode_key_momentary(uint8_t mode_id, bool pressed)` | Momentary change of pointing mode while key is held (_for use in custom keycodes pass `record->event.pressed` for `bool`_) | _None_ |
| `pointing_mode_key_toggle(uint8_t mode_id, bool pressed)` | Toggle pointing mode on/off on key release (_for use in custom keycodes pass `record->event.pressed` for `bool`_) | _None_ |
These can be used to activate pointing device modes outside of the range of the built in keycodes as well as adding custom features to a mode that activate on key press such as registering a keycode and holding it until key release (_see code examples below_).
#### Example code for adding an open app navigation mode outside of basic mode range
```c
// in keymap.c
enum my_pointing_modes {
// place Pointing device mode id outside of basic range as it will not use PM_MO or PM_TG macros
PM_APP = PM_SAFE_RANGE // Control alt tabbing through open applications [mode id: 16]
};
enum my_custom_keycodes {
KC_MO_APP = SAFE_RANGE
};
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch(keycode) {
case KC_MO_APP:
// hold alt while mode and key are active
if(record->event.pressed) {
register_code(KC_LALT);
} else {
unregister_code(KC_LALT);
}
pointing_mode_key_momentary(PM_APP, record);
return false; // stop key record processing
}
return true; // allow normal key record processing
}
// This callback and other functions are explained below in advanced use but is needed here for completeness
bool process_pointing_mode_user(pointing_mode_t pointing_mode, report_mouse_t* mouse_report) {
switch(pointing_mode.id) {
// Open App scrolling mode (requires alt key to be held while active)
case PM_APP:
pointing_tap_codes(S(KC_TAB), KC_NO, KC_NO, KC_TAB);
return false; // stop pointing mode processing
}
return true; // allow normal pointing mode processing
}
```
The above code will create an Application switching pointing mode that should work on windows and linux allowing for navigation through currently open applications having the alt key held will keep the application/window UI active as long as the `KC_MO_APP` key is held (there is a slightly more advanced version of this in a below example that delays registering the alt key until enough scroll has accumulated).
## Pointing Modes Advanced use
There are a number of functions allowing access and control of different aspects of the pointing modes feature most of these are intended for more advanced control such as custom keycodes to activate pointing modes or pointing modes that are manipulating something other than key presses (pointing device data, internal keyboard variables, etc.).
### `pointing_mode_t` structure
The current active pointing mode is controlled by tracking an internal `pointing_mode_t` data structure. The `pointing_mode.direction`, and `pointing_mode.divisor` variables are updated immediately after `pointing_mode.x`, and `pointing_mode.y` at the start of the pointing device modes process before the callback functions are called. Therefore if the `h`,`v`, or `mode_id` are modified and it is desired to have the direction and divisor reflect the changes they will need to be updated by calling `pointing_mode_update()`. A few other variables are tracked outside of the pointing mode structure and there are specialized functions to access/modify them. all of these variables are cleared on mode changes/resets (_mode id will be set to tg_mode_id_)
| Variable | Description | Data type | Functions to access/modify outside of mode processing |
| :------------------------ | :---------------------------------------- | :--------: | :--------------------------------------------------------------------- |
| `pointing_mode.id` | Id number of current active mode | `uint8_t` | `get_pointing_mode_id`, `set_pointing_mode_id` |
| `pointing_mode.x` | Stored horizontal axis value | `uint16_t` | _None_ |
| `pointing_mode.y` | Stored vertical axis value | `uint16_t` | _None_ |
### Other tracked variables (Not directly accessible use functions)
These variables are tracked outside of the `pointing_mode_t` structure as they are not cleared on mode reset/change.
| Variable | Description | Data type | Access/Control Functions |
| :---------- | :--------------------------------------------------------- | :-------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `toggle_id` | Mode id of last active toggle mode | `uint8_t` | `toggle_pointing_mode_id`, `get_toggled_pointing_mode_id` |
| `device` | Active device index [see here](#multiple-pointing-devices) | `uint8_t` | `get_pointing_mode_device`, `set_pointing_mode_device` |
| `divisor` | Divisor of current mode id and direction | `uint8_t` | `current_pointing_mode_divisor`, `pointing_mode_divisor_override`, `get_pointing_mode_divisor_*`, `pointing_mode_divisor_postprocess_*` |
| `direction` | Direction based on stored x and y values | `uint8_t` | `current_pointing_mode_direction` |
#### Controlling pointing device modes
| Function | Description | Return type |
| :----------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | :---------: |
| `set_pointing_mode_id(uint8_t mode_id)` | Set active `device`'s `pointing_mode.id` to `mode_id` | _None_ |
| `toggle_pointing_mode_id(uint8_t mode_id)` | Set active `device`'s `toggle_id` to `mode_id`, or `POINTING_MODE_DEFAULT` if `mode_id == toggle_id`, reset if `mode_id != toggle_id` | _None_ |
| `get_pointing_mode_id(void)` | Return active `device`'s `pointing_mode.id` | `uint8_t` |
| `get_toggled_pointing_mode_id(void)` | Return active `denvice`'s `toggle_id` | `uint8_t` |
| `pointing_mode_reset(void)` | Set current device `pointing_mode.id` to `toggle_id` and `x`, `y` to zero | `uint8_t` |
These can be used to manipulate the active `device`'s `pointing_mode.id` and `toggle_id` at any time such as during layer changes, keypresses, tap dance sequences, and anywhere else in code (see examples below).
#### Code example for changing modes on layer changes (will keep current toggle mode id)
```c
// in keymap.c
// assuming enum and Layout for layers are already defined
layer_state_t layer_state_set_user(layer_state_t state) {
// reset mode id to toggle_id
pointing_mode_reset();
switch(get_highest_layer(state)) {
case _NAVI: // Navigation layer
set_pointing_mode_id(PM_CARET);
break;
case _MEDIA: // Media control layer
set_pointing_mode_id(PM_VOL);
break;
}
return state;
}
```
The above approach will maintain the current toggle mode id and set layer pointing modes as temporary modes on top of it so that when changing to a different layer the current toggle mode will be re asserted.
!> Note that the above approach will also cause the pointing mode of the active device to be reset on every layer change.
#### Example that treats toggle modes as a "default" mode that changes depending on layer
```c
// in keymap.c
// assuming enum and Layout for layers are already defined
layer_state_t layer_state_set_user(layer_state_t state) {
switch(get_highest_layer(state)) {
case _NAVI: // Example Navigation layer
toggle_pointing_mode_id(PM_CARET);
break;
case _MEDIA: // Example Media control layer
toggle_pointing_mode_id(PM_VOL);
break;
default:
// disable toggled pointing mode if
switch(get_toggled_pointing_mode_id()) {
case PM_CARET:
case PM_VOL:
toggle_pointing_mode_id(POINTING_MODE_DEFAULT);
}
}
return state;
}
```
This approach will overwrite any toggled modes upon a layer switch but will allow for momentary mode switching while on other layers returning to the current toggled mode for that layer once a momentary mode is released. This approach works best when keys primarily are used for momentary modes and layers trigger changes to toggled modes.
### Advanced custom modes
Creating pointing device modes outside of the keycode mapped modes requires using the mode processing callbacks.
#### Callbacks for adding custom modes
These callbacks work similar to keycode processing callbacks in that returning false will prevent further processing of the pointing device mode. The processing order of pointing device modes is: `user`->`kb`->`maps`->`built in`.
| Callback | Description |
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| `bool process_pointing_mode_kb(pointing_mode_t pointing_mode, report_mouse_t* mouse_report)` | keyboard level callback for adding pointing device modes |
| `bool process_pointing_mode_user(pointing_mode_t pointing_mode, report_mouse_t* mouse_report)` | user/keymap level callback for adding pointing device modes |
There are several functions available to assist with the creation of custom modes. These allow for setting the internal `pointing_mode` values with any changes that have been made, as well as updating divisor and direction after changes.
| Function | Description | Return type |
| :------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------- | :----------: |
| `set_pointing_mode(pointing_mode_t pointing_mode)` | Set stored pointing mode state to `pointing_mode` | _None_ |
| `pointing_mode_update(void)` | Update stored direction and divisor based on current mode id and h/v values | _None_ |
| `pointing_tap_codes(uint16_t kc_left, uint16_t kc_down, uint16_t kc_up, uint16_t kc_right)` | Convert stored h/v axis value to key taps depending on direction, 1 key tap per current divisor | _None_ |
| `apply_divisor_xy(int16_t value)` | Divides value by the current divisor clamped to mouse cursor output | `mouse_xy_t` |
| `apply_divisor_hv(int16_t value)` | Divides value by the current divisor clamped to mouse scroll output | `int8_t` |
| `multiply_divisor_xy(mouse_xy_report_t value)` | Multiplies cursor value by the current divisor clamped to `int16_t` (_to collect the residual_) | `int16_t` |
| `multiply_divisor_hv(int8_t value)` | Multiplies scroll value by the current divisor clamped to `int16_t` (_to collect the residual_) | `int16_t` |
| `pointing_mode_divisor_override(uint8_t divisor)` | Override current pointing mode divisor and still apply post processing for divisors | _None_ |
!> `pointing_tap_codes` only supports basic keycodes with modifiers (custom and quantum keycodes are unsupported) _it uses `tap_code16_delay` internally to send keycode taps_
#### Creating modes using callback functions:
```c
// in <keyboard>.h or <keyboard>.c
// add custom pointing device mode
enum my_kb_pointing_modes {
// start at the end of basic range
PM_BROW = PM_ADVANCED_RANGE_START, // [mode id: 16]
PM_CUR_ACCEL, // [mode id: 17]
PM_APP_2, // [mode id: 18]
// good practice to allow users to expand further
KB_PM_SAFE_RANGE
};
// add custom keycodes
enum my_kb_keycodes {
// start of keyboard custom keycode range
KB_MO_BROW = QK_KB,
KB_TG_ACCEL,
KB_MO_APP_2
};
// in <keyboard>.c
static bool app_alt = false;
// define keybaord level divisors
uint8_t get_pointing_mode_divisor_kb(uint8_t mode_id, uint8_t direction) {
switch(mode_id) {
case PM_BROW:
return 64;
case PM_CUR_ACCEL:
return 8;
case PM_APP_2:
return 64;
}
return 0; // continue processing
}
#define CONSTRAIN_XY(value) (value > REPORT_XY_MAX? REPORT_XY_MAX : value < REPORT_XY_MIN? REPORT_XY_MIN : value)
bool process_pointing_mode_kb(pointing_mode_t pointing_mode, report_mouse_t* mouse_report) {
switch(pointing_mode.id){
/** Manipulate browser tabs (win/linux) (switch to left tab, move tab left, move tab right, switch to right tab)
* Note that this mode could be put in a mode map but is here as an example of going past the bottom support 10 modes
* without overwriting any built in modes
*/
case PM_BROW:
pointing_tap_codes(C(S(KC_TAB)), C(S(KC_PGDN)), C(S(KC_PGUP)), C(KC_TAB));
return false; // stop pointing mode processing
// Manipulating pointing_mode & mouse_report (cursor speed boost mode example)
case PM_CUR_ACCEL:
// reset mouse_report note that mouse_report is a pointer in this function's context
*mouse_report = pointing_device_get_report();
// set up temp variable and context
{
// add linear boost to cursor x speed
mouse_xy_report_t temp_mouse_axis = apply_divisor_xy(pointing_mode.x);
#ifdef POINTING_DEVICE_INVERT_H
mouse_report->x = CONSTRAIN_XY(mouse_report->x - temp_mouse_axis);
#else
mouse_report->x = CONSTRAIN_XY(mouse_report->x + temp_mouse_axis);
#endif
// collect residual
pointing_mode.x -= multiply_divisor_xy(temp_mouse_axis);
// add linear boost to cursor y speed
temp_mouse_axis = apply_divisor_xy(pointing_mode.y);
#ifdef POINTING_DEVICE_INVERT_V
mouse_report->y = CONSTRAIN_XY(mouse_report->y - apply_divisor_xy(pointing_mode.y));
#else
mouse_report->y = CONSTRAIN_XY(mouse_report->y + apply_divisor_xy(pointing_mode.y));
#endif
// collect residual
pointing_mode.y -= multiply_divisor_xy(temp_mouse_axis);
}
// update pointing_mode with residual stored x & y
set_pointing_mode(pointing_mode);
// NOTE: mouse_report does not need to be set or sent here as it will be carried forward
return false; // stop pointing mode processing
// Alternative method for app scrolling that only toggles left ALT modifier when there is movement.
case PM_APP_2:
// activate alt mod if greater/equal to divisor and set flag
if((abs(pointing_mode.x)) >= current_pointing_mode_divisor()) {
add_mods(MOD_BIT(KC_LALT));
app_alt = true;
}
pointing_tap_codes(S(KC_TAB), KC_NO, KC_NO, KC_TAB);
return false;
}
return true;
}
bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
switch(keycode) {
case KB_MO_BROW:
pointing_mode_key_momentary(PM_BROW, record);
return true; // continue key record processing
case KB_TG_ACCEL:
pointing_mode_key_toggle(PM_CUR_ACCEL, record);
return true; // continue key record processing
case KB_MO_APP_2:
if(!(record->event.pressed) && app_alt) {
del_mods(MOD_BIT(KC_LALT))
app_alt = false;
}
pointing_mode_key_momentary(PM_APP_2, record);
return true; // continue key record processing
}
}
```
!> Note that in the above code example there needed to be some additional handling of `POINTING_DEVICE_INVERT_V` and `POINTING_DEVICE_INVERT_H` when modifying cursor movement specifically as these may not work as expected if either of these options is defined.
## Multiple Pointing Devices
Pointing modes supports multiple pointing devices allowing for either control of one pointing device at a time or simultaneous control of multiple pointing devices. This supports left and right devices natively when both `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED` as either single or simultaneous control (depending if `POINTING_MODES_SINGLE_CONTROL` is defined), However while controlling 3 or more pointing devices is possible this will need to be handled by using a custom `pointing_device_task` function.
#### Relevant Settings
| Define | Description all (optional) | Data type | Range | Default |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------- | :-------: | :-----: | ------------: |
| `POINTING_MODES_NUM_DEVICES` | (optional) Number of devices available for control (intended for when using more than 2 pointing devices) | `uint8_t` | `1-255` | `1` or `2` |
| `POINTING_MODES_SINGLE_CONTROL` | (optional) Force control of only one device at a time (but the active device can be switched) | _None_ | _None_ | _Not defined_ |
| `POINTING_MODES_DEFAULT_DEVICE_ID` | (optional) Default device id controlled (`PM_RIGHT_DEVICE`, `PM_LEFT_DEVICE` depending on `MASTER_RIGHT`) | `uint8_t` | `1-255` | `0` |
!> `POINTING_MODES_NUM_DEVICES` defaults to 2 if both `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED` are defined
#### Default device id defines
| Device code | Value | Description |
| :---------------- | ----- | ------------------------------------------------------- |
| `PM_RIGHT_DEVICE` | `0` | Device index of right side pointing device |
| `PM_LEFT_DEVICE` | `1` | Device index of left side pointing device |
!> Although up to `255` device id's are technically supported (ignoring hardware limitations) only two are given labels by default as that is the most common configuration
#### Relevant keycodes
| Keycode | Alias | Description |
| :--------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `QK_PM_CYCLE_DEVICES` | `PMR_CYD` | Cycles the active device through available devices on each key release (e.g. toggling between `PM_RIGHT_DEVICE` and `PM_LEFT_DEVICE` with 2 devices) |
| `QK_PM_DEVICE_RIGHT` | `PMR_RGHT` | Sets device id to `PM_RIGHT_DEVICE` on key release |
| `QK_PM_DEVICE_LEFT` | `PMR_LEFT` | Sets device id to `PM_LEFT_DEVICE` on key release |
### Simultaneous device control
When `POINTING_MODES_SINGLE_CONTROL` is not defined and `POINTING_MODES_NUM_DEVICES > 1` simultaneous control of multiple devices is enabled, which is the default behaviour when `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED` are both defined. This will cause separate tracking of `pointing_mode`s (`.id`, `.x` and `.y`), and `toggle_id`s for each device separately. All functions under this scheme require setting the desired active device before calling any pointing modes function and the functions will only target that active mode (this also affects builtin keycode macros). Once set the active device will remain so until changed or the keyboard is powered down so keypresses or functions elswhere in code will impact the new device once changed. Changing the active device is made easier through use of the above keycodes (`PMR_CYD`, `PMR_RGHT`, `PMR_LEFT`) so the mode can be set with a keypress and then subsequent `PM_MO` or `PM_TG` keypress will affect the newly active device.
!> Note all keycodes/functions for changing `pointing_mode.id` or `toggle_id` will only affect the current active device and the data for all other devices will be unchanged until it is the active device again.
The default behaviour when using `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED` are both defined will assign `left_mouse_report` to the `PM_LEFT_DEVICE` index and `right_mouse_report` to the `PM_RIGHT_DEVICE` and will process both devices pointing modes every time `pointing_device_task` is run reseting the device id after updating. Simply setting the active device and
The code below is an example of how a hypothetical three device scenario using PMW33XX devices as the driver supports multiple devices per mcu.
```c
// in keyboard config.h
#define POINTING_DEVICE_MODES_ENABLE
#define POINTING_MODES_NUM_DEVICES 3
#define PMW33XX_CS_PINS { <pd_pin_1>, <pd_pin_2>, <pd_pin_3> }
// in <keyboard>.h
enum my_pointing_device_ids{
PD_LEFT == 0, // index 0
PD_CENTRE, // index 1
PD_RIGHT // index 2
};
// in <keyboard>.c
#ifdef POINTING_DEVICE_ENABLE
// constrain to mouse movement
# define constrain_hid_xy(amt) ((amt) < XY_REPORT_MIN ? XY_REPORT_MIN : ((amt) > XY_REPORT_MAX ? XY_REPORT_MAX : (amt)))
void pointing_device_init_kb(void) {
// initialize non-default devices i.e. device id > 0
pmw33xx_init(PD_CENTRE);
pmw33xx_init(PD_RIGHT);
pmw33xx_set_cpi_all_sensors(800); // set all sensors to 800 cpi
// set default pointing modes for devices
set_pointing_mode_device(PD_LEFT);
set_pointing_mode_id(PM_DRAG);
set_pointing_mode_device(PD_CENTRE);
set_pointing_mode_id(PM_CARET);
// PD_RIGHT is left as POINTING_MODE_DEFAULT which is PM_NONE if undefined
// reset default device ID
set_pointing_mode_device(POINTING_MODES_DEFAULT_DEVICE_ID);
pointing_device_init_user();
}
// Contains report from sensor #0 already, need to apply modes and merge in from other sensors
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
// start at highest device id
uint8_t device = PD_RIGHT;
// store active device id
uint8_t stored_device = get_pointing_mode_device();
do {
pmw33xx_report_t device_report = pmw33xx_read_burst(device);
if (!device_report.motion.b.is_lifted && device_report.motion.b.is_motion) {
// create temporary mouse_report
report_mouse_t device_mouse_report = {0};
device_mouse_report.x = constrain_hid_xy(device_report.delta_x);
device_mouse_report.y = constrain_hid_xy(device_report.delta_y);
// set pointing mode device and process pointing mode for that device
set_pointing_mode_device(device);
device_mouse_report = pointing_device_modes_task(device_mouse_report);
// merge modified mouse report
mouse_report.x = constrain_hid_xy(mouse_report.x + device_mouse_report.x);
mouse_report.y = constrain_hid_xy(mouse_report.y + device_mouse_report.y);
}
} while(device--);
// reset device id
set_pointing_mode_device(stored_device);
return pointing_device_task_user(mouse_report);
}
#endif
```
### Single device control
When `POINTING_MODES_NUM_DEVICES > 1` and `POINTING_MODES_SINGLE_CONTROL` is defined then pointing modes will use the single device control scheme which will only track a single `pointing_mode` (`.id`, `.x` and `.y`), and `toggle_id`, but will be able to switch witch device is currently controlled through use of `set_pointing_mode_device` or the . This handles left and right pointing devices natively when `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED` are both defined but for more than two pointing devices custom `pointing_device_task` code will be needed.
!> Note that the `*_pointing_mode_device` functions do not change any internal behaviour when pointing modes is in single device mode, instead these functions are intended to simply track the desired controlled pointing device (this is handled automatically for two devices with `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED`)
#### Functions when using two or more pointing devices
These functions control and query the current active device id.
| Function | Description | Return type |
| :------------------------------------------ | ------------------------------------------------------------------ | :---------: |
| `get_pointing_mode_device(void)` | Return active device index | `uint8_t` |
| `set_pointing_mode_device(uint8_t device)` | Set active device index | `void` |
## Further customization
If not using standard QMK functions `pointing_device_task` or `process_record` code (_Not using the built in QMK functions_) then the following functions will be needed to use pointing device modes.
Also there is an ability to modify how pointing device output is converted to stored x & y axes by overwriting `the pointing_modes_axes_conv` function (_see notes and warnings below_).
| Function | Description | Return type |
| :------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | :--------------: |
| `pointing_device_modes_task(report_mouse_t mouse_report)` | Intercepts and changes mouse_report for pointing modes | `report_mouse_t` |
| `process_pointing_mode_records(uint16_t keyrecord, keyrecord_t* record)` | Handle processing of pointing mode keyrecords returning true when processed or false otherwise | `bool` |
| `pointing_modes_axes_conv(pointing_mode_t pointing_mode, report_mouse_t mouse_report)` | Accumulate (_and clear_) pointing device x/y output to stored h/v (_see notes_) | `report_mouse_t` |
***Notes:***
-***`pointing_modes_axes_conv` is only weakly defined and can be overwritten allowing for customization on what happens during accumulation such as adjusting additional variables, dynamic rate of accumulation etc.***
-***Additional Note: `pointing_modes_axes_conv` does not need to be overwritten to avoid clearing pointing device x/y***
-***!Warning!: changes made to `pointing_mode` within `pointing_modes_axes_conv` must be saved by calling `set_pointing_mode(pointing_mode)` before returning the updated `mouse_report`***

+ 10
- 1
quantum/keyboard.h View File

@ -32,7 +32,7 @@ typedef struct {
uint8_t row;
} keypos_t;
typedef enum keyevent_type_t { TICK_EVENT = 0, KEY_EVENT = 1, ENCODER_CW_EVENT = 2, ENCODER_CCW_EVENT = 3, COMBO_EVENT = 4, DIP_SWITCH_ON_EVENT = 5, DIP_SWITCH_OFF_EVENT = 6 } keyevent_type_t;
typedef enum keyevent_type_t { TICK_EVENT = 0, KEY_EVENT = 1, ENCODER_CW_EVENT = 2, ENCODER_CCW_EVENT = 3, COMBO_EVENT = 4, DIP_SWITCH_ON_EVENT = 5, DIP_SWITCH_OFF_EVENT = 6, POINTING_MODE_EVENT = 7} keyevent_type_t;
/* key event */
typedef struct {
@ -50,6 +50,7 @@ typedef struct {
#define KEYLOC_ENCODER_CCW 252
#define KEYLOC_DIP_SWITCH_ON 251
#define KEYLOC_DIP_SWITCH_OFF 250
#define KEYLOC_POINTING_MODE 249
static inline bool IS_NOEVENT(const keyevent_t event) {
return event.type == TICK_EVENT;
@ -69,6 +70,9 @@ static inline bool IS_ENCODEREVENT(const keyevent_t event) {
static inline bool IS_DIPSWITCHEVENT(const keyevent_t event) {
return event.type == DIP_SWITCH_ON_EVENT || event.type == DIP_SWITCH_OFF_EVENT;
}
static inline bool IS_POINTINGEVENT(const keyevent_t event) {
return event.type == POINTING_MODE_EVENT;
}
/* Common keypos_t object factory */
#define MAKE_KEYPOS(row_num, col_num) ((keypos_t){.row = (row_num), .col = (col_num)})
@ -103,6 +107,11 @@ static inline bool IS_DIPSWITCHEVENT(const keyevent_t event) {
# define MAKE_DIPSWITCH_OFF_EVENT(switch_id, press) MAKE_EVENT(KEYLOC_DIP_SWITCH_OFF, (switch_id), (press), DIP_SWITCH_OFF_EVENT)
#endif // DIP_SWITCH_MAP_ENABLE
#ifdef POINTING_MODE_MAP_ENABLE
/* Pointing mode events */
# define MAKE_POINTING_MODE_EVENT(map_id, dir, press) MAKE_EVENT(KEYLOC_POINTING_MODE, (uint8_t)(((map_id) << 2) | (dir)), (press), POINTING_MODE_EVENT)
#endif
/* it runs once at early stage of startup before keyboard_init. */
void keyboard_setup(void);
/* it runs once after initializing host side protocol, debug and MCU peripherals. */


+ 17
- 0
quantum/keycodes.h View File

@ -52,6 +52,12 @@ enum qk_keycode_ranges {
QK_ONE_SHOT_MOD_MAX = 0x52BF,
QK_LAYER_TAP_TOGGLE = 0x52C0,
QK_LAYER_TAP_TOGGLE_MAX = 0x52DF,
QK_POINTING_MODE_MO = 0x52E0,
QK_POINTING_MODE_MO_MAX = 0x52EF,
QK_POINTING_MODE_TG = 0x52F0,
QK_POINTING_MODE_TG_MAX = 0x52FF,
QK_POINTING_MODE_UTIL = 0x5300,
QK_POINTING_MODE_UTIL_MAX = 0x5302,
QK_SWAP_HANDS = 0x5600,
QK_SWAP_HANDS_MAX = 0x56FF,
QK_TAP_DANCE = 0x5700,
@ -310,6 +316,9 @@ enum qk_keycode_defines {
KC_RIGHT_SHIFT = 0x00E5,
KC_RIGHT_ALT = 0x00E6,
KC_RIGHT_GUI = 0x00E7,
QK_PM_CYCLE_DEVICES = 0x5300,
QK_PM_DEVICE_RIGHT = 0x5301,
QK_PM_DEVICE_LEFT = 0x5302,
QK_SWAP_HANDS_TOGGLE = 0x56F0,
QK_SWAP_HANDS_TAP_TOGGLE = 0x56F1,
QK_SWAP_HANDS_MOMENTARY_ON = 0x56F2,
@ -947,6 +956,9 @@ enum qk_keycode_defines {
KC_RGUI = KC_RIGHT_GUI,
KC_RCMD = KC_RIGHT_GUI,
KC_RWIN = KC_RIGHT_GUI,
PMR_CYD = QK_PM_CYCLE_DEVICES,
PMR_RGHT = QK_PM_DEVICE_RIGHT,
PMR_LEFT = QK_PM_DEVICE_LEFT,
SH_TOGG = QK_SWAP_HANDS_TOGGLE,
SH_TT = QK_SWAP_HANDS_TAP_TOGGLE,
SH_MON = QK_SWAP_HANDS_MOMENTARY_ON,
@ -1399,6 +1411,9 @@ enum qk_keycode_defines {
#define IS_QK_ONE_SHOT_LAYER(code) ((code) >= QK_ONE_SHOT_LAYER && (code) <= QK_ONE_SHOT_LAYER_MAX)
#define IS_QK_ONE_SHOT_MOD(code) ((code) >= QK_ONE_SHOT_MOD && (code) <= QK_ONE_SHOT_MOD_MAX)
#define IS_QK_LAYER_TAP_TOGGLE(code) ((code) >= QK_LAYER_TAP_TOGGLE && (code) <= QK_LAYER_TAP_TOGGLE_MAX)
#define IS_QK_POINTING_MODE_MO(code) ((code) >= QK_POINTING_MODE_MO && (code) <= QK_POINTING_MODE_MO_MAX)
#define IS_QK_POINTING_MODE_TG(code) ((code) >= QK_POINTING_MODE_TG && (code) <= QK_POINTING_MODE_TG_MAX)
#define IS_QK_POINTING_MODE_UTIL(code) ((code) >= QK_POINTING_MODE_UTIL && (code) <= QK_POINTING_MODE_UTIL_MAX)
#define IS_QK_SWAP_HANDS(code) ((code) >= QK_SWAP_HANDS && (code) <= QK_SWAP_HANDS_MAX)
#define IS_QK_TAP_DANCE(code) ((code) >= QK_TAP_DANCE && (code) <= QK_TAP_DANCE_MAX)
#define IS_QK_MAGIC(code) ((code) >= QK_MAGIC && (code) <= QK_MAGIC_MAX)
@ -1424,6 +1439,7 @@ enum qk_keycode_defines {
#define IS_CONSUMER_KEYCODE(code) ((code) >= KC_AUDIO_MUTE && (code) <= KC_LAUNCHPAD)
#define IS_MOUSE_KEYCODE(code) ((code) >= KC_MS_UP && (code) <= KC_MS_ACCEL2)
#define IS_MODIFIER_KEYCODE(code) ((code) >= KC_LEFT_CTRL && (code) <= KC_RIGHT_GUI)
#define IS_POINTING_MODE_UTIL_KEYCODE(code) ((code) >= QK_PM_CYCLE_DEVICES && (code) <= QK_PM_DEVICE_LEFT)
#define IS_SWAP_HANDS_KEYCODE(code) ((code) >= QK_SWAP_HANDS_TOGGLE && (code) <= QK_SWAP_HANDS_ONE_SHOT)
#define IS_MAGIC_KEYCODE(code) ((code) >= QK_MAGIC_SWAP_CONTROL_CAPS_LOCK && (code) <= QK_MAGIC_TOGGLE_ESCAPE_CAPS_LOCK)
#define IS_MIDI_KEYCODE(code) ((code) >= QK_MIDI_ON && (code) <= QK_MIDI_PITCH_BEND_UP)
@ -1447,6 +1463,7 @@ enum qk_keycode_defines {
#define CONSUMER_KEYCODE_RANGE KC_AUDIO_MUTE ... KC_LAUNCHPAD
#define MOUSE_KEYCODE_RANGE KC_MS_UP ... KC_MS_ACCEL2
#define MODIFIER_KEYCODE_RANGE KC_LEFT_CTRL ... KC_RIGHT_GUI
#define POINTING_MODE_UTIL_KEYCODE_RANGE QK_PM_CYCLE_DEVICES ... QK_PM_DEVICE_LEFT
#define SWAP_HANDS_KEYCODE_RANGE QK_SWAP_HANDS_TOGGLE ... QK_SWAP_HANDS_ONE_SHOT
#define MAGIC_KEYCODE_RANGE QK_MAGIC_SWAP_CONTROL_CAPS_LOCK ... QK_MAGIC_TOGGLE_ESCAPE_CAPS_LOCK
#define MIDI_KEYCODE_RANGE QK_MIDI_ON ... QK_MIDI_PITCH_BEND_UP


+ 7
- 0
quantum/keymap_common.c View File

@ -208,6 +208,7 @@ __attribute__((weak)) uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key
return keycode_at_encodermap_location(layer, key.col, false);
}
#endif // ENCODER_MAP_ENABLE
#ifdef DIP_SWITCH_MAP_ENABLE
else if (key.row == KEYLOC_DIP_SWITCH_ON && key.col < NUM_DIP_SWITCHES) {
return keycode_at_dip_switch_map_location(key.col, true);
@ -216,5 +217,11 @@ __attribute__((weak)) uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key
}
#endif // DIP_SWITCH_MAP_ENABLE
#if defined(POINTING_MODE_MAP_ENABLE)
else if (key.row == KEYLOC_POINTING_MODE && key.col < ((pointing_mode_map_count() << 2) | 0x03)) {
return keycode_at_pointing_mode_map_location(key.col);
}
#endif // defined(POINTING_MODE_MAP_ENABLE)
return KC_NO;
}

+ 55
- 0
quantum/keymap_introspection.c View File

@ -109,3 +109,58 @@ __attribute__((weak)) combo_t* combo_get(uint16_t combo_idx) {
}
#endif // defined(COMBO_ENABLE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pointing mode mapping
#if defined(POINTING_MODE_MAP_ENABLE)
# define POINTING_MODE_MAP_COUNT_RAW (uint8_t)(sizeof(pointing_mode_maps) / ((POINTING_NUM_DIRECTIONS) * sizeof(uint16_t)))
uint8_t pointing_mode_map_count_raw(void) {
return POINTING_MODE_MAP_COUNT_RAW;
}
__attribute__((weak)) uint8_t pointing_mode_map_count(void) {
return pointing_mode_map_count_raw();
}
/*
* @brief Retrieve keycode from pointing mode map
*
* Returns keycode from pointing mode map based on 8 bit index location
* breakdown of location:
* mode id | direction
* XXXX XX | XX
*
* NOTE: silently fails and returns KC_NO if mode map out of range
*
* @param[in] map_loc uint8_t
*
* @return uint16_t keycode at pointing mode map location
*/
uint16_t keycode_at_pointing_mode_map_location_raw(uint8_t map_loc) {
uint8_t map_id = map_loc >> 2;
uint8_t dir = map_loc & 0x03;
if (map_id < pointing_mode_map_count()) {
return pgm_read_word(&pointing_mode_maps[map_id][dir]);
}
return KC_NO;
}
/*
* @brief Weakly defined function for retreiving keycode from pointing mode map
*
* Defaults to passing map_loc to raw function, would allow interception of
* keycode retrieval process for pointing mode maps
*
* @param[in] map_loc uint8_t
*
* @return uint16_t keycode at pointing mode map location
*/
__attribute__((weak)) uint16_t keycode_at_pointing_mode_map_location(uint8_t map_loc) {
return keycode_at_pointing_mode_map_location_raw(map_loc);
}
#endif // defined(POINTING_MODE_MAP_ENABLE)

+ 17
- 0
quantum/keymap_introspection.h View File

@ -67,3 +67,20 @@ combo_t* combo_get_raw(uint16_t combo_idx);
combo_t* combo_get(uint16_t combo_idx);
#endif // defined(COMBO_ENABLE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pointing Mode Mapping
#if defined(POINTING_MODE_MAP_ENABLE)
// Get the number of pointing mode maps, stored in firmware
uint8_t pointing_mode_map_count_raw(void);
// Get the number of pointing mode maps, potentially stored dynamically
uint8_t pointing_mode_map_count(void);
// Get the keycode for the pointing mode map location, stored in firmware
uint16_t keycode_at_pointing_mode_map_location_raw(uint8_t map_loc);
// Get the keycode for the encoder mapping location, potentially stored dynamically
uint16_t keycode_at_pointing_mode_map_location(uint8_t map_loc);
#endif // defined(POINTING_MODE_MAP_ENABLE)

+ 46
- 3
quantum/pointing_device/pointing_device.c View File

@ -177,6 +177,7 @@ __attribute__((weak)) bool pointing_device_send(void) {
uint8_t buttons = local_mouse_report.buttons;
memset(&local_mouse_report, 0, sizeof(local_mouse_report));
local_mouse_report.buttons = buttons;
memcpy(&old_report, &local_mouse_report, sizeof(local_mouse_report));
return should_send_report || buttons;
@ -282,7 +283,7 @@ __attribute__((weak)) bool pointing_device_task(void) {
local_mouse_report = pointing_device_adjust_by_defines_right(local_mouse_report);
shared_mouse_report = pointing_device_adjust_by_defines(shared_mouse_report);
}
local_mouse_report = is_keyboard_left() ? pointing_device_task_combined_kb(local_mouse_report, shared_mouse_report) : pointing_device_task_combined_kb(shared_mouse_report, local_mouse_report);
local_mouse_report = is_keyboard_left() ? pointing_device_task_combined(local_mouse_report, shared_mouse_report) : pointing_device_task_combined(shared_mouse_report, local_mouse_report);
#else
local_mouse_report = pointing_device_adjust_by_defines(local_mouse_report);
local_mouse_report = pointing_device_task_kb(local_mouse_report);
@ -290,6 +291,10 @@ __attribute__((weak)) bool pointing_device_task(void) {
// automatic mouse layer function
#ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
pointing_device_task_auto_mouse(local_mouse_report);
#endif
// pointing device modes handling for single pointing device
#if defined(POINTING_DEVICE_MODES_ENABLE) && !(defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED))
local_mouse_report = pointing_device_modes_task(local_mouse_report);
#endif
// combine with mouse report to ensure that the combined is sent correctly
#ifdef MOUSEKEY_ENABLE
@ -393,7 +398,7 @@ static inline int8_t pointing_device_hv_clamp(int16_t value) {
}
/**
* @brief clamps int16_t to int8_t
* @brief clamps clamp_range_t to mouse_xy_report_t
*
* @param[in] clamp_range_t value
* @return mouse_xy_report_t clamped value
@ -465,6 +470,44 @@ report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_repo
return mouse_report;
}
/**
* @brief Handle core combined pointing device tasks
*
* Takes 2 report_mouse_t structs allowing individual modification of either side and then returns pointing_device_task_combined_kb.
*
* NOTE: Only available when using both SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
*
* @param[in] left_report report_mouse_t
* @param[in] right_report report_mouse_t
* @return pointing_device_task_combined_kb(left_report, right_report) by default
*/
report_mouse_t pointing_device_task_combined(report_mouse_t left_report, report_mouse_t right_report) {
# ifdef POINTING_DEVICE_MODES_ENABLE
# if POINTING_MODES_SINGLE_CONTROL
// only one side controlled at any one time
switch (get_pointing_mode_device()) {
case PM_RIGHT_DEVICE:
right_report = pointing_device_modes_task(right_report);
break;
default:
left_report = pointing_device_modes_task(left_report);
}
# else
// both sides controlled independently
// save current device id
uint8_t current_device = get_pointing_mode_device();
set_pointing_mode_device(PM_RIGHT_DEVICE);
right_report = pointing_device_modes_task(right_report);
set_pointing_mode_device(PM_LEFT_DEVICE);
left_report = pointing_device_modes_task(left_report);
// set device id back
set_pointing_mode_device(current_device);
# endif
# endif
return pointing_device_task_combined_kb(left_report, right_report);
}
/**
* @brief Weak function allowing for keyboard level mouse report modification
*
@ -494,7 +537,7 @@ __attribute__((weak)) report_mouse_t pointing_device_task_combined_kb(report_mou
__attribute__((weak)) report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report) {
return pointing_device_combine_reports(left_report, right_report);
}
#endif
#endif // defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
__attribute__((weak)) void pointing_device_keycode_handler(uint16_t keycode, bool pressed) {
if IS_MOUSEKEY_BUTTON (keycode) {


+ 4
- 0
quantum/pointing_device/pointing_device.h View File

@ -24,6 +24,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
# include "pointing_device_auto_mouse.h"
#endif
#ifdef POINTING_DEVICE_MODES_ENABLE
# include "pointing_device_modes.h"
#endif
#if defined(POINTING_DEVICE_DRIVER_adns5050)
# include "drivers/sensors/adns5050.h"
@ -127,6 +130,7 @@ uint16_t pointing_device_get_shared_cpi(void);
# if defined(POINTING_DEVICE_COMBINED)
void pointing_device_set_cpi_on_side(bool left, uint16_t cpi);
report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, report_mouse_t right_report);
report_mouse_t pointing_device_task_combined(report_mouse_t left_report, report_mouse_t right_report);
report_mouse_t pointing_device_task_combined_kb(report_mouse_t left_report, report_mouse_t right_report);
report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report);
report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_report);


+ 7
- 2
quantum/pointing_device/pointing_device_auto_mouse.c View File

@ -1,5 +1,5 @@
/* Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2022 Alabastard
* Copyright 2022 Alabastard (@Alabastard-64)
*
* 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
@ -428,7 +428,12 @@ bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
*/
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;
if (is_mouse_record_kb(keycode, record) || IS_MOUSEKEY(keycode) ||
# ifdef POINTING_DEVICE_MODES_ENABLE
IS_QK_POINTING_MODE_MO(keycode) || IS_QK_POINTING_MODE_TG(keycode) ||
# endif
false)
return true;
return false;
}


+ 678
- 0
quantum/pointing_device/pointing_device_modes.c View File

@ -0,0 +1,678 @@
/* Copyright 2023 Alabastard (@Alabastard-64)
*
* 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_MODES_ENABLE
# include "pointing_device_modes.h"
// initialize local functions
static report_mouse_t process_pointing_mode(pointing_mode_t pointing_mode, report_mouse_t mouse_report);
static uint8_t get_pointing_mode_direction(void);
static uint8_t get_pointing_mode_divisor(void);
static uint8_t divisor_postprocess(uint8_t divisor);
// local variables
# if POINTING_MODES_DEVICE_CONTROL_COUNT > 1
static uint8_t current_device = POINTING_MODES_DEFAULT_DEVICE;
# else
static const uint8_t current_device = 0;
# if defined(POINTING_MODES_SINGLE_CONTROL)
static uint8_t selected_device = POINTING_MODES_DEFAULT_DEVICE;
# endif
# endif
static uint8_t current_direction = 0;
static uint8_t current_divisor = POINTING_DEFAULT_DIVISOR;
static uint8_t toggle_ids[] = {[0 ... POINTING_MODES_DEVICE_CONTROL_COUNT - 1] = POINTING_MODE_DEFAULT};
static pointing_mode_t pointing_modes[] = {[0 ... POINTING_MODES_DEVICE_CONTROL_COUNT - 1] = {.id = POINTING_MODE_DEFAULT}};
// set up clamping and divisor application functions
static inline int8_t clamp_int_16_to_8(int16_t value) {
if (value < INT8_MIN) {
return INT8_MIN;
} else if (value > INT8_MAX) {
return INT8_MAX;
} else {
return value;
}
}
static inline int16_t clamp_int_32_to_16(int32_t value) {
if (value < INT16_MIN) {
return INT16_MIN;
} else if (value > INT16_MAX) {
return INT16_MAX;
} else {
return value;
}
}
static inline uint16_t clamp_uint_16_to_8(uint16_t value) {
if (value > UINT8_MAX) return UINT8_MAX;
return value;
}
static int16_t divisor_multiply16(int16_t value) {
return clamp_int_32_to_16(value * (int16_t)current_divisor);
}
static int8_t divisor_divide8(int16_t value) {
return clamp_int_16_to_8(value / (int16_t)current_divisor);
}
static int16_t divisor_divide16(int16_t value) {
return value / (int16_t)current_divisor;
}
/**
* @brief divides value by current divisor return mouse x/y
*
* Intended for modifying pointing_mode x/y values
*
* @params value[in] value int16_t
*
* @return quotient clamped to mouse_xy_report_t range
*/
mouse_xy_report_t apply_divisor_xy(int16_t value) {
# ifdef MOUSE_EXTENDED_REPORT
return divisor_divide16(value);
# else
return divisor_divide8(value);
# endif
}
/**
* @brief divides value by current divisor return mouse h/v
*
* Intended for modifying pointing_mode x/y values
*
* @params value[in] value int16_t
*
* @return quotient clamped to int8_t range
*/
int8_t apply_divisor_hv(int16_t value) {
return divisor_divide8(value);
}
/**
* @brief multiplies mouse x/y value by current divisor
*
* Intended for collecting residuals by subtracting quotient * divisor from the dividend
* as modulo can be large and slow on some chips
*
* @params value[in] value mouse_xy_report_t mouse x/y axis value to multiply
*
* @return product clamped to int16_t range
*/
int16_t multiply_divisor_xy(mouse_xy_report_t value) {
return divisor_multiply16((int16_t)value);
}
/**
* @brief multiplies mouse h/v value by current divisor
*
* Intended for collecting residuals by subtracting quotient * divisor from the dividend
* as modulo can be large and slow on some chips
*
* @params value[in] value int8_t mouse h/v axis value to multiply
*
* @return modified and clamped value int16_t
*/
int16_t multiply_divisor_hv(int8_t value) {
return divisor_multiply16((int16_t)value);
}
/**
* @brief Return device id of current controlled device
*
* @return current device id [uint8_t]
*/
uint8_t get_pointing_mode_device(void) {
# ifdef POINTING_MODES_SINGLE_CONTROL
return selected_device;
# else
return current_device;
# endif
}
/**
* @brief Change current device
*
* Will change which device (PM_LEFT_DEVICE, PM_RIGHT_DEVICE, etc.) is currently being modified
*
* NOTE: If mode is set to out of range device number device is silently set to zero (this allows cycling)
*
* @params[in] new side uint8_t
*/
void set_pointing_mode_device(uint8_t device) {
# if (POINTING_MODES_NUM_DEVICES > 1)
if (device > (POINTING_MODES_NUM_DEVICES - 1)) device = 0;
# ifdef POINTING_MODES_SINGLE_CONTROL
selected_device = device;
# else
current_device = device;
# endif
# else
;
# endif
}
/**
* @brief Reset pointing mode data
*
* Clear poiting mode and set to defaults
*/
void pointing_mode_reset(void) {
memset(&pointing_modes[current_device], 0, sizeof(pointing_mode_t));
pointing_modes[current_device].id = toggle_ids[current_device];
}
/**
* @brief set current pointing mode data
*
* @param[in] pointing_mode pointing_mode_t
*/
void set_pointing_mode(pointing_mode_t pointing_mode) {
// skip if same
if (!memcmp(&pointing_modes[current_device], &pointing_mode, sizeof(pointing_mode_t))) return;
memcpy(&pointing_modes[current_device], &pointing_mode, sizeof(pointing_mode_t));
}
/**
* @brief access current pointing mode id
*
* @return uint8_t current pointing mode id
*/
uint8_t get_pointing_mode_id(void) {
return pointing_modes[current_device].id;
}
/**
* @brief set pointing mode id
*
* @param[in] mode_id uint8_t
*/
void set_pointing_mode_id(uint8_t mode_id) {
if (get_pointing_mode_id() != mode_id) {
pointing_mode_reset();
pointing_modes[current_device].id = mode_id;
}
}
/**
* @brief Access current toggled pointing mode
*
* @return uint8_t toggle pointing mode
*/
uint8_t get_toggled_pointing_mode_id(void) {
return toggle_ids[current_device];
}
/**
* @brief Toggle pointing mode id on/off
*
* Will change tg_mode_id setting to POINTING_MODE_DEFAULT when toggling "off"
*
* @param[in] mode_id uint8_t
*/
void toggle_pointing_mode_id(uint8_t mode_id) {
if (get_toggled_pointing_mode_id() == mode_id) {
toggle_ids[current_device] = POINTING_MODE_DEFAULT;
} else {
toggle_ids[current_device] = mode_id;
}
if (get_pointing_mode_id() != get_toggled_pointing_mode_id()) pointing_mode_reset();
}
/**
* @brief Weak function to convert x/y axes to h/v
*
* The default uses accumulation based on inversion defines and
* halts cursor movement
*
* @params pointing_modes[in] pointing_mode_t
* @params mouse_report[in] report_mouse_t
*
* @return updated mouse_report report_mouse_t
*/
__attribute__((weak)) report_mouse_t pointing_modes_axes_conv(pointing_mode_t pointing_mode, report_mouse_t mouse_report) {
# ifdef POINTING_DEVICE_MODES_INVERT_X
pointing_mode.x -= mouse_report.x;
# else
pointing_mode.x += mouse_report.x;
# endif
# ifdef POINTING_DEVICE_MODES_INVERT_Y
pointing_mode.y -= mouse_report.y;
# else
pointing_mode.y += mouse_report.y;
# endif
set_pointing_mode(pointing_mode);
mouse_report.x = 0;
mouse_report.y = 0;
return mouse_report;
}
/**
* @brief Modifies divisor after
*
* @params pointing_modes[in] uint8_t
* @params direction[in] uint8_t
*
* @return divisor uint8_t
*/
static uint8_t divisor_postprocess(uint8_t divisor) {
if (!(pointing_mode_divisor_postprocess_user(&divisor) && pointing_mode_divisor_postprocess_kb(&divisor))) {
// never return without zero checking
return divisor ? divisor : POINTING_DEFAULT_DIVISOR;
}
// Modify divisor if precision is toggled
if (get_toggled_pointing_mode_id() == PM_PRECISION && !(get_pointing_mode_id() == PM_PRECISION)) {
divisor = clamp_uint_16_to_8(divisor * POINTING_PRECISION_DIVISOR);
}
// never return without zero checking
return divisor ? divisor : POINTING_DEFAULT_DIVISOR;
}
/**
* @brief override current divisor value
*
* Will only take effect until next cycle update or next call of this process
*
* @param[in] divisor uint8_t
*/
void pointing_mode_divisor_override(uint8_t divisor) {
current_divisor = divisor_postprocess(divisor);
}
/**
* @brief local function to get pointing mode divisor
*
* Will handle default divisors and call weak kb and user functions
*
* NOTE: Expects that pointing mode and direction has been updated
*
* @return divisor uint8_t
*/
static uint8_t get_pointing_mode_divisor(void) {
uint8_t divisor = 0;
// allow for user and keyboard overrides
divisor = get_pointing_mode_divisor_user(get_pointing_mode_id(), current_pointing_mode_direction());
if (divisor) return divisor_postprocess(divisor);
divisor = get_pointing_mode_divisor_kb(get_pointing_mode_id(), current_pointing_mode_direction());
if (divisor) return divisor_postprocess(divisor);
// built in divisors
switch (get_pointing_mode_id()) {
case PM_PRECISION:
divisor = POINTING_PRECISION_DIVISOR;
break;
case PM_DRAG:
divisor = POINTING_DRAG_DIVISOR;
break;
case PM_CARET:
divisor = current_pointing_mode_direction() < PD_LEFT ? POINTING_CARET_DIVISOR_V : POINTING_CARET_DIVISOR_H;
break;
case PM_HISTORY:
divisor = POINTING_HISTORY_DIVISOR;
break;
# ifdef EXTRAKEY_ENABLE
case PM_VOLUME:
divisor = POINTING_VOLUME_DIVISOR;
break;
# endif
}
return divisor_postprocess(divisor);
}
/**
* @brief local function to get single direction based on h/v
*
* Determines direction based on axis with largest magnitude
*
* NOTE: Defaults to PD_DOWN
*
* @return direction uint8_t
*/
static uint8_t get_pointing_mode_direction(void) {
if (abs(pointing_modes[current_device].x) > abs(pointing_modes[current_device].y)) {
if (pointing_modes[current_device].x > 0) {
return PD_RIGHT;
} else {
return PD_LEFT;
}
} else {
if (pointing_modes[current_device].y > 0) {
return PD_UP;
} else {
return PD_DOWN;
}
}
}
/**
* @brief update dependent parameters of pointing_mode
*
* Will update the direction and divisor values based on mode id and h, and v axis values
*
*/
void pointing_mode_update(void) {
current_direction = get_pointing_mode_direction();
current_divisor = get_pointing_mode_divisor();
}
/**
* @brief Access current pointing mode direction
*
* @return uint8_t direction
*/
uint8_t current_pointing_mode_direction(void) {
return current_direction;
}
/**
* @brief Access current pointing mode divisor
*
* @return uint8_t divisor
*/
uint8_t current_pointing_mode_divisor(void) {
return current_divisor;
}
/**
* @brief Tap keycodes based on mode axis values
*
* Will translate current mode x & y values into keycode taps based on divisor value.
* Array should be ordered {DOWN, UP, LEFT, RIGHT} and will output 1 keytap/divisor
* x and y values will be automatically updated
*
* NOTE: favours staying on axis and weakly favours the horizontal over the vertical axis
* and will clear the orthogonal axis
*
* @param[in] uint16_t* array pointer to set of four keycodes
*/
static void pointing_tap_keycodes_raw(uint16_t* pm_keycodes) {
int16_t count = 0;
uint8_t dir = current_pointing_mode_direction();
switch (dir) {
case PD_DOWN ... PD_UP:
count = divisor_divide16(pointing_modes[current_device].y);
if (!count) return; // exit if accumulated y is too low
pointing_modes[current_device].y -= divisor_multiply16(count);
pointing_modes[current_device].x = 0;
break;
case PD_LEFT ... PD_RIGHT:
count = divisor_divide16(pointing_modes[current_device].x);
if (!count) return; // exit if accumulated x is too low
pointing_modes[current_device].x -= divisor_multiply16(count);
pointing_modes[current_device].y = 0;
break;
}
// skip if KC_TRNS or KC_NO (but allow for axes update above)
// if (pm_keycodes[dir] < 2) return;
// tap codes
uint8_t taps = clamp_uint_16_to_8((uint16_t)abs(count));
do {
tap_code16_delay(pm_keycodes[dir], POINTING_TAP_DELAY);
} while (--taps);
}
/**
* @brief external wrapper for pointing_tap_keycodes to allow easier input
*
* keycode order follow VIM convention (LEFT, DOWN, UP, RIGHT).s
*
* @params kc_left[in] uint16_t keycode for negative x
* @params kc_down[in] uint16_t keycode for negative y
* @params kc_up[in] uint16_t keycode for positive y
* @params kc_right[in] uint16_t keycode for positive x
*/
void pointing_tap_codes(uint16_t kc_left, uint16_t kc_down, uint16_t kc_up, uint16_t kc_right) {
uint16_t pm_keycodes[4] = {kc_down, kc_up, kc_left, kc_right};
pointing_tap_keycodes_raw(pm_keycodes);
}
/**
* @brief Tap keycodes from pointing mode maps
*
* Will translate internal x & y axes into mode map keycode taps.
* and will update the x and y values.-
*
* NOTE: favours staying on axis and favours the horizontal over the vertical axis
* and will clear the orthogonal axis
*
* @params map_id id of current map
*/
# ifdef POINTING_MODE_MAP_ENABLE
static void pointing_exec_mapping(uint8_t map_id) {
int16_t count = 0;
uint8_t dir = current_pointing_mode_direction();
switch (dir) {
case PD_DOWN ... PD_UP:
count = divisor_divide16(pointing_modes[current_device].y);
if (!count) return; // exit if accumulated y is too low
pointing_modes[current_device].y -= divisor_multiply16(count);
pointing_modes[current_device].x = 0;
break;
case PD_LEFT ... PD_RIGHT:
count = divisor_divide16(pointing_modes[current_device].x);
if (!count) return; // exit if accumulated x is too low
pointing_modes[current_device].x -= divisor_multiply16(count);
pointing_modes[current_device].y = 0;
break;
}
// tap codes
uint8_t taps = clamp_uint_16_to_8((uint16_t)abs(count));
do {
action_exec(MAKE_POINTING_MODE_EVENT(map_id, dir, true));
# if POINTING_TAP_DELAY > 0
wait_ms(POINTING_TAP_DELAY);
# endif // POINTING_TAP_DELAY > 0
action_exec(MAKE_POINTING_MODE_EVENT(map_id, dir, false));
} while (--taps);
}
# endif
/**
* @brief Core function to handle pointing mode task
*
* Meant to be implemented in pointing_device_task
*
* @param[in] mouse_report report_mouse_t
*
* @return mouse_report report_mouse_t
*/
report_mouse_t pointing_device_modes_task(report_mouse_t mouse_report) {
// skip all processing if pointing mode is PM_NONE
if (get_pointing_mode_id() == PM_NONE) return mouse_report;
mouse_report = pointing_modes_axes_conv(pointing_modes[current_device], mouse_report);
pointing_mode_update();
mouse_report = process_pointing_mode(pointing_modes[current_device], mouse_report);
return mouse_report;
}
/**
* @brief Handle processing of pointing modes
*
* Takes in report_mouse_t and pointing_mode_t allowing manipulation of mouse_report
* and pointing_mode via set_pointing_mode
*
* @return mouse_report report_mouse_t
*/
static report_mouse_t process_pointing_mode(pointing_mode_t pointing_mode, report_mouse_t mouse_report) {
// allow overwrite of pointing modes by user, and kb
if (!(process_pointing_mode_user(pointing_mode, &mouse_report) && process_pointing_mode_kb(pointing_mode, &mouse_report))) {
return mouse_report;
}
switch (pointing_mode.id) {
// precision mode (reduce x y sensitivity temporarily)
case PM_PRECISION:
# ifdef POINTING_DEVICE_MODES_INVERT_X
mouse_report.x -= apply_divisor_xy(pointing_mode.x);
pointing_mode.x += multiply_divisor_xy(mouse_report.x);
# else
mouse_report.x += apply_divisor_xy(pointing_mode.x);
pointing_mode.x -= multiply_divisor_xy(mouse_report.x);
# endif
# ifdef POINTING_DEVICE_MODES_INVERT_Y
mouse_report.y -= apply_divisor_xy(pointing_mode.y);
pointing_mode.y += multiply_divisor_xy(mouse_report.y);
# else
mouse_report.y += apply_divisor_xy(pointing_mode.y);
pointing_mode.y -= multiply_divisor_xy(mouse_report.y);
# endif
set_pointing_mode(pointing_mode);
break;
// drag scroll mode (sets mouse axes to mouse_report h & v with divisor)
case PM_DRAG:
mouse_report.h = apply_divisor_hv(pointing_mode.x);
pointing_mode.x -= multiply_divisor_hv(mouse_report.h);
mouse_report.v = apply_divisor_hv(pointing_mode.y);
pointing_mode.y -= multiply_divisor_hv(mouse_report.v);
set_pointing_mode(pointing_mode);
break;
// caret mode (uses arrow keys to move cursor)
case PM_CARET:
pointing_tap_codes(KC_LEFT, KC_DOWN, KC_UP, KC_RIGHT);
break;
// history scroll mode (will scroll through undo/redo history)
case PM_HISTORY:
pointing_tap_codes(C(KC_Z), KC_NO, KC_NO, C(KC_Y));
break;
# ifdef EXTRAKEY_ENABLE
// volume scroll mode (adjusts audio volume)
case PM_VOLUME:
pointing_tap_codes(KC_NO, KC_VOLD, KC_VOLU, KC_NO);
break;
# endif
# ifdef POINTING_MODE_MAP_ENABLE
default:
if (pointing_mode.id > POINTING_MODE_MAP_START) {
pointing_exec_mapping(pointing_mode.id - POINTING_MODE_MAP_START);
}
# endif
}
return mouse_report;
}
/**
* @brief User level processing of pointing device modes
*
* Takes pointing_mode_t struct, and pointer to the report_mouse_t struct used in pointing mode process allowing
* modification of mouse_report directly and pointing mode using set_pointing_mode(pointing_mode) and the returned
* bool controls the continued processing of pointing modes.
*
* NOTE: returning false will stop mode processing (for overwriting modes)
*
* @params pointing_modes[in] pointing_mode_t
* @params mouse_report[in] pointer to report_mouse_t
*
* @return bool continue processing flag
*/
__attribute__((weak)) bool process_pointing_mode_user(pointing_mode_t pointing_mode, report_mouse_t* mouse_report) {
return true; // continue processing
}
/**
* @brief Keyboard level processing of pointing device modes
*
* Takes pointing_mode_t struct, and pointer to the report_mouse_t struct used in pointing mode process allowing
* modification of mouse_report directly and pointing mode using set_pointing_mode(pointing_mode) and the returned
* bool controls the continued processing of pointing modes.
*
* NOTE: returning false will stop mode processing (for overwriting modes)
*
* @params pointing_modes[in] pointing_mode_t
* @params mouse_report[in] pointer to report_mouse_t
*
* @return bool continue processing flag
*/
__attribute__((weak)) bool process_pointing_mode_kb(pointing_mode_t pointing_mode, report_mouse_t* mouse_report) {
return true; // continue processing
}
/**
* @brief Weak function for user level adding of pointing mode divisors
*
* Takes in mode id and direction allowing for divisor values to be
* determined based on these parameters
*
* @params pointing_modes[in] uint8_t
* @params direction[in] uint8_t
*
* @return divisor uint8_t
*/
__attribute__((weak)) uint8_t get_pointing_mode_divisor_user(uint8_t mode_id, uint8_t direction) {
return 0; // continue processing
}
/**
* @brief Weak function for user level adding of pointing mode divisors
*
* Takes in mode id and direction allowing for divisor values to be
* determined based on these parameters
*
* @params pointing_modes[in] uint8_t
* @params direction[in] uint8_t
*
* @return divisor uint8_t
*/
__attribute__((weak)) uint8_t get_pointing_mode_divisor_kb(uint8_t mode_id, uint8_t direction) {
return 0; // continue processing
}
/**
* @brief Keyboard level modifying of divisors after being set and before use
*
* Allows modification the divisor after being set by get_pointing_mode_divisor stack before
* handing off to default post processing
*
* @params[in] pointer to divisor uint8_t
*
* @return bool continue processing flag
*/
__attribute__((weak)) bool pointing_mode_divisor_postprocess_kb(uint8_t* divisor) {
return true;
}
/**
* @brief User level modifying of divisors after being set and before use
*
* Allows modification the divisor after being set by get_pointing_mode_divisor stack before
* handing off to default post processing
*
* @params[in] pointer to divisor uint8_t
*
* @return bool continue processing flag
*/
__attribute__((weak)) bool pointing_mode_divisor_postprocess_user(uint8_t* divisor) {
return true;
}
#endif // POINTING_DEVICE_MODES_ENABLE

+ 166
- 0
quantum/pointing_device/pointing_device_modes.h View File

@ -0,0 +1,166 @@
/* Copyright 2023 Alabastard (@Alabastard-64)
*
* 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"
// default settings
#ifndef POINTING_TAP_DELAY
# define POINTING_TAP_DELAY TAP_CODE_DELAY
#endif
#ifndef POINTING_MODE_DEFAULT
# define POINTING_MODE_DEFAULT PM_NONE
#endif
#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
# ifndef POINTING_MODES_NUM_DEVICES
# define POINTING_MODES_NUM_DEVICES 2
# endif
#else
# ifndef POINTING_MODES_NUM_DEVICES
# define POINTING_MODES_NUM_DEVICES 1
# endif
#endif
#ifdef POINTING_MODES_SINGLE_CONTROL
# define POINTING_MODES_DEVICE_CONTROL_COUNT 1
#else
# define POINTING_MODES_DEVICE_CONTROL_COUNT POINTING_MODES_NUM_DEVICES
#endif
#ifndef POINTING_MODES_DEFAULT_DEVICE
# define POINTING_MODES_DEFAULT_DEVICE 0
#endif
#ifdef POINTING_NUM_DIRECTIONS
# undefine POINTING_NUM_DIRECTIONS
#endif
#define POINTING_NUM_DIRECTIONS 4
// default divisors
#ifndef POINTING_DEFAULT_DIVISOR
# define POINTING_DEFAULT_DIVISOR 64
#endif
#ifndef POINTING_HISTORY_DIVISOR
# define POINTING_HISTORY_DIVISOR 64
#endif
#ifndef POINTING_VOLUME_DIVISOR
# define POINTING_VOLUME_DIVISOR 64
#endif
#ifndef POINTING_CARET_DIVISOR
# define POINTING_CARET_DIVISOR 32
#endif
#ifndef POINTING_CARET_DIVISOR_H
# define POINTING_CARET_DIVISOR_H POINTING_CARET_DIVISOR
#endif
#ifndef POINTING_CARET_DIVISOR_V
# define POINTING_CARET_DIVISOR_V POINTING_CARET_DIVISOR
#endif
#ifndef POINTING_PRECISION_DIVISOR
# define POINTING_PRECISION_DIVISOR 2
#endif
#ifndef POINTING_DRAG_DIVISOR
# define POINTING_DRAG_DIVISOR 4
#endif
// error checking
#if POINTING_DEFAULT_DIVISOR < 1
# pragma message "DEFAULT_DIVISOR must be 1 or greater"
# error DEFAULT_DIVISOR less than 1
#endif
#if POINTING_MODES_NUM_DEVICES < 2 && ((defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)) || defined(POINTING_MODES_SINGLE_CONTROL))
# pragma message "POINTING_MODE_NUM_DEVICES should be at least 2 with SPLIT_POINTING_ENABLE & POINTING_DEVICE_COMBINED or POINTING_MODES_SINGLE_CONTROL defined"
# error POINTING_MODE_NUM_DEVICES set too low
#endif
/* enums */
enum pointing_device_directions {
PD_DOWN = 0, // default value
PD_UP = 1,
PD_LEFT = 2,
PD_RIGHT = 3
};
enum pointing_devide_mode_devices {
#ifdef MASTER_RIGHT
PM_RIGHT_DEVICE = 0,
PM_LEFT_DEVICE
#else
PM_LEFT_DEVICE = 0,
PM_RIGHT_DEVICE
#endif
};
/* local data structures */
typedef struct {
uint8_t id;
int16_t x;
int16_t y;
} pointing_mode_t;
/* ----------Controlling pointing device modes-------------------------------------------------------------------- */
void set_pointing_mode_id(uint8_t mode_id); // set current pointing_mode.id to mode_id
void toggle_pointing_mode_id(uint8_t mode_id); // toggle pointing mode
uint8_t get_pointing_mode_id(void); // return current pointing_mode.id
uint8_t get_toggled_pointing_mode_id(void); // return current tg_mode_id
void pointing_mode_reset(void); // reset pointing mode to current toggle mode
/* ----------Controlling pointing device data conversion---------------------------------------------------------- */
report_mouse_t pointing_modes_axes_conv(pointing_mode_t pointing_mode, report_mouse_t mouse_report); // converts x & y axes to local h & v
/* ----------Crontrolling pointing mode data---------------------------------------------------------------------- */
void set_pointing_mode(pointing_mode_t pointing_mode); // overwrite local pointing_mode status
void pointing_mode_update(void); // update direction & divisor from current mode id, x, y
mouse_xy_report_t apply_divisor_xy(int16_t value); // divide value by current divisor and clamp to x/y range
int8_t apply_divisor_hv(int16_t value); // divide value by current divisor and clamps to h/v range
int16_t multiply_divisor_xy(mouse_xy_report_t value); // multiply mouse x/y value by current divisor
int16_t multiply_divisor_hv(int8_t value); // multiply mousen h/v value by current divisor
void pointing_mode_divisor_override(uint8_t divisor); // override current divisor
uint8_t current_pointing_mode_divisor(void); // return current divisor
uint8_t current_pointing_mode_direction(void); // return current direction
/* ----------For Custom modes without maps---------------------------------------------------------------------- */
void pointing_tap_codes(uint16_t kc_left, uint16_t kc_down, uint16_t kc_up, uint16_t kc_right); // pointing_mode x/y to keycode taps
/* ----------Callbacks for modifying and adding pointing modes---------------------------------------------------- */
bool process_pointing_mode_kb(pointing_mode_t pointing_mode, report_mouse_t* mouse_report); // keyboard level
bool process_pointing_mode_user(pointing_mode_t pointing_mode, report_mouse_t* mouse_report); // user/keymap level
/* ----------Callbacks for adding/modifying pointing device mode divisors----------------------------------------- */
uint8_t get_pointing_mode_divisor_kb(uint8_t mode_id, uint8_t direction); // adding new divisors at keyboard level
uint8_t get_pointing_mode_divisor_user(uint8_t mode_id, uint8_t direction); // adding new divisors at user/keymap level
bool pointing_mode_divisor_postprocess_kb(uint8_t* divisor); // keyboard level modifying of divisors after get_pointing_mode_divisor_* stack
bool pointing_mode_divisor_postprocess_user(uint8_t* divisor); // user/keymap level modifying of divisors after get_pointing_mode_divisor_* stack
/* ----------Core functions (only used in custom pointing devices or key processing)------------------------------ */
report_mouse_t pointing_device_modes_task(report_mouse_t mouse_report); // intercepts mouse_report (in pointing_device_task_* stack)
/* ----------Pointing Device mode Mapping------------------------------------------------------------------------- */
#ifdef POINTING_MODE_MAP_ENABLE
# define POINTING_MODE_LAYOUT(Y_POS, X_NEG, X_POS, Y_NEG) \
{ Y_NEG, Y_POS, X_NEG, X_POS }
# ifndef POINTING_MODE_MAP_START
# define POINTING_MODE_MAP_START PM_SAFE_RANGE
# endif
extern const uint16_t PROGMEM pointing_mode_maps[][POINTING_NUM_DIRECTIONS];
#endif // POINTING_MODE_MAP_ENABLE
/* ----------For multiple pointing devices------------------------------------------------------------------------ */
uint8_t get_pointing_mode_device(void); // get current active device for pointing modes
void set_pointing_mode_device(uint8_t device); // set current active pointing mode side (PM_LEFT_SIDE, PM_RIGHT_SIDE, etc.)

+ 108
- 0
quantum/process_keycode/process_pointing_mode_records.c View File

@ -0,0 +1,108 @@
/* Copyright 2023 Alabastard (@Alabastard-64)
*
* 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_MODES_ENABLE
# include "process_pointing_mode_records.h"
/**
* @brief Handle pointing mode change keypress: Momentary
*
* Will set pointing mode id when key is pressed and reset on release
*
* NOTE: if pointing mode has changed since key down, reset is skipped
*
* @params mode_id[in] uint8_t
* @params pressed[in] bool
*/
void pointing_mode_key_momentary(uint8_t mode_id, bool pressed) {
if (pressed) {
set_pointing_mode_id(mode_id);
} else if (get_pointing_mode_id() == mode_id) {
// reset mode only if the current mode matches (in case mode has changed before release)
pointing_mode_reset();
}
}
/**
* @brief Handle pointing mode change keypress: Toggle
*
* Toggle pointing mode on key release
*
* @params mode_id[in] uint8_t
* @params pressed[in] bool
*/
void pointing_mode_key_toggle(uint8_t mode_id, bool pressed) {
// only attempt to change mode on key release event (matches layer toggle behaviour)
if (!pressed) toggle_pointing_mode_id(mode_id);
}
/**
* @brief Handle keypress to change current device
*
* Set current device to device on key release
*
* @params device[in] uint8_t
* @params pressed[in] bool
*/
void pointing_mode_key_set_device(uint8_t device, bool pressed) {
if (!pressed) set_pointing_mode_device(device);
}
/**
* @brief Core function to process pointing mode key records
*
* Only handles built in keyrecords and functions
*
* @params keycode[in] uint16_t
* @params record[in] keyrecord_t pointer
*
* @return should keycode processing continue bool
*/
bool process_pointing_mode_records(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
// handle built in keycods for bottom 16 pointing modes (0-15)
// momentary
case QK_POINTING_MODE_MO ... QK_POINTING_MODE_MO_MAX:
pointing_mode_key_momentary((keycode - QK_POINTING_MODE_MO) & (QK_POINTING_MODE_MO_MAX - QK_POINTING_MODE_MO), record->event.pressed);
return true; // allow further processing
// toggle
case QK_POINTING_MODE_TG ... QK_POINTING_MODE_TG_MAX:
pointing_mode_key_toggle((keycode - QK_POINTING_MODE_TG) & (QK_POINTING_MODE_TG_MAX - QK_POINTING_MODE_TG), record->event.pressed);
return true; // allow further processing
# if (POINTING_MODES_NUM_DEVICES > 1)
// utils: Cycle devices
case QK_PM_CYCLE_DEVICES:
pointing_mode_key_set_device(get_pointing_mode_device() + 1, record->event.pressed);
return true;
// utils: DEVICE RIGHT
case QK_PM_DEVICE_RIGHT:
pointing_mode_key_set_device(PM_RIGHT_DEVICE, record->event.pressed);
return true;
// utils: DEVICE LEFT
case QK_PM_DEVICE_LEFT:
pointing_mode_key_set_device(PM_RIGHT_DEVICE, record->event.pressed);
return true;
# endif
// end
default:
return true;
}
}
#endif // POINTING_DEVICE_MODES_ENABLE

+ 28
- 0
quantum/process_keycode/process_pointing_mode_records.h View File

@ -0,0 +1,28 @@
/* Copyright 2023 Alabastard (@Alabastard-64)
*
* 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 "quantum.h"
#include "pointing_device_modes.h"
/* ----------Handling pointing mode id changes on keypress-------------------------------------------------------- */
void pointing_mode_key_momentary(uint8_t mode_id, bool pressed); // momentary change of pointing mode while button is held
void pointing_mode_key_toggle(uint8_t mode_id, bool pressed); // toggle pointing mode on/off on keypress
void pointing_mode_key_set_device(uint8_t device, bool pressed); // set current device
/* ----------For builtin keycodes--------------------------------------------------------------------------------- */
bool process_pointing_mode_records(uint16_t keyrecord, keyrecord_t* record); // handle processing of built in keyrecords (in process_record stack)

+ 4
- 0
quantum/quantum.c View File

@ -391,8 +391,12 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef AUTOCORRECT_ENABLE
process_autocorrect(keycode, record) &&
#endif
#ifdef TRI_LAYER_ENABLE
process_tri_layer(keycode, record) &&
#endif
#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_MODES_ENABLE)
process_pointing_mode_records(keycode, record) &&
#endif
true)) {
return false;


+ 3
- 0
quantum/quantum.h View File

@ -212,6 +212,9 @@ extern layer_state_t layer_state;
#ifdef POINTING_DEVICE_ENABLE
# include "pointing_device.h"
# ifdef POINTING_DEVICE_MODES_ENABLE
# include "process_pointing_mode_records.h"
# endif
#endif
#ifdef MOUSEKEY_ENABLE


+ 27
- 0
quantum/quantum_keycodes.h View File

@ -222,4 +222,31 @@
#define SQ_R(n) (n < SEQUENCER_RESOLUTIONS ? SEQUENCER_RESOLUTION_MIN + n : KC_NO)
#define SQ_T(n) (n < SEQUENCER_TRACKS ? SEQUENCER_TRACK_MIN + n : KC_NO)
// Pointing device mode key macros
// Momentary scroll mode
#define PM_MO(pm) (MIN((pm), (QK_POINTING_MODE_MO_MAX - QK_POINTING_MODE_MO)) + QK_POINTING_MODE_MO)
// Toggle default scroll mode
#define PM_TG(pm) (MIN((pm), (QK_POINTING_MODE_TG_MAX - QK_POINTING_MODE_TG)) + QK_POINTING_MODE_TG)
// Default Pointing device pointing modes
enum pointing_device_mode_list {
PM_NONE = 0,
PM_PRECISION,
PM_DRAG,
PM_CARET,
PM_HISTORY,
PM_VOLUME,
// safe range for custom modes with built in keycodes
PM_SAFE_RANGE,
// range for custom modes requiring custom activation/new keycodes
PM_ADVANCED_RANGE_START = ((QK_POINTING_MODE_MO_MAX - QK_POINTING_MODE_MO) + (QK_POINTING_MODE_TG_MAX - QK_POINTING_MODE_TG) + 1) / 2 + 1
};
// pointing mode aliases
#define PM_PRE PM_PRECISION
#define PM_DRG PM_DRAG
#define PM_CRT PM_CARET
#define PM_HST PM_HISTORY
#define PM_VOL PM_VOLUME
#include "quantum_keycodes_legacy.h"

+ 3
- 0
tests/test_common/keycode_table.cpp View File

@ -252,6 +252,9 @@ std::map<uint16_t, std::string> KEYCODE_ID_TABLE = {
{KC_RIGHT_SHIFT, "KC_RIGHT_SHIFT"},
{KC_RIGHT_ALT, "KC_RIGHT_ALT"},
{KC_RIGHT_GUI, "KC_RIGHT_GUI"},
{QK_PM_CYCLE_DEVICES, "QK_PM_CYCLE_DEVICES"},
{QK_PM_DEVICE_RIGHT, "QK_PM_DEVICE_RIGHT"},
{QK_PM_DEVICE_LEFT, "QK_PM_DEVICE_LEFT"},
{QK_SWAP_HANDS_TOGGLE, "QK_SWAP_HANDS_TOGGLE"},
{QK_SWAP_HANDS_TAP_TOGGLE, "QK_SWAP_HANDS_TAP_TOGGLE"},
{QK_SWAP_HANDS_MOMENTARY_ON, "QK_SWAP_HANDS_MOMENTARY_ON"},


Loading…
Cancel
Save