// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Red Hat, Inc
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include "hid_report_helpers.h"
#include <bpf/bpf_tracing.h>
#define HID_BPF_ASYNC_MAX_CTX 1
#include "hid_bpf_async.h"
#define VID_UGEE 0 x28BD
/* same PID whether connected directly or through the provided dongle: */
#define PID_ACK05_REMOTE 0 x0202
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ACK05_REMOTE),
);
/*
* By default , the pad reports the buttons through a set of key sequences .
*
* The pad reports a classic keyboard report descriptor :
* # HANVON UGEE Shortcut Remote
* Report descriptor length : 102 bytes
* 0 x05 , 0 x01 , // Usage Page (Generic Desktop) 0
* 0 x09 , 0 x02 , // Usage (Mouse) 2
* 0 xa1 , 0 x01 , // Collection (Application) 4
* 0 x85 , 0 x09 , // Report ID (9) 6
* 0 x09 , 0 x01 , // Usage (Pointer) 8
* 0 xa1 , 0 x00 , // Collection (Physical) 10
* 0 x05 , 0 x09 , // Usage Page (Button) 12
* 0 x19 , 0 x01 , // UsageMinimum (1) 14
* 0 x29 , 0 x03 , // UsageMaximum (3) 16
* 0 x15 , 0 x00 , // Logical Minimum (0) 18
* 0 x25 , 0 x01 , // Logical Maximum (1) 20
* 0 x95 , 0 x03 , // Report Count (3) 22
* 0 x75 , 0 x01 , // Report Size (1) 24
* 0 x81 , 0 x02 , // Input (Data,Var,Abs) 26
* 0 x95 , 0 x05 , // Report Count (5) 28
* 0 x81 , 0 x01 , // Input (Cnst,Arr,Abs) 30
* 0 x05 , 0 x01 , // Usage Page (Generic Desktop) 32
* 0 x09 , 0 x30 , // Usage (X) 34
* 0 x09 , 0 x31 , // Usage (Y) 36
* 0 x26 , 0 xff , 0 x7f , // Logical Maximum (32767) 38
* 0 x95 , 0 x02 , // Report Count (2) 41
* 0 x75 , 0 x10 , // Report Size (16) 43
* 0 x81 , 0 x02 , // Input (Data,Var,Abs) 45
* 0 x05 , 0 x0d , // Usage Page (Digitizers) 47
* 0 x09 , 0 x30 , // Usage (Tip Pressure) 49
* 0 x26 , 0 xff , 0 x07 , // Logical Maximum (2047) 51
* 0 x95 , 0 x01 , // Report Count (1) 54
* 0 x75 , 0 x10 , // Report Size (16) 56
* 0 x81 , 0 x02 , // Input (Data,Var,Abs) 58
* 0 xc0 , // End Collection 60
* 0 xc0 , // End Collection 61
* 0 x05 , 0 x01 , // Usage Page (Generic Desktop) 62
* 0 x09 , 0 x06 , // Usage (Keyboard) 64
* 0 xa1 , 0 x01 , // Collection (Application) 66
* 0 x85 , 0 x06 , // Report ID (6) 68
* 0 x05 , 0 x07 , // Usage Page (Keyboard/Keypad) 70
* 0 x19 , 0 xe0 , // UsageMinimum (224) 72
* 0 x29 , 0 xe7 , // UsageMaximum (231) 74
* 0 x15 , 0 x00 , // Logical Minimum (0) 76
* 0 x25 , 0 x01 , // Logical Maximum (1) 78
* 0 x75 , 0 x01 , // Report Size (1) 80
* 0 x95 , 0 x08 , // Report Count (8) 82
* 0 x81 , 0 x02 , // Input (Data,Var,Abs) 84
* 0 x05 , 0 x07 , // Usage Page (Keyboard/Keypad) 86
* 0 x19 , 0 x00 , // UsageMinimum (0) 88
* 0 x29 , 0 xff , // UsageMaximum (255) 90
* 0 x26 , 0 xff , 0 x00 , // Logical Maximum (255) 92
* 0 x75 , 0 x08 , // Report Size (8) 95
* 0 x95 , 0 x06 , // Report Count (6) 97
* 0 x81 , 0 x00 , // Input (Data,Arr,Abs) 99
* 0 xc0 , // End Collection 101
*
* Each button gets assigned the following events :
*
* Buttons released : 06 00 00 00 00 00 00 00
* Button 1 : 06 01 12 00 00 00 00 00 - > LControl + o
* Button 2 : 06 01 11 00 00 00 00 00 - > LControl + n
* Button 3 : 06 00 3 e 00 00 00 00 00 - > F5
* Button 4 : 06 02 00 00 00 00 00 00 - > LShift
* Button 5 : 06 01 00 00 00 00 00 00 - > LControl
* Button 6 : 06 04 00 00 00 00 00 00 - > LAlt
* Button 7 : 06 01 16 00 00 00 00 00 - > LControl + s
* Button 8 : 06 01 1 d 00 00 00 00 00 - > LControl + z
* Button 9 : 06 00 2 c 00 00 00 00 00 - > Space
* Button 10 : 06 03 1 d 00 00 00 00 00 - > LControl + LShift + z
* Wheel : 06 01 57 00 00 00 00 00 - > clockwise rotation ( LControl + Keypad Plus )
* Wheel : 06 01 56 00 00 00 00 00 - > counter - clockwise rotation
* ( LControl + Keypad Minus )
*
* However , multiple buttons can be pressed at the same time , and when this happens ,
* each button gets assigned a new slot in the Input ( Data , Arr , Abs ) :
*
* Button 1 + 3 : 06 01 12 3 e 00 00 00 00 - > LControl + o + F5
*
* When a modifier is pressed ( Button 4 , 5 , or 6 ) , the assigned key is set to 00 :
*
* Button 5 + 7 : 06 01 00 16 00 00 00 00 - > LControl + s
*
* This is mostly fine , but with Button 8 and Button 10 sharing the same
* key value ( " z " ) , there are cases where we can not know which is which .
*
*/
#define PAD_WIRED_DESCRIPTOR_LENGTH 102
#define PAD_DONGLE_DESCRIPTOR_LENGTH 177
#define STYLUS_DESCRIPTOR_LENGTH 109
#define VENDOR_DESCRIPTOR_LENGTH 36
#define PAD_REPORT_ID 6
#define RAW_PAD_REPORT_ID 0 xf0
#define RAW_BATTERY_REPORT_ID 0 xf2
#define VENDOR_REPORT_ID 2
#define PAD_REPORT_LENGTH 8
#define VENDOR_REPORT_LENGTH 12
__u16 last_button_state;
static const __u8 disabled_rdesc[] = {
// Make sure we match our original report length
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
};
static const __u8 fixed_rdesc_vendor[] = {
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// -- Byte 0 in report
ReportId(RAW_PAD_REPORT_ID)
// Byte 1 in report - same than report ID
ReportCount(1 )
ReportSize(8 )
Input(Const ) // padding (internal report ID)
LogicalMaximum_i8(0 )
LogicalMaximum_i8(1 )
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 2-3 is the button state
UsagePage_Button
UsageMinimum_i8(0 x01)
UsageMaximum_i8(0 x0a)
LogicalMinimum_i8(0 x0)
LogicalMaximum_i8(0 x1)
ReportCount(10 )
ReportSize(1 )
Input(Var|Abs)
Usage_i8(0 x31) // will be mapped as BTN_A / BTN_SOUTH
ReportCount(1 )
Input(Var|Abs)
ReportCount(5 ) // padding
Input(Const )
// Byte 4 in report - just exists so we get to be a tablet pad
UsagePage_Digitizers
Usage_Dig_BarrelSwitch // BTN_STYLUS
ReportCount(1 )
ReportSize(1 )
Input(Var|Abs)
ReportCount(7 ) // padding
Input(Const )
// Bytes 5/6 in report - just exists so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2 )
ReportSize(8 )
Input(Var|Abs)
// Byte 7 in report is the dial
Usage_GD_Wheel
LogicalMinimum_i8(-1 )
LogicalMaximum_i8(1 )
ReportCount(1 )
ReportSize(8 )
Input(Var|Rel)
)
// -- Byte 0 in report
ReportId(RAW_BATTERY_REPORT_ID)
// Byte 1 in report - same than report ID
ReportCount(1 )
ReportSize(8 )
Input(Const ) // padding (internal report ID)
// Byte 2 in report - always 0x01
Input(Const ) // padding (internal report ID)
UsagePage_Digitizers
/*
* We represent the device as a stylus to force the kernel to not
* directly query its battery state . Instead the kernel will rely
* only on the provided events .
*/
Usage_Dig_Stylus
CollectionPhysical(
// Byte 3 in report - battery value
UsagePage_BatterySystem
Usage_BS_AbsoluteStateOfCharge
LogicalMinimum_i8(0 )
LogicalMaximum_i8(100 )
ReportCount(1 )
ReportSize(8 )
Input(Var|Abs)
// Byte 4 in report - charging state
Usage_BS_Charging
LogicalMinimum_i8(0 )
LogicalMaximum_i8(1 )
ReportCount(1 )
ReportSize(8 )
Input(Var|Abs)
)
)
};
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(ack05_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
__s32 rdesc_size = hctx->size;
if (!data)
return 0 ; /* EPERM check */
if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
/*
* The vendor fixed rdesc is appended after the current one ,
* to keep the output reports working .
*/
__builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof (fixed_rdesc_vendor));
return sizeof (fixed_rdesc_vendor) + rdesc_size;
}
hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote" );
__builtin_memcpy(data, disabled_rdesc, sizeof (disabled_rdesc));
return sizeof (disabled_rdesc);
}
static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid)
{
static __u8 magic_0[32 ] = {0 x02, 0 xb0, 0 x04, 0 x00, 0 x00};
int err;
/*
* The proprietary driver sends the 3 following packets after the
* above one .
* These don ' t seem to have any effect , so we don ' t send them to save
* some processing time .
*
* static _ _ u8 magic_1 [ 32 ] = { 0 x02 , 0 xb4 , 0 x01 , 0 x00 , 0 x01 } ;
* static _ _ u8 magic_2 [ 32 ] = { 0 x02 , 0 xb4 , 0 x01 , 0 x00 , 0 xff } ;
* static _ _ u8 magic_3 [ 32 ] = { 0 x02 , 0 xb8 , 0 x04 , 0 x00 , 0 x00 } ;
*/
err = hid_bpf_hw_output_report(hid, magic_0, sizeof (magic_0));
if (err < 0 )
return err;
return 0 ;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH);
int ret = 0 ;
if (!data)
return 0 ; /* EPERM check */
if (data[0 ] != VENDOR_REPORT_ID)
return 0 ;
/* reconnect event */
if (data[1 ] == 0 xf8 && data[2 ] == 02 && data[3 ] == 0 x01)
HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10 );
/* button event */
if (data[1 ] == RAW_PAD_REPORT_ID) {
data[0 ] = data[1 ];
if (data[7 ] == 0 x02)
data[7 ] = 0 xff;
ret = 8 ;
} else if (data[1 ] == RAW_BATTERY_REPORT_ID) {
data[0 ] = data[1 ];
ret = 5 ;
}
return ret;
}
HID_BPF_OPS(xppen_ack05_remote) = {
.hid_device_event = (void *)ack05_fix_events,
.hid_rdesc_fixup = (void *)ack05_fix_rdesc,
};
SEC("syscall" )
int probe(struct hid_bpf_probe_args *ctx)
{
switch (ctx->rdesc_size) {
case PAD_WIRED_DESCRIPTOR_LENGTH:
case PAD_DONGLE_DESCRIPTOR_LENGTH:
case STYLUS_DESCRIPTOR_LENGTH:
case VENDOR_DESCRIPTOR_LENGTH:
ctx->retval = 0 ;
break ;
default :
ctx->retval = -EINVAL;
break ;
}
if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
if (!hctx) {
ctx->retval = -EINVAL;
return 0 ;
}
ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) ||
switch_to_raw_mode(hctx);
hid_bpf_release_context(hctx);
}
return 0 ;
}
char _license[] SEC("license" ) = "GPL" ;
Messung V0.5 in Prozent C=79 H=96 G=87
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland