summaryrefslogtreecommitdiffstats
path: root/quantum/pointing_device/pointing_device.c
blob: 09f9e5efb7ea7e1492c5caa45c073362adc9ab39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
 * Copyright 2020 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 * Copyright 2021 Dasky (@daskygit)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pointing_device.h"
#include <string.h>
#include "timer.h"
#ifdef MOUSEKEY_ENABLE
#    include "mousekey.h"
#endif
#if (defined(POINTING_DEVICE_ROTATION_90) + defined(POINTING_DEVICE_ROTATION_180) + defined(POINTING_DEVICE_ROTATION_270)) > 1
#    error More than one rotation selected.  This is not supported.
#endif

#if defined(POINTING_DEVICE_LEFT) || defined(POINTING_DEVICE_RIGHT) || defined(POINTING_DEVICE_COMBINED)
#    ifndef SPLIT_POINTING_ENABLE
#        error "Using POINTING_DEVICE_LEFT or POINTING_DEVICE_RIGHT or POINTING_DEVICE_COMBINED, then SPLIT_POINTING_ENABLE is required but has not been defined"
#    endif
#endif

#if defined(SPLIT_POINTING_ENABLE)
#    include "transactions.h"
#    include "keyboard.h"

report_mouse_t shared_mouse_report = {};
uint16_t       shared_cpi          = 0;

/**
 * @brief Sets the shared mouse report used be pointing device task
 *
 * NOTE : Only available when using SPLIT_POINTING_ENABLE
 *
 * @param[in] new_mouse_report report_mouse_t
 */
void pointing_device_set_shared_report(report_mouse_t new_mouse_report) {
    shared_mouse_report = new_mouse_report;
}

/**
 * @brief Gets current pointing device CPI if supported
 *
 * Gets current cpi of the shared report and returns it as uint16_t
 *
 * NOTE : Only available when using SPLIT_POINTING_ENABLE
 *
 * @return cpi value as uint16_t
 */
uint16_t pointing_device_get_shared_cpi(void) {
    return shared_cpi;
}

#    if defined(POINTING_DEVICE_LEFT)
#        define POINTING_DEVICE_THIS_SIDE is_keyboard_left()
#    elif defined(POINTING_DEVICE_RIGHT)
#        define POINTING_DEVICE_THIS_SIDE !is_keyboard_left()
#    elif defined(POINTING_DEVICE_COMBINED)
#        define POINTING_DEVICE_THIS_SIDE true
#    endif

#endif // defined(SPLIT_POINTING_ENABLE)

static report_mouse_t local_mouse_report = {};

extern const pointing_device_driver_t pointing_device_driver;

/**
 * @brief Keyboard level code pointing device initialisation
 *
 */
__attribute__((weak)) void pointing_device_init_kb(void) {}

/**
 * @brief User level code pointing device initialisation
 *
 */
__attribute__((weak)) void pointing_device_init_user(void) {}

/**
 * @brief Weak function allowing for keyboard level mouse report modification
 *
 * Takes report_mouse_t struct allowing modification at keyboard level then returns report_mouse_t.
 *
 * @param[in] mouse_report report_mouse_t
 * @return report_mouse_t
 */
__attribute__((weak)) report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
    return pointing_device_task_user(mouse_report);
}

/**
 * @brief Weak function allowing for user level mouse report modification
 *
 * Takes report_mouse_t struct allowing modification at user level then returns report_mouse_t.
 *
 * @param[in] mouse_report report_mouse_t
 * @return report_mouse_t
 */
__attribute__((weak)) report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
    return mouse_report;
}

/**
 * @brief Handles pointing device buttons
 *
 * Returns modified button bitmask using bool pressed and selected pointing_device_buttons_t button in uint8_t buttons bitmask.
 *
 * @param buttons[in] uint8_t bitmask
 * @param pressed[in] bool
 * @param button[in] pointing_device_buttons_t value
 * @return Modified uint8_t bitmask buttons
 */
__attribute__((weak)) uint8_t pointing_device_handle_buttons(uint8_t buttons, bool pressed, pointing_device_buttons_t button) {
    if (pressed) {
        buttons |= 1 << (button);
    } else {
        buttons &= ~(1 << (button));
    }
    return buttons;
}

