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
SerialMouse()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
~SerialMouse()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 *
MouseDescription()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
IsMousePresent()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", 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
157 mouse_id
DetectMouse()158 SerialMouse::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
246 mouse_id
ParseID(char buffer[],uint8 length)247 SerialMouse::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.
270 status_t
SetPortOptions()271 SerialMouse::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 -
299 status_t
GetMouseEvent(mouse_movement * mm)300 SerialMouse::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
333 status_t
GetPacket(char data[])334 SerialMouse::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.
378 status_t
PacketToMM(char data[],mouse_movement * mm)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
DumpData(mouse_movement * mm)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