pointing_device_auto_mouse.c 15 KB

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