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(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 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 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 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 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") 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