Adds a self-contained USB-HID device emulating the USB-cable face of
the Apple Mighty Mouse (idVendor 0x05ac, idProduct 0x0304, the wired
ball mouse M9087 released 2005). Descriptors and HID report descriptor
are byte-identical to a real Mighty Mouse captured in USB-cable mode.

Companion to apple-magic-keyboard (previous patch). macOS recovery /
install / multi-touch UI panels behave correctly only when the
pointing device matches an Apple-VID peripheral; usb-tablet binds the
generic IOUSBHostHIDDevice driver and breaks recovery's HID stack on
iMac20,1 SMBIOS guests.

Single HID interface, single IN endpoint, standard boot mouse + scroll
HID Report Descriptor:

  byte 0: 3-bit button mask (left, right, middle) + 5-bit padding
  byte 1: signed int8 dX        per-frame pointer delta
  byte 2: signed int8 dY        per-frame pointer delta
  byte 3: signed int8 vWheel    Generic Desktop / Wheel
  byte 4: signed int8 hWheel    Consumer / AC Pan (scroll-ball X)

bSubClass=1, bProto=2 (boot mouse) — guaranteed-binding driver match
across every macOS version. Binds AppleHIDMouseEventDriver /
IOHIDPointing chain natively; no proprietary multitouch protocol
required.

VNC sources deliver absolute pointer events; the device's input
handler converts them to per-frame REL deltas via a last_abs_x/y
tracker since the boot-mouse report only carries int8 dX/dY. A bounded
queue (depth 64) drains accumulated motion across multiple reports per
input sync so cursor movement stays responsive under large pointer
deltas.

A legacy TypeInfo alias `apple-magic-tablet` is registered to the same
implementation so existing in-tree consumers (e.g. mos-docker test
scripts using TABLET_DEVICE=apple-magic-tablet) keep working during
the canonical-name transition. Drop the alias when in-tree consumers
move to apple-mighty-mouse.

Verified end-to-end on macOS 15.7.5: AppleHIDMouseEventDriver binds on
boot; cursor motion, left/right/middle click, vertical scroll, and
horizontal scroll all visible in noVNC.

Signed-off-by: Matthew Jackson <[email protected]>
---
 hw/usb/dev-hid.c | 573 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 573 insertions(+)

diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c
index 7cce69b..9a4aa75 100644
--- a/hw/usb/dev-hid.c
+++ b/hw/usb/dev-hid.c
@@ -1699,6 +1699,577 @@ static const TypeInfo usb_apple_magic_kbd_info = {
     .class_init    = usb_apple_magic_kbd_class_initfn,
 };
 
