Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 19 Sep 2015 05:51:18 +0200
From:      Dirk Engling <erdgeist@erdgeist.org>
To:        Iain Hibbert <plunky@ogmig.net>
Cc:        "freebsd-bluetooth@freebsd.org" <freebsd-bluetooth@freebsd.org>
Subject:   Re: Apple Magic Mouse
Message-ID:  <55FCDBB6.6090004@erdgeist.org>
In-Reply-To: <55F78EB8.8060408@erdgeist.org>
References:  <1437909200.57929.3.camel@yandex.com> <alpine.NEB.2.11.1508092034290.5638@galant.ogmig.net> <55F4362A.4050203@erdgeist.org> <CAFPOs6p%2BEZDctPodY6DBbnhfZQFz%2Bo0uKSRvvqQBcM1QL3ACXw@mail.gmail.com> <55F60ED8.8080203@erdgeist.org> <alpine.NEB.2.11.1509141802300.690@galant.ogmig.net> <55F78EB8.8060408@erdgeist.org>

next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format.
--------------030507000703030402030603
Content-Type: text/plain; charset=windows-1252
Content-Transfer-Encoding: 8bit

On 15.09.15 05:21, Dirk Engling wrote:

> Also contained in the patch is successful probing for my Magic Mouse and
> extraction of basic mouse features when track pad events are sent by the
> mouse. Next up: translation of finger tracking to z-scroll events.

With the attached patch I now do successfully operate my magic mouse on
my FreeBSD, scroll wheel emulation and middle button works.

I tried to keep everything apple mouse specific in one block. I would
have loved to move it to its own function, but it needs access to many
variables from hid_interrupt()'s scope. Moving all those variables to a
state struct would work but blow up the patch. Either way is messy.

In theory we now can cherry pick all the nice features from linux and
netbsd drivers, e.g. acceleration, but for now my itches are scratched.

Feedback is welcome.

  erdgeist

--------------030507000703030402030603
Content-Type: text/plain; charset=UTF-8; x-mac-type="0"; x-mac-creator="0";
 name="bluetooth.magic.patch"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
 filename="bluetooth.magic.patch"

diff -ru bthidd/bthid_config.h /usr/src/usr.sbin/bluetooth/bthidd/bthid_c=
onfig.h
--- bthidd/bthid_config.h	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/bthid_config.h	2015-09-15 02:47:06=
=2E046363156 +0200
@@ -42,6 +42,9 @@
 	bdaddr_t		bdaddr;		/* HID device BDADDR */
 	uint16_t		control_psm;	/* control PSM */
 	uint16_t		interrupt_psm;	/* interrupt PSM */
+	uint16_t		vendor_id;	/* primary vendor id */
+	uint16_t		product_id;
+	uint16_t		version;
 	unsigned		new_device           : 1;
 	unsigned		reconnect_initiate   : 1;
 	unsigned		battery_power        : 1;
diff -ru bthidd/bthidd.conf.sample /usr/src/usr.sbin/bluetooth/bthidd/bth=
idd.conf.sample
--- bthidd/bthidd.conf.sample	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/bthidd.conf.sample	2015-09-15 05:0=
8:15.624443300 +0200
@@ -2,6 +2,9 @@
=20
 device {
 	bdaddr			00:50:f2:e5:68:84;
+	vendor_id		0x0000;
+	product_id		0x0000;
+	version			0x0000;
 	control_psm		0x11;
 	interrupt_psm		0x13;
 	reconnect_initiate	true;
@@ -24,6 +27,9 @@
=20
 device {
 	bdaddr			00:50:f2:e3:fb:e1;
+	vendor_id		0x0000;
+	product_id		0x0000;
+	version			0x0000;
 	control_psm		0x11;
 	interrupt_psm		0x13;
 	reconnect_initiate	true;
diff -ru bthidd/bthidd.h /usr/src/usr.sbin/bluetooth/bthidd/bthidd.h
--- bthidd/bthidd.h	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/bthidd.h	2015-09-17 00:09:09.14657=
6846 +0200
@@ -60,6 +60,7 @@
 	int32_t				 ctrl;	/* control channel */
 	int32_t				 intr;	/* interrupt channel */
 	int32_t				 vkbd;	/* virual keyboard */
+	void				*ctx;   /* product specific dev state */
 	bdaddr_t			 bdaddr;/* remote bdaddr */
 	uint16_t			 state;	/* session state */
 #define CLOSED	0
@@ -86,6 +87,7 @@
 bthid_session_p	session_by_fd    (bthid_server_p srv, int32_t fd);
 void		session_close    (bthid_session_p s);
=20
+void		hid_initialise	 (bthid_session_p s);
 int32_t		hid_control      (bthid_session_p s, uint8_t *data, int32_t len=
);
 int32_t		hid_interrupt    (bthid_session_p s, uint8_t *data, int32_t len=
);
=20
diff -ru bthidd/hid.c /usr/src/usr.sbin/bluetooth/bthidd/hid.c
--- bthidd/hid.c	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/hid.c	2015-09-18 08:18:02.87230420=
6 +0200
@@ -31,6 +31,7 @@
  * $FreeBSD: releng/10.2/usr.sbin/bluetooth/bthidd/hid.c 281567 2015-04-=
15 22:07:51Z rakuco $
  */
=20
+#include <stdlib.h>
 #include <sys/consio.h>
 #include <sys/mouse.h>
 #include <sys/queue.h>
@@ -49,6 +50,48 @@
 #include "kbd.h"
=20
 /*
+ * Inoffical and unannounced report ids for Apple Mice and trackpad
+ */
+#define TRACKPAD_REPORT_ID	0x28
+#define MOUSE_REPORT_ID		0x29
+#define BATT_STAT_REPORT_ID	0x30
+#define BATT_STRENGTH_REPORT_ID	0x47
+#define SURFACE_REPORT_ID	0x61
+
+/*
+ * Magic mouse specific device state
+ */
+#define MAGIC_MOUSE_MAX_BUTTONS 16
+struct apple_state {
+	int x[MAGIC_MOUSE_MAX_BUTTONS];
+	int y[MAGIC_MOUSE_MAX_BUTTONS];
+	int button_state;
+} apple_state;
+#define MAGIC_MOUSE(D) (((D)->vendor_id =3D=3D 0x5ac) && ((D)->product_i=
d =3D=3D 0x30d))
+#define SCROLL_WHEEL_SPEED 100
+
+/*
+ * Probe for per-device initialisation
+ */
+void
+hid_initialise(bthid_session_p s)
+{
+	hid_device_p	hid_device =3D get_hid_device(&s->bdaddr);
+	assert(hid_device !=3D NULL);
+
+	/* Magic report to enable trackpad on Apple's Magic Mouse */
+	if (hid_device->vendor_id =3D=3D 0x5ac &&
+	    hid_device->product_id =3D=3D 0x30d) {
+		static uint8_t rep[] =3D { 0x53, 0xd7, 0x01 };
+		s->ctx =3D malloc(sizeof(struct apple_state));
+		if (!s->ctx)
+			return;
+		memset(s->ctx, 0, sizeof(struct apple_state));
+		write(s->ctrl, rep, 3 );
+	}
+}
+
+/*
  * Process data from control channel
  */
=20
@@ -370,6 +413,88 @@
 	hid_end_parse(d);
