Apple's "magic" bluetooth mouse
Iain Hibbert
plunky at ogmig.net
Sat Oct 12 21:38:45 UTC 2013
On Sat, 12 Oct 2013, Alexey Dokuchaev wrote:
> Today I got a chance to play with this glamorous rat from Apple, and was
> curious how it gets along with FreeBSD. Well, it worked, but only as a
> pointer. Even simplest features like vertical scrolling did not work.
> xev(1) reported of no events coming from when I touch the stupid mouse.
> It looks like they are not being proxied as virtual buttons clicks, but
> implemented somehow differently. I've also found that in Linux, they
> kinda use a special driver to make it work [1].
yes, also I wrote one for NetBSD[1]
the mouse itself provides a HID profile but the descriptor does not
properly describe the actions of the mouse, and the extra reports are
enabled by setting a feature. it should work as a basic mouse with x & y
and two buttons though..
although the driver is long, mostly the only bits you need to care about
are the input routines and the btmagic_enable() function. You can probably
add this stuff to bthidd(8) I don't know how easy that would be
> Any clues how to investigate this issue? I probably won't be able to
> make use of all fancy multi-touch features of the mouse, but I'd like to
> at least "export" some of the common gestures, like mouse wheel scroll,
> as a legacy button clicks (so they can be propagated up to the X.org).
I don't know why it doesn't work but I thought legacy button clicks were
built into the normal mouse (did you push? it is not just touch..)
middle button and scrolling will not work without a driver though, that is
all handled in software.. the mouse itself just reports up to five finger
positions, which are stored and compared for each report. after a
threshold then scrolling is activated.
> I guess I could study how we ourselves handle Synaptics touchpads, but
> given this mouse is bluetooth, I figured I better ask here first.
I have a tool i wrote to parse the input via a hci sniffer (attached for
interest) and another tool which I used to send something to the mouse (i
don't remember how that worked, if I had a special driver during
development)
regards,
iain
[1] http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/dev/bluetooth/btmagic.c
-------------- next part --------------
#include <sys/param.h>
#include <bluetooth.h>
#include <err.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <usbhid.h>
#include <bthid.h>
/*
* provide const locators because we don't have a descriptor.
*/
static const struct {
hid_item_t button1;
hid_item_t button2;
hid_item_t padding;
hid_item_t dX;
hid_item_t dY;
} basic = {
.button1 = { .pos = 0, .report_size = 1 },
.button2 = { .pos = 1, .report_size = 1 },
.padding = { .pos = 2, .report_size = 6 },
.dX = { .pos = 8, .report_size = 16, .logical_minimum = -511 },
.dY = { .pos = 24, .report_size = 16, .logical_minimum = -511 },
};
static const struct {
/* 40-bit header */
hid_item_t dXl;
hid_item_t dYl;
hid_item_t button1;
hid_item_t button2;
hid_item_t dXm;
hid_item_t dYm;
hid_item_t timestamp;
/* 64-bit touch report */
hid_item_t aW;
hid_item_t aZ;
hid_item_t major;
hid_item_t minor;
hid_item_t pressure;
hid_item_t id;
hid_item_t angle;
hid_item_t unknown;
hid_item_t phase;
} mouse = {
/* header */
.dXl = { .pos = 0, .report_size = 8 },
.dYl = { .pos = 8, .report_size = 8 },
.button1 = { .pos = 16, .report_size = 1 },
.button2 = { .pos = 17, .report_size = 1 },
.dXm = { .pos = 18, .report_size = 2, .logical_minimum = -2 },
.dYm = { .pos = 20, .report_size = 2, .logical_minimum = -2 },
.timestamp = { .pos = 22, .report_size = 18 },
/* per touch */
.aW = { .pos = 0, .report_size = 12, .logical_minimum = -1440 },
.aZ = { .pos = 12, .report_size = 12, .logical_minimum = -2048 },
.major = { .pos = 24, .report_size = 8 },
.minor = { .pos = 32, .report_size = 8 },
.pressure = { .pos = 40, .report_size = 6 },
.id = { .pos = 46, .report_size = 4 },
.angle = { .pos = 50, .report_size = 6 },
.unknown = { .pos = 56, .report_size = 4 },
.phase = { .pos = 60, .report_size = 4 },
};
static const struct {
/* 32-bit header */
hid_item_t clicks;
hid_item_t unknown1;
hid_item_t timestamp;
/* 72-bit touch report */
hid_item_t aX;
hid_item_t aY;
hid_item_t unknown2;
hid_item_t major;
hid_item_t minor;
hid_item_t pressure;
hid_item_t id;
hid_item_t angle;
hid_item_t unknown3;
hid_item_t phase;
} track = {
/* header */
.clicks = { .pos = 0, .report_size = 8 },
.unknown1 = { .pos = 8, .report_size = 6 },
.timestamp = { .pos = 14, .report_size = 18 },
/* per touch */
.aX = { .pos = 0, .report_size = 13 },
.aY = { .pos = 13, .report_size = 13 },
.unknown2 = { .pos = 26, .report_size = 6 },
.major = { .pos = 32, .report_size = 8 },
.minor = { .pos = 40, .report_size = 8 },
.pressure = { .pos = 48, .report_size = 6 },
.id = { .pos = 54, .report_size = 8 },
.angle = { .pos = 62, .report_size = 2 },
.unknown3 = { .pos = 64, .report_size = 4 },
.phase = { .pos = 68, .report_size = 4 },
};
static void read_file(char *);
static void read_device(char *);
static void usage(void);
static void dump(const char *, uint8_t *, size_t);
static void printb(const char *, uint8_t *, const hid_item_t *);
static int hci_acl(int, uint8_t *, size_t);
static int hci_event(int, uint8_t *, size_t);
static int l2cap_recv(uint8_t *, size_t);
static int hid_recv(uint8_t *, size_t);
static int basic_input(uint8_t *, size_t);
static int track_input(uint8_t *, size_t);
static int mouse_input(uint8_t *, size_t);
int
main(int ac, char *av[])
{
char *device, *path;
int ch;
device = NULL;
path = NULL;
while ((ch = getopt(ac, av, "d:r:")) != -1) {
switch (ch) {
case 'd': /* local device */
device = optarg;
break;
case 'r': /* read file */
path = optarg;
break;
default:
usage();
/* NOT REACHED */
}
}
if (device != NULL && path != NULL)
usage();
if (path != NULL)
read_file(path);
else
read_device(device);
return 0;
}
static void
read_file(char *path)
{
}
static void
read_device(char *device)
{
struct bt_devfilter f;
uint8_t buf[1024];
ssize_t nr;
int fd, handle;
fd = bt_devopen(device, 0);
if (fd == -1)
err(EXIT_FAILURE, "bt_devopen");
memset(&f, 0, sizeof(f));
bt_devfilter_pkt_set(&f, HCI_ACL_DATA_PKT);
bt_devfilter_pkt_set(&f, HCI_EVENT_PKT);
bt_devfilter_evt_set(&f, HCI_EVENT_DISCON_COMPL);
if (bt_devfilter(fd, &f, NULL) == -1)
err(EXIT_FAILURE, "bt_devfilter");
handle = 0;
for (;;) {
nr = bt_devrecv(fd, buf, sizeof(buf), -1);
if (nr < 0)
err(EXIT_FAILURE, "bt_devrecv");
if (nr == 0)
break;
switch (buf[0]) {
case HCI_ACL_DATA_PKT:
handle = hci_acl(handle, buf, (size_t)nr);
break;
case HCI_EVENT_PKT:
handle = hci_event(handle, buf, (size_t)nr);
break;
default:
break;
}
}
close(fd);
}
static void
usage(void)
{
fprintf(stderr, "Usage: %s { [-d device] | -r path }\n",
getprogname());
exit(0);
}
/*
* dump buffer as hex bytes
*/
static void
dump(const char *str, uint8_t *buf, size_t len)
{
printf("%s:", str);
while (len-- > 0)
printf(" 0x%02x", *buf++);
printf("\n");
}
/*
* print hid item as binary
*/
static void
printb(const char *str, uint8_t *buf, const hid_item_t *item)
{
int num = item->report_size;
unsigned int value = hid_get_data(buf, item);
printf("%s", str);
while (num-- > 0)
printf("%c", (value & __BIT(num)) ? '1' : '0');
}
static int
hci_acl(int handle, uint8_t *buf, size_t len)
{
static uint8_t xbuf[1024];
static size_t xlen;
int h;
//dump("acl", buf, len);
if (len < 5 || len != (size_t)(le16dec(buf + 3) + 5))
return handle;
h = le16dec(buf + 1);
if (HCI_PB_FLAG(h) == HCI_PACKET_START)
xlen = 0;
memcpy(xbuf + xlen, buf + 5, len - 5);
xlen += len - 5;
if (xlen < le16dec(xbuf))
return handle; /* to be continued... */
if (handle == 0) {
handle = HCI_CON_HANDLE(h);
printf("trying handle %d\n", handle);
}
if (handle == HCI_CON_HANDLE(h)) {
if (l2cap_recv(xbuf, xlen) == 0) {
printf("abandon handle %d\n", handle);
handle = 0;
}
}
return handle;
}
static int
hci_event(int handle, uint8_t *buf, size_t len)
{
//dump("event", buf, len);
if (len < 3 || len != (size_t)(buf[2] + 3))
return handle;
switch (buf[1]) {
case HCI_EVENT_DISCON_COMPL:
if (len != 7 || buf[3] != 0x00 || le16dec(buf + 4) != handle)
break;
printf("handle %d disconnected (reason 0x%02x)\n", handle, buf[6]);
handle = 0;
break;
default:
break;
}
return handle;
}
static int
l2cap_recv(uint8_t *buf, size_t len)
{
int dcid;
//dump("l2cap", buf, len);
if (len < 4 || len != (size_t)(le16dec(buf) + 4))
return 0;
dcid = le16dec(buf + 2);
if (dcid < L2CAP_FIRST_CID)
/* ignore signals */;
else if (hid_recv(buf + 4, len - 4))
return 1;
return 0;
}
#define DATA(type, id) ((((BTHID_DATA << 4) | BTHID_DATA_##type) << 8) | id)
static int
hid_recv(uint8_t *buf, size_t len)
{
//dump("hid", buf, len);
if (len < 1)
return 0;
switch (BTHID_TYPE(buf[0])) {
case BTHID_HANDSHAKE:
if (len != 1) return 0;
printf("handshake: 0x%x\n", BTHID_HANDSHAKE_PARAM(buf[0]));
break;
case BTHID_DATA:
if (len < 2) return 0;
switch (be16dec(buf)) {
case DATA(INPUT, 0x10): /* Basic report */
if (!basic_input(buf + 2, len - 2))
return 0;
break;
case DATA(INPUT, 0x28): /* Track report */
if (!track_input(buf + 2, len - 2))
return 0;
break;
case DATA(INPUT, 0x29): /* Mouse report */
if (!mouse_input(buf + 2, len - 2))
return 0;
break;
case DATA(INPUT, 0x2a): /* Battery warning */
if (len != 3) return 0;
printf("battery %s\n", buf[2] > 1 ? "critical" : "warning");
break;
case DATA(INPUT, 0x61): /* Surface detection */
if (len != 3) return 0;
printf("mouse %s\n", buf[2] > 0 ? "raised" : "lowered");
break;
case DATA(FEATURE, 0x47): /* Battery status */
if (len != 2) return 0;
printf("battery %d%%\n", buf[2]);
break;
case DATA(FEATURE, 0xd7): /* Touch reports */
if (len != 3) return 0;
printf("touch reports %sabled\n", buf[2] ? "en" : "dis");
break;
case DATA(INPUT, 0x30):
case DATA(INPUT, 0x60):
case DATA(INPUT, 0xf7):
dump("input", buf, len);
break;
case DATA(FEATURE, 0xf0):
case DATA(FEATURE, 0xf1):
dump("feature", buf, len);
break;
default:
dump("data", buf, len);
return 0;
}
break;
case BTHID_GET_REPORT:
dump("get report", buf, len);
break;
case BTHID_SET_REPORT:
dump("set report", buf, len);
break;
case BTHID_CONTROL:
case BTHID_GET_PROTOCOL:
case BTHID_SET_PROTOCOL:
case BTHID_GET_IDLE:
case BTHID_SET_IDLE:
case BTHID_DATC:
default:
dump("hid", buf, len);
return 0;
}
return 1;
}
static int
basic_input(uint8_t *buf, size_t len)
{
if (len != 5)
return 0;
printf("basic dx%4d, dy%4d, mb %d%d",
hid_get_data(buf, &basic.dX),
hid_get_data(buf, &basic.dY),
hid_get_data(buf, &basic.button1) ? 1 : 0,
hid_get_data(buf, &basic.button2) ? 1 : 0);
printf("\n");
return 1;
}
static int
track_input(uint8_t *buf, size_t len)
{
if (((len - 4) % 9) != 0)
return 0;
printf("track clicks%4d, ts 0x%05x",
hid_get_data(buf, &track.clicks),
hid_get_data(buf, &track.timestamp));
printb(", ", buf, &track.unknown1);
for (buf += 4, len -= 4; len > 0; len -= 9, buf += 9) {
printf(" id %x { %d:%d(%d), %d.%d/%d",
hid_get_data(buf, &track.id),
hid_get_data(buf, &track.aX),
hid_get_data(buf, &track.aY),
hid_get_data(buf, &track.pressure),
hid_get_data(buf, &track.major),
hid_get_data(buf, &track.minor),
hid_get_data(buf, &track.angle));
printb(", ", buf, &track.unknown2);
printb(", ", buf, &track.unknown3);
printb(", ", buf, &track.phase);
printf("}");
}
printf("\n");
return 1;
}
static int
mouse_input(uint8_t *buf, size_t len)
{
if (((len - 5) % 8) != 0)
return 0;
printf("mouse dx%4d, dy%4d, mb %d%d, ts 0x%05x",
(hid_get_data(buf, &mouse.dXm) << 8) | (hid_get_data(buf, &mouse.dXl) & 0xff),
(hid_get_data(buf, &mouse.dYm) << 8) | (hid_get_data(buf, &mouse.dYl) & 0xff),
hid_get_data(buf, &mouse.button1) ? 1 : 0,
hid_get_data(buf, &mouse.button2) ? 1 : 0,
hid_get_data(buf, &mouse.timestamp));
for (buf += 5, len -= 5; len > 0; len -= 8, buf += 8) {
printf(", id %x {%d:%d(%d), %d.%d/%d",
hid_get_data(buf, &mouse.id),
hid_get_data(buf, &mouse.aW),
hid_get_data(buf, &mouse.aZ),
hid_get_data(buf, &mouse.pressure),
hid_get_data(buf, &mouse.major),
hid_get_data(buf, &mouse.minor),
hid_get_data(buf, &mouse.angle));
printb(", ", buf, &mouse.unknown);
printb(", ", buf, &mouse.phase);
printf("}");
}
printf("\n");
return 1;
}
-------------- next part --------------
#include <sys/param.h>
#include <sys/ioctl.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <bthid.h>
static void usage(void);
const char *device = "/dev/wsmouse";
struct btmagic_io {
size_t len;
uint8_t buf[256];
};
int
main(int ac, char *av[])
{
struct btmagic_io b;
int ch, fd;
while ((ch = getopt(ac, av, "d:")) != -1) {
switch (ch) {
case 'd':
device = optarg;
break;
default:
usage();
/* NOT REACHED */
}
}
ac -= optind;
av += optind;
if (ac == 0)
usage();
memset(&b, 0, sizeof(b));
while (ac-- > 0)
b.buf[b.len++] = (uint8_t)strtoul(*av++, NULL, 0);
fd = open(device, O_WRONLY, 0);
if (fd == -1)
fd = open(device, O_RDONLY, 0);
if (fd == -1)
err(EXIT_FAILURE, "%s", device);
if (ioctl(fd, _IOW('b', 0xff, struct btmagic_io), &b) == -1)
err(EXIT_FAILURE, "btmagic_send");
close(fd);
return 0;
}
static void
usage(void)
{
fprintf(stderr, "Usage: %s [-d device] ...\n",
getprogname());
exit(0);
}
More information about the freebsd-bluetooth
mailing list