logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

pointing_device_auto_mouse.c (16887B)


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