diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 9b69ead27e..191edabbc1 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -19,6 +19,7 @@ #include "gamepadButton.h" #include "keyboardButton.h" #include "mouseButton.h" +#include "inputDeviceManager.h" #include #include @@ -32,19 +33,6 @@ static InputDevice::ControlAxis axis_map[] = { TypeHandle EvdevInputDevice::_type_handle; -//////////////////////////////////////////////////////////////////// -// Function: EvdevInputDevice::Constructor -// Access: Published -// Description: Creates a new device using the Linux joystick -// device using the given file descriptor. It will -// be closed when this object destructs. -//////////////////////////////////////////////////////////////////// -EvdevInputDevice:: -EvdevInputDevice(int fd) : - _fd(fd) { - init_device(); -} - //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::Constructor // Access: Published @@ -52,15 +40,18 @@ EvdevInputDevice(int fd) : // device using the given device filename. //////////////////////////////////////////////////////////////////// EvdevInputDevice:: -EvdevInputDevice(const string &fn) { - _fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK); +EvdevInputDevice(int index) : _index(index) { + char path[64]; + sprintf(path, "/dev/input/event%d", index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd >= 0) { init_device(); } else { _is_connected = false; device_cat.error() - << "Opening raw input device: " << strerror(errno) << " " << fn << "\n"; + << "Opening raw input device: " << strerror(errno) << " " << path << "\n"; } } @@ -77,6 +68,18 @@ EvdevInputDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::do_poll // Access: Private, Virtual @@ -88,8 +91,15 @@ EvdevInputDevice:: //////////////////////////////////////////////////////////////////// void EvdevInputDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -104,10 +114,8 @@ init_device() { uint8_t evtypes[(EV_CNT + 7) >> 3]; memset(evtypes, 0, sizeof(evtypes)); - char name[256]; - char uniq[256]; + char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || - ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { close(_fd); _fd = -1; @@ -116,29 +124,59 @@ init_device() { return false; } - for (char *p=name; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - for (char *p=uniq; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } + _name.assign(name); + + struct input_id id; + if (ioctl(_fd, EVIOCGID, &id) >= 0) { + _vendor_id = id.vendor; + _product_id = id.product; } - _name = ((string)name) + "." + uniq; - + bool all_values_zero = true; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. uint8_t axes[(ABS_CNT + 7) >> 3]; memset(axes, 0, sizeof(axes)); + AxisRange range; + range._scale = 1.0; + range._bias = 0.0; + _axis_ranges.resize(ABS_CNT, range); + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { if (i >= 0 && i < 6) { set_control_map(i, axis_map[i]); + + // Check the initial value and ranges. + struct input_absinfo absinfo; + if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { + double factor, bias; + if (absinfo.minimum < 0) { + // Centered, eg. for sticks. + factor = 2.0 / (absinfo.maximum - absinfo.minimum); + bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); + } else { + // 0-based, eg. for triggers. + factor = 1.0 / absinfo.maximum; + bias = 0.0; + } + + // Flip Y axis to match Windows implementation. + if (i == ABS_Y || i == ABS_RY) { + factor = -factor; + bias = -bias; + } + + _axis_ranges[i]._scale = factor; + _axis_ranges[i]._bias = bias; + _controls[i]._state = fma(absinfo.value, factor, bias); + + if (absinfo.value != 0) { + all_values_zero = false; + } + } } } } @@ -148,14 +186,40 @@ init_device() { // Check which buttons are on the device. uint8_t keys[(KEY_CNT + 7) >> 3]; memset(keys, 0, sizeof(keys)); + ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + + // Also check whether the buttons are currently pressed. + uint8_t states[(KEY_CNT + 7) >> 3]; + memset(states, 0, sizeof(states)); + ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3; int bi = 0; - for (int i = 0; i < num_bits; ++i) { + for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - set_button_map(bi++, map_button(i)); + set_button_map(bi, map_button(i)); + if (test_bit(i, states)) { + _buttons[bi]._state = S_down; + all_values_zero = false; + } else { + _buttons[bi]._state = S_up; + } + ++bi; } } + + // Check device type. + if (test_bit(BTN_GAMEPAD, keys)) { + _device_class = DC_gamepad; + + } else if (test_bit(BTN_JOYSTICK, keys)) { + _device_class = DC_flight_stick; + + } else if (test_bit(BTN_MOUSE, keys)) { + _device_class = DC_mouse; + + } else if (test_bit(BTN_WHEEL, keys)) { + _device_class = DC_steering_wheel; + } } if (test_bit(EV_REL, evtypes)) { @@ -166,14 +230,52 @@ init_device() { _flags |= IDF_has_vibration; } - _is_connected = true; + char path[64]; + char buffer[256]; + sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); + FILE *f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + + // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel + // driver always reports 4 connected gamepads, regardless of the number + // of gamepads actually present. This hack partially remedies this. + if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) { + _is_connected = false; + } else { + _is_connected = true; + } return true; } //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::process_events // Access: Private -// Description: Reads a number of events from the device. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool EvdevInputDevice:: process_events() { @@ -186,10 +288,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV || errno == EINVAL) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -210,23 +314,31 @@ process_events() { double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; + // It seems that some devices send a single EV_SYN event when being + // unplugged. Boo. Ignore it. + if (n_read == 1 && events[0].code == EV_SYN) { + return false; + } + for (int i = 0; i < n_read; ++i) { + int code = events[i].code; + switch (events[i].type) { case EV_SYN: break; case EV_REL: - if (events[i].code == REL_X) x += events[i].value; - if (events[i].code == REL_Y) y += events[i].value; + if (code == REL_X) x += events[i].value; + if (code == REL_Y) y += events[i].value; have_pointer = true; break; case EV_ABS: - set_control_state(events[i].code, events[i].value / 32767.0); + set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); break; case EV_KEY: - button = map_button(events[i].code); + button = map_button(code); _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index d3d4203625..a86a7d1434 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -25,11 +25,12 @@ // /dev/input/event# API to read data from a raw mouse. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { -PUBLISHED: - EvdevInputDevice(int fd); - EvdevInputDevice(const string &device); +public: + EvdevInputDevice(int index); virtual ~EvdevInputDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,8 +38,15 @@ private: bool process_events(); private: + int _index; int _fd; + struct AxisRange { + double _scale; + double _bias; + }; + pvector _axis_ranges; + public: static ButtonHandle map_button(int code); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 8f04226896..5083e3fdda 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -35,7 +35,8 @@ InputDevice() : //////////////////////////////////////////////////////////////////// // Function: InputDevice::get_name // Access: Public -// Description: +// Description: Returns a human-readable name for the device. May +// not necessarily be unique. //////////////////////////////////////////////////////////////////// INLINE string InputDevice:: get_name() const { @@ -43,6 +44,42 @@ get_name() const { return _name; } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_manufacturer +// Access: Public +// Description: Returns a string containing the manufacturer of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE string InputDevice:: +get_manufacturer() const { + LightMutexHolder holder(_lock); + return _manufacturer; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_vendor_id +// Access: Public +// Description: Returns a string containing the USB vendor ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_vendor_id() const { + LightMutexHolder holder(_lock); + return _vendor_id; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_product_id +// Access: Public +// Description: Returns a string containing the USB product ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_product_id() const { + LightMutexHolder holder(_lock); + return _product_id; +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::is_connected // Access: Public diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index a6aaa5a015..5e33115a3e 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -111,12 +111,24 @@ PUBLISHED: }; INLINE string get_name() const; + INLINE string get_manufacturer() const; + INLINE unsigned short get_vendor_id() const; + INLINE unsigned short get_product_id() const; INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; // The human-readable name of this input device. MAKE_PROPERTY(name, get_name); + // The device's manufacturer, or the empty string if not known. + MAKE_PROPERTY(manufacturer, get_manufacturer); + + // USB vendor ID of the device, or 0 if not known. + MAKE_PROPERTY(vendor_id, get_vendor_id); + + // USB product ID of the device, or 0 if not known. + MAKE_PROPERTY(product_id, get_product_id); + // This is false if we know that the device is not currently connected. // May report false positives if we can't know this with certainty. MAKE_PROPERTY(connected, is_connected); @@ -212,8 +224,8 @@ protected: DeviceClass _device_class; int _flags; int _event_sequence; - short _vendor_id; - short _product_id; + unsigned short _vendor_id; + unsigned short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 0e5907fa3f..e160a70746 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -39,33 +39,6 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; #ifdef PHAVE_LINUX_INPUT_H InputDeviceManager:: InputDeviceManager() : _inotify_fd(-1) { - - // Scan /dev/input for a list of input devices. - DIR *dir = opendir("/dev/input"); - if (dir) { - dirent *entry = readdir(dir); - while (entry != NULL) { - if (entry->d_type == DT_CHR) { - string name(entry->d_name); - if (!consider_add_linux_device(name)) { - // We can't access it. That's pretty normal for most devices. - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } - errno = 0; - } - } - entry = readdir(dir); - } - closedir(dir); - } else { - device_cat.error() - << "Error opening directory /dev/input: " << strerror(errno) << "\n"; - return; - } - // Use inotify to watch /dev/input for hotplugging of devices. _inotify_fd = inotify_init1(O_NONBLOCK); @@ -77,6 +50,35 @@ InputDeviceManager() : _inotify_fd(-1) { device_cat.error() << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } + + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + vector_int indices; + dirent *entry = readdir(dir); + while (entry != NULL) { + int index = -1; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + indices.push_back(index); + } + entry = readdir(dir); + } + closedir(dir); + + // We'll want to sort the devices by index, since the order may be + // meaningful (eg. for the Xbox wireless receiver). + std::sort(indices.begin(), indices.end()); + _evdev_devices.resize(indices.back() + 1, NULL); + + vector_int::const_iterator it; + for (it = indices.begin(); it != indices.end(); ++it) { + consider_add_evdev_device(*it); + } + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } } #elif defined(_WIN32) InputDeviceManager:: @@ -129,41 +131,124 @@ InputDeviceManager:: #ifdef PHAVE_LINUX_INPUT_H //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::consider_add_linux_device +// Function: InputDeviceManager::consider_add_evdev_device // Access: Private -// Description: Checks whether the given device is accessible, and -// if so, adds it. Returns false on error. +// Description: Checks whether the event device with the given index +// is accessible, and if so, adds it. Returns the +// device if it was newly connected. //////////////////////////////////////////////////////////////////// -bool InputDeviceManager:: -consider_add_linux_device(const string &name) { - // Get the full path name first. - string path = "/dev/input/"; - path += name; - - if (access(path.c_str(), R_OK) < 0) { - return false; +InputDevice *InputDeviceManager:: +consider_add_evdev_device(int ev_index) { + if (ev_index < _evdev_devices.size()) { + if (_evdev_devices[ev_index] != NULL) { + // We already have this device. FIXME: probe it and add it to the + // list of connected devices? + return NULL; + } + } else { + // Make room to store this index. + _evdev_devices.resize(ev_index + 1, NULL); } - if (_devices_by_path.count(name)) { - // We already have this device. - return true; - } + // Check if we can directly read the event device. + char path[64]; + sprintf(path, "/dev/input/event%d", ev_index); - // Check if it's a joystick or game controller device. - if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) { - PT(InputDevice) device = new LinuxJoystickDevice(path); - if (device_cat.is_info()) { - device_cat.info() - << "Discovered input device " << *device << "\n"; + if (access(path, R_OK) == 0) { + PT(InputDevice) device = new EvdevInputDevice(ev_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered evdev input device " << *device << "\n"; } - _devices_by_path[name] = device; - _connected_devices.add_device(MOVE(device)); - return true; + _evdev_devices[ev_index] = device; + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return _evdev_devices[ev_index]; } - return true; + // Nope. The permissions haven't been configured to allow it. + // Check if this corresponds to a /dev/input/jsX interface, which has + // a better chance of having read permissions set, but doesn't export + // all of the features (notably, force feedback). + + // We do this by checking for a js# directory inside the sysfs + // device directory. + sprintf(path, "/sys/class/input/event%d/device", ev_index); + + DIR *dir = opendir(path); + if (dir == NULL) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Error opening directory " << path << ": " << strerror(errno) << "\n"; + } + return NULL; + } + + dirent *entry = readdir(dir); + while (entry != NULL) { + int js_index = -1; + if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + // Yes, we fonud a corresponding js device. Try adding it. + closedir(dir); + + InputDevice *device = consider_add_js_device(js_index); + if (device != NULL && device_cat.is_warning()) { + // This seemed to work. Display a warning to the user indicating + // that they might want to configure udev properly. + device_cat.warning() + << "Some features of " << device->get_device_class() + << " device " << device->get_name() << " are not available due" + " to lack of read permissions on /dev/input/event" << ev_index + << ".\n"; + } + _evdev_devices[ev_index] = device; + return device; + } + entry = readdir(dir); + } + + closedir(dir); + return NULL; } + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::consider_add_evdev_device +// Access: Private +// Description: Checks whether the joystick device with the given +// index is accessible, and if so, adds it. Returns +// the device if it was newly connected. +//////////////////////////////////////////////////////////////////// +InputDevice *InputDeviceManager:: +consider_add_js_device(int js_index) { + char path[64]; + sprintf(path, "/dev/input/js%d", js_index); + + if (access(path, R_OK) == 0) { + PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered joydev input device " << *device << "\n"; + } + InputDevice *device_p = device.p(); + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return device_p; + } + + return NULL; +} + #endif //////////////////////////////////////////////////////////////////// @@ -199,6 +284,12 @@ add_device(InputDevice *device) { { LightMutexHolder holder(_lock); _connected_devices.add_device(device); + +#ifdef PHAVE_LINUX_INPUT_H + // If we had added it pending activity on the device, remove it + // from the list of inactive devices. + _inactive_devices.remove_device(device); +#endif } throw_event("connect-device", device); } @@ -229,9 +320,23 @@ remove_device(InputDevice *device) { void InputDeviceManager:: update() { #ifdef PHAVE_LINUX_INPUT_H + // Check for any devices that may be disconnected and need to be probed + // in order to see whether they have been reconnected. + InputDeviceSet inactive_devices; + { + LightMutexHolder holder(_lock); + inactive_devices = _inactive_devices; + } + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != NULL && !device->is_connected()) { + device->poll(); + } + } + // We use inotify to tell us whether a device was added, removed, // or has changed permissions to allow us to access it. - unsigned int avail; + unsigned int avail = 0; ioctl(_inotify_fd, FIONREAD, &avail); if (avail == 0) { return; @@ -261,34 +366,39 @@ update() { if (event->mask & IN_DELETE) { // The device was deleted. If we have it, remove it. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it != _devices_by_path.end()) { - PT(InputDevice) device = it->second; - device->set_connected(false); - _devices_by_path.erase(it); - _connected_devices.remove_device(device); + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + // Check if we have this evdev device. If so, disconnect it. + if (index < _evdev_devices.size()) { + PT(InputDevice) device = _evdev_devices[index]; + if (device != NULL) { + _evdev_devices[index] = NULL; + _inactive_devices.remove_device(device); + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } - if (device_cat.is_info()) { - device_cat.info() - << "Removed input device " << *device << "\n"; + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } } - throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { - // The device was created, or it was chmodded to be accessible. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it == _devices_by_path.end()) { - // We don't know about this device yet. - if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } + // The device was created, or it was chmodded to be accessible. We + // need to check for the latter since it seems that the device may + // get the IN_CREATE event before the driver gets the permissions + // set properly. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + InputDevice *device = consider_add_evdev_device(index); + if (device != NULL && device->is_connected()) { + throw_event("connect-device", device); } - errno = 0; } } @@ -301,6 +411,8 @@ update() { // check if it's connected every so often. Perhaps we can switch to // using RegisterDeviceNotification in the future. double time_now = ClockObject::get_global_clock()->get_real_time(); + LightMutexHolder holder(_update_lock); + if (time_now - _last_detection > xinput_detection_delay.get_value()) { // I've heard this can be quite slow if no device is detected. We // should probably move it to a thread. diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 3f2c2cea43..2c96a5876f 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -36,7 +36,8 @@ private: ~InputDeviceManager(); #ifdef PHAVE_LINUX_INPUT_H - bool consider_add_linux_device(const string &name); + InputDevice *consider_add_evdev_device(int index); + InputDevice *consider_add_js_device(int index); #endif public: @@ -59,12 +60,13 @@ private: #ifdef PHAVE_LINUX_INPUT_H int _inotify_fd; - typedef pmap DevicesByPath; - DevicesByPath _devices_by_path; + pvector _evdev_devices; + InputDeviceSet _inactive_devices; #endif #ifdef _WIN32 // There are always exactly four of these in existence. + LightMutex _update_lock; XInputDevice _xinput_device0; XInputDevice _xinput_device1; XInputDevice _xinput_device2; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index b3566554e6..f15b4cf1dd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -30,14 +30,14 @@ TypeHandle LinuxJoystickDevice::_type_handle; // device using the given device filename. //////////////////////////////////////////////////////////////////// LinuxJoystickDevice:: -LinuxJoystickDevice(const string &device) : +LinuxJoystickDevice(int index) : _fd(-1), - _device(device) + _index(index) { LightMutexHolder holder(_lock); if (!open_device()) { device_cat.error() - << "Could not open joystick device " << _device + << "Could not open joystick device /dev/input/js" << index << ": " << strerror(errno) << "\n"; } } @@ -55,6 +55,18 @@ LinuxJoystickDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::do_poll // Access: Private, Virtual @@ -66,10 +78,15 @@ LinuxJoystickDevice:: //////////////////////////////////////////////////////////////////// void LinuxJoystickDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} - } else { - open_device(); + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -84,14 +101,18 @@ bool LinuxJoystickDevice:: open_device() { nassertr(_lock.debug_is_locked(), false); - _fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK); + char path[64]; + sprintf(path, "/dev/input/js%d", _index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd == -1) { _is_connected = false; return false; } - // Read the name from the device. + // Read the name from the device. We'll later try to use sysfs to read + // the proper product name from the device, but this is a good fallback. char name[128]; name[0] = 0; ioctl(_fd, JSIOCGNAME(sizeof(name)), name); @@ -249,16 +270,76 @@ open_device() { } } + // Get additional information from sysfs. + sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); + FILE *f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_vendor_id); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/id/product", _index); + f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_product_id); + fclose(f); + } + char buffer[256]; + sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + // Read the init events. - _is_connected = true; while (process_events()) {}; + + // Special case handling for the wireless Xbox receiver - the Linux + // joystick API doesn't seem to have a way to report whether the device + // is actually turned on. The best we can do is check whether the axes + // are all 0, which indicates that the driver hasn't received any data for + // this gamepad yet (which means it hasn't been plugged in for this session) + if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { + for (int i = 0; i < _controls.size(); ++i) { + if (_controls[i]._state != 0.0) { + _is_connected = true; + return true; + } + } + _is_connected = false; + } else { + _is_connected = true; + } + return true; } //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::process_events // Access: Private -// Description: Reads a number of events from the joystick. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool LinuxJoystickDevice:: process_events() { @@ -271,10 +352,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -301,6 +384,8 @@ process_events() { if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { // We'd like to use 0.0 to indicate the resting position. set_control_state(index, (events[i].value + 32767) / 65534.0); + } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + set_control_state(index, events[i].value / -32767.0); } else { set_control_state(index, events[i].value / 32767.0); } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index d71ad45c17..c2952d061f 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -25,10 +25,12 @@ // /dev/input/js# API to read data from a game controller. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { -PUBLISHED: - LinuxJoystickDevice(const string &device); +public: + LinuxJoystickDevice(int index); virtual ~LinuxJoystickDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,7 +39,7 @@ private: private: int _fd; - string _device; + int _index; public: static TypeHandle get_class_type() {