pointing_device_auto_mouse.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /* Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
  2. * Copyright 2022 Alabastard
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
  18. # include <string.h>
  19. # include "pointing_device_auto_mouse.h"
  20. # include "debug.h"
  21. # include "action_util.h"
  22. # include "quantum_keycodes.h"
  23. /* local data structure for tracking auto mouse */
  24. static auto_mouse_context_t auto_mouse_context = {
  25. .config.layer = (uint8_t)(AUTO_MOUSE_DEFAULT_LAYER),
  26. .config.timeout = (uint16_t)(AUTO_MOUSE_TIME),
  27. .config.debounce = (uint8_t)(AUTO_MOUSE_DEBOUNCE),
  28. };
  29. /* local functions */
  30. static bool is_mouse_record(uint16_t keycode, keyrecord_t* record);
  31. static void auto_mouse_reset(void);
  32. /* check for target layer deactivation overrides */
  33. static inline bool layer_hold_check(void) {
  34. return get_auto_mouse_toggle() ||
  35. # ifndef NO_ACTION_ONESHOT
  36. get_oneshot_layer() == (AUTO_MOUSE_TARGET_LAYER) ||
  37. # endif
  38. false;
  39. }
  40. /* check all layer activation criteria */
  41. static inline bool is_auto_mouse_active(void) {
  42. return auto_mouse_context.status.is_activated || auto_mouse_context.status.mouse_key_tracker || layer_hold_check();
  43. }
  44. /**
  45. * @brief Get auto mouse enable state
  46. *
  47. * Return is_enabled value
  48. *
  49. * @return bool true: auto mouse enabled false: auto mouse disabled
  50. */
  51. bool get_auto_mouse_enable(void) {
  52. return auto_mouse_context.config.is_enabled;
  53. }
  54. /**
  55. * @brief get current target layer index
  56. *
  57. * NOTE: (AUTO_MOUSE_TARGET_LAYER) is an alias for this function
  58. *
  59. * @return uint8_t target layer index
  60. */
  61. uint8_t get_auto_mouse_layer(void) {
  62. return auto_mouse_context.config.layer;
  63. }
  64. /**
  65. * @brief Get the current timeout to turn off mouse layer
  66. *
  67. * @return uint16_t timeout in ms
  68. */
  69. uint16_t get_auto_mouse_timeout(void) {
  70. return auto_mouse_context.config.timeout;
  71. }
  72. /**
  73. * @brief Get the auto mouse debouncing timeout
  74. *
  75. * @return uint8_t
  76. */
  77. uint8_t get_auto_mouse_debounce(void) {
  78. return auto_mouse_context.config.debounce;
  79. }
  80. /**
  81. * @brief get layer_toggled value
  82. *
  83. * @return bool of current layer_toggled state
  84. */
  85. bool get_auto_mouse_toggle(void) {
  86. return auto_mouse_context.status.is_toggled;
  87. }
  88. /**
  89. * @brief Reset auto mouse context
  90. *
  91. * Clear timers and status
  92. *
  93. * NOTE: this will set is_toggled to false so careful when using it
  94. */
  95. static void auto_mouse_reset(void) {
  96. memset(&auto_mouse_context.status, 0, sizeof(auto_mouse_context.status));
  97. memset(&auto_mouse_context.timer, 0, sizeof(auto_mouse_context.timer));
  98. }
  99. /**
  100. * @brief Set auto mouse enable state
  101. *
  102. * Set local auto mouse enabled state
  103. *
  104. * @param[in] state bool
  105. */
  106. void set_auto_mouse_enable(bool enable) {
  107. // skip if unchanged
  108. if (auto_mouse_context.config.is_enabled == enable) return;
  109. auto_mouse_context.config.is_enabled = enable;
  110. auto_mouse_reset();
  111. }
  112. /**
  113. * @brief Change target layer for auto mouse
  114. *
  115. * Sets input as the new target layer if different from current and resets auto mouse
  116. *
  117. * NOTE: remove_auto_mouse_layer(state, false) or auto_mouse_layer_off should be called
  118. * before this function to avoid issues with layers getting stuck
  119. *
  120. * @param[in] layer uint8_t
  121. */
  122. void set_auto_mouse_layer(uint8_t layer) {
  123. // skip if unchanged
  124. if (auto_mouse_context.config.layer == layer) return;
  125. auto_mouse_context.config.layer = layer;
  126. auto_mouse_reset();
  127. }
  128. /**
  129. * @brief Changes the timeout for the mouse auto layer to be disabled
  130. *
  131. * @param timeout
  132. */
  133. void set_auto_mouse_timeout(uint16_t timeout) {
  134. if (auto_mouse_context.config.timeout == timeout) return;
  135. auto_mouse_context.config.timeout = timeout;
  136. auto_mouse_reset();
  137. }
  138. /**
  139. * @brief Set the auto mouse key debounce
  140. *
  141. * @param debounce
  142. */
  143. void set_auto_mouse_debounce(uint8_t debounce) {
  144. if (auto_mouse_context.config.debounce == debounce) return;
  145. auto_mouse_context.config.debounce = debounce;
  146. auto_mouse_reset();
  147. }
  148. /**
  149. * @brief toggle mouse layer setting
  150. *
  151. * Change state of local layer_toggled bool meant to track when the mouse layer is toggled on by other means
  152. *
  153. * NOTE: While is_toggled is true it will prevent deactiving target layer (but not activation)
  154. */
  155. void auto_mouse_toggle(void) {
  156. auto_mouse_context.status.is_toggled ^= 1;
  157. auto_mouse_context.timer.delay = 0;
  158. }
  159. /**
  160. * @brief Remove current auto mouse target layer from layer state
  161. *
  162. * Will remove auto mouse target layer from given layer state if appropriate.
  163. *
  164. * NOTE: Removal can be forced, ignoring appropriate critera
  165. *
  166. * @params state[in] layer_state_t original layer state
  167. * @params force[in] bool force removal
  168. *
  169. * @return layer_state_t modified layer state
  170. */
  171. layer_state_t remove_auto_mouse_layer(layer_state_t state, bool force) {
  172. if (force || ((AUTO_MOUSE_ENABLED) && !layer_hold_check())) {
  173. state &= ~((layer_state_t)1 << (AUTO_MOUSE_TARGET_LAYER));
  174. }
  175. return state;
  176. }
  177. /**
  178. * @brief Disable target layer
  179. *
  180. * Will disable target layer if appropriate.
  181. * NOTE: NOT TO BE USED in layer_state_set stack!!!
  182. */
  183. void auto_mouse_layer_off(void) {
  184. if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && (AUTO_MOUSE_ENABLED) && !layer_hold_check()) {
  185. layer_off((AUTO_MOUSE_TARGET_LAYER));
  186. }
  187. }
  188. /**
  189. * @brief Weak function to handel testing if pointing_device is active
  190. *
  191. * Will trigger target layer activation(if delay timer has expired) and prevent deactivation when true.
  192. * May be replaced by bool in report_mouse_t in future
  193. *
  194. * NOTE: defined weakly to allow for changing and adding conditions for specific hardware/customization
  195. *
  196. * @param[in] mouse_report report_mouse_t
  197. * @return bool of pointing_device activation
  198. */
  199. __attribute__((weak)) bool auto_mouse_activation(report_mouse_t mouse_report) {
  200. return mouse_report.x != 0 || mouse_report.y != 0 || mouse_report.h != 0 || mouse_report.v != 0 || mouse_report.buttons;
  201. }
  202. /**
  203. * @brief Update the auto mouse based on mouse_report
  204. *
  205. * Handel activation/deactivation of target layer based on auto_mouse_activation and state timers and local key/layer tracking data
  206. *
  207. * @param[in] mouse_report report_mouse_t
  208. */
  209. void pointing_device_task_auto_mouse(report_mouse_t mouse_report) {
  210. // skip if disabled, delay timer running, or debounce
  211. if (!(AUTO_MOUSE_ENABLED) || timer_elapsed(auto_mouse_context.timer.active) <= auto_mouse_context.config.debounce || timer_elapsed(auto_mouse_context.timer.delay) <= AUTO_MOUSE_DELAY) {
  212. return;
  213. }
  214. // update activation and reset debounce
  215. auto_mouse_context.status.is_activated = auto_mouse_activation(mouse_report);
  216. if (is_auto_mouse_active()) {
  217. auto_mouse_context.timer.active = timer_read();
  218. auto_mouse_context.timer.delay = 0;
  219. if (!layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
  220. layer_on((AUTO_MOUSE_TARGET_LAYER));
  221. }
  222. } else if (layer_state_is((AUTO_MOUSE_TARGET_LAYER)) && timer_elapsed(auto_mouse_context.timer.active) > auto_mouse_context.config.timeout) {
  223. layer_off((AUTO_MOUSE_TARGET_LAYER));
  224. auto_mouse_context.timer.active = 0;
  225. }
  226. }
  227. /**
  228. * @brief Handle mouskey event
  229. *
  230. * Increments/decrements mouse_key_tracker and restart active timer
  231. *
  232. * @param[in] pressed bool
  233. */
  234. void auto_mouse_keyevent(bool pressed) {
  235. if (pressed) {
  236. auto_mouse_context.status.mouse_key_tracker++;
  237. } else {
  238. auto_mouse_context.status.mouse_key_tracker--;
  239. }
  240. auto_mouse_context.timer.delay = 0;
  241. }
  242. /**
  243. * @brief Handle auto mouse non mousekey reset
  244. *
  245. * Start/restart delay timer and reset auto mouse on keydown as well as turn the
  246. * target layer off if on and reset toggle status
  247. *
  248. * NOTE: NOT TO BE USED in layer_state_set stack!!!
  249. *
  250. * @param[in] pressed bool
  251. */
  252. void auto_mouse_reset_trigger(bool pressed) {
  253. if (pressed) {
  254. if (layer_state_is((AUTO_MOUSE_TARGET_LAYER))) {
  255. layer_off((AUTO_MOUSE_TARGET_LAYER));
  256. };
  257. auto_mouse_reset();
  258. }
  259. auto_mouse_context.timer.delay = timer_read();
  260. }
  261. /**
  262. * @brief handle key events processing for auto mouse
  263. *
  264. * Will process keys differently depending on if key is defined as mousekey or not.
  265. * Some keys have built in behaviour(not overwritable):
  266. * mouse buttons : auto_mouse_keyevent()
  267. * non-mouse keys : auto_mouse_reset_trigger()
  268. * mod keys : skip auto mouse key processing
  269. * mod tap : skip on hold (mod keys)
  270. * QK mods e.g. LCTL(kc): default to non-mouse key, add at kb/user level as needed
  271. * non target layer keys: skip auto mouse key processing (same as mod keys)
  272. * MO(target layer) : auto_mouse_keyevent()
  273. * target layer toggles : auto_mouse_toggle() (on both key up and keydown)
  274. * target layer tap : default processing on tap mouse key on hold
  275. * all other keycodes : default to non-mouse key, add at kb/user level as needed
  276. *
  277. * Will deactivate target layer once a non mouse key is pressed if nothing is holding the layer active
  278. * such as held mousekey, toggled current target layer, or auto_mouse_activation is true
  279. *
  280. * @params keycode[in] uint16_t
  281. * @params record[in] keyrecord_t pointer
  282. */
  283. bool process_auto_mouse(uint16_t keycode, keyrecord_t* record) {
  284. // skip if not enabled or mouse_layer not set
  285. if (!(AUTO_MOUSE_ENABLED)) return true;
  286. switch (keycode) {
  287. // Skip Mod keys to avoid layer reset
  288. case KC_LEFT_CTRL ... KC_RIGHT_GUI:
  289. case QK_MODS ... QK_MODS_MAX:
  290. break;
  291. // TO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
  292. case QK_TO ... QK_TO_MAX:
  293. if (QK_TO_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  294. if (!(record->event.pressed)) auto_mouse_toggle();
  295. }
  296. break;
  297. // TG((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
  298. case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
  299. if (QK_TOGGLE_LAYER_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  300. if (!(record->event.pressed)) auto_mouse_toggle();
  301. }
  302. break;
  303. // MO((AUTO_MOUSE_TARGET_LAYER))-------------------------------------------------------------------------------
  304. case QK_MOMENTARY ... QK_MOMENTARY_MAX:
  305. if (QK_MOMENTARY_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  306. auto_mouse_keyevent(record->event.pressed);
  307. }
  308. // DF ---------------------------------------------------------------------------------------------------------
  309. case QK_DEF_LAYER ... QK_DEF_LAYER_MAX:
  310. # ifndef NO_ACTION_ONESHOT
  311. // OSL((AUTO_MOUSE_TARGET_LAYER))------------------------------------------------------------------------------
  312. case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
  313. case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
  314. # endif
  315. break;
  316. // LM((AUTO_MOUSE_TARGET_LAYER), mod)--------------------------------------------------------------------------
  317. case QK_LAYER_MOD ... QK_LAYER_MOD_MAX:
  318. if (QK_LAYER_MOD_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  319. auto_mouse_keyevent(record->event.pressed);
  320. }
  321. break;
  322. // TT((AUTO_MOUSE_TARGET_LAYER))---------------------------------------------------------------------------
  323. # ifndef NO_ACTION_TAPPING
  324. case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
  325. if (QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  326. auto_mouse_keyevent(record->event.pressed);
  327. # if TAPPING_TOGGLE != 0
  328. if (record->tap.count == TAPPING_TOGGLE) {
  329. if (record->event.pressed) {
  330. auto_mouse_context.status.mouse_key_tracker--;
  331. } else {
  332. auto_mouse_toggle();
  333. auto_mouse_context.status.mouse_key_tracker++;
  334. }
  335. }
  336. # endif
  337. }
  338. break;
  339. // LT((AUTO_MOUSE_TARGET_LAYER), kc)---------------------------------------------------------------------------
  340. case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
  341. if (!record->tap.count) {
  342. if (QK_LAYER_TAP_GET_LAYER(keycode) == (AUTO_MOUSE_TARGET_LAYER)) {
  343. auto_mouse_keyevent(record->event.pressed);
  344. }
  345. break;
  346. }
  347. // MT(kc) only skip on hold
  348. case QK_MOD_TAP ... QK_MOD_TAP_MAX:
  349. if (!record->tap.count) break;
  350. # endif
  351. // QK_MODS goes to default
  352. default:
  353. // skip on no event
  354. if (IS_NOEVENT(record->event)) break;
  355. // check if keyrecord is mousekey
  356. if (is_mouse_record(keycode, record)) {
  357. auto_mouse_keyevent(record->event.pressed);
  358. } else if (!is_auto_mouse_active()) {
  359. // all non-mousekey presses restart delay timer and reset status
  360. auto_mouse_reset_trigger(record->event.pressed);
  361. }
  362. }
  363. if (auto_mouse_context.status.mouse_key_tracker < 0) {
  364. auto_mouse_context.status.mouse_key_tracker = 0;
  365. dprintf("key tracker error (<0) \n");
  366. }
  367. return true;
  368. }
  369. /**
  370. * @brief Local function to handle checking if a keycode is a mouse button
  371. *
  372. * Starts code stack for checking keyrecord if defined as mousekey
  373. *
  374. * @params keycode[in] uint16_t
  375. * @params record[in] keyrecord_t pointer
  376. * @return bool true: keyrecord is mousekey false: keyrecord is not mousekey
  377. */
  378. static bool is_mouse_record(uint16_t keycode, keyrecord_t* record) {
  379. // allow for keyboard to hook in and override if need be
  380. if (is_mouse_record_kb(keycode, record) || IS_MOUSEKEY(keycode)) return true;
  381. return false;
  382. }
  383. /**
  384. * @brief Weakly defined keyboard level callback for adding keyrecords as mouse keys
  385. *
  386. * Meant for redefinition at keyboard level and should return is_mouse_record_user by default at end of function
  387. *
  388. * @params keycode[in] uint16_t
  389. * @params record[in] keyrecord_t pointer
  390. * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
  391. */
  392. __attribute__((weak)) bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
  393. return is_mouse_record_user(keycode, record);
  394. }
  395. /**
  396. * @brief Weakly defined keymap/user level callback for adding keyrecords as mouse keys
  397. *
  398. * Meant for redefinition at keymap/user level and should return false by default at end of function
  399. *
  400. * @params keycode[in] uint16_t
  401. * @params record[in] keyrecord_t pointer
  402. * @return bool true: keyrecord is defined as mouse key false: keyrecord is not defined as mouse key
  403. */
  404. __attribute__((weak)) bool is_mouse_record_user(uint16_t keycode, keyrecord_t* record) {
  405. return false;
  406. }
  407. #endif // POINTING_DEVICE_AUTO_MOUSE_ENABLE