=20
 	/*
+	 * Apple adheres to no standards and sends reports it does
+	 * not introduce in its hid descriptor for its magic mouse.
+	 * Handle those reports here.
+	 */
+	if (MAGIC_MOUSE(hid_device) && s->ctx) {
+		struct apple_state *c =3D (struct apple_state *)s->ctx;
+		int firm =3D 0, middle =3D 0;
+		int16_t v;
+
+		if (report_id !=3D MOUSE_REPORT_ID ||
+		    len < 6 || len > 6 + 8*15 || ((len - 6) % 8))
+			goto check_middle_button;
+
+		/* The basics. When touches are detected, no normal mouse
+		   reports are sent. Collect clicks and dx/dy */
+
+		++data, --len; /* Chomp report_id */
+
+		if (data[2] & 1)
+			mouse_butt |=3D 0x1;
+		if (data[2] & 2)
+			mouse_butt |=3D 0x4;
+
+		if ((v =3D data[0] + ((data[2] & 0x0C) << 6)))
+			mouse_x +=3D ((int16_t)(v << 6)) >> 6, mevents++;
+		if ((v =3D data[1] + ((data[2] & 0x30) << 4)))
+			mouse_y +=3D ((int16_t)(v << 6)) >> 6, mevents++;
+
+		data +=3D 5, len -=3D 5; /* Chomp fixed header */
+
+		/* The hard part: accumulate touch events and emulate middle */
+		while (len >=3D 8) {
+			int x, y, z, force, id;
+
+			v =3D data[0] | ((data[1] & 0xf) << 8 );
+			x =3D ((int16_t)(v << 4)) >> 4;
+		=09
+			v =3D (data[1] >> 4) | (data[2]<<4);
+			y =3D -(((int16_t)(v << 4)) >> 4);
+
+			force =3D data[5] & 0x3f;
+			id =3D 0xf & ((data[5] >> 6) | (data[6] << 2));
+
+			switch ((data[7] >> 4) & 0x7) { /* Phase */
+			case 3: /* First touch */
+				c->y[id] =3D y;
+				break;
+			case 4: /* Touch dragged */
+				z =3D (y - c->y[id]) / SCROLL_WHEEL_SPEED;
+				mouse_z +=3D z;
+				if (mouse_z)
+					mevents++;
+				c->y[id] +=3D z * SCROLL_WHEEL_SPEED;
+				break;
+			default:
+				break;
+			}
+			/* Count firm touches vs. firm+middle touches */
+			if (force >=3D 8 && ++firm && x > -350 && x < 350)
+				++middle;
+
+			data +=3D 8, len -=3D 8;
+		}
+
+		/* If a new click is registered by mouse and there are firm
+		   touches which are all in center, make it a middle click */
+		if (mouse_butt && !c->button_state && firm && middle =3D=3D firm)
+			mouse_butt =3D 0x2;
+
+		/* If we're still clicking and have converted the click
+		   to a middle click, keep it middle clicking */
+check_middle_button:
+		if (mouse_butt && c->button_state =3D=3D 0x2)
+			mouse_butt =3D 0x2;
+
+		if (mouse_butt !=3D c->button_state) {
+			mevents++;
+			c->button_state =3D mouse_butt;
+		}
+	}
+
+	/*
 	 * XXX FIXME Feed keyboard events into kernel.
 	 * The code below works, bit host also needs to track
 	 * and handle repeat.
@@ -403,7 +528,6 @@
 		mi.u.data.y =3D mouse_y;
 		mi.u.data.z =3D mouse_z;
 		mi.u.data.buttons =3D mouse_butt;
-
 		if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
 			syslog(LOG_ERR, "Could not process mouse events from " \
 				"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
diff -ru bthidd/lexer.l /usr/src/usr.sbin/bluetooth/bthidd/lexer.l
--- bthidd/lexer.l	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/lexer.l	2015-09-15 05:05:49.762445=
199 +0200
@@ -50,9 +50,13 @@
=20
 hexdigit			[0-9a-fA-F]
 hexbyte				{hexdigit}{hexdigit}?
+hexword				{hexdigit}{hexdigit}?{hexdigit}?{hexdigit}?
=20
 device_word			device
 bdaddr_word			bdaddr
+vendor_id_word			vendor_id
+product_id_word			product_id
+version_word			version
 control_psm_word		control_psm
 interrupt_psm_word		interrupt_psm
 reconnect_initiate_word		reconnect_initiate
@@ -64,6 +68,7 @@
=20
 bdaddrstring			{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyt=
e}
 hexbytestring			0x{hexbyte}
+hexwordstring			0x{hexword}
=20
 %%
=20
@@ -78,6 +83,9 @@
=20
 {device_word}			return (T_DEVICE);
 {bdaddr_word}			return (T_BDADDR);
+{vendor_id_word}		return (T_VENDOR_ID);
+{product_id_word}		return (T_PRODUCT_ID);
+{version_word}			return (T_VERSION);
 {control_psm_word}		return (T_CONTROL_PSM);
 {interrupt_psm_word}		return (T_INTERRUPT_PSM);
 {reconnect_initiate_word}	return (T_RECONNECT_INITIATE);
@@ -100,6 +108,14 @@
 				return (*ep =3D=3D '\0'? T_HEXBYTE : T_ERROR);
 				}
=20
+{hexwordstring}			{
+				char	*ep;
+
+				yylval.num =3D strtoul(yytext, &ep, 16);
+
+				return (*ep =3D=3D '\0'? T_HEXWORD : T_ERROR);
+				}
+
 .				return (T_ERROR);
=20
 %%
diff -ru bthidd/parser.y /usr/src/usr.sbin/bluetooth/bthidd/parser.y
--- bthidd/parser.y	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/parser.y	2015-09-15 05:03:43.18345=
8248 +0200
@@ -86,8 +86,10 @@
=20
 %token <bdaddr> T_BDADDRSTRING
 %token <num>	T_HEXBYTE
-%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITI=
ATE
-%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
+%token <num>	T_HEXWORD
+%token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PS=
M
+%token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER
+%token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
 %token T_TRUE T_FALSE T_ERROR
=20
 %%
@@ -123,6 +125,9 @@
 		;
=20
 option:		bdaddr
+		| vendor_id
+		| product_id
+		| version
 		| control_psm
 		| interrupt_psm
 		| reconnect_initiate
@@ -138,6 +143,24 @@
 			}
 		;
=20
+vendor_id:	T_VENDOR_ID T_HEXWORD
+			{
+			hid_device->vendor_id =3D $2;
+			}
+		;
+
+product_id:	T_PRODUCT_ID T_HEXWORD
+			{
+			hid_device->product_id =3D $2;
+			}
+		;
+
+version:	T_VERSION T_HEXWORD
+			{
+			hid_device->version =3D $2;
+			}
+		;
+
 control_psm:	T_CONTROL_PSM T_HEXBYTE
 			{
 			hid_device->control_psm =3D $2;
@@ -306,6 +329,9 @@
 	fprintf(f,
 "device {\n"					\
 "	bdaddr			%s;\n"		\
+"	vendor_id		0x%04x;\n"	\
+"	product_id		0x%04x;\n"	\
+"	version			0x%04x;\n"	\
 "	control_psm		0x%x;\n"	\
 "	interrupt_psm		0x%x;\n"	\
 "	reconnect_initiate	%s;\n"		\
@@ -313,6 +339,7 @@
 "	normally_connectable	%s;\n"		\
 "	hid_descriptor		{",
 		bt_ntoa(&d->bdaddr, NULL),
+		d->vendor_id, d->product_id, d->version,
 		d->control_psm, d->interrupt_psm,
                 d->reconnect_initiate? "true" : "false",
                 d->battery_power? "true" : "false",
diff -ru bthidd/server.c /usr/src/usr.sbin/bluetooth/bthidd/server.c
--- bthidd/server.c	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/server.c	2015-09-15 05:15:15.66445=
9031 +0200
@@ -286,6 +286,10 @@
 			srv->maxfd =3D s->vkbd;
 	}
=20
+	/* Pass device for probing after both channels are established */
+	if (s->state =3D=3D OPEN)
+		hid_initialise(s);
+
 	return (0);
 }
