Skip to content

Instantly share code, notes, and snippets.

@muppetjones
Last active August 26, 2023 15:18
Show Gist options
  • Save muppetjones/bd7e7ff9bd4c503b37f567289a356721 to your computer and use it in GitHub Desktop.
Save muppetjones/bd7e7ff9bd4c503b37f567289a356721 to your computer and use it in GitHub Desktop.
QMK Mouse Movement via Encoders

Etch-a-mouse

NOTE: This gist is deprecated. I've moved everything into /users/muppetjones, and I have an open PR on the QMK repo. Eventually, this will probably be merged, and I'll (hopefully) remember to update this note.

/* Copyright 2020 Stephen J. Bush
*
* 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/>.
# Example
See https://github.com/muppetjones/qmk_firmware/tree/master/keyboards/kyria/keymaps/muppetjones
# Description
Use encoders to control mouse movement. Movement speed is determined by
how quickly and how many times you've "clicked" the encoder. Turning both
encoders together will yield diagonal movement.
Several of the macros defined in quantum/mousekey.h are reused here.
However, the mouse movement is actually performed using the pointing_device
features instead.
# Usage
- Add the following to your rules.mk
ENCODER_ENABLE = yes # Enables the use of one or more encoders
POINTING_DEVICE_ENABLE = yes
SRC += "encoder_mouse.c"
- Add the following block to your keymap.c
(NOTE: The `encoder_update_*` signature returns a bool in
more recent versions of QMK.)
#ifdef ENCODER_ENABLE
void encoder_update_user(uint8_t index, bool clockwise) {
# ifdef POINTING_DEVICE_ENABLE
encoder_update_mouse(index, clockwise);
# endif
return;
#endif
# Description
- The faster you turn, the faster the mouse moves.
- The more turns for a given axis, the greater the movement on that axis.
*/
#include QMK_KEYBOARD_H
#include "encoder_mouse.h"
#include "pointing_device.h"
#ifdef POINTING_DEVICE_ENABLE
# ifdef ENCODER_ENABLE
/** Track movement separately in both directions. This will allow us to
* smooth out the movement along diagonals
*/
typedef struct {
bool clockwise;
uint8_t count;
uint16_t timer;
uint16_t elapsed;
} key_tracker_t;
static key_tracker_t tracker_x = {false, 0, 0, 0};
static key_tracker_t tracker_y = {false, 0, 0, 0};
/**
* @brief Calculate the mouse move units for the given tracker.
*
* By using a key tracker rederence, we can minimize the amount of space
* required on the stack. As we will have the tracker object, we will also
* take the clockwise direction into account, which completely internalizes
* the movement unit logic within this single function.
*
* @param tracker: Pointer to a key tracker object.
* @return A integer from -127 to 127
*/
static int8_t move_unit(key_tracker_t *tracker) {
if (0 == tracker->count) return 0;
const uint16_t modifier = TAPPING_TERM_MOUSE_ENCODER < tracker->elapsed ? 1 : (TAPPING_TERM_MOUSE_ENCODER - tracker->elapsed) >> 1;
uint16_t speed = MOUSEKEY_INITIAL_SPEED + MOUSEKEY_MOVE_DELTA * modifier * (tracker->count >> 1);
/* convert speed to USB mouse speed 1 to 127 */
speed = (uint8_t)(speed / (1000.0f / MOUSEKEY_INTERVAL));
speed = speed < 1 ? 1 : speed;
return (tracker->clockwise ? 1 : -1) * (speed > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : speed);
}
/**
* @brief Update key press tracker
*
* Update the time elapsed since the last keypress.
* If the key has not been pressed since the tapping term, then reset the count to zero.
* If the key was pressed, update the timer and increment the count.
* Number of keypresses will degrade based on tapping term and zero out based
* on the persistenc term.
*
* @param tracker: The object to update
* @param pressed: A boolean indicating whether or not the key was pressed
* @return None.
*/
static void update_tracker(key_tracker_t *tracker, bool pressed, bool clockwise) {
tracker->elapsed = timer_elapsed(tracker->timer);
if (pressed) {
tracker->timer = timer_read();
tracker->count += 1;
tracker->clockwise = clockwise;
} else if (TAPPING_TERM_PERSISTENCE < tracker->elapsed) {
tracker->count = 0;
} else if (TAPPING_TERM_MOUSE_ENCODER < tracker->elapsed) {
tracker->count >>= 1;
}
}
void encoder_update_mouse(uint8_t index, bool clockwise) {
report_mouse_t curr_report = pointing_device_get_report();
update_tracker(&tracker_x, 0 == index, clockwise);
update_tracker(&tracker_y, 1 == index, clockwise);
curr_report.x += move_unit(&tracker_x);
curr_report.y += move_unit(&tracker_y);
pointing_device_set_report(curr_report);
pointing_device_send();
}
# endif
#endif
/* Copyright 2020 Stephen J. Bush
*
* 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_ENABLE
# ifdef ENCODER_ENABLE
/* max value on report descriptor */
# ifndef MOUSEKEY_MOVE_MAX
# define MOUSEKEY_MOVE_MAX 127
# elif MOUSEKEY_MOVE_MAX > 127
# error MOUSEKEY_MOVE_MAX needs to be smaller than 127
# endif
# ifndef MOUSEKEY_MOVE_DELTA
# define MOUSEKEY_MOVE_DELTA 25
# endif
# ifndef MOUSEKEY_INITIAL_SPEED
# define MOUSEKEY_INITIAL_SPEED 100
# endif
# ifndef MOUSEKEY_INTERVAL
# define MOUSEKEY_INTERVAL 50
# endif
/** Amount of time (ms) before zeroing out the count.
* A higher value will result in smoother curves but may lower accuracy
*/
# ifndef TAPPING_TERM_PERSISTENCE
# define TAPPING_TERM_PERSISTENCE 601
# endif
/** Amount of time (ms) to register consecutive key presses
* A higher value will smooth out mouse movement and increase speed for
* consecutive presses.
*/
# ifndef TAPPING_TERM_MOUSE_ENCODER
# define TAPPING_TERM_MOUSE_ENCODER 200
# endif
/** @brief Update mouse position based on encoder movement.
* @param index The encoder index. 0 controls x-axis; 1 controls y-axis.
* @param clockwise Indicates direction encoder was turned.
* @returns None.
*/
void encoder_update_mouse(uint8_t index, bool clockwise);
# endif
#endif
@rflcrz
Copy link

rflcrz commented Mar 8, 2022

Hi, @muppetjones! Thanks for sharing.

I added the suggested lines to rules.mk and keymap.c, but I got:

QMK Firmware 0.16.0
Making splitkb/kyria/rev1 with keymap rflcrz


 * POINTING_DEVICE_DRIVER= is not a valid pointing device type                                         [ERRORS]

builddefs/common_features.mk:114: *** Invalid POINTING_DEVICE_DRIVER.  Stop.
make: *** [splitkb/kyria/rev1:rflcrz] Error 1
Make finished with errors

I think this is related to breaking changes made to the Pointing Device support.

"Usages of POINTING_DEVICE_ENABLE = yes in rules.mk files now need to be accompanied by a corresponding POINTING_DEVICE_DRIVER = ??? line, specifying which driver to use during the build. Existing keyboards have already been migrated across to the new usage pattern, so most likely no change is required by users." (source)

Unfortunately I don't have the programming skills to figure out the solution.

Could you give me some help?

Thanks!

@muppetjones
Copy link
Author

@rflcrz I've incorporated this into my QMK userspace. I recently updated it, and the compile worked (I was not able to flash by board at the time).

I'd recommend starting with the updates found in that PR. Happy to help if that doesn't solve the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment