diff --git a/hw/usb-hid.c b/hw/usb-hid.c
index c850a91..4d889d6 100644
--- a/hw/usb-hid.c
+++ b/hw/usb-hid.c
@@ -43,30 +43,41 @@
 #define USB_TABLET    2
 #define USB_KEYBOARD  3
 
-typedef struct USBMouseState {
-    int dx, dy, dz, buttons_state;
-    int x, y;
-    int mouse_grabbed;
+typedef struct USBPointerEvent {
+    int xdx, ydy; /* relative iff it's a mouse, otherwise absolute */
+    int dz, buttons_state;
+} USBPointerEvent;
+
+#define QUEUELENSHIFT 4 /* 16 events should be enough for a triple-click */
+#define QUEUELEN (1u<<QUEUELENSHIFT)
+#define QUEUEINDEXMASK (QUEUELEN-1u)
+#define QUEUE_INCR(v) ((v)++, (v) &= QUEUEINDEXMASK)
+
+typedef struct USBPointerState {
+    USBPointerEvent queue[QUEUELEN];
+    unsigned head, tail; /* indices into circular queue */
+    
+    int mouse_grabbed, xyrel;
     QEMUPutMouseEntry *eh_entry;
-} USBMouseState;
+} USBPointerState;
 
 typedef struct USBKeyboardState {
     uint16_t modifiers;
     uint8_t leds;
     uint8_t key[16];
     int keys;
+    int changed;
 } USBKeyboardState;
 
 typedef struct USBHIDState {
     USBDevice dev;
     union {
-        USBMouseState ptr;
+        USBPointerState ptr;
         USBKeyboardState kbd;
     };
     int kind;
     int protocol;
     uint8_t idle;
-    int changed;
     void *datain_opaque;
     void (*datain)(void *);
 } USBHIDState;
@@ -404,40 +415,64 @@ static const uint8_t usb_hid_usage_keys[0x100] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
 
-static void usb_hid_changed(USBHIDState *hs)
+static void usb_notify_datain_cb(USBHIDState *hs)
 {
-    hs->changed = 1;
-
     if (hs->datain)
         hs->datain(hs->datain_opaque);
 }
 
-static void usb_mouse_event(void *opaque,
-                            int dx1, int dy1, int dz1, int buttons_state)
-{
-    USBHIDState *hs = opaque;
-    USBMouseState *s = &hs->ptr;
-
-    s->dx += dx1;
-    s->dy += dy1;
-    s->dz += dz1;
-    s->buttons_state = buttons_state;
-
-    usb_hid_changed(hs);
+static void usb_pointer_event_clear(USBPointerEvent *e, int buttons) {
+    e->xdx = e->ydy = e->dz = 0;
+    e->buttons_state = buttons;
 }
 
-static void usb_tablet_event(void *opaque,
-			     int x, int y, int dz, int buttons_state)
-{
-    USBHIDState *hs = opaque;
-    USBMouseState *s = &hs->ptr;
+static void usb_pointer_event_combine(USBPointerEvent *e, int xyrel,
+				      int x1, int y1, int z1) {
+    if (xyrel) {
+	e->xdx += x1;
+	e->ydy += y1;
+    } else {
+	e->xdx = x1;
+	e->ydy = y1;
+    }
+    e->dz += z1;
+}
 
-    s->x = x;
-    s->y = y;
-    s->dz += dz;
-    s->buttons_state = buttons_state;
+static void usb_pointer_event(void *hs_v, int x1, int y1, int z1,
+			      int buttons_state) {
+    /* We combine events where feasible to keep the queue small.
+     * We shouldn't combine anything with the first event with
+     * a particular button state, as that would change the
+     * location of the button state change. */
+    USBHIDState *hs= hs_v;
+    USBPointerState *s = &hs->ptr;
+    unsigned use_slot= (s->tail-1) & QUEUEINDEXMASK;
+    unsigned previous_slot= (use_slot-1) & QUEUEINDEXMASK;
+
+    if (s->tail == s->head) {
+        use_slot= s->tail;
+        QUEUE_INCR(s->tail);
+        usb_pointer_event_clear(&s->queue[use_slot], buttons_state);
+    } else if (use_slot == s->head ||
+	s->queue[use_slot].buttons_state != buttons_state ||
+	s->queue[previous_slot].buttons_state != buttons_state) {
+	/* can't or shouldn't combine this event with previous one */
+	use_slot= s->tail;
+	QUEUE_INCR(s->tail);
+	if (use_slot == s->head) {
+	    /* queue full, oh well, discard something */
+	    s->head++;  s->head &= QUEUEINDEXMASK;
+	    /* but we preserve the relative motions */
+	    usb_pointer_event_combine(&s->queue[s->head], s->xyrel,
+				      s->queue[use_slot].xdx,
+				      s->queue[use_slot].ydy,
+				      s->queue[use_slot].dz);
+	}
+	usb_pointer_event_clear(&s->queue[use_slot], buttons_state);
+    }
+    usb_pointer_event_combine(&s->queue[use_slot],s->xyrel, x1,y1,z1);
 
-    usb_hid_changed(hs);
+    usb_notify_datain_cb(hs);
 }
 
 static void usb_keyboard_event(void *opaque, int keycode)
@@ -451,6 +486,9 @@ static void usb_keyboard_event(void *opaque, int keycode)
     hid_code = usb_hid_usage_keys[key | ((s->modifiers >> 1) & (1 << 7))];
     s->modifiers &= ~(1 << 8);
 
+    s->changed = 1;
+    usb_notify_datain_cb(hs);
+
     switch (hid_code) {
     case 0x00:
         return;
@@ -475,7 +513,6 @@ static void usb_keyboard_event(void *opaque, int keycode)
             if (s->key[i] == hid_code) {
                 s->key[i] = s->key[-- s->keys];
                 s->key[s->keys] = 0x00;
-                usb_hid_changed(hs);
                 break;
             }
         if (i < 0)
@@ -490,8 +527,6 @@ static void usb_keyboard_event(void *opaque, int keycode)
         } else
             return;
     }
-
-    usb_hid_changed(hs);
 }
 
 static inline int int_clamp(int val, int vmin, int vmax)
@@ -504,85 +539,100 @@ static inline int int_clamp(int val, int vmin, int vmax)
         return val;
 }
 
-static int usb_mouse_poll(USBHIDState *hs, uint8_t *buf, int len)
+static int usb_suppress_report(USBHIDState *hs, int unchanged) {
+    /* TODO: Implement finite idle delays.  */
+    return !hs->idle && unchanged; /* SET_IDLE 0 means do not report */
+}
+
+static int usb_pointer_poll(USBHIDState *hs, uint8_t *buf, int len)
 {
     int dx, dy, dz, b, l;
-    USBMouseState *s = &hs->ptr;
+    USBPointerState *s = &hs->ptr;
+    USBPointerEvent *e;
 
     if (!s->mouse_grabbed) {
-	s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, hs,
-                                                  0, "QEMU USB Mouse");
+	s->eh_entry = qemu_add_mouse_event_handler(usb_pointer_event, hs,
+                                          !s->xyrel, "QEMU USB Pointer");
 	s->mouse_grabbed = 1;
     }
 
-    dx = int_clamp(s->dx, -127, 127);
-    dy = int_clamp(s->dy, -127, 127);
-    dz = int_clamp(s->dz, -127, 127);
-
-    s->dx -= dx;
-    s->dy -= dy;
-    s->dz -= dz;
-
-    /* Appears we have to invert the wheel direction */
-    dz = 0 - dz;
+    if (usb_suppress_report(hs, s->head == s->tail))
+	return USB_RET_NAK;
 
-    b = 0;
-    if (s->buttons_state & MOUSE_EVENT_LBUTTON)
-        b |= 0x01;
-    if (s->buttons_state & MOUSE_EVENT_RBUTTON)
-        b |= 0x02;
-    if (s->buttons_state & MOUSE_EVENT_MBUTTON)
-        b |= 0x04;
+    if (s->head == s->tail)
+        /* use the last report */
+        s->head = (s->head - 1) & QUEUEINDEXMASK;
 
-    l = 0;
-    if (len > l)
-        buf[l ++] = b;
-    if (len > l)
-        buf[l ++] = dx;
-    if (len > l)
-        buf[l ++] = dy;
-    if (len > l)
-        buf[l ++] = dz;
-    return l;
-}
+    e = &s->queue[s->head];
 
-static int usb_tablet_poll(USBHIDState *hs, uint8_t *buf, int len)
-{
-    int dz, b, l;
-    USBMouseState *s = &hs->ptr;
+    dz = int_clamp(e->dz, -128, 127);
 
-    if (!s->mouse_grabbed) {
-	s->eh_entry = qemu_add_mouse_event_handler(usb_tablet_event, hs,
-                                                  1, "QEMU USB Tablet");
-	s->mouse_grabbed = 1;
+    e->dz -= dz;
+    if (s->xyrel) {
+        dx = int_clamp(e->xdx, -128, 127);
+        dy = int_clamp(e->ydy, -128, 127);
+	e->xdx -= dx;
+	e->ydy -= dy;
+    } else {
+        dx = e->xdx;
+        dy = e->ydy;
     }
-
-    dz = int_clamp(s->dz, -127, 127);
-    s->dz -= dz;
-
     /* Appears we have to invert the wheel direction */
     dz = 0 - dz;
+
+    if (!(e->dz ||
+	  (s->xyrel && (e->xdx || e->ydy)))) {
+	/* that deals with this event */
+	QUEUE_INCR(s->head);
+    }
+
     b = 0;
-    if (s->buttons_state & MOUSE_EVENT_LBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_LBUTTON)
         b |= 0x01;
-    if (s->buttons_state & MOUSE_EVENT_RBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_RBUTTON)
         b |= 0x02;
-    if (s->buttons_state & MOUSE_EVENT_MBUTTON)
+    if (e->buttons_state & MOUSE_EVENT_MBUTTON)
         b |= 0x04;
 
-    buf[0] = b;
-    buf[1] = s->x & 0xff;
-    buf[2] = s->x >> 8;
-    buf[3] = s->y & 0xff;
-    buf[4] = s->y >> 8;
-    buf[5] = dz;
-    l = 6;
+    switch (hs->kind) {
+    case USB_MOUSE:
+	l = 0;
+	if (len > l)
+	    buf[l ++] = b;
+	if (len > l)
+	    buf[l ++] = dx;
+	if (len > l)
+	    buf[l ++] = dy;
+	if (len > l)
+	    buf[l ++] = dz;
+	break;
+
+    case USB_TABLET:
+	buf[0] = b;
+	buf[1] = dx & 0xff;
+	buf[2] = dx >> 8;
+	buf[3] = dy & 0xff;
+	buf[4] = dy >> 8;
+	buf[5] = dz;
+	l = 6;
+	break;
+
+    default:
+	abort();
+    }
 
     return l;
 }
 
-static int usb_keyboard_poll(USBKeyboardState *s, uint8_t *buf, int len)
+static int usb_keyboard_poll(USBHIDState *hs, uint8_t *buf, int len)
 {
+    USBKeyboardState *s= &hs->kbd;
+
+    if (usb_suppress_report(hs, !s->changed))
+	return USB_RET_NAK;
+
+    s->changed= 0;
+
     if (len < 2)
         return 0;
 
@@ -609,16 +659,11 @@ static int usb_keyboard_write(USBKeyboardState *s, uint8_t *buf, int len)
     return 0;
 }
 
-static void usb_mouse_handle_reset(USBDevice *dev)
+static void usb_pointer_handle_reset(USBDevice *dev)
 {
     USBHIDState *s = (USBHIDState *)dev;
 
-    s->ptr.dx = 0;
-    s->ptr.dy = 0;
-    s->ptr.dz = 0;
-    s->ptr.x = 0;
-    s->ptr.y = 0;
-    s->ptr.buttons_state = 0;
+    s->ptr.head= s->ptr.tail = 0;
     s->protocol = 1;
 }
 
@@ -764,12 +809,10 @@ static int usb_hid_handle_control(USBDevice *dev, int request, int value,
         }
         break;
     case GET_REPORT:
-	if (s->kind == USB_MOUSE)
-            ret = usb_mouse_poll(s, data, length);
-	else if (s->kind == USB_TABLET)
-            ret = usb_tablet_poll(s, data, length);
+	if (s->kind == USB_MOUSE || s->kind == USB_TABLET)
+            ret = usb_pointer_poll(s, data, length);
         else if (s->kind == USB_KEYBOARD)
-            ret = usb_keyboard_poll(&s->kbd, data, length);
+            ret = usb_keyboard_poll(s, data, length);
         break;
     case SET_REPORT:
         if (s->kind == USB_KEYBOARD)
@@ -813,16 +856,10 @@ static int usb_hid_handle_data(USBDevice *dev, USBPacket *p)
     switch(p->pid) {
     case USB_TOKEN_IN:
         if (p->devep == 1) {
-            /* TODO: Implement finite idle delays.  */
-            if (!(s->changed || s->idle))
-                return USB_RET_NAK;
-            s->changed = 0;
-            if (s->kind == USB_MOUSE)
-                ret = usb_mouse_poll(s, p->data, p->len);
-            else if (s->kind == USB_TABLET)
-                ret = usb_tablet_poll(s, p->data, p->len);
+            if (s->kind == USB_MOUSE || s->kind == USB_TABLET)
+                ret = usb_pointer_poll(s, p->data, p->len);
             else if (s->kind == USB_KEYBOARD)
-                ret = usb_keyboard_poll(&s->kbd, p->data, p->len);
+                ret = usb_keyboard_poll(s, p->data, p->len);
         } else {
             goto fail;
         }
@@ -846,7 +883,7 @@ static void usb_hid_handle_destroy(USBDevice *dev)
     qemu_free(s);
 }
 
-USBDevice *usb_tablet_init(void)
+static USBDevice *usb_pointer_init(int kind, int xyrel, const char *devname)
 {
     USBHIDState *s;
 
@@ -854,38 +891,31 @@ USBDevice *usb_tablet_init(void)
     s->dev.speed = USB_SPEED_FULL;
     s->dev.handle_packet = usb_generic_handle_packet;
 
-    s->dev.handle_reset = usb_mouse_handle_reset;
+    s->dev.handle_reset = usb_pointer_handle_reset;
     s->dev.handle_control = usb_hid_handle_control;
     s->dev.handle_data = usb_hid_handle_data;
     s->dev.handle_destroy = usb_hid_handle_destroy;
-    s->kind = USB_TABLET;
-    /* Force poll routine to be run and grab input the first time.  */
-    s->changed = 1;
 
-    pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Tablet");
+    s->kind = kind;
+    s->ptr.xyrel = xyrel;
+    usb_pointer_handle_reset((USBDevice*)s);
 
+    /* Force poll routine to be run and grab input the first time.  */
+    usb_pointer_event_clear(&s->ptr.queue[0], 0);
+    s->ptr.tail = 1;
+
+    pstrcpy(s->dev.devname, sizeof(s->dev.devname), devname);
     return (USBDevice *)s;
 }
 
-USBDevice *usb_mouse_init(void)
+USBDevice *usb_tablet_init(void)
 {
-    USBHIDState *s;
-
-    s = qemu_mallocz(sizeof(USBHIDState));
-    s->dev.speed = USB_SPEED_FULL;
-    s->dev.handle_packet = usb_generic_handle_packet;
-
-    s->dev.handle_reset = usb_mouse_handle_reset;
-    s->dev.handle_control = usb_hid_handle_control;
-    s->dev.handle_data = usb_hid_handle_data;
-    s->dev.handle_destroy = usb_hid_handle_destroy;
-    s->kind = USB_MOUSE;
-    /* Force poll routine to be run and grab input the first time.  */
-    s->changed = 1;
-
-    pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Mouse");
+    return usb_pointer_init(USB_TABLET, 0, "QEMU USB Tablet");
+}
 
-    return (USBDevice *)s;
+USBDevice *usb_mouse_init(void)
+{
+    return usb_pointer_init(USB_MOUSE, 1, "QEMU USB Mouse");
 }
 
 USBDevice *usb_keyboard_init(void)
@@ -901,6 +931,7 @@ USBDevice *usb_keyboard_init(void)
     s->dev.handle_data = usb_hid_handle_data;
     s->dev.handle_destroy = usb_hid_handle_destroy;
     s->kind = USB_KEYBOARD;
+    s->kbd.changed= 0;
 
     pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Keyboard");
 
