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 return B_ERROR; // something went wrong 311 312 #ifdef DEBUG_SERIAL_MOUSE 313 DumpData(mm); 314 #endif 315 316 return B_OK; 317 } 318 319 320 // #pragma mark - 321 322 // Block until we read enough bytes for the current protocol. See if we're in 323 // sync with data stream, if not, just skip data until we regain sync. 324 325 // TODO: syncronization needs a re-write. We should keep track of what byte we 326 // are currently working on, validate it against mice's protocol, and if valid, 327 // increment a "current_packet_byte" counter. 328 // Also: I'm currently skipping the optional 4th byte in the Logitech protocol. 329 // (may work, but 3th button will not). 330 331 332 status_t 333 SerialMouse::GetPacket(char data[]) 334 { 335 status_t result = B_ERROR; 336 uchar c = 0; 337 uint8 bytes_read; 338 size_t mpsize = mp[fMouseID].num_bytes; 339 340 for (bytes_read = 0; bytes_read < mpsize; bytes_read++) { 341 // TODO: Shall we block here instead of leaving after a timeout? 342 // Yes, if we get called it's because there IS a mouse out there. 343 344 if (fSerialPort->Read(&c, 1) != 1) { 345 snooze(5000); // this is a realtime thread, and something is wrong... 346 break; 347 } 348 349 if (bytes_read == 0) { 350 if ((c & mp[fMouseID].sync[0]) != mp[fMouseID].sync[1]) { 351 LOG(("Out of sync: skipping byte = %x\n", c)); 352 continue; // skip bytes until we get a "header" byte. 353 } 354 } 355 data[bytes_read] = c; 356 } 357 358 if (bytes_read == mpsize) { 359 result = B_OK; 360 361 // validate data... 362 for (uint8 i = 1; i <= mpsize; i++) { 363 if ((data[i] & mp[fMouseID].sync[2]) != 0) { 364 LOG(("Out of sync: wrong data byte = %x\n", data[i])); 365 result = B_ERROR; 366 break; // skip the packet. 367 } 368 } 369 } 370 371 return result; 372 } 373 374 375 // kMicroSoft, kMouseSystem, kIntelliMouse: working OK. 376 // kLogitech: untested by lack of such devices. 3th button probably won't work. 377 status_t 378 SerialMouse::PacketToMM(char data[], mouse_movement* mm) 379 { 380 const uint8 kPrimaryButton = 1; 381 const uint8 kSecondaryButton = 2; 382 const uint8 kTertiaryButton = 4; 383 384 static uint8 previous_buttons = 0; // only meaningful for kMicrosoft. 385 386 mm->timestamp = system_time(); 387 388 switch (fMouseID) { 389 case kMicrosoft: 390 mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + 391 ((data[0] & 0x10) ? kSecondaryButton : 0); 392 393 // Higher 2 bits Lower 6 bits 394 mm->xdelta = (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F)); 395 mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F)); 396 397 // up to here we've handled "ye olde" 2-buttons MS packet. 398 // There's a 3-buttons extension to it, consisting in sending a 399 // "null packet" (no changes in x/y nor in standard buttons) 400 // whenever the third button is pressed/released (how clever!! :-P). 401 402 if ((mm->xdelta == 0) && (mm->ydelta == 0) && 403 ((uint8) mm->buttons == (previous_buttons & ~kTertiaryButton))) { 404 // no movement, nor button change: toggle middle 405 mm->buttons = previous_buttons ^ kTertiaryButton; 406 } else { 407 // change: preserve middle 408 mm->buttons |= previous_buttons & kTertiaryButton; 409 } 410 411 previous_buttons = mm->buttons; 412 break; 413 414 case kIntelliMouse: 415 // TODO: add support for 4th and 5th buttons? 416 mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + 417 ((data[0] & 0x10) ? kSecondaryButton : 0) + 418 ((data[3] & 0x10) ? kTertiaryButton : 0); 419 420 mm->xdelta = ((int8) ((data[0] & 0x03) << 6) + (int8) (data[1] & 0x3F)); 421 mm->ydelta = - ((int8) ((data[0] & 0x0C) << 4) + (int8) (data[2] & 0x3F)); 422 423 switch (data[3] & 0x0F) { 424 case 0x1: mm->wheel_ydelta = +1; break; // wheel 1 down 425 case 0xF: mm->wheel_ydelta = -1; break; // wheel 1 up 426 case 0x2: mm->wheel_xdelta = +1; break; // wheel 2 down 427 case 0xE: mm->wheel_xdelta = -1; break; // wheel 2 up 428 } 429 break; 430 431 case kMouseSystems: 432 { 433 uint8 tmp = (~data[0] & 0x07); 434 435 mm->buttons = ((tmp & 0x4) ? kPrimaryButton : 0) + 436 ((tmp & 0x1) ? kSecondaryButton : 0) + 437 ((tmp & 0x2) ? kTertiaryButton : 0); 438 439 mm->xdelta = ((int8) data[1] + (int8) data[3]); 440 mm->ydelta = ((int8) data[2] + (int8) data[4]); 441 break; 442 } 443 444 case kLogitech: 445 { 446 // uint8 tmp = (data[3] & 0x20); // 3th button bit. 447 448 mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + 449 ((data[0] & 0x10) ? kSecondaryButton : 0); 450 // + ((tmp) ? kTertiaryButton : 0); 451 452 // Higher 2 bits Lower 6 bits 453 mm->xdelta = (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F)); 454 mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F)); 455 break; 456 } 457 458 default: 459 LOG(("Unhandled protocol. Should not happen.\n")); 460 return B_ERROR; 461 } 462 463 return B_OK; 464 } 465 466 467 // #pragma mark - 468 469 470 void 471 SerialMouse::DumpData(mouse_movement* mm) 472 { 473 #ifdef DEBUG_SERIAL_MOUSE 474 if (mm->buttons ^ fButtonsState) 475 LOG(("Buttons = %ld\n", mm->buttons)); 476 477 if (mm->xdelta || mm->ydelta) 478 LOG(("xdelta = %ld; ydelta = %ld\n", mm->xdelta, mm->ydelta)); 479 480 if (fMouseID == kIntelliMouse && (mm->wheel_xdelta || mm->wheel_xdelta)) { 481 LOG(("wheel_xdelta = %ld; wheel_ydelta = %ld\n", mm->wheel_xdelta, 482 mm->wheel_ydelta)); 483 } 484 #endif 485 } 486