289 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* Copyright (c) 2023 Benjamin Tissoires3 */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 0x095A12#define PID_ARTIST_PRO16_GEN2 0x095B13 14HID_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 Switch22 * - when the eraser button is pressed and the stylus is touching the tablet,23 * the device sends Tip Switch instead of sending Eraser24 *25 * This descriptor uses physical dimensions of the 16" device.26 */27static const __u8 fixed_rdesc[] = {28 0x05, 0x0d, // Usage Page (Digitizers) 029 0x09, 0x02, // Usage (Pen) 230 0xa1, 0x01, // Collection (Application) 431 0x85, 0x07, // Report ID (7) 632 0x09, 0x20, // Usage (Stylus) 833 0xa1, 0x00, // Collection (Physical) 1034 0x09, 0x42, // Usage (Tip Switch) 1235 0x09, 0x44, // Usage (Barrel Switch) 1436 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */37 0x09, 0x3c, // Usage (Invert) 1838 0x09, 0x45, // Usage (Eraser) 16 /* created over a padding bit at offset 29-33 */39 0x15, 0x00, // Logical Minimum (0) 2040 0x25, 0x01, // Logical Maximum (1) 2241 0x75, 0x01, // Report Size (1) 2442 0x95, 0x05, // Report Count (5) 26 /* changed from 4 to 5 */43 0x81, 0x02, // Input (Data,Var,Abs) 2844 0x09, 0x32, // Usage (In Range) 3445 0x15, 0x00, // Logical Minimum (0) 3646 0x25, 0x01, // Logical Maximum (1) 3847 0x95, 0x01, // Report Count (1) 4048 0x81, 0x02, // Input (Data,Var,Abs) 4249 0x95, 0x02, // Report Count (2) 4450 0x81, 0x03, // Input (Cnst,Var,Abs) 4651 0x75, 0x10, // Report Size (16) 4852 0x95, 0x01, // Report Count (1) 5053 0x35, 0x00, // Physical Minimum (0) 5254 0xa4, // Push 5455 0x05, 0x01, // Usage Page (Generic Desktop) 5556 0x09, 0x30, // Usage (X) 5757 0x65, 0x13, // Unit (EnglishLinear: in) 5958 0x55, 0x0d, // Unit Exponent (-3) 6159 0x46, 0xff, 0x34, // Physical Maximum (13567) 6360 0x26, 0xff, 0x7f, // Logical Maximum (32767) 6661 0x81, 0x02, // Input (Data,Var,Abs) 6962 0x09, 0x31, // Usage (Y) 7163 0x46, 0x20, 0x21, // Physical Maximum (8480) 7364 0x26, 0xff, 0x7f, // Logical Maximum (32767) 7665 0x81, 0x02, // Input (Data,Var,Abs) 7966 0xb4, // Pop 8167 0x09, 0x30, // Usage (Tip Pressure) 8268 0x45, 0x00, // Physical Maximum (0) 8469 0x26, 0xff, 0x3f, // Logical Maximum (16383) 8670 0x81, 0x42, // Input (Data,Var,Abs,Null) 8971 0x09, 0x3d, // Usage (X Tilt) 9172 0x15, 0x81, // Logical Minimum (-127) 9373 0x25, 0x7f, // Logical Maximum (127) 9574 0x75, 0x08, // Report Size (8) 9775 0x95, 0x01, // Report Count (1) 9976 0x81, 0x02, // Input (Data,Var,Abs) 10177 0x09, 0x3e, // Usage (Y Tilt) 10378 0x15, 0x81, // Logical Minimum (-127) 10579 0x25, 0x7f, // Logical Maximum (127) 10780 0x81, 0x02, // Input (Data,Var,Abs) 10981 0xc0, // End Collection 11182 0xc0, // End Collection 11283};84 85SEC(HID_BPF_RDESC_FIXUP)86int 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 device96 * 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 108static 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 angles126 * Two tables are needed, because the logical coordinates are scaled127 *128 * The table can be generated by Python like this:129 * >>> full_scale = 11.874 # the display width/height in inches130 * >>> 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 coordinates132 * >>> [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" */137static 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, 123146};147static 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, 196156};157 158/* 16" inch screen 13.567" x 8.480" */159static 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, 107168};169static 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, 172178};179 180static 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 209static 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 coil220 * 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 * h229 *230 * Subtract the offset from the coordinates. Use the precomputed table!231 *232 * bytes 0 - report id233 * 1 - buttons234 * 2-3 - X coords (logical)235 * 4-5 - Y coords236 * 6-7 - pressure (ignore)237 * 8 - tilt X238 * 9 - tilt Y239 */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 255SEC(HID_BPF_DEVICE_EVENT)256int 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 266HID_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 271SEC("syscall")272int 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 288char _license[] SEC("license") = "GPL";289