1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2023 Benjamin Tissoires
3 */
4
5 #include "vmlinux.h"
6 #include "hid_bpf.h"
7 #include "hid_bpf_helpers.h"
8 #include <bpf/bpf_tracing.h>
9
10 #define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
11 #define PID_ARTIST_PRO14_GEN2 0x095A
12 #define PID_ARTIST_PRO16_GEN2 0x095B
13
14 HID_BPF_CONFIG(
15 HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO14_GEN2),
16 HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2)
17 );
18
19 /*
20 * We need to amend the report descriptor for the following:
21 * - the device reports Eraser instead of using Secondary Barrel Switch
22 * - when the eraser button is pressed and the stylus is touching the tablet,
23 * the device sends Tip Switch instead of sending Eraser
24 *
25 * This descriptor uses physical dimensions of the 16" device.
26 */
27 static const __u8 fixed_rdesc[] = {
28 0x05, 0x0d, // Usage Page (Digitizers) 0
29 0x09, 0x02, // Usage (Pen) 2
30 0xa1, 0x01, // Collection (Application) 4
31 0x85, 0x07, // Report ID (7) 6
32 0x09, 0x20, // Usage (Stylus) 8
33 0xa1, 0x00, // Collection (Physical) 10
34 0x09, 0x42, // Usage (Tip Switch) 12
35 0x09, 0x44, // Usage (Barrel Switch) 14
36 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
37 0x09, 0x3c, // Usage (Invert) 18
38 0x09, 0x45, // Usage (Eraser) 16 /* created over a padding bit at offset 29-33 */
39 0x15, 0x00, // Logical Minimum (0) 20
40 0x25, 0x01, // Logical Maximum (1) 22
41 0x75, 0x01, // Report Size (1) 24
42 0x95, 0x05, // Report Count (5) 26 /* changed from 4 to 5 */
43 0x81, 0x02, // Input (Data,Var,Abs) 28
44 0x09, 0x32, // Usage (In Range) 34
45 0x15, 0x00, // Logical Minimum (0) 36
46 0x25, 0x01, // Logical Maximum (1) 38
47 0x95, 0x01, // Report Count (1) 40
48 0x81, 0x02, // Input (Data,Var,Abs) 42
49 0x95, 0x02, // Report Count (2) 44
50 0x81, 0x03, // Input (Cnst,Var,Abs) 46
51 0x75, 0x10, // Report Size (16) 48
52 0x95, 0x01, // Report Count (1) 50
53 0x35, 0x00, // Physical Minimum (0) 52
54 0xa4, // Push 54
55 0x05, 0x01, // Usage Page (Generic Desktop) 55
56 0x09, 0x30, // Usage (X) 57
57 0x65, 0x13, // Unit (EnglishLinear: in) 59
58 0x55, 0x0d, // Unit Exponent (-3) 61
59 0x46, 0xff, 0x34, // Physical Maximum (13567) 63
60 0x26, 0xff, 0x7f, // Logical Maximum (32767) 66
61 0x81, 0x02, // Input (Data,Var,Abs) 69
62 0x09, 0x31, // Usage (Y) 71
63 0x46, 0x20, 0x21, // Physical Maximum (8480) 73
64 0x26, 0xff, 0x7f, // Logical Maximum (32767) 76
65 0x81, 0x02, // Input (Data,Var,Abs) 79
66 0xb4, // Pop 81
67 0x09, 0x30, // Usage (Tip Pressure) 82
68 0x45, 0x00, // Physical Maximum (0) 84
69 0x26, 0xff, 0x3f, // Logical Maximum (16383) 86
70 0x81, 0x42, // Input (Data,Var,Abs,Null) 89
71 0x09, 0x3d, // Usage (X Tilt) 91
72 0x15, 0x81, // Logical Minimum (-127) 93
73 0x25, 0x7f, // Logical Maximum (127) 95
74 0x75, 0x08, // Report Size (8) 97
75 0x95, 0x01, // Report Count (1) 99
76 0x81, 0x02, // Input (Data,Var,Abs) 101
77 0x09, 0x3e, // Usage (Y Tilt) 103
78 0x15, 0x81, // Logical Minimum (-127) 105
79 0x25, 0x7f, // Logical Maximum (127) 107
80 0x81, 0x02, // Input (Data,Var,Abs) 109
81 0xc0, // End Collection 111
82 0xc0, // End Collection 112
83 };
84
SEC(HID_BPF_RDESC_FIXUP)85 SEC(HID_BPF_RDESC_FIXUP)
86 int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
87 {
88 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
89
90 if (!data)
91 return 0; /* EPERM check */
92
93 __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
94
95 /* Fix the Physical maximum values for different sizes of the device
96 * The 14" screen device descriptor size is 11.874" x 7.421"
97 */
98 if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
99 data[63] = 0x2e;
100 data[62] = 0x62;
101 data[73] = 0x1c;
102 data[72] = 0xfd;
103 }
104
105 return sizeof(fixed_rdesc);
106 }
107
xppen_16_fix_eraser(struct hid_bpf_ctx * hctx)108 static int xppen_16_fix_eraser(struct hid_bpf_ctx *hctx)
109 {
110 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
111
112 if (!data)
113 return 0; /* EPERM check */
114
115 if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
116 return 0;
117
118 /* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
119 data[1] ^= 0x19;
120
121 return 0;
122 }
123
124 /*
125 * Static coordinate offset table based on positive only angles
126 * Two tables are needed, because the logical coordinates are scaled
127 *
128 * The table can be generated by Python like this:
129 * >>> full_scale = 11.874 # the display width/height in inches
130 * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
131 * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
132 * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
133 * [0, 13, 26, ....]
134 */
135
136 /* 14" inch screen 11.874" x 7.421" */
137 static const __u16 angle_offsets_horizontal_14[128] = {
138 0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
139 55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
140 101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
141 129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
142 147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
143 154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
144 148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
145 132, 130, 129, 127, 126, 124, 123
146 };
147 static const __u16 angle_offsets_vertical_14[128] = {
148 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
149 88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
150 151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
151 199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
152 231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
153 245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
154 240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
155 217, 215, 213, 211, 208, 206, 204, 201, 199, 196
156 };
157
158 /* 16" inch screen 13.567" x 8.480" */
159 static const __u16 angle_offsets_horizontal_16[128] = {
160 0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
161 50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
162 92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
163 116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
164 130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
165 134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
166 128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
167 111, 110, 109, 107
168 };
169 static const __u16 angle_offsets_vertical_16[128] = {
170 0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
171 77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
172 135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
173 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
174 203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
175 215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
176 210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
177 188, 186, 184, 182, 180, 178, 176, 174, 172
178 };
179
compensate_coordinates_by_tilt(__u8 * data,const __u8 idx,const __s8 tilt,const __u16 (* compensation_table)[128])180 static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
181 const __s8 tilt, const __u16 (*compensation_table)[128])
182 {
183 __u16 coords = data[idx+1];
184
185 coords <<= 8;
186 coords += data[idx];
187
188 __u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
189 __u8 angle = tilt > 0 ? tilt : -tilt;
190
191 if (angle > 127)
192 return;
193
194 __u16 compensation = (*compensation_table)[angle];
195
196 if (direction == 0) {
197 coords = (coords > compensation) ? coords - compensation : 0;
198 } else {
199 const __u16 logical_maximum = 32767;
200 __u16 max = logical_maximum - compensation;
201
202 coords = (coords < max) ? coords + compensation : logical_maximum;
203 }
204
205 data[idx] = coords & 0xff;
206 data[idx+1] = coords >> 8;
207 }
208
xppen_16_fix_angle_offset(struct hid_bpf_ctx * hctx)209 static int xppen_16_fix_angle_offset(struct hid_bpf_ctx *hctx)
210 {
211 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
212
213 if (!data)
214 return 0; /* EPERM check */
215
216 /*
217 * Compensate X and Y offset caused by tilt.
218 *
219 * The magnetic center moves when the pen is tilted, because the coil
220 * is not touching the screen.
221 *
222 * a (tilt angle)
223 * | /... h (coil distance from tip)
224 * | /
225 * |/______
226 * |x (position offset)
227 *
228 * x = sin a * h
229 *
230 * Subtract the offset from the coordinates. Use the precomputed table!
231 *
232 * bytes 0 - report id
233 * 1 - buttons
234 * 2-3 - X coords (logical)
235 * 4-5 - Y coords
236 * 6-7 - pressure (ignore)
237 * 8 - tilt X
238 * 9 - tilt Y
239 */
240
241 __s8 tilt_x = (__s8) data[8];
242 __s8 tilt_y = (__s8) data[9];
243
244 if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
245 compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
246 compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
247 } else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
248 compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
249 compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
250 }
251
252 return 0;
253 }
254
SEC(HID_BPF_DEVICE_EVENT)255 SEC(HID_BPF_DEVICE_EVENT)
256 int BPF_PROG(xppen_artist_pro_16_device_event, struct hid_bpf_ctx *hctx)
257 {
258 int ret = xppen_16_fix_angle_offset(hctx);
259
260 if (ret)
261 return ret;
262
263 return xppen_16_fix_eraser(hctx);
264 }
265
266 HID_BPF_OPS(xppen_artist_pro_16) = {
267 .hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artistpro16gen2,
268 .hid_device_event = (void *)xppen_artist_pro_16_device_event,
269 };
270
271 SEC("syscall")
probe(struct hid_bpf_probe_args * ctx)272 int probe(struct hid_bpf_probe_args *ctx)
273 {
274 /*
275 * The device exports 3 interfaces.
276 */
277 ctx->retval = ctx->rdesc_size != 113;
278 if (ctx->retval)
279 ctx->retval = -EINVAL;
280
281 /* ensure the kernel isn't fixed already */
282 if (ctx->rdesc[17] != 0x45) /* Eraser */
283 ctx->retval = -EINVAL;
284
285 return 0;
286 }
287
288 char _license[] SEC("license") = "GPL";
289