xref: /haiku/src/add-ons/input_server/devices/serial_mouse/SerialMouse.cpp (revision fef6144999c2fa611f59ee6ffe6dd7999501385c)
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 
45 const static bigtime_t kSerialTimeOut = 200000;	// 200 ms
46 const 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 */
55 struct mouse_protocol {
56 	const char* name;
57 	uint8 num_bytes;
58 	uint8 sync[3];
59 };
60 
61 
62 const static
63 struct 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 
72 SerialMouse::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 
84 SerialMouse::~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 
95 const char *
96 SerialMouse::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 
110 status_t
111 SerialMouse::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", 5) != 0)
121 			continue;
122 
123 		if (fSerialPort->Open(dev_name) <= 0) {
124 			LOG(("SerialMouse: Failed to open %s.\n", dev_name));
125 			continue;	// try next port.
126 		}
127 
128 		LOG(("SerialMouse : Opened %s.\n", dev_name));
129 
130 		// This 4x retries helps, a little, to detect one of my mouse (buggy?).
131 		// Not perfect, but catchs it more often than before.
132 		uint8 retries = 4;
133 		do {
134 			fMouseID = DetectMouse();
135 			if (fMouseID > kNotSet) {
136 				// We found a mouse, just break the loop.
137 				LOG(("SerialMouse: Found a %s Mouse.\n", MouseDescription()));
138 				fSerialPort->SetBlocking(true);
139 				fPortNumber = i;
140 				return fPortNumber; 		// Return the port # in use.
141 			}
142 		} while ((fMouseID == kUnknown) && (retries--));
143 
144 		LOG(("SerialMouse: Mouse not detected.\n"));
145 		fSerialPort->Close();
146 	}
147 
148 	// If we get here its because we didn't found a mouse in any of the
149 	// available serial ports.
150 	// (Caller can do: "while (sm->IsMousePresent() > 0)").
151 
152 	return B_NO_INIT;
153 }
154 
155 
156 mouse_id
157 SerialMouse::DetectMouse()
158 {
159 	int bytes_read = 0;
160 	char c;
161 	char id_buffer[20];			// If the mouse sends an ID, we store it here.
162 	char buffer[kMaxBytesToRead];
163 	uint8 id_length = 0;
164 
165 	fSerialPort->SetBlocking(false);
166 	fSerialPort->SetTimeout(kSerialTimeOut);
167 
168 	SetPortOptions();
169 
170 	snooze(10000);
171 
172 	// Toggle RTS line in order to 'wake up' the mouse
173 	fSerialPort->SetRTS(false);
174 	snooze(120000);				// RTS low pulse width must be at least 100ms.
175 	fSerialPort->ClearInput();
176 	fSerialPort->SetRTS(true);
177 
178 	// wait upto kSerialTimeOut ms while trying to read mouse ID string.
179 	if (fSerialPort->WaitForInput() == 0)
180 		return kNoDevice;			// nothing there, quit.
181 
182 	// we make sure to 'eat' everything the mouse sends at init time, albeit
183 	// we are interested only on the few first bytes, not doing it so will
184 	// confuse things later.
185 	while ((fSerialPort->Read(&c, 1) == 1) && (bytes_read < kMaxBytesToRead)) {
186 		LOG(("read = %c (%d d - %x h)\n", c, c, c));
187 
188 		// Collect the bytes we care about.
189 		if (c == 'M' || c == 'H' || c == '3' || c == 'Z' || c == '@') {
190 			if (id_length < 4) {
191 				id_buffer[id_length] = c;
192 				id_length++;
193 			}
194 		} else
195 			buffer[bytes_read] = c;	// store the garbage for futher processing.
196 
197 		bytes_read++;
198 
199 		// is there something else waiting to be read?
200 		if (fSerialPort->WaitForInput() == 0)
201 			break;								// no, break the loop.
202 	}
203 
204 	// This can't happen, but... if we didn't get any data, just quit.
205 	if (bytes_read == 0)
206 		return kNoDevice;
207 
208 	fSerialPort->ClearInput();	// Toldya, I have a very noisy mouse.
209 
210 	if (id_length) {
211 		fMouseID = ParseID(id_buffer, id_length);
212 		SetPortOptions();				// Set new options according to MouseID.
213 		return fMouseID;
214 	}
215 
216 	// TODO: Below this line is work in progress (ie. temporal hacks until I,
217 	// or someone else, get something better).
218 
219 	// Ok, last resort... try to identify the beast according to its packets.
220 
221 	if (bytes_read < 3)		// not enough data to even start... quit.
222 		return kUnknown;
223 
224 	// First attempt to identify a MouseSystems mouse, because some (most?) of
225 	// them either send: nothing, an standard packet or just garbage.
226 	if (bytes_read == 5) {
227 		// TODO: validate the packet!
228 		fMouseID = kMouseSystems;
229 		SetPortOptions();
230 		return fMouseID;
231 	}
232 
233 	return kUnknown;
234 }
235 
236 
237 // See if we recognize the mouse ID (first bytes mice usually send after
238 // toggling the DTR/RTS lines on the serial port). Usual values:
239 //
240 // Microsoft mode    = 'M'
241 // Microsoft mode    = 'M3'
242 // IntelliMouse mode = 'MZ' and a valid 4-bytes null packet (0x40,0,0,0)
243 // MouseSystems mode = 'HH', nothing at all, 5-bytes packet, or just garbage.
244 
245 mouse_id
246 SerialMouse::ParseID(char buffer[], uint8 length)
247 {
248 	LOG(("data length = $d\n", length));
249 
250 	if ((length == 1) && (buffer[0] == 'M'))
251 		return kMicrosoft;
252 
253 	if (length == 2) {
254 		if (buffer[0] == 'M' && buffer[1] == '3')
255 			return kLogitech;
256 		else if (buffer[0] == 'H' && buffer[1] == 'H')
257 			return kMouseSystems;
258 	}
259 
260 	if ((length == 4) &&
261 		(buffer[0] == 'M' && buffer[1] == 'Z') && (buffer[2] == '@'))
262 		return kIntelliMouse;
263 
264 	return kUnknown;
265 }
266 
267 
268 // Set serial port options according to our different needs.
269 status_t
270 SerialMouse::SetPortOptions()
271 {
272 	switch (fMouseID) {
273 		case kLogitech:
274 			fSerialPort->SetDataRate(B_1200_BPS);
275 			fSerialPort->Write("*q", 2);
276 			fSerialPort->SetDataRate(B_9600_BPS);
277 			fSerialPort->SetDataBits(B_DATA_BITS_7);
278 			break;
279 
280 		case kMouseSystems:
281 			fSerialPort->SetDataRate(B_1200_BPS);
282 			fSerialPort->SetDataBits(B_DATA_BITS_8);
283 			break;
284 
285 		case kNotSet:
286 		default:
287 			// other defaults values are ok for us: no parity, 1 stop bit.
288 			fSerialPort->SetDataRate(B_1200_BPS);
289 			fSerialPort->SetDataBits(B_DATA_BITS_7);
290 			break;
291 	}
292 
293 	return B_OK;
294 }
295 
296 
297 //	#pragma mark -
298 status_t
299 SerialMouse::GetMouseEvent(mouse_movement* mm)
300 {
301 	char data[5];
302 
303 	if (fMouseID <= kNotSet)
304 		return B_ERROR;
305 
306 	if (GetPacket(data) != B_OK)
307 		return B_ERROR;		// not enough, or out-of-sync, data.
308 
309 	if (PacketToMM(data, mm) != B_OK) {
310 		// Something went wrong, clean up.
311 		memset(mm, 0, sizeof(mouse_movement));
312 		return B_ERROR;
313 	}
314 
315 #ifdef DEBUG_SERIAL_MOUSE
316 	DumpData(mm);
317 #endif
318 
319 	return B_OK;
320 }
321 
322 
323 //	#pragma mark -
324 
325 // Block until we read enough bytes for the current protocol. See if we're in
326 // sync with data stream, if not, just skip data until we regain sync.
327 
328 // TODO: syncronization needs a re-write. We should keep track of what byte we
329 // are currently working on, validate it against mice's protocol, and if valid,
330 // increment a "current_packet_byte" counter.
331 // Also: I'm currently skipping the optional 4th byte in the Logitech protocol.
332 // (may work, but 3th button will not).
333 
334 
335 status_t
336 SerialMouse::GetPacket(char data[])
337 {
338 	status_t result = B_ERROR;
339 	uchar c = 0;
340 	uint8    bytes_read;
341 	size_t   mpsize = mp[fMouseID].num_bytes;
342 
343 	for (bytes_read = 0; bytes_read < mpsize; bytes_read++) {
344 		// TODO: Shall we block here instead of leaving after a timeout?
345 		// Yes, if we get called it's because there IS a mouse out there.
346 
347 		if (fSerialPort->Read(&c, 1) != 1)
348 			break;
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.
378 status_t
379 SerialMouse::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 
471 void
472 SerialMouse::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