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