Digitizer feature improvements (#19034)

This commit is contained in:
Ryan 2022-11-13 10:28:11 +11:00 committed by GitHub
parent 8cecf7fad8
commit 6cc9513ab0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 298 additions and 118 deletions

View File

@ -1,35 +1,117 @@
## Digitizer # Digitizer :id=digitizer
The digitizer HID interface allows setting the mouse cursor position at absolute coordinates, unlike the Pointing Device feature that applies relative displacements. Digitizers allow the mouse cursor to be placed at absolute coordinates, unlike the [Pointing Device](feature_pointing_device.md) feature which applies relative displacements.
To enable the digitizer interface, add the following line to your rules.mk: This feature implements a stylus device with a tip switch and barrel switch (generally equivalent to the primary and secondary mouse buttons respectively). Tip pressure is not currently implemented.
## Usage :id=usage
Add the following to your `rules.mk`:
```make ```make
DIGITIZER_ENABLE = yes DIGITIZER_ENABLE = yes
``` ```
In order to change the mouse cursor position from your keymap.c file, include the digitizer header : ## Positioning :id=positioning
The X and Y coordinates are normalized, meaning their value must be set between 0 and 1. For the X component, the value `0` is the leftmost position, whereas the value `1` is the rightmost position. Similarly for the Y component, `0` is at the top and `1` at the bottom.
?> Since there is no display attached, the OS will likely map these coordinates to the virtual desktop. This may be important to know if you have multiple monitors.
## Examples :id=examples
This example simply places the cursor in the middle of the screen:
```c ```c
#include "digitizer.h" digitizer_in_range_on();
digitizer_set_position(0.5, 0.5);
``` ```
This gives you access to the `digitizer` structure which members allow you to change the cursor position. The "in range" indicator is required to be on for the change in coordinates to be taken. It can then be turned off again to signal the end of the digitizer interaction, but it is not strictly required.
The coordinates are normalized, meaning there value must be set between 0 and 1. For the `x` coordinate, the value `0` is the leftmost position, whereas the value `1` is the rightmost position. You can also modify the digitizer state directly, if you need to change multiple fields in a single report:
For the `y` coordinate, `0` is at the top and `1` at the bottom.
Here is an example setting the cursor in the middle of the screen:
```c ```c
digitizer_t digitizer; digitizer_state.in_range = true;
digitizer.x = 0.5; digitizer_state.dirty = true;
digitizer.y = 0.5; digitizer_flush();
digitizer.tipswitch = 0;
digitizer.inrange = 1;
digitizer_set_report(digitizer);
``` ```
The `tipswitch` member triggers what equates to a click when set to `1`. The `inrange` member is required for the change in coordinates to be taken. It can then be set to `0` in a new report to signal the end of the digitizer interaction, but it is not strictly required. `digitizer_state` is a struct of type `digitizer_t`.
Once all members are set to the desired value, the `status` member needs its bitmask `DZ_UPDATED` to be set so the report is sent during the next main loop iteration.
## API :id=api
### `struct digitizer_t` :id=api-digitizer-t
Contains the state of the digitizer.
#### Members :id=api-digitizer-t-members
- `bool in_range`
Indicates to the host that the contact is within range (ie. close to or in contact with the digitizer surface).
- `bool tip`
The state of the tip switch.
- `bool barrel`
The state of the barrel switch.
- `float x`
The X coordinate of the digitizer contact.
- `float y`
The Y coordinate of the digitizer contact.
- `bool dirty`
Whether the current state needs to be sent to the host.
---
### `void digitizer_flush(void)` :id=api-digitizer-flush
Send the digitizer report to the host if it is marked as dirty.
---
### `void digitizer_in_range_on(void)` :api-digitizer-in-range-on
Assert the "in range" indicator, and flush the report.
---
### `void digitizer_in_range_off(void)` :api-digitizer-in-range-off
Deassert the "in range" indicator, and flush the report.
---
### `void digitizer_tip_switch_on(void)` :api-digitizer-tip-switch-on
Assert the tip switch, and flush the report.
---
### `void digitizer_tip_switch_off(void)` :api-digitizer-tip-switch-off
Deassert the tip switch, and flush the report.
---
### `void digitizer_barrel_switch_on(void)` :api-digitizer-barrel-switch-on
Assert the barrel switch, and flush the report.
---
### `void digitizer_barrel_switch_off(void)` :api-digitizer-barrel-switch-off
Deassert the barrel switch, and flush the report.
---
### `void digitizer_set_position(float x, float y)` :api-digitizer-set-position
Set the absolute X and Y position of the digitizer contact, and flush the report.
#### Arguments :id=api-digitizer-set-position-arguments
- `float x`
The X value of the contact position, from 0 to 1.
- `float y`
The Y value of the contact position, from 0 to 1.

View File

@ -1,38 +1,58 @@
/* Copyright 2021 QMK /* Copyright 2021 QMK
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or * the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include QMK_KEYBOARD_H #include QMK_KEYBOARD_H
#include "digitizer.h" #include <math.h>
#include "math.h" enum custom_keycodes {
DG_TIP = SAFE_RANGE,
};
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {LAYOUT_ortho_1x1(KC_A)}; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
LAYOUT_ortho_1x1(DG_TIP)
};
uint32_t timer = 0; uint32_t timer = 0;
void keyboard_post_init_user(void) {
digitizer_in_range_on();
}
void matrix_scan_user() { void matrix_scan_user() {
if (timer_elapsed32(timer) < 200) { if (timer_elapsed32(timer) < 200) {
return; return;
} }
timer = timer_read32(); timer = timer_read32();
digitizer_t digitizer;
digitizer.x = 0.5 - 0.2 * cos(timer_read() / 250. / 6.28); float x = 0.5 - 0.2 * cos(timer / 250. / 6.28);
digitizer.y = 0.5 - 0.2 * sin(timer_read() / 250. / 6.28); float y = 0.5 - 0.2 * sin(timer / 250. / 6.28);
digitizer.tipswitch = 0; digitizer_set_position(x, y);
digitizer.inrange = 1; }
digitizer_set_report(digitizer);
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case DG_TIP:
if (record->event.pressed) {
digitizer_tip_switch_on();
} else {
digitizer_tip_switch_off();
}
return false;
}
return true;
} }

View File

@ -13,26 +13,64 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "digitizer.h" #include "digitizer.h"
digitizer_t digitizerReport = {.tipswitch = 0, .inrange = 0, .id = 0, .x = 0, .y = 0, .status = DZ_INITIALIZED}; digitizer_t digitizer_state = {
.in_range = false,
.tip = false,
.barrel = false,
.x = 0,
.y = 0,
.dirty = false,
};
__attribute__((weak)) void digitizer_send(void) { void digitizer_flush(void) {
if (digitizerReport.status & DZ_UPDATED) { if (digitizer_state.dirty) {
host_digitizer_send(&digitizerReport); host_digitizer_send(&digitizer_state);
digitizerReport.status &= ~DZ_UPDATED; digitizer_state.dirty = false;
} }
} }
__attribute__((weak)) void digitizer_task(void) { void digitizer_in_range_on(void) {
digitizer_send(); digitizer_state.in_range = true;
digitizer_state.dirty = true;
digitizer_flush();
} }
digitizer_t digitizer_get_report(void) { void digitizer_in_range_off(void) {
return digitizerReport; digitizer_state.in_range = false;
digitizer_state.dirty = true;
digitizer_flush();
} }
void digitizer_set_report(digitizer_t newDigitizerReport) { void digitizer_tip_switch_on(void) {
digitizerReport = newDigitizerReport; digitizer_state.tip = true;
digitizerReport.status |= DZ_UPDATED; digitizer_state.dirty = true;
} digitizer_flush();
}
void digitizer_tip_switch_off(void) {
digitizer_state.tip = false;
digitizer_state.dirty = true;
digitizer_flush();
}
void digitizer_barrel_switch_on(void) {
digitizer_state.barrel = true;
digitizer_state.dirty = true;
digitizer_flush();
}
void digitizer_barrel_switch_off(void) {
digitizer_state.barrel = false;
digitizer_state.dirty = true;
digitizer_flush();
}
void digitizer_set_position(float x, float y) {
digitizer_state.x = x;
digitizer_state.y = y;
digitizer_state.dirty = true;
digitizer_flush();
}

View File

@ -17,25 +17,70 @@
#include "quantum.h" #include "quantum.h"
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
enum digitizer_status { DZ_INITIALIZED = 1, DZ_UPDATED = 2 }; /**
* \defgroup digitizer
*
* HID Digitizer
* \{
*/
typedef struct { typedef struct {
int8_t tipswitch; bool in_range : 1;
int8_t inrange; bool tip : 1;
uint8_t id; bool barrel : 1;
float x; float x;
float y; float y;
uint8_t status : 2; bool dirty;
} digitizer_t; } digitizer_t;
extern digitizer_t digitizer; extern digitizer_t digitizer_state;
digitizer_t digitizer_get_report(void); /**
* \brief Send the digitizer report to the host if it is marked as dirty.
*/
void digitizer_flush(void);
void digitizer_set_report(digitizer_t newDigitizerReport); /**
* \brief Assert the "in range" indicator, and flush the report.
*/
void digitizer_in_range_on(void);
void digitizer_task(void); /**
* \brief Deassert the "in range" indicator, and flush the report.
*/
void digitizer_in_range_off(void);
/**
* \brief Assert the tip switch, and flush the report.
*/
void digitizer_tip_switch_on(void);
/**
* \brief Deassert the tip switch, and flush the report.
*/
void digitizer_tip_switch_off(void);
/**
* \brief Assert the barrel switch, and flush the report.
*/
void digitizer_barrel_switch_on(void);
/**
* \brief Deassert the barrel switch, and flush the report.
*/
void digitizer_barrel_switch_off(void);
/**
* \brief Set the absolute X and Y position of the digitizer contact, and flush the report.
*
* \param x The X value of the contact position, from 0 to 1.
* \param y The Y value of the contact position, from 0 to 1.
*/
void digitizer_set_position(float x, float y);
void host_digitizer_send(digitizer_t *digitizer); void host_digitizer_send(digitizer_t *digitizer);
/** \} */

View File

@ -90,9 +90,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#if defined(CRC_ENABLE) #if defined(CRC_ENABLE)
# include "crc.h" # include "crc.h"
#endif #endif
#ifdef DIGITIZER_ENABLE
# include "digitizer.h"
#endif
#ifdef VIRTSER_ENABLE #ifdef VIRTSER_ENABLE
# include "virtser.h" # include "virtser.h"
#endif #endif
@ -662,10 +659,6 @@ void keyboard_task(void) {
joystick_task(); joystick_task();
#endif #endif
#ifdef DIGITIZER_ENABLE
digitizer_task();
#endif
#ifdef BLUETOOTH_ENABLE #ifdef BLUETOOTH_ENABLE
bluetooth_task(); bluetooth_task();
#endif #endif

View File

@ -207,6 +207,10 @@ extern layer_state_t layer_state;
# include "joystick.h" # include "joystick.h"
#endif #endif
#ifdef DIGITIZER_ENABLE
# include "digitizer.h"
#endif
#ifdef VIA_ENABLE #ifdef VIA_ENABLE
# include "via.h" # include "via.h"
#endif #endif

View File

@ -217,10 +217,11 @@ void host_digitizer_send(digitizer_t *digitizer) {
# ifdef DIGITIZER_SHARED_EP # ifdef DIGITIZER_SHARED_EP
.report_id = REPORT_ID_DIGITIZER, .report_id = REPORT_ID_DIGITIZER,
# endif # endif
.tip = digitizer->tipswitch & 0x1, .in_range = digitizer->in_range,
.inrange = digitizer->inrange & 0x1, .tip = digitizer->tip,
.x = (uint16_t)(digitizer->x * 0x7FFF), .barrel = digitizer->barrel,
.y = (uint16_t)(digitizer->y * 0x7FFF), .x = (uint16_t)(digitizer->x * 0x7FFF),
.y = (uint16_t)(digitizer->y * 0x7FFF),
}; };
send_digitizer(&report); send_digitizer(&report);

View File

@ -226,9 +226,10 @@ typedef struct {
#ifdef DIGITIZER_SHARED_EP #ifdef DIGITIZER_SHARED_EP
uint8_t report_id; uint8_t report_id;
#endif #endif
uint8_t tip : 1; bool in_range : 1;
uint8_t inrange : 1; bool tip : 1;
uint8_t pad2 : 6; bool barrel : 1;
uint8_t reserved : 5;
uint16_t x; uint16_t x;
uint16_t y; uint16_t y;
} __attribute__((packed)) report_digitizer_t; } __attribute__((packed)) report_digitizer_t;