=20
diff -ru bthidd/session.c /usr/src/usr.sbin/bluetooth/bthidd/session.c
--- bthidd/session.c	2015-08-12 16:21:36.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidd/session.c	2015-09-17 00:08:19.1853=
83406 +0200
@@ -65,6 +65,7 @@
 	memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr));
 	s->ctrl =3D -1;
 	s->intr =3D -1;
+	s->ctx =3D NULL;
=20
 	if (d->keyboard) {
 		/* Open /dev/vkbdctl */
@@ -175,6 +176,7 @@
 			s->srv->maxfd --;
 	}
=20
+	free(s->ctx);
 	free(s->keys1);
 	free(s->keys2);
=20
diff -ru bthidcontrol/sdp.c /usr/src/usr.sbin/bluetooth/bthidcontrol/sdp.=
c
--- bthidcontrol/sdp.c	2015-08-12 16:21:37.000000000 +0200
+++ /usr/src/usr.sbin/bluetooth/bthidcontrol/sdp.c	2015-09-15 04:14:53.41=
3719623 +0200
@@ -46,7 +46,20 @@
 static int32_t hid_sdp_parse_hid_descriptor		(sdp_attr_p a);
 static int32_t hid_sdp_parse_boolean			(sdp_attr_p a);
=20
+/*
+ * Hard coded attibute IDs taken from the
+ * DEVICE IDENTIFICATION PROFILE SPECIFICATION V13 p.12
+ */
+
+#define SDP_DEVICE_ID_SERVICE_ATTR_VENDORID  0x0201
+#define SDP_DEVICE_ID_SERVICE_ATTR_PRODUCTID 0x0202
+#define SDP_DEVICE_ID_SERVICE_ATTR_VERSION   0x0203
+#define SDP_DEVICE_ID_RANGE SDP_ATTR_RANGE( \
+ SDP_DEVICE_ID_SERVICE_ATTR_VENDORID, SDP_DEVICE_ID_SERVICE_ATTR_VERSION=
 )
+
 static uint16_t		service =3D SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE;
