[PATCH] HID: magicmouse: add battery reporting for Magic Trackpad v1

From: Damiano Gragnaniello

Date: Wed Apr 15 2026 - 11:56:18 EST


The Magic Trackpad v1 (USB_DEVICE_ID_APPLE_MAGICTRACKPAD, 0x030e)
connects over Bluetooth and uses two AA batteries. When the device
sends Report ID 0x47, byte 1 carries the battery level as a
percentage (0-100) already expressed in firmware units.

This patch:
- Registers a power_supply instance via devm_power_supply_register()
during probe() for the v1 trackpad only (BT, vendor 0x05ac).
- Parses Report ID 0x47 in magicmouse_raw_event() and calls
power_supply_changed() to propagate the value to userspace.
- Exposes PRESENT, CAPACITY, SCOPE and STATUS properties, consistent
with how hid-sony and hid-logitech-hidpp expose battery data.
- Registration failure is treated as non-fatal so the input device
continues to work even if power_supply cannot be allocated.

Tested on Linux Mint / kernel 6.17 with an Apple Magic Trackpad A1339
(first generation, AA cells, firmware 0x0291).

Note: Technical analysis and initial boilerplate structure were assisted by
Claude (Anthropic AI). The logic has been manually verified against hardware
descriptors (rdesc) and tested on physical A1339 hardware.

Cc: Jiri Kosina <jikos@xxxxxxxxxx>
Cc: Benjamin Tissoires <benjamin.tissoires@xxxxxxxxxx>
Signed-off-by: Damiano Gragnaniello <damianogragnaniello@xxxxxxxxx>
---
--- /home/claude/hid-patch/hid-magicmouse.c.orig 2026-04-15 13:34:50.358612695 +0000
+++ /home/claude/hid-patch/hid-magicmouse.c 2026-04-15 13:35:39.402309524 +0000
@@ -15,6 +15,7 @@
#include <linux/hid.h>
#include <linux/input/mt.h>
#include <linux/module.h>
+#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/workqueue.h>

@@ -60,6 +61,7 @@
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#define DOUBLE_REPORT_ID 0xf7
+#define TRACKPAD_V1_BATTERY_REPORT_ID 0x47
#define USB_BATTERY_TIMEOUT_SEC 60

/* These definitions are not precise, but they're close enough. (Bits
@@ -124,6 +126,10 @@
* @hdev: Pointer to the underlying HID device.
* @work: Workqueue to handle initialization retry for quirky devices.
* @battery_timer: Timer for obtaining battery level information.
+ * @battery: Power supply instance for Magic Trackpad v1 AA battery reporting.
+ * @battery_desc: Descriptor for the power_supply registration.
+ * @battery_name: Name buffer for the power_supply instance.
+ * @battery_capacity: Last known battery level (0-100%) for Magic Trackpad v1.
*/
struct magicmouse_sc {
struct input_dev *input;
@@ -149,8 +155,46 @@
struct hid_device *hdev;
struct delayed_work work;
struct timer_list battery_timer;
+
+ /* Magic Trackpad v1 (AA battery) power_supply support */
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ char battery_name[64];
+ int battery_capacity;
+};
+
+static const enum power_supply_property magicmouse_v1_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_STATUS,
};

+static int magicmouse_v1_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct magicmouse_sc *msc = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = msc->battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
{
int touch = -1;
@@ -391,6 +435,19 @@
int x = 0, y = 0, ii, clicks = 0, npoints;

switch (data[0]) {
+ case TRACKPAD_V1_BATTERY_REPORT_ID:
+ /*
+ * Magic Trackpad v1 (AA battery, 0x030e) sends battery level
+ * in byte 1, already expressed as a percentage (0-100).
+ * Clamp defensively and notify the power_supply framework.
+ */
+ if (size < 2)
+ return 0;
+ if (msc->battery) {
+ msc->battery_capacity = clamp_val((int)data[1], 0, 100);
+ power_supply_changed(msc->battery);
+ }
+ return 0;
case TRACKPAD_REPORT_ID:
case TRACKPAD2_BT_REPORT_ID:
/* Expect four bytes of prefix, and N*9 bytes of touch data. */
@@ -890,6 +947,38 @@
magicmouse_fetch_battery(hdev);
}

+ /* Register power_supply for Magic Trackpad v1 (AA battery, BT only) */
+ if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
+ id->vendor == USB_VENDOR_ID_APPLE) {
+ struct power_supply_config psy_cfg = {};
+
+ msc->battery_capacity = 0;
+ snprintf(msc->battery_name, sizeof(msc->battery_name),
+ "hid-magictrackpad-v1-%s", dev_name(&hdev->dev));
+
+ msc->battery_desc.name = msc->battery_name;
+ msc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ msc->battery_desc.properties = magicmouse_v1_battery_props;
+ msc->battery_desc.num_properties =
+ ARRAY_SIZE(magicmouse_v1_battery_props);
+ msc->battery_desc.get_property =
+ magicmouse_v1_battery_get_property;
+
+ psy_cfg.drv_data = msc;
+
+ msc->battery = devm_power_supply_register(&hdev->dev,
+ &msc->battery_desc,
+ &psy_cfg);
+ if (IS_ERR(msc->battery)) {
+ ret = PTR_ERR(msc->battery);
+ hid_err(hdev,
+ "unable to register trackpad v1 battery: %d\n",
+ ret);
+ msc->battery = NULL;
+ /* Non-fatal: continue without battery reporting */
+ }
+ }
+
if (is_usb_magicmouse2(id->vendor, id->product) ||
(is_usb_magictrackpad2(id->vendor, id->product) &&
hdev->type != HID_TYPE_USBMOUSE))