Commit | Line | Data |
---|---|---|
e0599675 BT |
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 | ||
85 | SEC("fmod_ret/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 | ||
108 | SEC("fmod_ret/hid_bpf_device_event") | |
109 | int BPF_PROG(xppen_16_fix_eraser, struct hid_bpf_ctx *hctx) | |
110 | { | |
111 | __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */); | |
112 | ||
113 | if (!data) | |
114 | return 0; /* EPERM check */ | |
115 | ||
116 | if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */ | |
117 | return 0; | |
118 | ||
119 | /* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */ | |
120 | data[1] ^= 0x19; | |
121 | ||
122 | return 0; | |
123 | } | |
124 | ||
125 | /* | |
126 | * Static coordinate offset table based on positive only angles | |
127 | * Two tables are needed, because the logical coordinates are scaled | |
128 | * | |
129 | * The table can be generated by Python like this: | |
130 | * >>> full_scale = 11.874 # the display width/height in inches | |
131 | * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical) | |
132 | * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates | |
133 | * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)] | |
134 | * [0, 13, 26, ....] | |
135 | */ | |
136 | ||
137 | /* 14" inch screen 11.874" x 7.421" */ | |
138 | static const __u16 angle_offsets_horizontal_14[128] = { | |
139 | 0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53, | |
140 | 55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99, | |
141 | 101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127, | |
142 | 129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, | |
143 | 147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154, | |
144 | 154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149, | |
145 | 148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133, | |
146 | 132, 130, 129, 127, 126, 124, 123 | |
147 | }; | |
148 | static const __u16 angle_offsets_vertical_14[128] = { | |
149 | 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84, | |
150 | 88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148, | |
151 | 151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196, | |
152 | 199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230, | |
153 | 231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245, | |
154 | 245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241, | |
155 | 240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219, | |
156 | 217, 215, 213, 211, 208, 206, 204, 201, 199, 196 | |
157 | }; | |
158 | ||
159 | /* 16" inch screen 13.567" x 8.480" */ | |
160 | static const __u16 angle_offsets_horizontal_16[128] = { | |
161 | 0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48, | |
162 | 50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, | |
163 | 92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115, | |
164 | 116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130, | |
165 | 130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134, | |
166 | 134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129, | |
167 | 128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113, | |
168 | 111, 110, 109, 107 | |
169 | }; | |
170 | static const __u16 angle_offsets_vertical_16[128] = { | |
171 | 0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74, | |
172 | 77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132, | |
173 | 135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174, | |
174 | 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202, | |
175 | 203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215, | |
176 | 215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210, | |
177 | 210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190, | |
178 | 188, 186, 184, 182, 180, 178, 176, 174, 172 | |
179 | }; | |
180 | ||
181 | static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx, | |
182 | const __s8 tilt, const __u16 (*compensation_table)[128]) | |
183 | { | |
184 | __u16 coords = data[idx+1]; | |
185 | ||
186 | coords <<= 8; | |
187 | coords += data[idx]; | |
188 | ||
189 | __u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */ | |
190 | __u8 angle = tilt > 0 ? tilt : -tilt; | |
191 | ||
192 | if (angle > 127) | |
193 | return; | |
194 | ||
195 | __u16 compensation = (*compensation_table)[angle]; | |
196 | ||
197 | if (direction == 0) { | |
198 | coords = (coords > compensation) ? coords - compensation : 0; | |
199 | } else { | |
200 | const __u16 logical_maximum = 32767; | |
201 | __u16 max = logical_maximum - compensation; | |
202 | ||
203 | coords = (coords < max) ? coords + compensation : logical_maximum; | |
204 | } | |
205 | ||
206 | data[idx] = coords & 0xff; | |
207 | data[idx+1] = coords >> 8; | |
208 | } | |
209 | ||
210 | SEC("fmod_ret/hid_bpf_device_event") | |
211 | int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx) | |
212 | { | |
213 | __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */); | |
214 | ||
215 | if (!data) | |
216 | return 0; /* EPERM check */ | |
217 | ||
218 | /* | |
219 | * Compensate X and Y offset caused by tilt. | |
220 | * | |
221 | * The magnetic center moves when the pen is tilted, because the coil | |
222 | * is not touching the screen. | |
223 | * | |
224 | * a (tilt angle) | |
225 | * | /... h (coil distance from tip) | |
226 | * | / | |
227 | * |/______ | |
228 | * |x (position offset) | |
229 | * | |
230 | * x = sin a * h | |
231 | * | |
232 | * Subtract the offset from the coordinates. Use the precomputed table! | |
233 | * | |
234 | * bytes 0 - report id | |
235 | * 1 - buttons | |
236 | * 2-3 - X coords (logical) | |
237 | * 4-5 - Y coords | |
238 | * 6-7 - pressure (ignore) | |
239 | * 8 - tilt X | |
240 | * 9 - tilt Y | |
241 | */ | |
242 | ||
243 | __s8 tilt_x = (__s8) data[8]; | |
244 | __s8 tilt_y = (__s8) data[9]; | |
245 | ||
246 | if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) { | |
247 | compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14); | |
248 | compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14); | |
249 | } else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) { | |
250 | compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16); | |
251 | compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16); | |
252 | } | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | SEC("syscall") | |
258 | int probe(struct hid_bpf_probe_args *ctx) | |
259 | { | |
260 | /* | |
261 | * The device exports 3 interfaces. | |
262 | */ | |
263 | ctx->retval = ctx->rdesc_size != 113; | |
264 | if (ctx->retval) | |
265 | ctx->retval = -EINVAL; | |
266 | ||
267 | /* ensure the kernel isn't fixed already */ | |
268 | if (ctx->rdesc[17] != 0x45) /* Eraser */ | |
269 | ctx->retval = -EINVAL; | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | char _license[] SEC("license") = "GPL"; |