// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2023 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_UGEE 0 x28BD /* VID is shared with SinoWealth and Glorious and prob others */
#define PID_ARTIST_24 0 x093A
#define PID_ARTIST_24_PRO 0 x092D
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
);
/*
* We need to amend the report descriptor for the following :
* - the device reports Eraser instead of using Secondary Barrel Switch
* - the pen doesn ' t have a rubber tail , so basically we are removing any
* eraser / invert bits
*/
static const __u8 fixed_rdesc[] = {
0 x05, 0 x0d, // Usage Page (Digitizers) 0
0 x09, 0 x02, // Usage (Pen) 2
0 xa1, 0 x01, // Collection (Application) 4
0 x85, 0 x07, // Report ID (7) 6
0 x09, 0 x20, // Usage (Stylus) 8
0 xa1, 0 x00, // Collection (Physical) 10
0 x09, 0 x42, // Usage (Tip Switch) 12
0 x09, 0 x44, // Usage (Barrel Switch) 14
0 x09, 0 x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
0 x15, 0 x00, // Logical Minimum (0) 18
0 x25, 0 x01, // Logical Maximum (1) 20
0 x75, 0 x01, // Report Size (1) 22
0 x95, 0 x03, // Report Count (3) 24
0 x81, 0 x02, // Input (Data,Var,Abs) 26
0 x95, 0 x02, // Report Count (2) 28
0 x81, 0 x03, // Input (Cnst,Var,Abs) 30
0 x09, 0 x32, // Usage (In Range) 32
0 x95, 0 x01, // Report Count (1) 34
0 x81, 0 x02, // Input (Data,Var,Abs) 36
0 x95, 0 x02, // Report Count (2) 38
0 x81, 0 x03, // Input (Cnst,Var,Abs) 40
0 x75, 0 x10, // Report Size (16) 42
0 x95, 0 x01, // Report Count (1) 44
0 x35, 0 x00, // Physical Minimum (0) 46
0 xa4, // Push 48
0 x05, 0 x01, // Usage Page (Generic Desktop) 49
0 x09, 0 x30, // Usage (X) 51
0 x65, 0 x13, // Unit (EnglishLinear: in) 53
0 x55, 0 x0d, // Unit Exponent (-3) 55
0 x46, 0 xf0, 0 x50, // Physical Maximum (20720) 57
0 x26, 0 xff, 0 x7f, // Logical Maximum (32767) 60
0 x81, 0 x02, // Input (Data,Var,Abs) 63
0 x09, 0 x31, // Usage (Y) 65
0 x46, 0 x91, 0 x2d, // Physical Maximum (11665) 67
0 x26, 0 xff, 0 x7f, // Logical Maximum (32767) 70
0 x81, 0 x02, // Input (Data,Var,Abs) 73
0 xb4, // Pop 75
0 x09, 0 x30, // Usage (Tip Pressure) 76
0 x45, 0 x00, // Physical Maximum (0) 78
0 x26, 0 xff, 0 x1f, // Logical Maximum (8191) 80
0 x81, 0 x42, // Input (Data,Var,Abs,Null) 83
0 x09, 0 x3d, // Usage (X Tilt) 85
0 x15, 0 x81, // Logical Minimum (-127) 87
0 x25, 0 x7f, // Logical Maximum (127) 89
0 x75, 0 x08, // Report Size (8) 91
0 x95, 0 x01, // Report Count (1) 93
0 x81, 0 x02, // Input (Data,Var,Abs) 95
0 x09, 0 x3e, // Usage (Y Tilt) 97
0 x15, 0 x81, // Logical Minimum (-127) 99
0 x25, 0 x7f, // Logical Maximum (127) 101
0 x81, 0 x02, // Input (Data,Var,Abs) 103
0 xc0, // End Collection 105
0 xc0, // End Collection 106
};
#define TIP_SWITCH BIT(0 )
#define BARREL_SWITCH BIT(1 )
#define ERASER BIT(2 )
/* padding BIT(3) */
/* padding BIT(4) */
#define IN_RANGE BIT(5 )
/* padding BIT(6) */
/* padding BIT(7) */
#define U16(index) (data[index] | (data[index + 1 ] << 8 ))
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0 ; /* EPERM check */
__builtin_memcpy(data, fixed_rdesc, sizeof (fixed_rdesc));
return sizeof (fixed_rdesc);
}
static __u8 prev_state = 0 ;
/*
* There are a few cases where the device is sending wrong event
* sequences , all related to the second button ( the pen doesn ' t
* have an eraser switch on the tail end ) :
*
* whenever the second button gets pressed or released , an
* out - of - proximity event is generated and then the firmware
* compensate for the missing state ( and the firmware uses
* eraser for that button ) :
*
* - if the pen is in range , an extra out - of - range is sent
* when the second button is pressed / released :
* // Pen is in range
* E : InRange
*
* // Second button is pressed
* E :
* E : Eraser InRange
*
* // Second button is released
* E :
* E : InRange
*
* This case is ignored by this filter , it ' s " valid "
* and userspace knows how to deal with it , there are just
* a few out - of - prox events generated , but the user doesn ´ t
* see them .
*
* - if the pen is in contact , 2 extra events are added when
* the second button is pressed / released : an out of range
* and an in range :
*
* // Pen is in contact
* E : TipSwitch InRange
*
* // Second button is pressed
* E : < - false release , needs to be filtered out
* E : Eraser InRange < - false release , needs to be filtered out
* E : TipSwitch Eraser InRange
*
* // Second button is released
* E : < - false release , needs to be filtered out
* E : InRange < - false release , needs to be filtered out
* E : TipSwitch InRange
*
*/
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
__u8 current_state, changed_state;
bool prev_tip;
if (!data)
return 0 ; /* EPERM check */
current_state = data[1 ];
/* if the state is identical to previously, early return */
if (current_state == prev_state)
return 0 ;
prev_tip = !!(prev_state & TIP_SWITCH);
/*
* Illegal transition : pen is in range with the tip pressed , and
* it goes into out of proximity .
*
* Ideally we should hold the event , start a timer and deliver it
* only if the timer ends , but we are not capable of that now .
*
* And it doesn ' t matter because when we are in such cases , this
* means we are detecting a false release .
*/
if ((current_state & IN_RANGE) == 0 ) {
if (prev_tip)
return HID_IGNORE_EVENT;
return 0 ;
}
/*
* XOR to only set the bits that have changed between
* previous and current state
*/
changed_state = prev_state ^ current_state;
/* Store the new state for future processing */
prev_state = current_state;
/*
* We get both a tipswitch and eraser change in the same HID report :
* this is not an authorized transition and is unlikely to happen
* in real life .
* This is likely to be added by the firmware to emulate the
* eraser mode so we can skip the event .
*/
if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
return HID_IGNORE_EVENT;
return 0 ;
}
HID_BPF_OPS(xppen_artist_24) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artist24,
.hid_device_event = (void *)xppen_24_fix_eraser,
};
SEC("syscall" )
int probe(struct hid_bpf_probe_args *ctx)
{
/*
* The device exports 3 interfaces .
*/
ctx->retval = ctx->rdesc_size != 107 ;
if (ctx->retval)
ctx->retval = -EINVAL;
/* ensure the kernel isn't fixed already */
if (ctx->rdesc[17 ] != 0 x45) /* Eraser */
ctx->retval = -EINVAL;
return 0 ;
}
char _license[] SEC("license" ) = "GPL" ;
Messung V0.5 in Prozent C=91 H=100 G=95
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland