Add support for Apple Magic Trackpad.

3 button emulation by detecting in which area of the bottom of
the device the trackpad's button is pressed.
Pointer move support with 1 finger touch, X/Y scroll with 2-finger touch.
TODO:
- detect tap to emulate button press and drag/n/drop.
- Detect and support zoom, if wsmouse allows to report this
This commit is contained in:
bouyer 2015-04-06 17:45:31 +00:00
parent 6a3c9cdcd1
commit 8692d33ed3
1 changed files with 237 additions and 17 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: btmagic.c,v 1.11 2014/08/05 07:55:31 rtr Exp $ */ /* $NetBSD: btmagic.c,v 1.12 2015/04/06 17:45:31 bouyer Exp $ */
/*- /*-
* Copyright (c) 2010 The NetBSD Foundation, Inc. * Copyright (c) 2010 The NetBSD Foundation, Inc.
@ -85,7 +85,7 @@
*****************************************************************************/ *****************************************************************************/
#include <sys/cdefs.h> #include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.11 2014/08/05 07:55:31 rtr Exp $"); __KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.12 2015/04/06 17:45:31 bouyer Exp $");
#include <sys/param.h> #include <sys/param.h>
#include <sys/conf.h> #include <sys/conf.h>
@ -163,11 +163,13 @@ struct btmagic_softc {
int sc_rw; int sc_rw;
/* previous touches */ /* previous touches */
uint32_t sc_smask; /* scrolling */ uint32_t sc_smask; /* active(s) IDs */
int sc_az[16]; int sc_nfingers; /* number of active IDs */
int sc_aw[16]; int sc_ax[16];
int sc_ay[16];
/* previous mouse buttons */ /* previous mouse buttons */
int sc_mb_id; /* which ID selects the button */
uint32_t sc_mb; uint32_t sc_mb;
}; };
@ -216,7 +218,16 @@ static void btmagic_complete(void *, int);
static void btmagic_linkmode(void *, int); static void btmagic_linkmode(void *, int);
static void btmagic_input(void *, struct mbuf *); static void btmagic_input(void *, struct mbuf *);
static void btmagic_input_basic(struct btmagic_softc *, uint8_t *, size_t); static void btmagic_input_basic(struct btmagic_softc *, uint8_t *, size_t);
static void btmagic_input_magic(struct btmagic_softc *, uint8_t *, size_t); static void btmagic_input_magicm(struct btmagic_softc *, uint8_t *, size_t);
static void btmagic_input_magict(struct btmagic_softc *, uint8_t *, size_t);
/* report types (data[1]) */
#define BASIC_REPORT_ID 0x10
#define TRACKPAD_REPORT_ID 0x28
#define MOUSE_REPORT_ID 0x29
#define BATT_STAT_REPORT_ID 0x30
#define BATT_STRENGHT_REPORT_ID 0x47
#define SURFACE_REPORT_ID 0x61
static const struct btproto btmagic_ctl_proto = { static const struct btproto btmagic_ctl_proto = {
btmagic_connecting, btmagic_connecting,
@ -259,7 +270,8 @@ btmagic_match(device_t self, cfdata_t cfdata, void *aux)
if (prop_dictionary_get_uint16(aux, BTDEVvendor, &v) if (prop_dictionary_get_uint16(aux, BTDEVvendor, &v)
&& prop_dictionary_get_uint16(aux, BTDEVproduct, &p) && prop_dictionary_get_uint16(aux, BTDEVproduct, &p)
&& v == USB_VENDOR_APPLE && v == USB_VENDOR_APPLE
&& p == USB_PRODUCT_APPLE_MAGICMOUSE) && (p == USB_PRODUCT_APPLE_MAGICMOUSE ||
p == USB_PRODUCT_APPLE_MAGICTRACKPAD))
return 2; /* trump bthidev(4) */ return 2; /* trump bthidev(4) */
return 0; return 0;
@ -1047,15 +1059,18 @@ btmagic_input(void *arg, struct mbuf *m)
break; break;
switch (data[1]) { switch (data[1]) {
case 0x10: /* Basic mouse (input) */ case BASIC_REPORT_ID: /* Basic mouse (input) */
btmagic_input_basic(sc, data + 2, len - 2); btmagic_input_basic(sc, data + 2, len - 2);
break; break;
case 0x29: /* Magic touch (input) */ case TRACKPAD_REPORT_ID: /* Magic trackpad (input) */
btmagic_input_magic(sc, data + 2, len - 2); btmagic_input_magict(sc, data + 2, len - 2);
break;
case MOUSE_REPORT_ID: /* Magic touch (input) */
btmagic_input_magicm(sc, data + 2, len - 2);
break; break;
case 0x30: /* Battery status (input) */ case BATT_STAT_REPORT_ID: /* Battery status (input) */
if (len != 3) if (len != 3)
break; break;
@ -1068,7 +1083,7 @@ btmagic_input(void *arg, struct mbuf *m)
} }
break; break;
case 0x47: /* Battery strength (feature) */ case BATT_STRENGHT_REPORT_ID: /* Battery strength (feature) */
if (len != 3) if (len != 3)
break; break;
@ -1076,7 +1091,7 @@ btmagic_input(void *arg, struct mbuf *m)
data[2]); data[2]);
break; break;
case 0x61: /* Surface detection (input) */ case SURFACE_REPORT_ID: /* Surface detection (input) */
if (len != 3) if (len != 3)
break; break;
@ -1246,7 +1261,7 @@ static const struct {
#define BTMAGIC_PHASE_CANCEL 0x0 #define BTMAGIC_PHASE_CANCEL 0x0
static void static void
btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len) btmagic_input_magicm(struct btmagic_softc *sc, uint8_t *data, size_t len)
{ {
uint32_t mb; uint32_t mb;
int dx, dy, dz, dw; int dx, dy, dz, dw;
@ -1290,10 +1305,12 @@ btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
switch (hid_get_udata(data, &touch.phase)) { switch (hid_get_udata(data, &touch.phase)) {
case BTMAGIC_PHASE_CONT: case BTMAGIC_PHASE_CONT:
#define sc_az sc_ay
#define sc_aw sc_ax
tz = az - sc->sc_az[id]; tz = az - sc->sc_az[id];
tw = aw - sc->sc_aw[id]; tw = aw - sc->sc_aw[id];
if (ISSET(sc->sc_smask, id)) { if (ISSET(sc->sc_smask, __BIT(id))) {
/* scrolling finger */ /* scrolling finger */
dz += btmagic_scale(tz, &sc->sc_rz, dz += btmagic_scale(tz, &sc->sc_rz,
sc->sc_resolution / sc->sc_scale); sc->sc_resolution / sc->sc_scale);
@ -1307,7 +1324,7 @@ btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
sc->sc_rw = 0; sc->sc_rw = 0;
} }
SET(sc->sc_smask, id); SET(sc->sc_smask, __BIT(id));
} else { } else {
/* not scrolling finger */ /* not scrolling finger */
az = sc->sc_az[id]; az = sc->sc_az[id];
@ -1321,12 +1338,14 @@ btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
break; break;
default: default:
CLR(sc->sc_smask, id); CLR(sc->sc_smask, __BIT(id));
break; break;
} }
sc->sc_az[id] = az; sc->sc_az[id] = az;
sc->sc_aw[id] = aw; sc->sc_aw[id] = aw;
#undef sc_az
#undef sc_aw
} }
/* /*
@ -1355,3 +1374,204 @@ btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
splx(s); splx(s);
} }
} }
/*
* the Magic touch trackpad report (0x28), according to the Linux driver
* written by Michael Poole and Chase Douglas, is variable length starting
* with the fixed 24-bit header
*
* button 1 1-bit
* unknown 5-bits
* timestamp 18-bits
*
* followed by (up to 5?) touch reports of 72-bits each
*
* abs X 13-bits (signed)
* abs Y 13-bits (signed)
* unknown 6-bits
* axis major 8-bits
* axis minor 8-bits
* pressure 6-bits
* id 4-bits
* angle 6-bits (from E(0)->N(32)->W(64))
* unknown 4-bits
* phase 4-bits
*/
static const struct {
struct hid_location button;
struct hid_location timestamp;
} magict = {
.button = { .pos = 0, .size = 1 },
.timestamp = { .pos = 6, .size = 18 },
};
static const struct {
struct hid_location aX;
struct hid_location aY;
struct hid_location major;
struct hid_location minor;
struct hid_location pressure;
struct hid_location id;
struct hid_location angle;
struct hid_location unknown;
struct hid_location phase;
} toucht = {
.aX = { .pos = 0, .size = 13 },
.aY = { .pos = 13, .size = 13 },
.major = { .pos = 32, .size = 8 },
.minor = { .pos = 40, .size = 8 },
.pressure = { .pos = 48, .size = 6 },
.id = { .pos = 54, .size = 4 },
.angle = { .pos = 58, .size = 6 },
.unknown = { .pos = 64, .size = 4 },
.phase = { .pos = 68, .size = 4 },
};
/*
* as for btmagic_input_magicm,
* the phase of the touch starts at 0x01 as the finger is first detected
* approaching the mouse, increasing to 0x04 while the finger is touching,
* then increases towards 0x07 as the finger is lifted, and we get 0x00
* when the touch is cancelled. The values below seem to be produced for
* every touch, the others less consistently depending on how fast the
* approach or departure is.
*
* In fact we ignore touches unless they are in the steady 0x04 phase.
*/
/* min and max values reported */
#define MAGICT_X_MIN (-2910)
#define MAGICT_X_MAX (3170)
#define MAGICT_Y_MIN (-2565)
#define MAGICT_Y_MAX (2455)
/*
* area for detecting the buttons: divide in 3 areas on X,
* below -1900 on y
*/
#define MAGICT_B_YMAX (-1900)
#define MAGICT_B_XSIZE ((MAGICT_X_MAX - MAGICT_X_MIN) / 3)
#define MAGICT_B_X1MAX (MAGICT_X_MIN + MAGICT_B_XSIZE)
#define MAGICT_B_X2MAX (MAGICT_X_MIN + MAGICT_B_XSIZE * 2)
static void
btmagic_input_magict(struct btmagic_softc *sc, uint8_t *data, size_t len)
{
bool bpress;
uint32_t mb;
int id, ax, ay, tx, ty;
int dx, dy, dz, dw;
int s;
if (((len - 3) % 9) != 0)
return;
bpress = 0;
if (hid_get_udata(data, &magict.button))
bpress = 1;
dx = dy = dz = dw = 0;
mb = 0;
len = (len - 3) / 9;
for (data += 3; len-- > 0; data += 9) {
id = hid_get_udata(data, &toucht.id);
ax = hid_get_data(data, &toucht.aX);
ay = hid_get_data(data, &toucht.aY);
DPRINTF(sc,
"btmagic_input_magicm: id %d ax %d ay %d phase %ld %s\n",
id, ax, ay, hid_get_udata(data, &toucht.phase),
bpress ? "button pressed" : "");
/*
* a single touch is interpreted as a mouse move.
* If a button is pressed, the touch in the button area
* defined above defines the button; a second touch is
* interpreted as a mouse move.
*/
switch (hid_get_udata(data, &toucht.phase)) {
case BTMAGIC_PHASE_CONT:
if (bpress) {
if (sc->sc_mb == 0 && ay < MAGICT_B_YMAX) {
/*
* we have a new button press,
* and this id tells which one
*/
if (ax < MAGICT_B_X1MAX)
mb = __BIT(0);
else if (ax > MAGICT_B_X2MAX)
mb = __BIT(2);
else
mb = __BIT(1);
sc->sc_mb_id = id;
} else {
/* keep previous state */
mb = sc->sc_mb;
}
} else {
/* no button pressed */
mb = 0;
sc->sc_mb_id = -1;
}
if (id == sc->sc_mb_id) {
/*
* this id selects the button
* ignore for move/scroll
*/
continue;
}
tx = ax - sc->sc_ax[id];
ty = ay - sc->sc_ay[id];
if (ISSET(sc->sc_smask, __BIT(id))) {
if (sc->sc_nfingers == 1 || mb != 0) {
/* single finger moving */
dx += btmagic_scale(tx, &sc->sc_rx,
sc->sc_resolution);
dy += btmagic_scale(ty, &sc->sc_ry,
sc->sc_resolution);
} else {
/* scrolling fingers */
dz += btmagic_scale(ty, &sc->sc_rz,
sc->sc_resolution / sc->sc_scale);
dw += btmagic_scale(tx, &sc->sc_rw,
sc->sc_resolution / sc->sc_scale);
}
} else if (ay > MAGICT_B_YMAX) { /* new finger */
sc->sc_rx = 0;
sc->sc_ry = 0;
sc->sc_rz = 0;
sc->sc_rw = 0;
KASSERT(!ISSET(sc->sc_smask, __BIT(id)));
SET(sc->sc_smask, __BIT(id));
sc->sc_nfingers++;
}
break;
default:
if (ISSET(sc->sc_smask, __BIT(id))) {
CLR(sc->sc_smask, __BIT(id));
sc->sc_nfingers--;
KASSERT(sc->sc_nfingers >= 0);
}
break;
}
sc->sc_ax[id] = ax;
sc->sc_ay[id] = ay;
}
if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || mb != sc->sc_mb) {
sc->sc_mb = mb;
s = spltty();
wsmouse_input(sc->sc_wsmouse, mb,
dx, dy, -dz, dw, WSMOUSE_INPUT_DELTA);
splx(s);
}
}