/**
 * @brief Initialises pointing device
 *
 * Initialises pointing device, perform driver init and optional keyboard/user level code.
 */
__attribute__((weak)) void pointing_device_init(void) {
#if defined(SPLIT_POINTING_ENABLE)
    if (!(POINTING_DEVICE_THIS_SIDE)) {
        return;
    }
#endif
    pointing_device_driver.init();
#ifdef POINTING_DEVICE_MOTION_PIN
    setPinInputHigh(POINTING_DEVICE_MOTION_PIN);
#endif
    pointing_device_init_kb();
    pointing_device_init_user();
}

/**
 * @brief Sends processed mouse report to host
 *
 * This sends the mouse report generated by pointing_device_task if changed since the last report. Once send zeros mouse report except buttons.
 *
 */
__attribute__((weak)) void pointing_device_send(void) {
    static report_mouse_t old_report = {};

    // If you need to do other things, like debugging, this is the place to do it.
    if (has_mouse_report_changed(&local_mouse_report, &old_report)) {
        host_mouse_send(&local_mouse_report);
    }
    // send it and 0 it out except for buttons, so those stay until they are explicity over-ridden using update_pointing_device
    local_mouse_report.x = 0;
    local_mouse_report.y = 0;
    local_mouse_report.v = 0;
    local_mouse_report.h = 0;

    memcpy(&old_report, &local_mouse_report, sizeof(local_mouse_report));
}

/**
 * @brief Adjust mouse report by any optional common pointing configuration defines
 *
 * This applies rotation or inversion to the mouse report as selected by the pointing device common configuration defines.
 *
 * @param mouse_report[in] takes a report_mouse_t to be adjusted
 * @return report_mouse_t with adjusted values
 */
report_mouse_t pointing_device_adjust_by_defines(report_mouse_t mouse_report) {
    // Support rotation of the sensor data
#if defined(POINTING_DEVICE_ROTATION_90) || defined(POINTING_DEVICE_ROTATION_180) || defined(POINTING_DEVICE_ROTATION_270)
    mouse_xy_report_t x = mouse_report.x;
    mouse_xy_report_t y = mouse_report.y;
#    if defined(POINTING_DEVICE_ROTATION_90)
    mouse_report.x = y;
    mouse_report.y = -x;
#    elif defined(POINTING_DEVICE_ROTATION_180)
    mouse_report.x = -x;
    mouse_report.y = -y;
#    elif defined(POINTING_DEVICE_ROTATION_270)
    mouse_report.x = -y;
    mouse_report.y = x;
#    else
#        error "How the heck did you get here?!"
#    endif
#endif
    // Support Inverting the X and Y Axises
#if defined(POINTING_DEVICE_INVERT_X)
    mouse_report.x = -mouse_report.x;
#endif
#if defined(POINTING_DEVICE_INVERT_Y)
    mouse_report.y = -mouse_report.y;
#endif
    return mouse_report;
}

/**
 * @brief Retrieves and processes pointing device data.
 *
 * This function is part of the keyboard loop and retrieves the mouse report from the pointing device driver.
 * It applies any optional configuration e.g. rotation or axis inversion and then initiates a send.
 *
 */
__attribute__((weak)) void pointing_device_task(void) {
#if defined(SPLIT_POINTING_ENABLE)
    // Don't poll the target side pointing device.
    if (!is_keyboard_master()) {
        return;
    };
#endif

#if (POINTING_DEVICE_TASK_THROTTLE_MS > 0)
    static uint32_t last_exec = 0;
    if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
        return;
    }
    last_exec = timer_read32();
#endif

    // Gather report info
#ifdef POINTING_DEVICE_MOTION_PIN
#    if defined(SPLIT_POINTING_ENABLE)
#        error POINTING_DEVICE_MOTION_PIN not supported when sharing the pointing device report between sides.
#    endif
    if (!readPin(POINTING_DEVICE_MOTION_PIN))
#endif

#if defined(SPLIT_POINTING_ENABLE)
#    if defined(POINTING_DEVICE_COMBINED)
        static uint8_t old_buttons = 0;
    local_mouse_report.buttons = old_buttons;
    local_mouse_report         = pointing_device_driver.get_report(local_mouse_report);
    old_buttons                = local_mouse_report.buttons;
