diff --git a/README.md b/README.md index 65a66ea..e9f54ef 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Who knew a keyboard could do so much? * [Word selection](https://getreuer.info/posts/keyboards/select-word/index.html) – QMK macro for convenient word or line selection +* [Mouse Turbo + Click](https://getreuer.info/posts/keyboards/mouse-turbo-click/index.html) + – QMK macro that clicks the mouse rapidly + ## My keymap diff --git a/features/mouse_turbo_click.c b/features/mouse_turbo_click.c new file mode 100644 index 0000000..f22ed9c --- /dev/null +++ b/features/mouse_turbo_click.c @@ -0,0 +1,112 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// For full documentation, see +// https://getreuer.info/posts/keyboards/mouse-turbo-click + +#include "features/mouse_turbo_click.h" + +// This library relies on that mouse keys and the deferred execution API are +// enabled, which we check for here. Enable them in you rules.mk by setting: +// MOUSEKEY_ENABLE = yes +// DEFERRED_EXEC_ENABLE = yes +#if !defined(MOUSEKEY_ENABLE) +#error "mouse_turbo_click: Please set `MOUSEKEY_ENABLE = yes` in rules.mk." +#elif !defined(DEFERRED_EXEC_ENABLE) +#error "mouse_turbo_click: Please set `DEFERRED_EXEC_ENABLE = yes` in rules.mk." +#else + +// The click period in milliseconds. For instance a period of 200 ms would be 5 +// clicks per second. Smaller period implies faster clicking. +// +// WARNING: The keyboard might become unresponsive if the period is too small. +// I suggest setting this no smaller than 50. +#define CLICK_PERIOD_MS 80 + +static deferred_token click_token = INVALID_DEFERRED_TOKEN; +static bool click_registered = false; + +// Callback used with deferred execution. It alternates between registering and +// unregisting the mouse button. +static uint32_t turbo_click_callback(uint32_t trigger_time, void *cb_arg) { + if (click_registered) { + unregister_code16(KC_MS_BTN1); + click_registered = false; + } else { + click_registered = true; + register_code16(KC_MS_BTN1); + } + return CLICK_PERIOD_MS / 2; // Execute callback again in half a period. +} + +// Starts Turbo Click, begins the callback. +static void turbo_click_start(void) { + if (click_token == INVALID_DEFERRED_TOKEN) { + uint32_t next_delay_ms = turbo_click_callback(0, NULL); + click_token = defer_exec(next_delay_ms, turbo_click_callback, NULL); + } +} + +// Stops Turbo Click, cancels the callback. +static void turbo_click_stop(void) { + if (click_token != INVALID_DEFERRED_TOKEN) { + cancel_deferred_exec(click_token); + click_token = INVALID_DEFERRED_TOKEN; + if (click_registered) { + // If mouse button is currently registered, release it. + unregister_code16(KC_MS_BTN1); + click_registered = false; + } + } +} + +bool process_mouse_turbo_click(uint16_t keycode, keyrecord_t* record, + uint16_t turbo_click_keycode) { + static bool locked = false; + static bool tapped = false; + static uint16_t tap_timer = 0; + + if (keycode == turbo_click_keycode) { + if (record->event.pressed) { // Turbo Click key was pressed. + if (tapped && !timer_expired(record->event.time, tap_timer)) { + // If the key was recently tapped, lock turbo click. + locked = true; + } else if (locked) { + // Otherwise if currently locked, unlock and stop. + locked = false; + tapped = false; + turbo_click_stop(); + return false; + } + // Set that the first tap occurred in a potential double tap. + tapped = true; + tap_timer = record->event.time + TAPPING_TERM; + + turbo_click_start(); + } else if (!locked) { + // If not currently locked, stop on key release. + turbo_click_stop(); + } + + return false; + } else { + // On an event with any other key, reset the double tap state. + tapped = false; + return true; + } +} + +#endif + diff --git a/features/mouse_turbo_click.h b/features/mouse_turbo_click.h new file mode 100644 index 0000000..0e2fa4f --- /dev/null +++ b/features/mouse_turbo_click.h @@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Mouse Turbo Click - click the mouse rapidly +// +// This library implements a "Turbo Click" button that clicks the mouse rapidly, +// implemented using mouse keys and a periodic callback function: +// +// * Pressing and holding the Turbo Click button sends rapid mouse clicks, about +// 12 clicks per second. +// +// * Quickly double tapping the Turbo Click button "locks" it. Rapid mouse +// clicks are sent until the Turbo Click button is tapped again. +// +// NOTE: Mouse keys and deferred execution must be enabled; in rules.mk set +// MOUSEKEY_ENABLE = yes, DEFERRED_EXEC_ENABLE = yes. +// +// +// For full documentation, see +// https://getreuer.info/posts/keyboards/mouse-turbo-click + +#pragma once + +#include QMK_KEYBOARD_H + +// In your keymap, define a custom keycode to use for Turbo Click. Then handle +// Turbo Click from your `process_record_user` function by calling +// `process_mouse_turbo_click`, passing your custom keycode for the +// `turbo_click_keycode` arg: +// +// #include "features/mouse_turbo_click.h" +// +// bool process_record_user(uint16_t keycode, keyrecord_t* record) { +// if (!process_mouse_turbo_click(keycode, record, TURBO)) { return false; } +// // Your macros ... +// +// return true; +// } +bool process_mouse_turbo_click(uint16_t keycode, keyrecord_t* record, + uint16_t turbo_click_keycode); +