1//------------------------------------------------------------------------------
2//	Copyright (c) 2004, Haiku, Inc.
3//
4//	Permission is hereby granted, free of charge, to any person obtaining a
5//	copy of this software and associated documentation files (the "Software"),
6//	to deal in the Software without restriction, including without limitation
7//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8//	and/or sell copies of the Software, and to permit persons to whom the
9//	Software is furnished to do so, subject to the following conditions:
10//
11//	The above copyright notice and this permission notice shall be included in
12//	all copies or substantial portions of the Software.
13//
14//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20//	DEALINGS IN THE SOFTWARE.
21//
22//	File Name:		SerialMouse.cpp
23//	Author(s):		Oscar Lesta (bipolar@softhome.net)
24//	Description:	SerialMouse detects and manages serial mice, duh!.
25//  References:		- http://www.hut.fi/~then/mytexts/mouse.html
26//					- Be's (binary) serial_mouse addon.
27//					- Haiku's CVS.
28//------------------------------------------------------------------------------
29
30#include <string.h>
31
32#include <SerialPort.h>
33
34#include "SerialMouse.h"
35
36//#define DEBUG_SERIAL_MOUSE
37#ifdef DEBUG_SERIAL_MOUSE
38	#include <stdio.h>
39	#define LOG(x)		printf x	// TODO: log to "MouseInputDevice::sLogFile"
40									// I used this in my console tests.
41#else
42	#define LOG(x)
43#endif
44
45const static bigtime_t kSerialTimeOut = 200000;	// 200 ms
46const static uint8 kMaxBytesToRead = 255;	// Serial PnP data can be this long.
47
48// The protocols we know how to handle. Indexed by mouse_protocol_id.
49/*
50  the sync[] is a protocol-identification/sync thingy:
51
52  if ((read_byte[0] & sync[0]) == sync[1]) then we are at the beggining of the
53  a packet. Next data bytes are OK... if ((read_byte[i] & sync[2]) == 0))
54*/
55struct mouse_protocol {
56	const char* name;
57	uint8 num_bytes;
58	uint8 sync[3];
59};
60
61
62const static
63struct mouse_protocol mp[] = {
64	{ "UNKNOWN",       0, { 0x00, 0x00, 0x00 } },
65	{ "Microsoft",     3, { 0x40, 0x40, 0x40,} },
66	{ "Logitech",      3, { 0x40, 0x40, 0x40 } }, // 3/4 bytes. FIX: only 3 are used now.
67	{ "MouseSystems",  5, { 0xF8, 0x80, 0x00 } },
68	{ "IntelliMouse",  4, { 0x40, 0x40, 0x00 } },
69};
70
71
72SerialMouse::SerialMouse()
73	: fSerialPort(NULL),
74	fPortsCount(0),
75	fPortNumber(0),
76	fMouseID(kNotSet),
77	fButtonsState(0)
78{
79	fSerialPort = new BSerialPort();
80	fPortsCount = fSerialPort->CountDevices() - 1;
81}
82
83
84SerialMouse::~SerialMouse()
85{
86	if (fSerialPort != NULL) {
87		fSerialPort->SetRTS(false);		// Put the mouse to sleep.
88		fSerialPort->Close();
89		delete fSerialPort;
90	}
91}
92
93//	#pragma mark -
94
95const char *
96SerialMouse::MouseDescription()
97{
98	// TODO: If we'll support more than just one mouse, this should be changed.
99	// Maybe we should also add the port number as suffix.
100
101	return mp[fMouseID].name;
102}
103
104
105// Find first usable serial port, try to detect a mouse there.
106// Returns:
107// - B_NO_INIT: all available ports were tested (no mouse present there).
108// - A positive value indicating in which serial port a mouse was found.
109
110status_t
111SerialMouse::IsMousePresent()
112{
113	for (uint8 i = 1; i <= fPortsCount; i++) {
114		char dev_name[B_PATH_NAME_LENGTH];
115
116		fSerialPort->GetDeviceName(i, dev_name);
117
118		// Skip internal modem (pctel, lucent or trimodem drivers).
119		// previously I checked only for != "pctel", now for == "serial#"
120		if (strncmp(dev_name, "serial", 6) != 0
121			&& strncmp(dev_name, "pc_serial", 9) != 0)
122			continue;
123
124		if (fSerialPort->Open(dev_name) <= 0) {
125			LOG(("SerialMouse: Failed to open %s.\n", dev_name));
126			continue;	// try next port.
127		}
128
129		LOG(("SerialMouse : Opened %s.\n", dev_name));
130
131		// This 4x retries helps, a little, to detect one of my mouse (buggy?).
132		// Not perfect, but catchs it more often than before.
133		uint8 retries = 4;
134		do {
135			fMouseID = DetectMouse();
136			if (fMouseID > kNotSet) {
137				// We found a mouse, just break the loop.
138				LOG(("SerialMouse: Found a %s Mouse.\n", MouseDescription()));
139				fSerialPort->SetBlocking(true);
140				fPortNumber = i;
141				return fPortNumber; 		// Return the port # in use.
142			}
143		} while ((fMouseID == kUnknown) && (retries--));
144
145		LOG(("SerialMouse: Mouse not detected.\n"));
146		fSerialPort->Close();
147	}
148
149	// If we get here its because we didn't found a mouse in any of the
150	// available serial ports.
151	// (Caller can do: "while (sm->IsMousePresent() > 0)").
152
153	return B_NO_INIT;
154}
155
156
157mouse_id
158SerialMouse::DetectMouse()
159{
160	int bytes_read = 0;
161	char c;
162	char id_buffer[20];			// If the mouse sends an ID, we store it here.
163	char buffer[kMaxBytesToRead];
164	uint8 id_length = 0;
165
166	fSerialPort->SetBlocking(false);
167	fSerialPort->SetTimeout(kSerialTimeOut);
168
169	SetPortOptions();
170
171	snooze(10000);
172
173	// Toggle RTS line in order to 'wake up' the mouse
174	fSerialPort->SetRTS(false);
175	snooze(120000);				// RTS low pulse width must be at least 100ms.
176	fSerialPort->ClearInput();
177	fSerialPort->SetRTS(true);
178
179	// wait upto kSerialTimeOut ms while trying to read mouse ID string.
180	if (fSerialPort->WaitForInput() == 0)
181		return kNoDevice;			// nothing there, quit.
182
183	// we make sure to 'eat' everything the mouse sends at init time, albeit
184	// we are interested only on the few first bytes, not doing it so will
185	// confuse things later.
186	while ((fSerialPort->Read(&c, 1) == 1) && (bytes_read < kMaxBytesToRead)) {
187		LOG(("read = %c (%d d - %x h)\n", c, c, c));
188
189		// Collect the bytes we care about.
190		if (c == 'M' || c == 'H' || c == '3' || c == 'Z' || c == '@') {
191			if (id_length < 4) {
192				id_buffer[id_length] = c;
193				id_length++;
194			}
195		} else
196			buffer[bytes_read] = c;	// store the garbage for futher processing.
197
198		bytes_read++;
199
200		// is there something else waiting to be read?
201		if (fSerialPort->WaitForInput() == 0)
202			break;								// no, break the loop.
203	}
204
205	// This can't happen, but... if we didn't get any data, just quit.
206	if (bytes_read == 0)
207		return kNoDevice;
208
209	fSerialPort->ClearInput();	// Toldya, I have a very noisy mouse.
210
211	if (id_length) {
212		fMouseID = ParseID(id_buffer, id_length);
213		SetPortOptions();				// Set new options according to MouseID.
214		return fMouseID;
215	}
216
217	// TODO: Below this line is work in progress (ie. temporal hacks until I,
218	// or someone else, get something better).
219
220	// Ok, last resort... try to identify the beast according to its packets.
221
222	if (bytes_read < 3)		// not enough data to even start... quit.
223		return kUnknown;
224
225	// First attempt to identify a MouseSystems mouse, because some (most?) of
226	// them either send: nothing, an standard packet or just garbage.
227	if (bytes_read == 5) {
228		// TODO: validate the packet!
229		fMouseID = kMouseSystems;
230		SetPortOptions();
231		return fMouseID;
232	}
233
234	return kUnknown;
235}
236
237
238// See if we recognize the mouse ID (first bytes mice usually send after
239// toggling the DTR/RTS lines on the serial port). Usual values:
240//
241// Microsoft mode    = 'M'
242// Microsoft mode    = 'M3'
243// IntelliMouse mode = 'MZ' and a valid 4-bytes null packet (0x40,0,0,0)
244// MouseSystems mode = 'HH', nothing at all, 5-bytes packet, or just garbage.
245
246mouse_id
247SerialMouse::ParseID(char buffer[], uint8 length)
248{
249	LOG(("data length = %d\n", (int)length));
250
251	if ((length == 1) && (buffer[0] == 'M'))
252		return kMicrosoft;
253
254	if (length == 2) {
255		if (buffer[0] == 'M' && buffer[1] == '3')
256			return kLogitech;
257		else if (buffer[0] == 'H' && buffer[1] == 'H')
258			return kMouseSystems;
259	}
260
261	if ((length == 4) &&
262		(buffer[0] == 'M' && buffer[1] == 'Z') && (buffer[2] == '@'))
263		return kIntelliMouse;
264
265	return kUnknown;
266}
267
268
269// Set serial port options according to our different needs.
270status_t
271SerialMouse::SetPortOptions()
272{
273	switch (fMouseID) {
274		case kLogitech:
275			fSerialPort->SetDataRate(B_1200_BPS);
276			fSerialPort->Write("*q", 2);
277			fSerialPort->SetDataRate(B_9600_BPS);
278			fSerialPort->SetDataBits(B_DATA_BITS_7);
279			break;
280
281		case kMouseSystems:
282			fSerialPort->SetDataRate(B_1200_BPS);
283			fSerialPort->SetDataBits(B_DATA_BITS_8);
284			break;
285
286		case kNotSet:
287		default:
288			// other defaults values are ok for us: no parity, 1 stop bit.
289			fSerialPort->SetDataRate(B_1200_BPS);
290			fSerialPort->SetDataBits(B_DATA_BITS_7);
291			break;
292	}
293
294	return B_OK;
295}
296
297
298//	#pragma mark -
299status_t
300SerialMouse::GetMouseEvent(mouse_movement* mm)
301{
302	char data[5];
303
304	if (fMouseID <= kNotSet)
305		return B_ERROR;
306
307	if (GetPacket(data) != B_OK)
308		return B_ERROR;		// not enough, or out-of-sync, data.
309
310	if (PacketToMM(data, mm) != B_OK)
311		return B_ERROR;     // something went wrong
312
313#ifdef DEBUG_SERIAL_MOUSE
314	DumpData(mm);
315#endif
316
317	return B_OK;
318}
319
320
321//	#pragma mark -
322
323// Block until we read enough bytes for the current protocol. See if we're in
324// sync with data stream, if not, just skip data until we regain sync.
325
326// TODO: syncronization needs a re-write. We should keep track of what byte we
327// are currently working on, validate it against mice's protocol, and if valid,
328// increment a "current_packet_byte" counter.
329// Also: I'm currently skipping the optional 4th byte in the Logitech protocol.
330// (may work, but 3th button will not).
331
332
333status_t
334SerialMouse::GetPacket(char data[])
335{
336	status_t result = B_ERROR;
337	uchar c = 0;
338	uint8    bytes_read;
339	size_t   mpsize = mp[fMouseID].num_bytes;
340
341	for (bytes_read = 0; bytes_read < mpsize; bytes_read++) {
342		// TODO: Shall we block here instead of leaving after a timeout?
343		// Yes, if we get called it's because there IS a mouse out there.
344
345		if (fSerialPort->Read(&c, 1) != 1) {
346			snooze(5000); // this is a realtime thread, and something is wrong...
347			break;
348		}
349
350		if (bytes_read == 0) {
351			if ((c & mp[fMouseID].sync[0]) != mp[fMouseID].sync[1]) {
352				LOG(("Out of sync: skipping byte = %x\n", c));
353				continue;	// skip bytes until we get a "header" byte.
354			}
355		}
356		data[bytes_read] = c;
357	}
358
359	if (bytes_read == mpsize) {
360		result = B_OK;
361
362		// validate data...
363		for (uint8 i = 1; i <= mpsize; i++) {
364			if ((data[i] & mp[fMouseID].sync[2]) != 0) {
365				LOG(("Out of sync: wrong data byte = %x\n", data[i]));
366				result = B_ERROR;
367				break;	// skip the packet.
368			}
369		}
370	}
371
372	return result;
373}
374
375
376// kMicroSoft, kMouseSystem, kIntelliMouse: working OK.
377// kLogitech: untested by lack of such devices. 3th button probably won't work.
378status_t
379SerialMouse::PacketToMM(char data[], mouse_movement* mm)
380{
381	const uint8 kPrimaryButton   = 1;
382	const uint8 kSecondaryButton = 2;
383	const uint8 kTertiaryButton  = 4;
384
385	static uint8 previous_buttons = 0;	// only meaningful for kMicrosoft.
386
387	mm->timestamp = system_time();
388
389	switch (fMouseID) {
390		case kMicrosoft:
391			mm->buttons = ((data[0] & 0x20) ? kPrimaryButton   : 0) +
392						  ((data[0] & 0x10) ? kSecondaryButton : 0);
393
394			//                        Higher 2 bits          Lower 6 bits
395			mm->xdelta =   (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F));
396			mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F));
397
398			// up to here we've handled "ye olde" 2-buttons MS packet.
399			// There's a 3-buttons extension to it, consisting in sending a
400			// "null packet" (no changes in x/y nor in standard buttons)
401			// whenever the third button is pressed/released (how clever!! :-P).
402
403			if ((mm->xdelta == 0) && (mm->ydelta == 0) &&
404				((uint8) mm->buttons == (previous_buttons & ~kTertiaryButton))) {
405				// no movement, nor button change: toggle middle
406				mm->buttons = previous_buttons ^ kTertiaryButton;
407			} else {
408				// change: preserve middle
409				mm->buttons |= previous_buttons & kTertiaryButton;
410			}
411
412			previous_buttons = mm->buttons;
413			break;
414
415		case kIntelliMouse:
416			// TODO: add support for 4th and 5th buttons?
417			mm->buttons =	((data[0] & 0x20) ? kPrimaryButton   : 0) +
418							((data[0] & 0x10) ? kSecondaryButton : 0) +
419							((data[3] & 0x10) ? kTertiaryButton  : 0);
420
421			mm->xdelta =   ((int8) ((data[0] & 0x03) << 6) + (int8) (data[1] & 0x3F));
422			mm->ydelta = - ((int8) ((data[0] & 0x0C) << 4) + (int8) (data[2] & 0x3F));
423
424			switch (data[3] & 0x0F) {
425				case 0x1: mm->wheel_ydelta = +1; break; // wheel 1 down
426				case 0xF: mm->wheel_ydelta = -1; break; // wheel 1 up
427				case 0x2: mm->wheel_xdelta = +1; break; // wheel 2 down
428				case 0xE: mm->wheel_xdelta = -1; break; // wheel 2 up
429			}
430			break;
431
432		case kMouseSystems:
433		{
434			uint8 tmp = (~data[0] & 0x07);
435
436			mm->buttons =	((tmp & 0x4) ? kPrimaryButton   : 0) +
437							((tmp & 0x1) ? kSecondaryButton : 0) +
438							((tmp & 0x2) ? kTertiaryButton  : 0);
439
440			mm->xdelta = ((int8) data[1] + (int8) data[3]);
441			mm->ydelta = ((int8) data[2] + (int8) data[4]);
442			break;
443		}
444
445		case kLogitech:
446		{
447//			uint8 tmp = (data[3] & 0x20);	// 3th button bit.
448
449			mm->buttons = ((data[0] & 0x20) ? kPrimaryButton   : 0) +
450						  ((data[0] & 0x10) ? kSecondaryButton : 0);
451//						 + ((tmp)            ? kTertiaryButton  : 0);
452
453			//                        Higher 2 bits          Lower 6 bits
454			mm->xdelta =   (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F));
455			mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F));
456			break;
457		}
458
459		default:
460			LOG(("Unhandled protocol. Should not happen.\n"));
461			return B_ERROR;
462	}
463
464	return B_OK;
465}
466
467
468//	#pragma mark -
469
470
471void
472SerialMouse::DumpData(mouse_movement* mm)
473{
474#ifdef DEBUG_SERIAL_MOUSE
475	if (mm->buttons ^ fButtonsState)
476		LOG(("Buttons = %ld\n", mm->buttons));
477
478	if (mm->xdelta || mm->ydelta)
479		LOG(("xdelta = %ld; ydelta = %ld\n", mm->xdelta, mm->ydelta));
480
481	if (fMouseID == kIntelliMouse && (mm->wheel_xdelta || mm->wheel_xdelta)) {
482		LOG(("wheel_xdelta = %ld; wheel_ydelta = %ld\n", mm->wheel_xdelta,
483			mm->wheel_ydelta));
484	}
485#endif
486}
487