//------------------------------------------------------------------------------ // Copyright (c) 2004, Haiku, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // File Name: SerialMouse.cpp // Author(s): Oscar Lesta (bipolar@softhome.net) // Description: SerialMouse detects and manages serial mice, duh!. // References: - http://www.hut.fi/~then/mytexts/mouse.html // - Be's (binary) serial_mouse addon. // - Haiku's CVS. //------------------------------------------------------------------------------ #include #include #include "SerialMouse.h" //#define DEBUG_SERIAL_MOUSE #ifdef DEBUG_SERIAL_MOUSE #include #define LOG(x) printf x // TODO: log to "MouseInputDevice::sLogFile" // I used this in my console tests. #else #define LOG(x) #endif const static bigtime_t kSerialTimeOut = 200000; // 200 ms const static uint8 kMaxBytesToRead = 255; // Serial PnP data can be this long. // The protocols we know how to handle. Indexed by mouse_protocol_id. /* the sync[] is a protocol-identification/sync thingy: if ((read_byte[0] & sync[0]) == sync[1]) then we are at the beggining of the a packet. Next data bytes are OK... if ((read_byte[i] & sync[2]) == 0)) */ struct mouse_protocol { const char* name; uint8 num_bytes; uint8 sync[3]; }; const static struct mouse_protocol mp[] = { { "UNKNOWN", 0, { 0x00, 0x00, 0x00 } }, { "Microsoft", 3, { 0x40, 0x40, 0x40,} }, { "Logitech", 3, { 0x40, 0x40, 0x40 } }, // 3/4 bytes. FIX: only 3 are used now. { "MouseSystems", 5, { 0xF8, 0x80, 0x00 } }, { "IntelliMouse", 4, { 0x40, 0x40, 0x00 } }, }; SerialMouse::SerialMouse() : fSerialPort(NULL), fPortsCount(0), fPortNumber(0), fMouseID(kNotSet), fButtonsState(0) { fSerialPort = new BSerialPort(); fPortsCount = fSerialPort->CountDevices() - 1; } SerialMouse::~SerialMouse() { if (fSerialPort != NULL) { fSerialPort->SetRTS(false); // Put the mouse to sleep. fSerialPort->Close(); delete fSerialPort; } } // #pragma mark - const char * SerialMouse::MouseDescription() { // TODO: If we'll support more than just one mouse, this should be changed. // Maybe we should also add the port number as suffix. return mp[fMouseID].name; } // Find first usable serial port, try to detect a mouse there. // Returns: // - B_NO_INIT: all available ports were tested (no mouse present there). // - A positive value indicating in which serial port a mouse was found. status_t SerialMouse::IsMousePresent() { for (uint8 i = 1; i <= fPortsCount; i++) { char dev_name[B_PATH_NAME_LENGTH]; fSerialPort->GetDeviceName(i, dev_name); // Skip internal modem (pctel, lucent or trimodem drivers). // previously I checked only for != "pctel", now for == "serial#" if (strncmp(dev_name, "serial", 6) != 0 && strncmp(dev_name, "pc_serial", 9) != 0) continue; if (fSerialPort->Open(dev_name) <= 0) { LOG(("SerialMouse: Failed to open %s.\n", dev_name)); continue; // try next port. } LOG(("SerialMouse : Opened %s.\n", dev_name)); // This 4x retries helps, a little, to detect one of my mouse (buggy?). // Not perfect, but catchs it more often than before. uint8 retries = 4; do { fMouseID = DetectMouse(); if (fMouseID > kNotSet) { // We found a mouse, just break the loop. LOG(("SerialMouse: Found a %s Mouse.\n", MouseDescription())); fSerialPort->SetBlocking(true); fPortNumber = i; return fPortNumber; // Return the port # in use. } } while ((fMouseID == kUnknown) && (retries--)); LOG(("SerialMouse: Mouse not detected.\n")); fSerialPort->Close(); } // If we get here its because we didn't found a mouse in any of the // available serial ports. // (Caller can do: "while (sm->IsMousePresent() > 0)"). return B_NO_INIT; } mouse_id SerialMouse::DetectMouse() { int bytes_read = 0; char c; char id_buffer[20]; // If the mouse sends an ID, we store it here. char buffer[kMaxBytesToRead]; uint8 id_length = 0; fSerialPort->SetBlocking(false); fSerialPort->SetTimeout(kSerialTimeOut); SetPortOptions(); snooze(10000); // Toggle RTS line in order to 'wake up' the mouse fSerialPort->SetRTS(false); snooze(120000); // RTS low pulse width must be at least 100ms. fSerialPort->ClearInput(); fSerialPort->SetRTS(true); // wait upto kSerialTimeOut ms while trying to read mouse ID string. if (fSerialPort->WaitForInput() == 0) return kNoDevice; // nothing there, quit. // we make sure to 'eat' everything the mouse sends at init time, albeit // we are interested only on the few first bytes, not doing it so will // confuse things later. while ((fSerialPort->Read(&c, 1) == 1) && (bytes_read < kMaxBytesToRead)) { LOG(("read = %c (%d d - %x h)\n", c, c, c)); // Collect the bytes we care about. if (c == 'M' || c == 'H' || c == '3' || c == 'Z' || c == '@') { if (id_length < 4) { id_buffer[id_length] = c; id_length++; } } else buffer[bytes_read] = c; // store the garbage for futher processing. bytes_read++; // is there something else waiting to be read? if (fSerialPort->WaitForInput() == 0) break; // no, break the loop. } // This can't happen, but... if we didn't get any data, just quit. if (bytes_read == 0) return kNoDevice; fSerialPort->ClearInput(); // Toldya, I have a very noisy mouse. if (id_length) { fMouseID = ParseID(id_buffer, id_length); SetPortOptions(); // Set new options according to MouseID. return fMouseID; } // TODO: Below this line is work in progress (ie. temporal hacks until I, // or someone else, get something better). // Ok, last resort... try to identify the beast according to its packets. if (bytes_read < 3) // not enough data to even start... quit. return kUnknown; // First attempt to identify a MouseSystems mouse, because some (most?) of // them either send: nothing, an standard packet or just garbage. if (bytes_read == 5) { // TODO: validate the packet! fMouseID = kMouseSystems; SetPortOptions(); return fMouseID; } return kUnknown; } // See if we recognize the mouse ID (first bytes mice usually send after // toggling the DTR/RTS lines on the serial port). Usual values: // // Microsoft mode = 'M' // Microsoft mode = 'M3' // IntelliMouse mode = 'MZ' and a valid 4-bytes null packet (0x40,0,0,0) // MouseSystems mode = 'HH', nothing at all, 5-bytes packet, or just garbage. mouse_id SerialMouse::ParseID(char buffer[], uint8 length) { LOG(("data length = %d\n", (int)length)); if ((length == 1) && (buffer[0] == 'M')) return kMicrosoft; if (length == 2) { if (buffer[0] == 'M' && buffer[1] == '3') return kLogitech; else if (buffer[0] == 'H' && buffer[1] == 'H') return kMouseSystems; } if ((length == 4) && (buffer[0] == 'M' && buffer[1] == 'Z') && (buffer[2] == '@')) return kIntelliMouse; return kUnknown; } // Set serial port options according to our different needs. status_t SerialMouse::SetPortOptions() { switch (fMouseID) { case kLogitech: fSerialPort->SetDataRate(B_1200_BPS); fSerialPort->Write("*q", 2); fSerialPort->SetDataRate(B_9600_BPS); fSerialPort->SetDataBits(B_DATA_BITS_7); break; case kMouseSystems: fSerialPort->SetDataRate(B_1200_BPS); fSerialPort->SetDataBits(B_DATA_BITS_8); break; case kNotSet: default: // other defaults values are ok for us: no parity, 1 stop bit. fSerialPort->SetDataRate(B_1200_BPS); fSerialPort->SetDataBits(B_DATA_BITS_7); break; } return B_OK; } // #pragma mark - status_t SerialMouse::GetMouseEvent(mouse_movement* mm) { char data[5]; if (fMouseID <= kNotSet) return B_ERROR; if (GetPacket(data) != B_OK) return B_ERROR; // not enough, or out-of-sync, data. if (PacketToMM(data, mm) != B_OK) return B_ERROR; // something went wrong #ifdef DEBUG_SERIAL_MOUSE DumpData(mm); #endif return B_OK; } // #pragma mark - // Block until we read enough bytes for the current protocol. See if we're in // sync with data stream, if not, just skip data until we regain sync. // TODO: syncronization needs a re-write. We should keep track of what byte we // are currently working on, validate it against mice's protocol, and if valid, // increment a "current_packet_byte" counter. // Also: I'm currently skipping the optional 4th byte in the Logitech protocol. // (may work, but 3th button will not). status_t SerialMouse::GetPacket(char data[]) { status_t result = B_ERROR; uchar c = 0; uint8 bytes_read; size_t mpsize = mp[fMouseID].num_bytes; for (bytes_read = 0; bytes_read < mpsize; bytes_read++) { // TODO: Shall we block here instead of leaving after a timeout? // Yes, if we get called it's because there IS a mouse out there. if (fSerialPort->Read(&c, 1) != 1) { snooze(5000); // this is a realtime thread, and something is wrong... break; } if (bytes_read == 0) { if ((c & mp[fMouseID].sync[0]) != mp[fMouseID].sync[1]) { LOG(("Out of sync: skipping byte = %x\n", c)); continue; // skip bytes until we get a "header" byte. } } data[bytes_read] = c; } if (bytes_read == mpsize) { result = B_OK; // validate data... for (uint8 i = 1; i <= mpsize; i++) { if ((data[i] & mp[fMouseID].sync[2]) != 0) { LOG(("Out of sync: wrong data byte = %x\n", data[i])); result = B_ERROR; break; // skip the packet. } } } return result; } // kMicroSoft, kMouseSystem, kIntelliMouse: working OK. // kLogitech: untested by lack of such devices. 3th button probably won't work. status_t SerialMouse::PacketToMM(char data[], mouse_movement* mm) { const uint8 kPrimaryButton = 1; const uint8 kSecondaryButton = 2; const uint8 kTertiaryButton = 4; static uint8 previous_buttons = 0; // only meaningful for kMicrosoft. mm->timestamp = system_time(); switch (fMouseID) { case kMicrosoft: mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + ((data[0] & 0x10) ? kSecondaryButton : 0); // Higher 2 bits Lower 6 bits mm->xdelta = (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F)); mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F)); // up to here we've handled "ye olde" 2-buttons MS packet. // There's a 3-buttons extension to it, consisting in sending a // "null packet" (no changes in x/y nor in standard buttons) // whenever the third button is pressed/released (how clever!! :-P). if ((mm->xdelta == 0) && (mm->ydelta == 0) && ((uint8) mm->buttons == (previous_buttons & ~kTertiaryButton))) { // no movement, nor button change: toggle middle mm->buttons = previous_buttons ^ kTertiaryButton; } else { // change: preserve middle mm->buttons |= previous_buttons & kTertiaryButton; } previous_buttons = mm->buttons; break; case kIntelliMouse: // TODO: add support for 4th and 5th buttons? mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + ((data[0] & 0x10) ? kSecondaryButton : 0) + ((data[3] & 0x10) ? kTertiaryButton : 0); mm->xdelta = ((int8) ((data[0] & 0x03) << 6) + (int8) (data[1] & 0x3F)); mm->ydelta = - ((int8) ((data[0] & 0x0C) << 4) + (int8) (data[2] & 0x3F)); switch (data[3] & 0x0F) { case 0x1: mm->wheel_ydelta = +1; break; // wheel 1 down case 0xF: mm->wheel_ydelta = -1; break; // wheel 1 up case 0x2: mm->wheel_xdelta = +1; break; // wheel 2 down case 0xE: mm->wheel_xdelta = -1; break; // wheel 2 up } break; case kMouseSystems: { uint8 tmp = (~data[0] & 0x07); mm->buttons = ((tmp & 0x4) ? kPrimaryButton : 0) + ((tmp & 0x1) ? kSecondaryButton : 0) + ((tmp & 0x2) ? kTertiaryButton : 0); mm->xdelta = ((int8) data[1] + (int8) data[3]); mm->ydelta = ((int8) data[2] + (int8) data[4]); break; } case kLogitech: { // uint8 tmp = (data[3] & 0x20); // 3th button bit. mm->buttons = ((data[0] & 0x20) ? kPrimaryButton : 0) + ((data[0] & 0x10) ? kSecondaryButton : 0); // + ((tmp) ? kTertiaryButton : 0); // Higher 2 bits Lower 6 bits mm->xdelta = (int8) (((data[0] & 0x03) << 6) + (data[1] & 0x3F)); mm->ydelta = - (int8) (((data[0] & 0x0C) << 4) + (data[2] & 0x3F)); break; } default: LOG(("Unhandled protocol. Should not happen.\n")); return B_ERROR; } return B_OK; } // #pragma mark - void SerialMouse::DumpData(mouse_movement* mm) { #ifdef DEBUG_SERIAL_MOUSE if (mm->buttons ^ fButtonsState) LOG(("Buttons = %ld\n", mm->buttons)); if (mm->xdelta || mm->ydelta) LOG(("xdelta = %ld; ydelta = %ld\n", mm->xdelta, mm->ydelta)); if (fMouseID == kIntelliMouse && (mm->wheel_xdelta || mm->wheel_xdelta)) { LOG(("wheel_xdelta = %ld; wheel_ydelta = %ld\n", mm->wheel_xdelta, mm->wheel_ydelta)); } #endif }