pointing_device_auto_mouse.c 15 KB

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