+static uint16_t		service_devid =3D SDP_SERVICE_CLASS_PNP_INFORMATION;
+static uint32_t 	attrs_devid   =3D SDP_DEVICE_ID_RANGE;
=20
 static uint32_t		attrs[] =3D {
 SDP_ATTR_RANGE(	SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
@@ -84,27 +97,34 @@
 	return (((e) =3D=3D 0)? 0 : -1);	\
 }
=20
+static void
+hid_init_return_values() {
+	int i;
+	for (i =3D 0; i < nvalues; i ++) {
+		values[i].flags =3D SDP_ATTR_INVALID;
+		values[i].attr =3D 0;
+		values[i].vlen =3D sizeof(buffer[i]);
+		values[i].value =3D buffer[i];
+	}
+}
+
 static int32_t
 hid_sdp_query(bdaddr_t const *local, struct hid_device *hd, int32_t *err=
or)
 {
 	void	*ss =3D NULL;
-	uint8_t	*hid_descriptor =3D NULL;
+	uint8_t	*hid_descriptor =3D NULL, *v;
 	int32_t	 i, control_psm =3D -1, interrupt_psm =3D -1,
 		 reconnect_initiate =3D -1,
 		 normally_connectable =3D 0, battery_power =3D 0,
-		 hid_descriptor_length =3D -1;
+		 hid_descriptor_length =3D -1, type;
+	int16_t  vendor_id, product_id, version;
=20
 	if (local =3D=3D NULL)
 		local =3D NG_HCI_BDADDR_ANY;
 	if (hd =3D=3D NULL)
 		hid_sdp_query_exit(EINVAL);
=20
-	for (i =3D 0; i < nvalues; i ++) {
-		values[i].flags =3D SDP_ATTR_INVALID;
-		values[i].attr =3D 0;
-		values[i].vlen =3D sizeof(buffer[i]);
-		values[i].value =3D buffer[i];
-	}
+	hid_init_return_values();
=20
 	if ((ss =3D sdp_open(local, &hd->bdaddr)) =3D=3D NULL)
 		hid_sdp_query_exit(ENOMEM);
@@ -113,9 +133,6 @@
 	if (sdp_search(ss, 1, &service, nattrs, attrs, nvalues, values) !=3D 0)=

                 hid_sdp_query_exit(sdp_error(ss));
=20
-        sdp_close(ss);
-        ss =3D NULL;
-
 	for (i =3D 0; i < nvalues; i ++) {
 		if (values[i].flags !=3D SDP_ATTR_OK)
 			continue;
@@ -150,11 +167,51 @@
 		}
 	}
=20
+	hid_init_return_values();
+
+	if (sdp_search(ss, 1, &service_devid, 1, &attrs_devid, nvalues, values)=
 !=3D 0)
+                hid_sdp_query_exit(sdp_error(ss));
+
+        sdp_close(ss);
+        ss =3D NULL;
+
+	/* If search is successful, scan through return vals */
+	for (i =3D 0; i < 3; i ++ ) {
+		if (values[i].flags =3D=3D SDP_ATTR_INVALID )
+			continue;
+
+		/* Expecting tag + uint16_t on all 3 attributes */
+		if (values[i].vlen !=3D 3)
+			continue;
+
+		/* Make sure, we're reading a uint16_t */
+		v =3D values[i].value;
+		SDP_GET8(type, v);
+		if (type !=3D SDP_DATA_UINT16 )
+			continue;
+
+		switch (values[i].attr) {
+			case SDP_DEVICE_ID_SERVICE_ATTR_VENDORID:
+				SDP_GET16(vendor_id, v);
+				break;
+			case SDP_DEVICE_ID_SERVICE_ATTR_PRODUCTID:
+				SDP_GET16(product_id, v);
+				break;
+			case SDP_DEVICE_ID_SERVICE_ATTR_VERSION:
+				SDP_GET16(version, v);
+				break;
+			default:
+				break;
+		}
+	}
+
 	if (control_psm =3D=3D -1 || interrupt_psm =3D=3D -1 ||
 	    reconnect_initiate =3D=3D -1 ||
 	    hid_descriptor =3D=3D NULL || hid_descriptor_length =3D=3D -1)
 		hid_sdp_query_exit(ENOATTR);
-
+	hd->vendor_id =3D vendor_id;
+	hd->product_id =3D product_id;
+	hd->version =3D version;
 	hd->control_psm =3D control_psm;
 	hd->interrupt_psm =3D interrupt_psm;
 	hd->reconnect_initiate =3D reconnect_initiate? 1 : 0;

--------------030507000703030402030603--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?55FCDBB6.6090004>