#    elif defined(POINTING_DEVICE_LEFT) || defined(POINTING_DEVICE_RIGHT)
        local_mouse_report = POINTING_DEVICE_THIS_SIDE ? pointing_device_driver.get_report(local_mouse_report) : shared_mouse_report;
#    else
#        error "You need to define the side(s) the pointing device is on. POINTING_DEVICE_COMBINED / POINTING_DEVICE_LEFT / POINTING_DEVICE_RIGHT"
#    endif
#else
    local_mouse_report = pointing_device_driver.get_report(local_mouse_report);
#endif // defined(SPLIT_POINTING_ENABLE)

    // allow kb to intercept and modify report
#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
    if (is_keyboard_left()) {
        local_mouse_report  = pointing_device_adjust_by_defines(local_mouse_report);
        shared_mouse_report = pointing_device_adjust_by_defines_right(shared_mouse_report);
    } else {
        local_mouse_report  = pointing_device_adjust_by_defines_right(local_mouse_report);
        shared_mouse_report = pointing_device_adjust_by_defines(shared_mouse_report);
    }
    local_mouse_report = is_keyboard_left() ? pointing_device_task_combined_kb(local_mouse_report, shared_mouse_report) : pointing_device_task_combined_kb(shared_mouse_report, local_mouse_report);
#else
    local_mouse_report = pointing_device_adjust_by_defines(local_mouse_report);
    local_mouse_report = pointing_device_task_kb(local_mouse_report);
#endif
    // combine with mouse report to ensure that the combined is sent correctly
#ifdef MOUSEKEY_ENABLE
    report_mouse_t mousekey_report = mousekey_get_report();
    local_mouse_report.buttons     = local_mouse_report.buttons | mousekey_report.buttons;
#endif
    pointing_device_send();
}

/**
 * @brief Gets current mouse report used by pointing device task
 *
 * @return report_mouse_t
 */
report_mouse_t pointing_device_get_report(void) {
    return local_mouse_report;
}

/**
 * @brief Sets mouse report used be pointing device task
 *
 * @param[in] mouse_report
 */
void pointing_device_set_report(report_mouse_t mouse_report) {
    local_mouse_report = mouse_report;
}

/**
 * @brief Gets current pointing device CPI if supported
 *
 * Gets current cpi from pointing device driver if supported and returns it as uint16_t
 *
 * @return cpi value as uint16_t
 */
uint16_t pointing_device_get_cpi(void) {
#if defined(SPLIT_POINTING_ENABLE)
    return POINTING_DEVICE_THIS_SIDE ? pointing_device_driver.get_cpi() : shared_cpi;
#else
    return pointing_device_driver.get_cpi();
#endif
}

/**
 * @brief Set pointing device CPI if supported
 *
 * Takes a uint16_t value to set pointing device cpi if supported by driver.
 *
 * @param[in] cpi uint16_t value.
 */
void pointing_device_set_cpi(uint16_t cpi) {
#if defined(SPLIT_POINTING_ENABLE)
    if (POINTING_DEVICE_THIS_SIDE) {
        pointing_device_driver.set_cpi(cpi);
    } else {
        shared_cpi = cpi;
    }
#else
    pointing_device_driver.set_cpi(cpi);
#endif
}

#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
/**
 * @brief Set pointing device CPI if supported
 *
 * Takes a bool and uint16_t and allows setting cpi for a single side when using 2 pointing devices with a split keyboard.
 *
 * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
 *
 * @param[in] left true = left, false = right.
 * @param[in] cpi uint16_t value.
 */
void pointing_device_set_cpi_on_side(bool left, uint16_t cpi) {
    bool local = (is_keyboard_left() & left) ? true : false;
    if (local) {
        pointing_device_driver.set_cpi(cpi);
    } else {
        shared_cpi = cpi;
    }
}

/**
 * @brief clamps int16_t to int8_t
 *
 * @param[in] int16_t value
 * @return int8_t clamped value
 */