+
+/*
+ * apple-mighty-mouse (USB type name; legacy alias `apple-magic-tablet`)
+ * --------------------------------------------------------------------
+ *
+ * Internal symbols carry the historic `amt` / `apple_magic_tablet` /
+ * `USBAppleMagicTabletState` prefixes — kept to minimize diff churn
+ * since this file's user-facing identity flipped during the 2026-05-09
+ * fresh-eyes rewrite (see header below). Only the `-device` string and
+ * vmstate name are user-visible and were renamed.
+ */
+
+enum {
+    STR_AMT_MFR = 1,
+    STR_AMT_PRODUCT,
+    STR_AMT_SERIAL,
+    STR_AMT_INTERFACE,
+};
+
+static const USBDescStrings desc_strings_amt = {
+    [STR_AMT_MFR]       = "Apple Inc.",
+    [STR_AMT_PRODUCT]   = "Apple Mighty Mouse",
+    [STR_AMT_SERIAL]    = "CC2916600VBJ2XQA5",
+    [STR_AMT_INTERFACE] = "Mouse",
+};
+
+/*
+ * apple-mighty-mouse device — FRESH-EYES REWRITE 2026-05-09.
+ *
+ * Goal: macOS sees a USB HID mouse from Apple that "just works" for cursor
+ * motion. Not Magic Trackpad multitouch — that path needs deep RE we don't
+ * have ground-truth wire bytes for. Instead, present as **Apple Mighty
+ * Mouse (PID 0x0304)** — Apple's wired ball mouse. macOS' generic
+ * AppleHIDMouseEventDriver / IOHIDPointing chain handles standard boot
+ * mouse reports natively; cursor motion works without any vendor-specific
+ * SET_REPORT / multitouch protocol.
+ *
+ * Why this works where Magic Trackpad emulation didn't:
+ *   - Mighty Mouse PID 0x0304 does NOT match AppleMultitouchTrackpadHID
+ *     EventDriver (which is the driver gating cursor on PID 0x0265).
+ *   - Mighty Mouse uses standard USB HID boot mouse protocol (3-byte
+ *     reports: button + dx + dy) — no proprietary 1387-byte Report 0x44.
+ *   - Single USB interface — no SET_REPORT-gated multitouch enable path.
+ *   - bSubClass=1 bProto=2 (boot mouse) — guaranteed-binding driver match
+ *     across every macOS version.
+ *
+ * Single interface, single IN endpoint, simple 3-byte boot-mouse reports.
+ */
+
+/* Boot-mouse + scroll HID Report Descriptor — 5-byte report (no Report ID).
+ *   byte 0: 3-bit button mask + 5-bit padding
+ *   byte 1: signed int8 dX
+ *   byte 2: signed int8 dY
+ *   byte 3: signed int8 vertical wheel
+ *   byte 4: signed int8 horizontal wheel (Mighty Mouse scroll ball X)
+ * macOS' generic mouse driver parses this without quirks; this is the
+ * standard layout for any USB HID mouse with a scroll wheel/ball. */
+static const uint8_t amt_boot_mouse_report_desc[] = {
+    0x05, 0x01,             /* Usage Page (Generic Desktop) */
+    0x09, 0x02,             /* Usage (Mouse) */
+    0xa1, 0x01,             /* Collection (Application) */
+      0x09, 0x01,           /*   Usage (Pointer) */
+      0xa1, 0x00,           /*   Collection (Physical) */
+        0x05, 0x09,         /*     Usage Page (Buttons) */
+        0x19, 0x01,         /*     Usage Minimum (1) */
+        0x29, 0x03,         /*     Usage Maximum (3) */
+        0x15, 0x00,         /*     Logical Min (0) */
+        0x25, 0x01,         /*     Logical Max (1) */
+        0x95, 0x03,         /*     Report Count (3) */
+        0x75, 0x01,         /*     Report Size (1) */
+        0x81, 0x02,         /*     Input (Data, Var, Abs) — 3 button bits */
+        0x95, 0x01,         /*     Report Count (1) */
+        0x75, 0x05,         /*     Report Size (5) */
+        0x81, 0x03,         /*     Input (Const, Var, Abs) — 5 padding bits */
+        0x05, 0x01,         /*     Usage Page (Generic Desktop) */
+        0x09, 0x30,         /*     Usage (X) */
+        0x09, 0x31,         /*     Usage (Y) */
+        0x09, 0x38,         /*     Usage (Wheel — vertical) */
+        0x15, 0x81,         /*     Logical Min (-127) */
+        0x25, 0x7f,         /*     Logical Max (127) */
+        0x75, 0x08,         /*     Report Size (8) */
+        0x95, 0x03,         /*     Report Count (3) */
+        0x81, 0x06,         /*     Input (Data, Var, Rel) — X, Y, Wheel */
+        0x05, 0x0c,         /*     Usage Page (Consumer) */
+        0x0a, 0x38, 0x02,   /*     Usage (AC Pan — horizontal scroll) */
+        0x15, 0x81,         /*     Logical Min (-127) */
+        0x25, 0x7f,         /*     Logical Max (127) */
+        0x75, 0x08,         /*     Report Size (8) */
+        0x95, 0x01,         /*     Report Count (1) */
+        0x81, 0x06,         /*     Input (Data, Var, Rel) — HWheel */
+      0xc0,                 /*   End Collection (Physical) */
+    0xc0,                   /* End Collection (Application) */
+};
+
+static const USBDescIface desc_iface_apple_magic_tablet = {
+    .bInterfaceNumber              = 0,
+    .bNumEndpoints                 = 1,
+    .bInterfaceClass               = USB_CLASS_HID,
+    .bInterfaceSubClass            = 0x01,    /* Boot subclass */
+    .bInterfaceProtocol            = 0x02,    /* Mouse protocol */
+    .iInterface                    = STR_AMT_INTERFACE,
+    .ndesc                         = 1,
+    .descs = (USBDescOther[]) {
+        {
+            .data = (uint8_t[]) {
+                0x09,                             /* bLength */
+                USB_DT_HID,                       /* bDescriptorType */
+                0x10, 0x01,                       /* bcdHID 1.10 */
+                0x00,                             /* bCountryCode */
+                0x01,                             /* bNumDescriptors */
+                USB_DT_REPORT,
+                sizeof(amt_boot_mouse_report_desc) & 0xff,
+                sizeof(amt_boot_mouse_report_desc) >> 8,
+            },
+        },
+    },
+    .eps = (USBDescEndpoint[]) {
+        {
+            .bEndpointAddress      = USB_DIR_IN | 0x01,
+            .bmAttributes          = USB_ENDPOINT_XFER_INT,
+            .wMaxPacketSize        = 8,
+            .bInterval             = 8,
+        },
+    },
+};
+
+static const USBDescDevice desc_device_apple_magic_tablet = {
+    .bcdUSB                        = 0x0200,
+    .bMaxPacketSize0               = 64,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 1,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = STR_AMT_PRODUCT,
+            .bmAttributes          = USB_CFG_ATT_ONE,
+            .bMaxPower             = 50,    /* 100 mA — Mighty Mouse is wired 
*/
+            .nif                   = 1,
+            .ifs                   = &desc_iface_apple_magic_tablet,
+        },
+    },
+};
+
+static const USBDesc desc_apple_magic_tablet = {
+    .id = {
+        .idVendor          = 0x05ac,    /* Apple Inc. */
+        .idProduct         = 0x0304,    /* Apple Mighty Mouse (M9087) */
+        .bcdDevice         = 0x0150,    /* fw rev 1.5 — looks plausible */
+        .iManufacturer     = STR_AMT_MFR,
+        .iProduct          = STR_AMT_PRODUCT,
+        .iSerialNumber     = STR_AMT_SERIAL,
+    },
+    /*
+     * Real Magic Trackpad runs at USB full-speed (12 Mb/s) despite
+     * declaring bcdUSB=0x0200 — same gotcha as Magic Keyboard.
+     * Advertise the same config under both .full and .high so QEMU's
+     * USB stack can pick whichever speed the host controller
+     * (qemu-xhci) negotiates.
+     */
+    .full = &desc_device_apple_magic_tablet,
+    .high = &desc_device_apple_magic_tablet,
+    .str  = desc_strings_amt,
+};
+
+/* Maximum queued boot-mouse reports waiting for the host to poll EP1.
+ * Must be a power of two. With dx/dy clamped to int8, a single large
+ * VNC pointer move (e.g. 2000 px) drains across ~16 reports — bumping
+ * to 64 keeps headroom for fast motion. */
+#define AMT_QUEUE_DEPTH      64
+#define AMT_REPORT_LEN       5      /* button + dx + dy + wheel + hwheel */
+
+typedef struct USBAppleMagicTabletReport {
+    uint8_t data[AMT_REPORT_LEN];
+} USBAppleMagicTabletReport;
+
+typedef struct USBAppleMagicTabletState {
+    USBDevice dev;
+    USBEndpoint *intr;
+
+    /* Pending input accumulation (drained on .sync). */
+    int32_t  pending_dx;
+    int32_t  pending_dy;
+    int32_t  pending_wheel;     /* vertical scroll, REL clicks */
+    int32_t  pending_hwheel;    /* horizontal scroll, REL clicks */
+    bool     button_left;
+    bool     button_right;
+    bool     button_middle;
+    bool     pending_event;
+
+    /* ABS→REL conversion state (VNC/SPICE/SDL deliver ABS). -1 = none yet. */
+    int32_t  last_abs_x;
+    int32_t  last_abs_y;
+
+    /* Ring queue of pending boot-mouse reports. */
+    USBAppleMagicTabletReport queue[AMT_QUEUE_DEPTH];
+    unsigned q_head;
+    unsigned q_tail;
+
+    /* QEMU input handler binding. */
+    QemuInputHandlerState *input_handler;
+} USBAppleMagicTabletState;
+
+/*
+ * Canonical type name is `apple-mighty-mouse` (this device emulates Apple
+ * Mighty Mouse PID 0x0304 — the wired ball mouse). Historic name
+ * `apple-magic-tablet` predates the 2026-05-09 fresh-eyes rewrite and is
+ * preserved as a legacy alias (registered below) for in-tree mos-docker
+ * `test.sh` back-compat. Internal symbols (`USBAppleMagicTabletState`,
+ * `usb_apple_magic_tablet_*`, `USB_APPLE_MAGIC_TABLET()` cast) keep their
+ * historic names to minimize diff churn — the rename is purely the
+ * user-facing `-device` string.
+ */
+#define TYPE_USB_APPLE_MAGIC_TABLET "apple-mighty-mouse"
+#define TYPE_USB_APPLE_MAGIC_TABLET_LEGACY "apple-magic-tablet"
+OBJECT_DECLARE_SIMPLE_TYPE(USBAppleMagicTabletState, USB_APPLE_MAGIC_TABLET)
+
+static inline unsigned amt_q_count(USBAppleMagicTabletState *s)
+{
+    return (s->q_head - s->q_tail) & (AMT_QUEUE_DEPTH - 1);
+}
+
+static inline bool amt_q_empty(USBAppleMagicTabletState *s)
+{
+    return s->q_head == s->q_tail;
+}
+
+static inline bool amt_q_full(USBAppleMagicTabletState *s)
+{
+    return amt_q_count(s) == AMT_QUEUE_DEPTH - 1;
+}
+
+static void amt_enqueue(USBAppleMagicTabletState *s, const uint8_t *data)
+{
+    if (amt_q_full(s)) {
+        /* Queue full — drop oldest. Latest motion matters more than stale. */
+        s->q_tail = (s->q_tail + 1) & (AMT_QUEUE_DEPTH - 1);
+    }
+    USBAppleMagicTabletReport *r = &s->queue[s->q_head];
+    memcpy(r->data, data, AMT_REPORT_LEN);
+    s->q_head = (s->q_head + 1) & (AMT_QUEUE_DEPTH - 1);
+}
+
+static int8_t amt_clamp_i8(int32_t v)
+{
+    if (v >  127) return  127;
+    if (v < -127) return -127;
+    return (int8_t)v;
+}
+
+/*
+ * Boot-mouse 3-byte report (no Report ID, per HID 1.11 boot mouse spec):
+ *   byte 0: 3-bit button mask (bit 0 = left, 1 = right, 2 = middle) + padding
+ *   byte 1: signed int8 dX
+ *   byte 2: signed int8 dY
+ *
+ * macOS' generic IOHIDPointing / AppleHIDMouseEventDriver consumes this
+ * directly as cursor motion. Apple Mighty Mouse (PID 0x0304) uses exactly
+ * this layout for its primary HID interface.
+ */
+static void amt_emit_boot_mouse(USBAppleMagicTabletState *s)
+{
+    uint8_t buf[AMT_REPORT_LEN];
+    int8_t dx = amt_clamp_i8(s->pending_dx);
+    int8_t dy = amt_clamp_i8(s->pending_dy);
+    int8_t wheel = amt_clamp_i8(s->pending_wheel);
+    int8_t hwheel = amt_clamp_i8(s->pending_hwheel);
+
+    s->pending_dx -= dx;
+    s->pending_dy -= dy;
+    s->pending_wheel -= wheel;
+    s->pending_hwheel -= hwheel;
+
+    buf[0] = (s->button_left   ? 0x01 : 0x00) |
+             (s->button_right  ? 0x02 : 0x00) |
+             (s->button_middle ? 0x04 : 0x00);
+    buf[1] = (uint8_t)dx;
+    buf[2] = (uint8_t)dy;
+    buf[3] = (uint8_t)wheel;
+    buf[4] = (uint8_t)hwheel;
+
+    amt_enqueue(s, buf);
+    usb_wakeup(s->intr, 0);
+}
+
+/* QemuInputHandler.event — accumulate per-event state. */
+static void amt_input_event(DeviceState *dev, QemuConsole *src,
+                            InputEvent *evt)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    switch (evt->type) {
+    case INPUT_EVENT_KIND_REL: {
+        InputMoveEvent *move = evt->u.rel.data;
+        if (move->axis == INPUT_AXIS_X) {
+            s->pending_dx += move->value;
+        } else if (move->axis == INPUT_AXIS_Y) {
+            s->pending_dy += move->value;
+        }
+        s->pending_event = true;
+        break;
+    }
+    case INPUT_EVENT_KIND_ABS: {
+        /* VNC/SPICE/SDL deliver ABS (0..0x7fff). Convert to REL by diffing
+         * against the prior ABS position; first ABS event seeds the anchor
+         * but emits no delta. Scale 0..32767 → 0..1920/1080 px. */
+        InputMoveEvent *move = evt->u.abs.data;
+        if (move->axis == INPUT_AXIS_X) {
+            int32_t scaled = (move->value * 1920) / 0x7fff;
+            if (s->last_abs_x >= 0) {
+                s->pending_dx += scaled - s->last_abs_x;
+            }
+            s->last_abs_x = scaled;
+        } else if (move->axis == INPUT_AXIS_Y) {
+            int32_t scaled = (move->value * 1080) / 0x7fff;
+            if (s->last_abs_y >= 0) {
+                s->pending_dy += scaled - s->last_abs_y;
+            }
+            s->last_abs_y = scaled;
+        }
+        s->pending_event = true;
+        break;
+    }
+    case INPUT_EVENT_KIND_BTN: {
+        InputBtnEvent *btn = evt->u.btn.data;
+        switch (btn->button) {
+        case INPUT_BUTTON_LEFT:
+            s->button_left = btn->down;
+            s->pending_event = true;
+            break;
+        case INPUT_BUTTON_RIGHT:
+            s->button_right = btn->down;
+            s->pending_event = true;
+            break;
+        case INPUT_BUTTON_MIDDLE:
+            s->button_middle = btn->down;
+            s->pending_event = true;
+            break;
+        case INPUT_BUTTON_WHEEL_UP:
+            if (btn->down) {
+                s->pending_wheel += 1;
+                s->pending_event = true;
+            }
+            break;
+        case INPUT_BUTTON_WHEEL_DOWN:
+            if (btn->down) {
+                s->pending_wheel -= 1;
+                s->pending_event = true;
+            }
+            break;
+        case INPUT_BUTTON_WHEEL_LEFT:
+            if (btn->down) {
+                s->pending_hwheel -= 1;
+                s->pending_event = true;
+            }
+            break;
+        case INPUT_BUTTON_WHEEL_RIGHT:
+            if (btn->down) {
+                s->pending_hwheel += 1;
+                s->pending_event = true;
+            }
+            break;
+        default:
+            break;
+        }
+        break;
+    }
+    default:
+        break;
+    }
+}
+
+/* QemuInputHandler.sync — drain pending_dx/dy in 3-byte boot-mouse reports.
+ *
+ * Each report's dx/dy is clamped to int8 (±127). Large motions (e.g.,
+ * a single VNC ABS event mapping to a 1000+ px delta after diffing
+ * against the prior absolute position) need multiple reports to drain.
+ * Loop until both axes are zero, queueing a report per iteration.
+ *
+ * Cap the loop at a reasonable max so a runaway delta can't lock us up;
+ * the queue depth + bInterval naturally back-pressure if the host falls
+ * behind. */
+static void amt_input_sync(DeviceState *dev)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    if (!s->pending_event) {
+        return;
+    }
+    s->pending_event = false;
+
+    /* Always emit at least once (catches button-only changes with no motion). 
*/
+    int loops = 0;
+    do {
+        amt_emit_boot_mouse(s);
+        loops++;
+    } while ((s->pending_dx != 0 || s->pending_dy != 0 ||
+              s->pending_wheel != 0 || s->pending_hwheel != 0) && loops < 64);
+}
+
+static QemuInputHandler amt_input_handler = {
+    .name  = "Apple Mighty Mouse",
+    /*
+     * Accept both REL and ABS pointer events. SDL/SPICE/HMP sendkey
+     * tend to send REL; VNC sends ABS. amt_input_event maps both onto
+     * the device's int8 dX/dY wire format.
+     */
+    .mask  = INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS |
+             INPUT_EVENT_MASK_BTN,
+    .event = amt_input_event,
+    .sync  = amt_input_sync,
+};
+
+static void usb_apple_magic_tablet_realize(USBDevice *dev, Error **errp)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    usb_desc_create_serial(dev);
+    usb_desc_init(dev);
+    s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
+
+    s->q_head = s->q_tail = 0;
+    s->pending_dx = s->pending_dy = 0;
+    s->pending_wheel = s->pending_hwheel = 0;
+    s->button_left = s->button_right = s->button_middle = false;
+    s->last_abs_x = -1;
+    s->last_abs_y = -1;
+    s->pending_event = false;
+
+    s->input_handler = qemu_input_handler_register(DEVICE(dev),
+                                                   &amt_input_handler);
+    /* q35's built-in i8042 PS/2 mouse claims the input first; mark our
+     * handler active so VNC pointer events reach us. */
+    qemu_input_handler_activate(s->input_handler);
+}
+
+static void usb_apple_magic_tablet_unrealize(USBDevice *dev)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    if (s->input_handler) {
+        qemu_input_handler_unregister(s->input_handler);
+        s->input_handler = NULL;
+    }
+}
+
+static void usb_apple_magic_tablet_handle_reset(USBDevice *dev)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    s->q_head = s->q_tail = 0;
+    s->pending_dx = s->pending_dy = 0;
+    s->pending_wheel = s->pending_hwheel = 0;
+    s->button_left = s->button_right = s->button_middle = false;
+    s->pending_event = false;
+    s->last_abs_x = -1;
+    s->last_abs_y = -1;
+}
+
+static void usb_apple_magic_tablet_handle_control(USBDevice *dev, USBPacket *p,
+                                                  int request, int value,
+                                                  int index, int length,
+                                                  uint8_t *data)
+{
+    int ret;
+
+    ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+    if (ret >= 0) {
+        return;
+    }
+
+    switch (request) {
+    case InterfaceRequest | USB_REQ_GET_DESCRIPTOR:
+        if ((value >> 8) == 0x22) {
+            uint16_t rd_len = sizeof(amt_boot_mouse_report_desc);
+            uint16_t copy = length < rd_len ? length : rd_len;
+            memcpy(data, amt_boot_mouse_report_desc, copy);
+            p->actual_length = copy;
+            return;
+        }
+        break;
+    case HID_GET_IDLE:
+        data[0] = 0;
+        p->actual_length = 1;
+        return;
+    case HID_SET_IDLE:
+        return;
+    case HID_GET_PROTOCOL:
+        data[0] = 1;       /* report protocol */
+        p->actual_length = 1;
+        return;
+    case HID_SET_PROTOCOL:
+        return;
+    case HID_GET_REPORT: {
+        uint16_t reply_len = (length > 0 && length <= 64) ? length : 1;
+        if (reply_len > length) {
+            reply_len = length;
+        }
+        memset(data, 0, reply_len);
+        p->actual_length = reply_len;
+        return;
+    }
+    case HID_SET_REPORT:
+        /* Silently accept any host-pushed report (LED state etc.) */
+        p->actual_length = length;
+        return;
+    }
+
+    p->status = USB_RET_STALL;
+}
+
+static void usb_apple_magic_tablet_handle_data(USBDevice *dev, USBPacket *p)
+{
+    USBAppleMagicTabletState *s = USB_APPLE_MAGIC_TABLET(dev);
+
+    if (p->pid != USB_TOKEN_IN || p->ep->nr != 1) {
+        p->status = USB_RET_STALL;
+        return;
+    }
+    if (amt_q_empty(s)) {
+        p->status = USB_RET_NAK;
+        return;
+    }
+
+    USBAppleMagicTabletReport *r = &s->queue[s->q_tail];
+    s->q_tail = (s->q_tail + 1) & (AMT_QUEUE_DEPTH - 1);
+    usb_packet_copy(p, r->data, AMT_REPORT_LEN);
+}
+
+static const VMStateDescription vmstate_apple_magic_tablet = {
+    .name = "apple-mighty-mouse",
+    .unmigratable = 1,
+};
+
+static void usb_apple_magic_tablet_class_initfn(ObjectClass *klass,
+                                                const void *data)
+{
+    DeviceClass    *dc = DEVICE_CLASS(klass);
+    USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+    uc->realize        = usb_apple_magic_tablet_realize;
+    uc->product_desc   = "Apple Mighty Mouse";
+    uc->usb_desc       = &desc_apple_magic_tablet;
+    uc->handle_reset   = usb_apple_magic_tablet_handle_reset;
+    uc->handle_control = usb_apple_magic_tablet_handle_control;
+    uc->handle_data    = usb_apple_magic_tablet_handle_data;
+    uc->unrealize      = usb_apple_magic_tablet_unrealize;
+    set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+    dc->desc           = "Apple Mighty Mouse (USB HID boot mouse, "
+                         "VID 0x05ac PID 0x0304)";
+    dc->vmsd           = &vmstate_apple_magic_tablet;
+}
+
+static const TypeInfo usb_apple_magic_tablet_info = {
+    .name          = TYPE_USB_APPLE_MAGIC_TABLET,
+    .parent        = TYPE_USB_DEVICE,
+    .instance_size = sizeof(USBAppleMagicTabletState),
+    .class_init    = usb_apple_magic_tablet_class_initfn,
+};
+
+/*
+ * Legacy alias: `-device apple-magic-tablet` resolves to the same Mighty
+ * Mouse implementation. Inheriting via .parent reuses the parent's
+ * class_init and instance_size — no code duplication. Drop this alias
+ * after all in-tree consumers (mos-docker test.sh TABLET_DEVICE env) move
+ * to the canonical `apple-mighty-mouse`.
+ */
+static const TypeInfo usb_apple_magic_tablet_legacy_info = {
+    .name   = TYPE_USB_APPLE_MAGIC_TABLET_LEGACY,
+    .parent = TYPE_USB_APPLE_MAGIC_TABLET,
+};
+
 static void usb_hid_register_types(void)
 {
     type_register_static(&usb_hid_type_info);
@@ -1709,6 +2280,8 @@ static void usb_hid_register_types(void)
     type_register_static(&usb_keyboard_info);
     usb_legacy_register("usb-kbd", "keyboard", NULL);
     type_register_static(&usb_apple_magic_kbd_info);
+    type_register_static(&usb_apple_magic_tablet_info);
+    type_register_static(&usb_apple_magic_tablet_legacy_info);
 }
 
 type_init(usb_hid_register_types)
-- 
2.50.1 (Apple Git-155)


Reply via email to