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", 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 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 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 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 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 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 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