static inline int8_t pointing_device_hv_clamp(int16_t value) {
    if (value < INT8_MIN) {
        return INT8_MIN;
    } else if (value > INT8_MAX) {
        return INT8_MAX;
    } else {
        return value;
    }
}

/**
 * @brief clamps int16_t to int8_t
 *
 * @param[in] clamp_range_t value
 * @return mouse_xy_report_t clamped value
 */
static inline mouse_xy_report_t pointing_device_xy_clamp(clamp_range_t value) {
    if (value < XY_REPORT_MIN) {
        return XY_REPORT_MIN;
    } else if (value > XY_REPORT_MAX) {
        return XY_REPORT_MAX;
    } else {
        return value;
    }
}
/**
 * @brief combines 2 mouse reports and returns 2
 *
 * Combines 2 report_mouse_t structs, clamping movement values to int8_t and ignores report_id then returns the resulting report_mouse_t struct.
 *
 * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
 *
 * @param[in] left_report left report_mouse_t
 * @param[in] right_report right report_mouse_t
 * @return combined report_mouse_t of left_report and right_report
 */
report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, report_mouse_t right_report) {
    left_report.x = pointing_device_xy_clamp((clamp_range_t)left_report.x + right_report.x);
    left_report.y = pointing_device_xy_clamp((clamp_range_t)left_report.y + right_report.y);
    left_report.h = pointing_device_hv_clamp((int16_t)left_report.h + right_report.h);
    left_report.v = pointing_device_hv_clamp((int16_t)left_report.v + right_report.v);
    left_report.buttons |= right_report.buttons;
    return left_report;
}

/**
 * @brief Adjust mouse report by any optional right pointing configuration defines
 *
 * This applies rotation or inversion to the mouse report as selected by the pointing device common configuration defines.
 *
 * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
 *
 * @param[in] mouse_report report_mouse_t to be adjusted
 * @return report_mouse_t with adjusted values
 */
report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_report) {
    // Support rotation of the sensor data
#    if defined(POINTING_DEVICE_ROTATION_90_RIGHT) || defined(POINTING_DEVICE_ROTATION_180_RIGHT) || defined(POINTING_DEVICE_ROTATION_270_RIGHT)
    mouse_xy_report_t x = mouse_report.x;
    mouse_xy_report_t y = mouse_report.y;
#        if defined(POINTING_DEVICE_ROTATION_90_RIGHT)
    mouse_report.x = y;
    mouse_report.y = -x;
#        elif defined(POINTING_DEVICE_ROTATION_180_RIGHT)
    mouse_report.x = -x;
    mouse_report.y = -y;
#        elif defined(POINTING_DEVICE_ROTATION_270_RIGHT)
    mouse_report.x = -y;
    mouse_report.y = x;
#        else
#            error "How the heck did you get here?!"
#        endif
#    endif
    // Support Inverting the X and Y Axises
#    if defined(POINTING_DEVICE_INVERT_X_RIGHT)
    mouse_report.x = -mouse_report.x;
#    endif
#    if defined(POINTING_DEVICE_INVERT_Y_RIGHT)
    mouse_report.y = -mouse_report.y;
#    endif
    return mouse_report;
}

/**
 * @brief Weak function allowing for keyboard level mouse report modification
 *
 * Takes 2 report_mouse_t structs allowing individual modification of sides at keyboard level then returns pointing_device_task_combined_user.
 *
 * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
 *
 * @param[in] left_report report_mouse_t
 * @param[in] right_report report_mouse_t
 * @return pointing_device_task_combined_user(left_report, right_report) by default
 */
__attribute__((weak)) report_mouse_t pointing_device_task_combined_kb(report_mouse_t left_report, report_mouse_t right_report) {
    return pointing_device_task_combined_user(left_report, right_report);
}

/**
 * @brief Weak function allowing for user level mouse report modification
 *
 * Takes 2 report_mouse_t structs allowing individual modification of sides at user level then returns pointing_device_combine_reports.
 *
 * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
 *
 * @param[in] left_report report_mouse_t
 * @param[in] right_report report_mouse_t
 * @return pointing_device_combine_reports(left_report, right_report) by default
 */
__attribute__((weak)) report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report) {
    return pointing_device_combine_reports(left_report, right_report);
}
#endif