View File

@ -181,39 +181,37 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM DigitizerReport[] = {
const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = { const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = {
# define SHARED_REPORT_STARTED # define SHARED_REPORT_STARTED
# endif # endif
HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers
HID_RI_USAGE(8, 0x01), // Digitizer HID_RI_USAGE(8, 0x01), // Digitizer
HID_RI_COLLECTION(8, 0x01), // Application HID_RI_COLLECTION(8, 0x01), // Application
# ifdef DIGITIZER_SHARED_EP # ifdef DIGITIZER_SHARED_EP
HID_RI_REPORT_ID(8, REPORT_ID_DIGITIZER), HID_RI_REPORT_ID(8, REPORT_ID_DIGITIZER),
# endif # endif
HID_RI_USAGE(8, 0x20), // Stylus HID_RI_USAGE(8, 0x20), // Stylus
HID_RI_COLLECTION(8, 0x00), // Physical HID_RI_COLLECTION(8, 0x00), // Physical
// Tip Switch (1 bit) // In Range, Tip Switch & Barrel Switch (3 bits)
HID_RI_USAGE(8, 0x42), // Tip Switch HID_RI_USAGE(8, 0x32), // In Range
HID_RI_USAGE(8, 0x42), // Tip Switch
HID_RI_USAGE(8, 0x44), // Barrel Switch
HID_RI_LOGICAL_MINIMUM(8, 0x00), HID_RI_LOGICAL_MINIMUM(8, 0x00),
HID_RI_LOGICAL_MAXIMUM(8, 0x01), HID_RI_LOGICAL_MAXIMUM(8, 0x01),
HID_RI_REPORT_COUNT(8, 0x03),
HID_RI_REPORT_SIZE(8, 0x01), HID_RI_REPORT_SIZE(8, 0x01),
HID_RI_REPORT_COUNT(8, 0x01), HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_RI_INPUT(8, HID_IOF_VARIABLE), // Padding (5 bits)
// In Range (1 bit) HID_RI_REPORT_COUNT(8, 0x05),
HID_RI_USAGE(8, 0x32), // In Range HID_RI_INPUT(8, HID_IOF_CONSTANT),
HID_RI_INPUT(8, HID_IOF_VARIABLE),
// Padding (6 bits)
HID_RI_REPORT_COUNT(8, 0x06),
HID_RI_INPUT(8, HID_IOF_CONSTANT | HID_IOF_VARIABLE),
// X/Y Position (4 bytes) // X/Y Position (4 bytes)
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
HID_RI_USAGE(8, 0x30), // X
HID_RI_USAGE(8, 0x31), // Y
HID_RI_LOGICAL_MAXIMUM(16, 0x7FFF), HID_RI_LOGICAL_MAXIMUM(16, 0x7FFF),
HID_RI_REPORT_COUNT(8, 0x02),
HID_RI_REPORT_SIZE(8, 0x10), HID_RI_REPORT_SIZE(8, 0x10),
HID_RI_REPORT_COUNT(8, 0x01), HID_RI_UNIT(8, 0x33), // Inch, English Linear
HID_RI_UNIT(8, 0x33), // Inch, English Linear HID_RI_UNIT_EXPONENT(8, 0x0E), // -2
HID_RI_UNIT_EXPONENT(8, 0x0E), // -2 HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_RI_USAGE(8, 0x30), // X
HID_RI_INPUT(8, HID_IOF_VARIABLE),
HID_RI_USAGE(8, 0x31), // Y
HID_RI_INPUT(8, HID_IOF_VARIABLE),
HID_RI_END_COLLECTION(0), HID_RI_END_COLLECTION(0),
HID_RI_END_COLLECTION(0), HID_RI_END_COLLECTION(0),
# ifndef DIGITIZER_SHARED_EP # ifndef DIGITIZER_SHARED_EP

View File

@ -532,32 +532,30 @@ const PROGMEM uchar shared_hid_report[] = {
0x09, 0x01, // Usage (Digitizer) 0x09, 0x01, // Usage (Digitizer)
0xA1, 0x01, // Collection (Application) 0xA1, 0x01, // Collection (Application)
0x85, REPORT_ID_DIGITIZER, // Report ID 0x85, REPORT_ID_DIGITIZER, // Report ID
0x09, 0x22, // Usage (Finger) 0x09, 0x20, // Usage (Stylus)
0xA1, 0x00, // Collection (Physical) 0xA1, 0x00, // Collection (Physical)
// Tip Switch (1 bit) // In Range, Tip Switch & Barrel Switch (3 bits)
0x09, 0x32, // Usage (In Range)
0x09, 0x42, // Usage (Tip Switch) 0x09, 0x42, // Usage (Tip Switch)
0x09, 0x44, // Usage (Barrel Switch)
0x15, 0x00, // Logical Minimum 0x15, 0x00, // Logical Minimum
0x25, 0x01, // Logical Maximum 0x25, 0x01, // Logical Maximum
0x95, 0x01, // Report Count (1) 0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (16) 0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data, Variable, Absolute) 0x81, 0x02, // Input (Data, Variable, Absolute)
// In Range (1 bit) // Padding (5 bits)
0x09, 0x32, // Usage (In Range) 0x95, 0x05, // Report Count (5)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Padding (6 bits)
0x95, 0x06, // Report Count (6)
0x81, 0x03, // Input (Constant) 0x81, 0x03, // Input (Constant)
// X/Y Position (4 bytes) // X/Y Position (4 bytes)
0x05, 0x01, // Usage Page (Generic Desktop) 0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x26, 0xFF, 0x7F, // Logical Maximum (32767) 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x95, 0x01, // Report Count (1) 0x95, 0x02, // Report Count (2)
0x75, 0x10, // Report Size (16) 0x75, 0x10, // Report Size (16)
0x65, 0x33, // Unit (Inch, English Linear) 0x65, 0x33, // Unit (Inch, English Linear)
0x55, 0x0E, // Unit Exponent (-2) 0x55, 0x0E, // Unit Exponent (-2)
0x09, 0x30, // Usage (X)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x09, 0x31, // Usage (Y)
0x81, 0x02, // Input (Data, Variable, Absolute) 0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0, // End Collection 0xC0, // End Collection
0xC0, // End Collection 0xC0, // End Collection