diff --git a/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.cpp b/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.cpp new file mode 100644 index 0000000000..1a11741f67 --- /dev/null +++ b/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.cpp @@ -0,0 +1,390 @@ +/*****************************************************************************/ +// Mouse input server device addon +// Written by Stefano Ceccherini +// Adapted for serial mice by Oscar Lesta +// +// MouseInputDevice.cpp +// +// Copyright (c) 2004 Haiku Project +// +// 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. +/*****************************************************************************/ + +#include "MouseInputDevice.h" +#include "kb_mouse_settings.h" +#include "kb_mouse_driver.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "SerialMouse.h" + +//------------------------------------------------------------------------------ + +#if DEBUG + inline void LOG(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); \ + fputs(buf, MouseInputDevice::sLogFile); fflush(MouseInputDevice::sLogFile); } + #define LOG_ERR(text...) LOG(text) +FILE *MouseInputDevice::sLogFile = NULL; +#else + #define LOG(text...) + #define LOG_ERR(text...) fprintf(stderr, text) +#endif + +#define CALLED() LOG("%s\n", __PRETTY_FUNCTION__) + +static MouseInputDevice *sSingletonMouseDevice = NULL; + + +const static uint32 kMouseThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4; + +//------------------------------------------------------------------------------ + +struct mouse_device { + mouse_device(); + ~mouse_device(); + + status_t init_check(); // > 0 if a mouse was detected. + + input_device_ref device_ref; + SerialMouse* sm; + thread_id device_watcher; + mouse_settings settings; + bool active; +}; + + +extern "C" +BInputServerDevice* instantiate_input_device() +{ + return new MouseInputDevice(); +} + +//------------------------------------------------------------------------------ +// #pragma mark - + +MouseInputDevice::MouseInputDevice() +{ + ASSERT(sSingletonMouseDevice == NULL); + sSingletonMouseDevice = this; + +#if DEBUG + sLogFile = fopen("/var/log/serial_mouse.log", "w"); +#endif + CALLED(); +} + +MouseInputDevice::~MouseInputDevice() +{ + CALLED(); + + for (int32 i = 0; i < fDevices.CountItems(); i++) + delete (mouse_device*) fDevices.ItemAt(i); + +#if DEBUG + fclose(sLogFile); +#endif +} + +// SerialMouse does not know anything about mouse_settings, I choose to let +// mouse_device hold that instead (bercause mice type, button mapping, +// speed/accel, etc., are all handled here, not in SerialMouse). + +status_t +MouseInputDevice::InitFromSettings(void* cookie, uint32 opcode) +{ + CALLED(); + mouse_device* device = (mouse_device*) cookie; + + // retrieve current values. + // TODO: shouldn't we use sane values if we get errors here? + + if (get_mouse_map(&device->settings.map) != B_OK) + LOG_ERR("error when get_mouse_map\n"); + + if (get_click_speed(&device->settings.click_speed) != B_OK) + LOG_ERR("error when get_click_speed\n"); + + if (get_mouse_speed(&device->settings.accel.speed) != B_OK) + LOG_ERR("error when get_mouse_speed\n"); + else + { + if (get_mouse_acceleration(&device->settings.accel.accel_factor) != B_OK) + LOG_ERR("error when get_mouse_acceleration\n"); + } + + if (get_mouse_type(&device->settings.type) != B_OK) + LOG_ERR("error when get_mouse_type\n"); + + return B_OK; +} + +status_t +MouseInputDevice::InitCheck() +{ + CALLED(); + + // TODO: We should iterate here to support more than just one mouse. + // (I've tried, but kept entering and endless loop or crashing + // the input_server :-( ). + + mouse_device* device = new mouse_device(); + if (!device) + { + LOG("No memory\n"); + return B_NO_MEMORY; + } + + if (device->init_check() <= B_OK) + { + LOG("The mouse was eaten by a rabid cat.\n"); + delete device; + return B_ERROR; + } + + input_device_ref* devices[2]; + devices[0] = &device->device_ref; + devices[1] = NULL; + + fDevices.AddItem(device); + + return RegisterDevices(devices); +} + + +status_t +MouseInputDevice::Start(const char* name, void* cookie) +{ + mouse_device* device = (mouse_device*) cookie; + + LOG("%s(%s)\n", __PRETTY_FUNCTION__, name); + + InitFromSettings(device); + + char threadName[B_OS_NAME_LENGTH]; + // "Microsoft watcher" sounded even more akward than this... + snprintf(threadName, B_OS_NAME_LENGTH, "%s Mouse", name); + + device->active = true; + device->device_watcher = spawn_thread(DeviceWatcher, threadName, + kMouseThreadPriority, device); + + resume_thread(device->device_watcher); + + return B_OK; +} + + +status_t +MouseInputDevice::Stop(const char* name, void* cookie) +{ + mouse_device* device = (mouse_device*) cookie; + + LOG("%s(%s)\n", __PRETTY_FUNCTION__, name); + + device->active = false; + + if (device->device_watcher >= 0) + { + suspend_thread(device->device_watcher); + resume_thread(device->device_watcher); + status_t dummy; + wait_for_thread(device->device_watcher, &dummy); + } + + return B_OK; +} + + +status_t +MouseInputDevice::Control(const char* name, void* cookie, + uint32 command, BMessage* message) +{ + LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command); + + if (command >= B_MOUSE_TYPE_CHANGED && + command <= B_MOUSE_ACCELERATION_CHANGED) + { + InitFromSettings(cookie, command); + } + + return B_OK; +} + + +int32 +MouseInputDevice::DeviceWatcher(void* arg) +{ + mouse_device* dev = (mouse_device*) arg; + + mouse_movement movements; + uint32 buttons_state = 0; + uint8 clicks_count = 0; + bigtime_t last_click_time = 0; + BMessage* message; + + CALLED(); + + while (dev->active) + { + memset(&movements, 0, sizeof(movements)); + + if (dev->sm->GetMouseEvent(&movements) < B_OK) + continue; +/* + LOG("%s: buttons: 0x%lx, x: %ld, y: %ld, clicks:%ld, wheel_x:%ld, \ + wheel_y:%ld\n", dev->device_ref.name, movements.buttons, + movements.xdelta, movements.ydelta, movements.clicks, + movements.wheel_xdelta, movements.wheel_ydelta); +*/ + // TODO: Apply buttons mapping here. + uint32 buttons = buttons_state ^ movements.buttons; + + if (movements.buttons) { + + if ((movements.timestamp - last_click_time) <= dev->settings.click_speed) + clicks_count = (clicks_count % 3) + 1; + else + clicks_count = 1; + + last_click_time = movements.timestamp; + } + + if (buttons != 0) { + message = new BMessage(B_MOUSE_UP); + + message->AddInt64("when", movements.timestamp); + message->AddInt32("buttons", movements.buttons); + + if ((buttons & movements.buttons) > 0) { + message->what = B_MOUSE_DOWN; + message->AddInt32("clicks", clicks_count); + LOG("B_MOUSE_DOWN\n"); + } else { + LOG("B_MOUSE_UP\n"); + } + + message->AddInt32("x", movements.xdelta); + message->AddInt32("y", movements.ydelta); + + sSingletonMouseDevice->EnqueueMessage(message); + buttons_state = movements.buttons; + } + + if (movements.xdelta != 0 || movements.ydelta != 0) { + int32 xdelta = movements.xdelta; + int32 ydelta = movements.ydelta; + + // TODO: properly scale these values. + // (s >> 14, a >> 8) or (s >> 15, a >> 11) feels more or less OK + // with the default values: yields to speed = 2; accel_factor = 32 + // Maybe we should use floats and then round to nearest integer? + uint32 speed = (dev->settings.accel.speed >> 15); + uint32 accel_factor = (dev->settings.accel.accel_factor >> 11); + +// LOG("accel.enabled? = %s\n", (dev->settings.accel.enabled) ? "Yes" : "No"); +// LOG("speed = %ld; accel_factor = %ld\n", speed, accel_factor); + + if (speed && accel_factor) { + xdelta *= speed; + ydelta *= speed; + + // preserve the sign. + bool xneg = (xdelta < 0); + bool yneg = (ydelta < 0); + + if (movements.xdelta != 0) { + xdelta = (xdelta * xdelta) / accel_factor; + (xdelta) ? : (xdelta = 1); + if (xneg) xdelta *= -1; + } + + if (movements.ydelta != 0) { + ydelta = (ydelta * ydelta) / accel_factor; + (ydelta) ? : (ydelta = 1); + if (yneg) ydelta *= -1; + } + } + +// LOG("%s: x: %ld, y: %ld\n", dev->device_ref.name, xdelta, ydelta); + + message = new BMessage(B_MOUSE_MOVED); + if (message) { + message->AddInt64("when", movements.timestamp); + message->AddInt32("buttons", movements.buttons); + message->AddInt32("x", xdelta); + message->AddInt32("y", ydelta); + + sSingletonMouseDevice->EnqueueMessage(message); + } + } + + if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) { + message = new BMessage(B_MOUSE_WHEEL_CHANGED); + if (message) { + message->AddInt64("when", movements.timestamp); + message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta); + message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta); + + sSingletonMouseDevice->EnqueueMessage(message); + } + } + } + + return B_OK; +} + +//============================================================================== +// #pragma mark - + +// mouse_device +mouse_device::mouse_device() +{ + device_watcher = -1; + active = false; + sm = new SerialMouse(); + device_ref.type = B_POINTING_DEVICE; + device_ref.cookie = this; +}; + + +status_t +mouse_device::init_check() +{ + status_t result = sm->IsMousePresent(); + + if (result > 0) // Positive values indicate a mouse present. + device_ref.name = (char *)sm->MouseDescription(); + + return result; +} + + +mouse_device::~mouse_device() +{ + delete sm; + free(device_ref.name); +} diff --git a/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.h b/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.h new file mode 100644 index 0000000000..21c2735b75 --- /dev/null +++ b/src/add-ons/input_server/devices/serial_mouse/MouseInputDevice.h @@ -0,0 +1,65 @@ +/*****************************************************************************/ +// Mouse input server device addon +// Written by Stefano Ceccherini and Jerome Duval +// Adapted for serial mice by Oscar Lesta +// +// MouseInputDevice.h +// +// Copyright (c) 2004 Haiku Project +// +// 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. +/*****************************************************************************/ + +#ifndef __MOUSEINPUTDEVICE_H +#define __MOUSEINPUTDEVICE_H + +#include +#include +#include + +#include + +class MouseInputDevice : public BInputServerDevice { +public: + MouseInputDevice(); + ~MouseInputDevice(); + + virtual status_t InitCheck(); + + virtual status_t Start(const char* name, void* cookie); + virtual status_t Stop(const char* name, void* cookie); + + virtual status_t Control(const char *name, void* cookie, + uint32 command, BMessage* message); +private: + status_t InitFromSettings(void* cookie, uint32 opcode = 0); + + static int32 DeviceWatcher(void* arg); + + BList fDevices; + +#ifdef DEBUG +public: + static FILE *sLogFile; +#endif +}; + +extern "C" BInputServerDevice *instantiate_input_device(); + +#endif diff --git a/src/add-ons/input_server/devices/serial_mouse/SerialMouse.cpp b/src/add-ons/input_server/devices/serial_mouse/SerialMouse.cpp new file mode 100644 index 0000000000..1858cb6cfe --- /dev/null +++ b/src/add-ons/input_server/devices/serial_mouse/SerialMouse.cpp @@ -0,0 +1,486 @@ +//------------------------------------------------------------------------------ +// 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", 5) != 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", 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) { + // Something went wrong, clean up. + memset(mm, 0, sizeof(mouse_movement)); + return B_ERROR; + } + +#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) + 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 +} diff --git a/src/add-ons/input_server/devices/serial_mouse/SerialMouse.h b/src/add-ons/input_server/devices/serial_mouse/SerialMouse.h new file mode 100644 index 0000000000..9fc0206079 --- /dev/null +++ b/src/add-ons/input_server/devices/serial_mouse/SerialMouse.h @@ -0,0 +1,46 @@ +#ifndef _SERIAL_MOUSE_ +#define _SERIAL_MOUSE_ + +#include + +typedef enum mouse_id { + kNoDevice = -2, + kUnknown = -1, // Something there, but can't recognize it yet. + kNotSet = 0, + kMicrosoft, // 3-bytes, 2 and 3 buttons. + kLogitech, // (3+1)-bytes, 3-buttons. + kMouseSystems, // 5-bytes, 3-buttons. + kIntelliMouse // 4-bytes, up to 5 buttons, 1 or 2 wheels. +}; + +class BSerialPort; +class SerialMouse { +public: + SerialMouse(); + virtual ~SerialMouse(); + + status_t IsMousePresent(); + mouse_id MouseID() { return fMouseID; }; + const char* MouseDescription(); + + status_t GetMouseEvent(mouse_movement* mm); + +private: + mouse_id DetectMouse(); + mouse_id ParseID(char buffer[], uint8 length); + status_t SetPortOptions(); + + status_t GetPacket(char data[]); + status_t PacketToMM(char data[], mouse_movement* mm); + + void DumpData(mouse_movement* mm); + + BSerialPort* fSerialPort; + uint8 fPortsCount; + uint8 fPortNumber; // The port in use. + mouse_id fMouseID; + + uint8 fButtonsState; +}; + +#endif // _SERIAL_MOUSE_