pointing_device_auto_mouse.c 14 KB

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