xref: /freebsd-src/usr.sbin/bluetooth/bthidd/hid.c (revision 42b388439bd3795e09258c57a74ce9eec3651c7b)
16490c2ffSMaksim Yevmenkin /*
26490c2ffSMaksim Yevmenkin  * hid.c
37aebfa93SMaksim Yevmenkin  */
47aebfa93SMaksim Yevmenkin 
57aebfa93SMaksim Yevmenkin /*-
6*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
71de7b4b8SPedro F. Giffuni  *
87aebfa93SMaksim Yevmenkin  * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
96490c2ffSMaksim Yevmenkin  * All rights reserved.
106490c2ffSMaksim Yevmenkin  *
116490c2ffSMaksim Yevmenkin  * Redistribution and use in source and binary forms, with or without
126490c2ffSMaksim Yevmenkin  * modification, are permitted provided that the following conditions
136490c2ffSMaksim Yevmenkin  * are met:
146490c2ffSMaksim Yevmenkin  * 1. Redistributions of source code must retain the above copyright
156490c2ffSMaksim Yevmenkin  *    notice, this list of conditions and the following disclaimer.
166490c2ffSMaksim Yevmenkin  * 2. Redistributions in binary form must reproduce the above copyright
176490c2ffSMaksim Yevmenkin  *    notice, this list of conditions and the following disclaimer in the
186490c2ffSMaksim Yevmenkin  *    documentation and/or other materials provided with the distribution.
196490c2ffSMaksim Yevmenkin  *
206490c2ffSMaksim Yevmenkin  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
216490c2ffSMaksim Yevmenkin  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
226490c2ffSMaksim Yevmenkin  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
236490c2ffSMaksim Yevmenkin  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
246490c2ffSMaksim Yevmenkin  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
256490c2ffSMaksim Yevmenkin  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
266490c2ffSMaksim Yevmenkin  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
276490c2ffSMaksim Yevmenkin  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
286490c2ffSMaksim Yevmenkin  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
296490c2ffSMaksim Yevmenkin  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
306490c2ffSMaksim Yevmenkin  * SUCH DAMAGE.
316490c2ffSMaksim Yevmenkin  *
327aebfa93SMaksim Yevmenkin  * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $
336490c2ffSMaksim Yevmenkin  */
346490c2ffSMaksim Yevmenkin 
356490c2ffSMaksim Yevmenkin #include <sys/consio.h>
366490c2ffSMaksim Yevmenkin #include <sys/mouse.h>
376490c2ffSMaksim Yevmenkin #include <sys/queue.h>
386490c2ffSMaksim Yevmenkin #include <assert.h>
398d6f425dSTakanori Watanabe #define L2CAP_SOCKET_CHECKED
406490c2ffSMaksim Yevmenkin #include <bluetooth.h>
416490c2ffSMaksim Yevmenkin #include <dev/usb/usb.h>
426490c2ffSMaksim Yevmenkin #include <dev/usb/usbhid.h>
437aebfa93SMaksim Yevmenkin #include <errno.h>
446490c2ffSMaksim Yevmenkin #include <stdio.h>
45a9a8884bSVladimir Kondratyev #include <stdlib.h>
466490c2ffSMaksim Yevmenkin #include <string.h>
476490c2ffSMaksim Yevmenkin #include <syslog.h>
483adfd74aSMaksim Yevmenkin #include <unistd.h>
496490c2ffSMaksim Yevmenkin #include <usbhid.h>
506490c2ffSMaksim Yevmenkin #include "bthid_config.h"
517aebfa93SMaksim Yevmenkin #include "bthidd.h"
5244af5666SVladimir Kondratyev #include "btuinput.h"
533adfd74aSMaksim Yevmenkin #include "kbd.h"
546490c2ffSMaksim Yevmenkin 
556490c2ffSMaksim Yevmenkin /*
56a9a8884bSVladimir Kondratyev  * Inoffical and unannounced report ids for Apple Mice and trackpad
57a9a8884bSVladimir Kondratyev  */
58a9a8884bSVladimir Kondratyev #define TRACKPAD_REPORT_ID	0x28
59a9a8884bSVladimir Kondratyev #define AMM_REPORT_ID		0x29
60a9a8884bSVladimir Kondratyev #define BATT_STAT_REPORT_ID	0x30
61a9a8884bSVladimir Kondratyev #define BATT_STRENGTH_REPORT_ID	0x47
62a9a8884bSVladimir Kondratyev #define SURFACE_REPORT_ID	0x61
63a9a8884bSVladimir Kondratyev 
64a9a8884bSVladimir Kondratyev /*
65a9a8884bSVladimir Kondratyev  * Apple magic mouse (AMM) specific device state
66a9a8884bSVladimir Kondratyev  */
67a9a8884bSVladimir Kondratyev #define AMM_MAX_BUTTONS 16
68a9a8884bSVladimir Kondratyev struct apple_state {
69a9a8884bSVladimir Kondratyev 	int	y   [AMM_MAX_BUTTONS];
70a9a8884bSVladimir Kondratyev 	int	button_state;
71a9a8884bSVladimir Kondratyev };
72a9a8884bSVladimir Kondratyev 
73a9a8884bSVladimir Kondratyev #define MAGIC_MOUSE(D) (((D)->vendor_id == 0x5ac) && ((D)->product_id == 0x30d))
74a9a8884bSVladimir Kondratyev #define AMM_BASIC_BLOCK   5
75a9a8884bSVladimir Kondratyev #define AMM_FINGER_BLOCK  8
76a9a8884bSVladimir Kondratyev #define AMM_VALID_REPORT(L) (((L) >= AMM_BASIC_BLOCK) && \
77a9a8884bSVladimir Kondratyev     ((L) <= 16*AMM_FINGER_BLOCK    + AMM_BASIC_BLOCK) && \
78a9a8884bSVladimir Kondratyev     ((L)  % AMM_FINGER_BLOCK)     == AMM_BASIC_BLOCK)
79a9a8884bSVladimir Kondratyev #define AMM_WHEEL_SPEED 100
80a9a8884bSVladimir Kondratyev 
81a9a8884bSVladimir Kondratyev /*
82a9a8884bSVladimir Kondratyev  * Probe for per-device initialisation
83a9a8884bSVladimir Kondratyev  */
84a9a8884bSVladimir Kondratyev void
hid_initialise(bthid_session_p s)85a9a8884bSVladimir Kondratyev hid_initialise(bthid_session_p s)
86a9a8884bSVladimir Kondratyev {
87a9a8884bSVladimir Kondratyev 	hid_device_p hid_device = get_hid_device(&s->bdaddr);
88a9a8884bSVladimir Kondratyev 
89a9a8884bSVladimir Kondratyev 	if (hid_device && MAGIC_MOUSE(hid_device)) {
90a9a8884bSVladimir Kondratyev 		/* Magic report to enable trackpad on Apple's Magic Mouse */
91a9a8884bSVladimir Kondratyev 		static uint8_t rep[] = {0x53, 0xd7, 0x01};
92a9a8884bSVladimir Kondratyev 
93a9a8884bSVladimir Kondratyev 		if ((s->ctx = calloc(1, sizeof(struct apple_state))) == NULL)
94a9a8884bSVladimir Kondratyev 			return;
95a9a8884bSVladimir Kondratyev 		write(s->ctrl, rep, 3);
96a9a8884bSVladimir Kondratyev 	}
97a9a8884bSVladimir Kondratyev }
98a9a8884bSVladimir Kondratyev 
99a9a8884bSVladimir Kondratyev /*
1006490c2ffSMaksim Yevmenkin  * Process data from control channel
1016490c2ffSMaksim Yevmenkin  */
1026490c2ffSMaksim Yevmenkin 
1037aebfa93SMaksim Yevmenkin int32_t
hid_control(bthid_session_p s,uint8_t * data,int32_t len)1047aebfa93SMaksim Yevmenkin hid_control(bthid_session_p s, uint8_t *data, int32_t len)
1056490c2ffSMaksim Yevmenkin {
1066490c2ffSMaksim Yevmenkin 	assert(s != NULL);
1076490c2ffSMaksim Yevmenkin 	assert(data != NULL);
1086490c2ffSMaksim Yevmenkin 	assert(len > 0);
1096490c2ffSMaksim Yevmenkin 
1106490c2ffSMaksim Yevmenkin 	switch (data[0] >> 4) {
1116490c2ffSMaksim Yevmenkin         case 0: /* Handshake (response to command) */
1126490c2ffSMaksim Yevmenkin 		if (data[0] & 0xf)
1136490c2ffSMaksim Yevmenkin 			syslog(LOG_ERR, "Got handshake message with error " \
1146490c2ffSMaksim Yevmenkin 				"response 0x%x from %s",
1156490c2ffSMaksim Yevmenkin 				data[0], bt_ntoa(&s->bdaddr, NULL));
1166490c2ffSMaksim Yevmenkin 		break;
1176490c2ffSMaksim Yevmenkin 
1186490c2ffSMaksim Yevmenkin 	case 1: /* HID Control */
1196490c2ffSMaksim Yevmenkin 		switch (data[0] & 0xf) {
1206490c2ffSMaksim Yevmenkin 		case 0: /* NOP */
1216490c2ffSMaksim Yevmenkin 			break;
1226490c2ffSMaksim Yevmenkin 
1236490c2ffSMaksim Yevmenkin 		case 1: /* Hard reset */
1246490c2ffSMaksim Yevmenkin 		case 2: /* Soft reset */
1256490c2ffSMaksim Yevmenkin 			syslog(LOG_WARNING, "Device %s requested %s reset",
1266490c2ffSMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL),
1276490c2ffSMaksim Yevmenkin 				((data[0] & 0xf) == 1)? "hard" : "soft");
1286490c2ffSMaksim Yevmenkin 			break;
1296490c2ffSMaksim Yevmenkin 
1306490c2ffSMaksim Yevmenkin 		case 3: /* Suspend */
1316490c2ffSMaksim Yevmenkin 			syslog(LOG_NOTICE, "Device %s requested Suspend",
1326490c2ffSMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL));
1336490c2ffSMaksim Yevmenkin 			break;
1346490c2ffSMaksim Yevmenkin 
1356490c2ffSMaksim Yevmenkin 		case 4: /* Exit suspend */
1366490c2ffSMaksim Yevmenkin 			syslog(LOG_NOTICE, "Device %s requested Exit Suspend",
1376490c2ffSMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL));
1386490c2ffSMaksim Yevmenkin 			break;
1396490c2ffSMaksim Yevmenkin 
1406490c2ffSMaksim Yevmenkin 		case 5: /* Virtual cable unplug */
1416490c2ffSMaksim Yevmenkin 			syslog(LOG_NOTICE, "Device %s unplugged virtual cable",
1426490c2ffSMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL));
1436490c2ffSMaksim Yevmenkin 			session_close(s);
1446490c2ffSMaksim Yevmenkin 			break;
1456490c2ffSMaksim Yevmenkin 
1466490c2ffSMaksim Yevmenkin 		default:
1476490c2ffSMaksim Yevmenkin 			syslog(LOG_WARNING, "Device %s sent unknown " \
1486490c2ffSMaksim Yevmenkin                                 "HID_Control message 0x%x",
1496490c2ffSMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL), data[0]);
1506490c2ffSMaksim Yevmenkin 			break;
1516490c2ffSMaksim Yevmenkin 		}
1526490c2ffSMaksim Yevmenkin 		break;
1536490c2ffSMaksim Yevmenkin 
1546490c2ffSMaksim Yevmenkin 	default:
1556490c2ffSMaksim Yevmenkin 		syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \
1566490c2ffSMaksim Yevmenkin 			"channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL));
1576490c2ffSMaksim Yevmenkin 		break;
1586490c2ffSMaksim Yevmenkin 	}
1596490c2ffSMaksim Yevmenkin 
1606490c2ffSMaksim Yevmenkin 	return (0);
1616490c2ffSMaksim Yevmenkin }
1626490c2ffSMaksim Yevmenkin 
1636490c2ffSMaksim Yevmenkin /*
1646490c2ffSMaksim Yevmenkin  * Process data from the interrupt channel
1656490c2ffSMaksim Yevmenkin  */
1666490c2ffSMaksim Yevmenkin 
1677aebfa93SMaksim Yevmenkin int32_t
hid_interrupt(bthid_session_p s,uint8_t * data,int32_t len)1687aebfa93SMaksim Yevmenkin hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len)
1696490c2ffSMaksim Yevmenkin {
1707aebfa93SMaksim Yevmenkin 	hid_device_p	hid_device;
1716490c2ffSMaksim Yevmenkin 	hid_data_t	d;
1726490c2ffSMaksim Yevmenkin 	hid_item_t	h;
1737aebfa93SMaksim Yevmenkin 	int32_t		report_id, usage, page, val,
174c9eb7bf7SVladimir Kondratyev 			mouse_x, mouse_y, mouse_z, mouse_t, mouse_butt,
175ef6de0e1SKai Wang 			mevents, kevents, i;
1766490c2ffSMaksim Yevmenkin 
1776490c2ffSMaksim Yevmenkin 	assert(s != NULL);
1783adfd74aSMaksim Yevmenkin 	assert(s->srv != NULL);
1796490c2ffSMaksim Yevmenkin 	assert(data != NULL);
1806490c2ffSMaksim Yevmenkin 
1816490c2ffSMaksim Yevmenkin 	if (len < 3) {
1826490c2ffSMaksim Yevmenkin 		syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \
1836490c2ffSMaksim Yevmenkin 			"channel from %s", len, bt_ntoa(&s->bdaddr, NULL));
1846490c2ffSMaksim Yevmenkin 		return (-1);
1856490c2ffSMaksim Yevmenkin 	}
1866490c2ffSMaksim Yevmenkin 
1877aebfa93SMaksim Yevmenkin 	if (data[0] != 0xa1) {
1886490c2ffSMaksim Yevmenkin 		syslog(LOG_ERR, "Got unexpected message 0x%x on " \
1896490c2ffSMaksim Yevmenkin 			"Interrupt channel from %s",
1906490c2ffSMaksim Yevmenkin 			data[0], bt_ntoa(&s->bdaddr, NULL));
1916490c2ffSMaksim Yevmenkin 		return (-1);
1926490c2ffSMaksim Yevmenkin 	}
1936490c2ffSMaksim Yevmenkin 
1946490c2ffSMaksim Yevmenkin 	report_id = data[1];
195ef6de0e1SKai Wang 	data ++;
196ef6de0e1SKai Wang 	len --;
1976490c2ffSMaksim Yevmenkin 
1986490c2ffSMaksim Yevmenkin 	hid_device = get_hid_device(&s->bdaddr);
1996490c2ffSMaksim Yevmenkin 	assert(hid_device != NULL);
2006490c2ffSMaksim Yevmenkin 
201c9eb7bf7SVladimir Kondratyev 	mouse_x = mouse_y = mouse_z = mouse_t = mouse_butt = 0;
202c9eb7bf7SVladimir Kondratyev 	mevents = kevents = 0;
2036490c2ffSMaksim Yevmenkin 
2046490c2ffSMaksim Yevmenkin 	for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1);
2056490c2ffSMaksim Yevmenkin 	     hid_get_item(d, &h) > 0; ) {
206fc780ca1SMaksim Yevmenkin 		if ((h.flags & HIO_CONST) || (h.report_ID != report_id) ||
207fc780ca1SMaksim Yevmenkin 		    (h.kind != hid_input))
2086490c2ffSMaksim Yevmenkin 			continue;
2096490c2ffSMaksim Yevmenkin 
2106490c2ffSMaksim Yevmenkin 		page = HID_PAGE(h.usage);
2116490c2ffSMaksim Yevmenkin 		val = hid_get_data(data, &h);
2126490c2ffSMaksim Yevmenkin 
213822a51b3SRaphael Kubo da Costa 		/*
214822a51b3SRaphael Kubo da Costa 		 * When the input field is an array and the usage is specified
215822a51b3SRaphael Kubo da Costa 		 * with a range instead of an ID, we have to derive the actual
216822a51b3SRaphael Kubo da Costa 		 * usage by using the item value as an index in the usage range
217822a51b3SRaphael Kubo da Costa 		 * list.
218822a51b3SRaphael Kubo da Costa 		 */
219822a51b3SRaphael Kubo da Costa 		if ((h.flags & HIO_VARIABLE)) {
220822a51b3SRaphael Kubo da Costa 			usage = HID_USAGE(h.usage);
221822a51b3SRaphael Kubo da Costa 		} else {
222822a51b3SRaphael Kubo da Costa 			const uint32_t usage_offset = val - h.logical_minimum;
223822a51b3SRaphael Kubo da Costa 			usage = HID_USAGE(h.usage_minimum + usage_offset);
224822a51b3SRaphael Kubo da Costa 		}
225822a51b3SRaphael Kubo da Costa 
2266490c2ffSMaksim Yevmenkin 		switch (page) {
2276490c2ffSMaksim Yevmenkin 		case HUP_GENERIC_DESKTOP:
2286490c2ffSMaksim Yevmenkin 			switch (usage) {
2296490c2ffSMaksim Yevmenkin 			case HUG_X:
2306490c2ffSMaksim Yevmenkin 				mouse_x = val;
2313adfd74aSMaksim Yevmenkin 				mevents ++;
2326490c2ffSMaksim Yevmenkin 				break;
2336490c2ffSMaksim Yevmenkin 
2346490c2ffSMaksim Yevmenkin 			case HUG_Y:
2356490c2ffSMaksim Yevmenkin 				mouse_y = val;
2363adfd74aSMaksim Yevmenkin 				mevents ++;
2376490c2ffSMaksim Yevmenkin 				break;
2386490c2ffSMaksim Yevmenkin 
2396490c2ffSMaksim Yevmenkin 			case HUG_WHEEL:
2406490c2ffSMaksim Yevmenkin 				mouse_z = -val;
2413adfd74aSMaksim Yevmenkin 				mevents ++;
2426490c2ffSMaksim Yevmenkin 				break;
2436490c2ffSMaksim Yevmenkin 
2446490c2ffSMaksim Yevmenkin 			case HUG_SYSTEM_SLEEP:
2456490c2ffSMaksim Yevmenkin 				if (val)
2466490c2ffSMaksim Yevmenkin 					syslog(LOG_NOTICE, "Sleep button pressed");
2476490c2ffSMaksim Yevmenkin 				break;
2486490c2ffSMaksim Yevmenkin 			}
2496490c2ffSMaksim Yevmenkin 			break;
2506490c2ffSMaksim Yevmenkin 
2516490c2ffSMaksim Yevmenkin 		case HUP_KEYBOARD:
2523adfd74aSMaksim Yevmenkin 			kevents ++;
2533adfd74aSMaksim Yevmenkin 
2546490c2ffSMaksim Yevmenkin 			if (h.flags & HIO_VARIABLE) {
2553adfd74aSMaksim Yevmenkin 				if (val && usage < kbd_maxkey())
2567aebfa93SMaksim Yevmenkin 					bit_set(s->keys1, usage);
2576490c2ffSMaksim Yevmenkin 			} else {
2583adfd74aSMaksim Yevmenkin 				if (val && val < kbd_maxkey())
2597aebfa93SMaksim Yevmenkin 					bit_set(s->keys1, val);
2603adfd74aSMaksim Yevmenkin 
261ef6de0e1SKai Wang 				for (i = 1; i < h.report_count; i++) {
262ef6de0e1SKai Wang 					h.pos += h.report_size;
2636490c2ffSMaksim Yevmenkin 					val = hid_get_data(data, &h);
2643adfd74aSMaksim Yevmenkin 					if (val && val < kbd_maxkey())
2657aebfa93SMaksim Yevmenkin 						bit_set(s->keys1, val);
2666490c2ffSMaksim Yevmenkin 				}
2676490c2ffSMaksim Yevmenkin 			}
2686490c2ffSMaksim Yevmenkin 			break;
2696490c2ffSMaksim Yevmenkin 
2706490c2ffSMaksim Yevmenkin 		case HUP_BUTTON:
2717aebfa93SMaksim Yevmenkin 			if (usage != 0) {
2727aebfa93SMaksim Yevmenkin 				if (usage == 2)
2737aebfa93SMaksim Yevmenkin 					usage = 3;
2747aebfa93SMaksim Yevmenkin 				else if (usage == 3)
2757aebfa93SMaksim Yevmenkin 					usage = 2;
2767aebfa93SMaksim Yevmenkin 
2777aebfa93SMaksim Yevmenkin 				mouse_butt |= (val << (usage - 1));
2783adfd74aSMaksim Yevmenkin 				mevents ++;
2797aebfa93SMaksim Yevmenkin 			}
2803adfd74aSMaksim Yevmenkin 			break;
2813adfd74aSMaksim Yevmenkin 
2823adfd74aSMaksim Yevmenkin 		case HUP_CONSUMER:
28344af5666SVladimir Kondratyev 			if (hid_device->keyboard && s->srv->uinput) {
28444af5666SVladimir Kondratyev 				if (h.flags & HIO_VARIABLE) {
28544af5666SVladimir Kondratyev 					uinput_rep_cons(s->ukbd, usage, !!val);
28644af5666SVladimir Kondratyev 				} else {
28744af5666SVladimir Kondratyev 					if (s->consk > 0)
28844af5666SVladimir Kondratyev 						uinput_rep_cons(s->ukbd,
28944af5666SVladimir Kondratyev 						    s->consk, 0);
29044af5666SVladimir Kondratyev 					if (uinput_rep_cons(s->ukbd, val, 1)
29144af5666SVladimir Kondratyev 					    == 0)
29244af5666SVladimir Kondratyev 						s->consk = val;
29344af5666SVladimir Kondratyev 				}
29444af5666SVladimir Kondratyev 			}
29544af5666SVladimir Kondratyev 
2963adfd74aSMaksim Yevmenkin 			if (!val)
2973adfd74aSMaksim Yevmenkin 				break;
2983adfd74aSMaksim Yevmenkin 
2993adfd74aSMaksim Yevmenkin 			switch (usage) {
300a28cc643SMaksim Yevmenkin 			case HUC_AC_PAN:
301a28cc643SMaksim Yevmenkin 				/* Horizontal scroll */
302c9eb7bf7SVladimir Kondratyev 				mouse_t = val;
303a28cc643SMaksim Yevmenkin 				mevents ++;
304a28cc643SMaksim Yevmenkin 				val = 0;
305a28cc643SMaksim Yevmenkin 				break;
306a28cc643SMaksim Yevmenkin 
3073adfd74aSMaksim Yevmenkin 			case 0xb5: /* Scan Next Track */
3083adfd74aSMaksim Yevmenkin 				val = 0x19;
3093adfd74aSMaksim Yevmenkin 				break;
3103adfd74aSMaksim Yevmenkin 
3113adfd74aSMaksim Yevmenkin 			case 0xb6: /* Scan Previous Track */
3123adfd74aSMaksim Yevmenkin 				val = 0x10;
3133adfd74aSMaksim Yevmenkin 				break;
3143adfd74aSMaksim Yevmenkin 
3153adfd74aSMaksim Yevmenkin 			case 0xb7: /* Stop */
3163adfd74aSMaksim Yevmenkin 				val = 0x24;
3173adfd74aSMaksim Yevmenkin 				break;
3183adfd74aSMaksim Yevmenkin 
3193adfd74aSMaksim Yevmenkin 			case 0xcd: /* Play/Pause */
3203adfd74aSMaksim Yevmenkin 				val = 0x22;
3213adfd74aSMaksim Yevmenkin 				break;
3223adfd74aSMaksim Yevmenkin 
3233adfd74aSMaksim Yevmenkin 			case 0xe2: /* Mute */
3243adfd74aSMaksim Yevmenkin 				val = 0x20;
3253adfd74aSMaksim Yevmenkin 				break;
3263adfd74aSMaksim Yevmenkin 
3273adfd74aSMaksim Yevmenkin 			case 0xe9: /* Volume Up */
3283adfd74aSMaksim Yevmenkin 				val = 0x30;
3293adfd74aSMaksim Yevmenkin 				break;
3303adfd74aSMaksim Yevmenkin 
3313adfd74aSMaksim Yevmenkin 			case 0xea: /* Volume Down */
3323adfd74aSMaksim Yevmenkin 				val = 0x2E;
3333adfd74aSMaksim Yevmenkin 				break;
3343adfd74aSMaksim Yevmenkin 
3353adfd74aSMaksim Yevmenkin 			case 0x183: /* Media Select */
3363adfd74aSMaksim Yevmenkin 				val = 0x6D;
3373adfd74aSMaksim Yevmenkin 				break;
3383adfd74aSMaksim Yevmenkin 
3393adfd74aSMaksim Yevmenkin 			case 0x018a: /* Mail */
3403adfd74aSMaksim Yevmenkin 				val = 0x6C;
3413adfd74aSMaksim Yevmenkin 				break;
3423adfd74aSMaksim Yevmenkin 
3433adfd74aSMaksim Yevmenkin 			case 0x192: /* Calculator */
3443adfd74aSMaksim Yevmenkin 				val = 0x21;
3453adfd74aSMaksim Yevmenkin 				break;
3463adfd74aSMaksim Yevmenkin 
3473adfd74aSMaksim Yevmenkin 			case 0x194: /* My Computer */
3483adfd74aSMaksim Yevmenkin 				val = 0x6B;
3493adfd74aSMaksim Yevmenkin 				break;
3503adfd74aSMaksim Yevmenkin 
3513adfd74aSMaksim Yevmenkin 			case 0x221: /* WWW Search */
3523adfd74aSMaksim Yevmenkin 				val = 0x65;
3533adfd74aSMaksim Yevmenkin 				break;
3543adfd74aSMaksim Yevmenkin 
3553adfd74aSMaksim Yevmenkin 			case 0x223: /* WWW Home */
3563adfd74aSMaksim Yevmenkin 				val = 0x32;
3573adfd74aSMaksim Yevmenkin 				break;
3583adfd74aSMaksim Yevmenkin 
3593adfd74aSMaksim Yevmenkin 			case 0x224: /* WWW Back */
3603adfd74aSMaksim Yevmenkin 				val = 0x6A;
3613adfd74aSMaksim Yevmenkin 				break;
3623adfd74aSMaksim Yevmenkin 
3633adfd74aSMaksim Yevmenkin 			case 0x225: /* WWW Forward */
3643adfd74aSMaksim Yevmenkin 				val = 0x69;
3653adfd74aSMaksim Yevmenkin 				break;
3663adfd74aSMaksim Yevmenkin 
3673adfd74aSMaksim Yevmenkin 			case 0x226: /* WWW Stop */
3683adfd74aSMaksim Yevmenkin 				val = 0x68;
3693adfd74aSMaksim Yevmenkin 				break;
3703adfd74aSMaksim Yevmenkin 
37113d38e1aSMarkus Brueffer 			case 0x227: /* WWW Refresh */
3723adfd74aSMaksim Yevmenkin 				val = 0x67;
3733adfd74aSMaksim Yevmenkin 				break;
3743adfd74aSMaksim Yevmenkin 
3753adfd74aSMaksim Yevmenkin 			case 0x22a: /* WWW Favorites */
3763adfd74aSMaksim Yevmenkin 				val = 0x66;
3773adfd74aSMaksim Yevmenkin 				break;
3783adfd74aSMaksim Yevmenkin 
3793adfd74aSMaksim Yevmenkin 			default:
3803adfd74aSMaksim Yevmenkin 				val = 0;
3813adfd74aSMaksim Yevmenkin 				break;
3823adfd74aSMaksim Yevmenkin 			}
3833adfd74aSMaksim Yevmenkin 
3843adfd74aSMaksim Yevmenkin 			/* XXX FIXME - UGLY HACK */
3853adfd74aSMaksim Yevmenkin 			if (val != 0) {
3867aebfa93SMaksim Yevmenkin 				if (hid_device->keyboard) {
3877aebfa93SMaksim Yevmenkin 					int32_t	buf[4] = { 0xe0, val,
3887aebfa93SMaksim Yevmenkin 							   0xe0, val|0x80 };
3893adfd74aSMaksim Yevmenkin 
3907aebfa93SMaksim Yevmenkin 					assert(s->vkbd != -1);
3917aebfa93SMaksim Yevmenkin 					write(s->vkbd, buf, sizeof(buf));
3927aebfa93SMaksim Yevmenkin 				} else
3937aebfa93SMaksim Yevmenkin 					syslog(LOG_ERR, "Keyboard events " \
3947aebfa93SMaksim Yevmenkin 						"received from non-keyboard " \
3957aebfa93SMaksim Yevmenkin 						"device %s. Please report",
3967aebfa93SMaksim Yevmenkin 						bt_ntoa(&s->bdaddr, NULL));
3973adfd74aSMaksim Yevmenkin 			}
3986490c2ffSMaksim Yevmenkin 			break;
3996490c2ffSMaksim Yevmenkin 
4006490c2ffSMaksim Yevmenkin 		case HUP_MICROSOFT:
4016490c2ffSMaksim Yevmenkin 			switch (usage) {
4026490c2ffSMaksim Yevmenkin 			case 0xfe01:
4036490c2ffSMaksim Yevmenkin 				if (!hid_device->battery_power)
4046490c2ffSMaksim Yevmenkin 					break;
4056490c2ffSMaksim Yevmenkin 
4066490c2ffSMaksim Yevmenkin 				switch (val) {
4076490c2ffSMaksim Yevmenkin 				case 1:
4086490c2ffSMaksim Yevmenkin 					syslog(LOG_INFO, "Battery is OK on %s",
4096490c2ffSMaksim Yevmenkin 						bt_ntoa(&s->bdaddr, NULL));
4106490c2ffSMaksim Yevmenkin 					break;
4116490c2ffSMaksim Yevmenkin 
4126490c2ffSMaksim Yevmenkin 				case 2:
4136490c2ffSMaksim Yevmenkin 					syslog(LOG_NOTICE, "Low battery on %s",
4146490c2ffSMaksim Yevmenkin 						bt_ntoa(&s->bdaddr, NULL));
4156490c2ffSMaksim Yevmenkin 					break;
4166490c2ffSMaksim Yevmenkin 
4176490c2ffSMaksim Yevmenkin 				case 3:
4186490c2ffSMaksim Yevmenkin 					syslog(LOG_WARNING, "Very low battery "\
4196490c2ffSMaksim Yevmenkin                                                 "on %s",
4206490c2ffSMaksim Yevmenkin 						bt_ntoa(&s->bdaddr, NULL));
4216490c2ffSMaksim Yevmenkin 					break;
4226490c2ffSMaksim Yevmenkin                                 }
4236490c2ffSMaksim Yevmenkin 				break;
4246490c2ffSMaksim Yevmenkin 			}
4256490c2ffSMaksim Yevmenkin 			break;
4266490c2ffSMaksim Yevmenkin 		}
4276490c2ffSMaksim Yevmenkin 	}
4286490c2ffSMaksim Yevmenkin 	hid_end_parse(d);
4296490c2ffSMaksim Yevmenkin 
4307aebfa93SMaksim Yevmenkin 	/*
431a9a8884bSVladimir Kondratyev 	 * Apple adheres to no standards and sends reports it does
432a9a8884bSVladimir Kondratyev 	 * not introduce in its hid descriptor for its magic mouse.
433a9a8884bSVladimir Kondratyev 	 * Handle those reports here.
434a9a8884bSVladimir Kondratyev 	 */
435a9a8884bSVladimir Kondratyev 	if (MAGIC_MOUSE(hid_device) && s->ctx) {
436a9a8884bSVladimir Kondratyev 		struct apple_state *c = (struct apple_state *)s->ctx;
437a9a8884bSVladimir Kondratyev 		int firm = 0, middle = 0;
438a9a8884bSVladimir Kondratyev 		int16_t v;
439a9a8884bSVladimir Kondratyev 
440a9a8884bSVladimir Kondratyev 		data++, len--;		/* Chomp report_id */
441a9a8884bSVladimir Kondratyev 
442a9a8884bSVladimir Kondratyev 		if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len))
443a9a8884bSVladimir Kondratyev 			goto check_middle_button;
444a9a8884bSVladimir Kondratyev 
445a9a8884bSVladimir Kondratyev 		/*
446a9a8884bSVladimir Kondratyev 		 * The basics. When touches are detected, no normal mouse
447a9a8884bSVladimir Kondratyev 		 * reports are sent. Collect clicks and dx/dy
448a9a8884bSVladimir Kondratyev 		 */
449a9a8884bSVladimir Kondratyev 		if (data[2] & 1)
450a9a8884bSVladimir Kondratyev 			mouse_butt |= 0x1;
451a9a8884bSVladimir Kondratyev 		if (data[2] & 2)
452a9a8884bSVladimir Kondratyev 			mouse_butt |= 0x4;
453a9a8884bSVladimir Kondratyev 
454a9a8884bSVladimir Kondratyev 		if ((v = data[0] + ((data[2] & 0x0C) << 6)))
455a9a8884bSVladimir Kondratyev 			mouse_x += ((int16_t)(v << 6)) >> 6, mevents++;
456a9a8884bSVladimir Kondratyev 		if ((v = data[1] + ((data[2] & 0x30) << 4)))
457a9a8884bSVladimir Kondratyev 			mouse_y += ((int16_t)(v << 6)) >> 6, mevents++;
458a9a8884bSVladimir Kondratyev 
459a9a8884bSVladimir Kondratyev 		/*
460a9a8884bSVladimir Kondratyev 		 * The hard part: accumulate touch events and emulate middle
461a9a8884bSVladimir Kondratyev 		 */
462a9a8884bSVladimir Kondratyev 		for (data += AMM_BASIC_BLOCK,  len -= AMM_BASIC_BLOCK;
463a9a8884bSVladimir Kondratyev 		     len >=  AMM_FINGER_BLOCK;
464a9a8884bSVladimir Kondratyev 		     data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) {
465a9a8884bSVladimir Kondratyev 			int x, y, z, force, id;
466a9a8884bSVladimir Kondratyev 
467a9a8884bSVladimir Kondratyev 			v = data[0] | ((data[1] & 0xf) << 8);
468a9a8884bSVladimir Kondratyev 			x = ((int16_t)(v << 4)) >> 4;
469a9a8884bSVladimir Kondratyev 
470a9a8884bSVladimir Kondratyev 			v = (data[1] >> 4) | (data[2] << 4);
471a9a8884bSVladimir Kondratyev 			y = -(((int16_t)(v << 4)) >> 4);
472a9a8884bSVladimir Kondratyev 
473a9a8884bSVladimir Kondratyev 			force = data[5] & 0x3f;
474a9a8884bSVladimir Kondratyev 			id = 0xf & ((data[5] >> 6) | (data[6] << 2));
475a9a8884bSVladimir Kondratyev 			z = (y - c->y[id]) / AMM_WHEEL_SPEED;
476a9a8884bSVladimir Kondratyev 
477a9a8884bSVladimir Kondratyev 			switch ((data[7] >> 4) & 0x7) {	/* Phase */
478a9a8884bSVladimir Kondratyev 			case 3:	/* First touch */
479a9a8884bSVladimir Kondratyev 				c->y[id] = y;
480a9a8884bSVladimir Kondratyev 				break;
481a9a8884bSVladimir Kondratyev 			case 4:	/* Touch dragged */
482a9a8884bSVladimir Kondratyev 				if (z) {
483a9a8884bSVladimir Kondratyev 					mouse_z += z;
484a9a8884bSVladimir Kondratyev 					c->y[id] += z * AMM_WHEEL_SPEED;
485a9a8884bSVladimir Kondratyev 					mevents++;
486a9a8884bSVladimir Kondratyev 				}
487a9a8884bSVladimir Kondratyev 				break;
488a9a8884bSVladimir Kondratyev 			default:
489a9a8884bSVladimir Kondratyev 				break;
490a9a8884bSVladimir Kondratyev 			}
491a9a8884bSVladimir Kondratyev 			/* Count firm touches vs. firm+middle touches */
492a9a8884bSVladimir Kondratyev 			if (force >= 8 && ++firm && x > -350 && x < 350)
493a9a8884bSVladimir Kondratyev 				++middle;
494a9a8884bSVladimir Kondratyev 		}
495a9a8884bSVladimir Kondratyev 
496a9a8884bSVladimir Kondratyev 		/*
497a9a8884bSVladimir Kondratyev 		 * If a new click is registered by mouse and there are firm
498a9a8884bSVladimir Kondratyev 		 * touches which are all in center, make it a middle click
499a9a8884bSVladimir Kondratyev 		 */
500a9a8884bSVladimir Kondratyev 		if (mouse_butt && !c->button_state && firm && middle == firm)
501a9a8884bSVladimir Kondratyev 			mouse_butt = 0x2;
502a9a8884bSVladimir Kondratyev 
503a9a8884bSVladimir Kondratyev 		/*
504a9a8884bSVladimir Kondratyev 		 * If we're still clicking and have converted the click
505a9a8884bSVladimir Kondratyev 		 * to a middle click, keep it middle clicking
506a9a8884bSVladimir Kondratyev 		 */
507a9a8884bSVladimir Kondratyev check_middle_button:
508a9a8884bSVladimir Kondratyev 		if (mouse_butt && c->button_state == 0x2)
509a9a8884bSVladimir Kondratyev 			mouse_butt = 0x2;
510a9a8884bSVladimir Kondratyev 
511a9a8884bSVladimir Kondratyev 		if (mouse_butt != c->button_state)
512a9a8884bSVladimir Kondratyev 			c->button_state = mouse_butt, mevents++;
513a9a8884bSVladimir Kondratyev 	}
514a9a8884bSVladimir Kondratyev 
515a9a8884bSVladimir Kondratyev 	/*
5167aebfa93SMaksim Yevmenkin 	 * XXX FIXME Feed keyboard events into kernel.
5177aebfa93SMaksim Yevmenkin 	 * The code below works, bit host also needs to track
5187aebfa93SMaksim Yevmenkin 	 * and handle repeat.
5197aebfa93SMaksim Yevmenkin 	 *
5207aebfa93SMaksim Yevmenkin 	 * Key repeat currently works in X, but not in console.
5217aebfa93SMaksim Yevmenkin 	 */
5227aebfa93SMaksim Yevmenkin 
5237aebfa93SMaksim Yevmenkin 	if (kevents > 0) {
5247aebfa93SMaksim Yevmenkin 		if (hid_device->keyboard) {
5257aebfa93SMaksim Yevmenkin 			assert(s->vkbd != -1);
5263adfd74aSMaksim Yevmenkin 			kbd_process_keys(s);
5277aebfa93SMaksim Yevmenkin 		} else
5287aebfa93SMaksim Yevmenkin 			syslog(LOG_ERR, "Keyboard events received from " \
5297aebfa93SMaksim Yevmenkin 				"non-keyboard device %s. Please report",
5307aebfa93SMaksim Yevmenkin 				bt_ntoa(&s->bdaddr, NULL));
5317aebfa93SMaksim Yevmenkin 	}
5323adfd74aSMaksim Yevmenkin 
5336490c2ffSMaksim Yevmenkin 	/*
5343adfd74aSMaksim Yevmenkin 	 * XXX FIXME Feed mouse events into kernel.
5353adfd74aSMaksim Yevmenkin 	 * The code block below works, but it is not good enough.
5363adfd74aSMaksim Yevmenkin 	 * Need to track double-clicks etc.
5377aebfa93SMaksim Yevmenkin 	 *
5387aebfa93SMaksim Yevmenkin 	 * Double click currently works in X, but not in console.
5396490c2ffSMaksim Yevmenkin 	 */
5406490c2ffSMaksim Yevmenkin 
5413adfd74aSMaksim Yevmenkin 	if (mevents > 0) {
5426490c2ffSMaksim Yevmenkin 		struct mouse_info	mi;
5436490c2ffSMaksim Yevmenkin 
544c9eb7bf7SVladimir Kondratyev 		memset(&mi, 0, sizeof(mi));
5456490c2ffSMaksim Yevmenkin 		mi.operation = MOUSE_ACTION;
546c9eb7bf7SVladimir Kondratyev 		mi.u.data.buttons = mouse_butt;
547c9eb7bf7SVladimir Kondratyev 
548c9eb7bf7SVladimir Kondratyev 		/* translate T-axis into button presses */
549c9eb7bf7SVladimir Kondratyev 		if (mouse_t != 0) {
550c9eb7bf7SVladimir Kondratyev 			mi.u.data.buttons |= 1 << (mouse_t > 0 ? 6 : 5);
551c9eb7bf7SVladimir Kondratyev 			if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
552c9eb7bf7SVladimir Kondratyev 				syslog(LOG_ERR, "Could not process mouse " \
553c9eb7bf7SVladimir Kondratyev 					"events from %s. %s (%d)",
554c9eb7bf7SVladimir Kondratyev 					bt_ntoa(&s->bdaddr, NULL),
555c9eb7bf7SVladimir Kondratyev 					strerror(errno), errno);
556c9eb7bf7SVladimir Kondratyev 		}
557c9eb7bf7SVladimir Kondratyev 
5586490c2ffSMaksim Yevmenkin 		mi.u.data.x = mouse_x;
5596490c2ffSMaksim Yevmenkin 		mi.u.data.y = mouse_y;
5606490c2ffSMaksim Yevmenkin 		mi.u.data.z = mouse_z;
5616490c2ffSMaksim Yevmenkin 		mi.u.data.buttons = mouse_butt;
5626490c2ffSMaksim Yevmenkin 
5636490c2ffSMaksim Yevmenkin 		if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
5646490c2ffSMaksim Yevmenkin 			syslog(LOG_ERR, "Could not process mouse events from " \
5656490c2ffSMaksim Yevmenkin 				"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
5666490c2ffSMaksim Yevmenkin 				strerror(errno), errno);
56744af5666SVladimir Kondratyev 
56844af5666SVladimir Kondratyev 		if (hid_device->mouse && s->srv->uinput &&
56944af5666SVladimir Kondratyev 		    uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z,
57044af5666SVladimir Kondratyev 					mouse_t, mouse_butt, s->obutt) < 0)
57144af5666SVladimir Kondratyev 			syslog(LOG_ERR, "Could not process mouse events from " \
57244af5666SVladimir Kondratyev 				"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
57344af5666SVladimir Kondratyev 				strerror(errno), errno);
57444af5666SVladimir Kondratyev 		s->obutt = mouse_butt;
5756490c2ffSMaksim Yevmenkin 	}
5766490c2ffSMaksim Yevmenkin 
5776490c2ffSMaksim Yevmenkin 	return (0);
5786490c2ffSMaksim Yevmenkin }
579