|
@@ -39,44 +39,46 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL;
|
|
|
#ifdef PHAVE_LINUX_INPUT_H
|
|
#ifdef PHAVE_LINUX_INPUT_H
|
|
|
InputDeviceManager::
|
|
InputDeviceManager::
|
|
|
InputDeviceManager() : _inotify_fd(-1) {
|
|
InputDeviceManager() : _inotify_fd(-1) {
|
|
|
|
|
+ // Use inotify to watch /dev/input for hotplugging of devices.
|
|
|
|
|
+ _inotify_fd = inotify_init1(O_NONBLOCK);
|
|
|
|
|
+
|
|
|
|
|
+ if (_inotify_fd < 0) {
|
|
|
|
|
+ device_cat.error()
|
|
|
|
|
+ << "Error initializing inotify: " << strerror(errno) << "\n";
|
|
|
|
|
+
|
|
|
|
|
+ } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) {
|
|
|
|
|
+ device_cat.error()
|
|
|
|
|
+ << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Scan /dev/input for a list of input devices.
|
|
// Scan /dev/input for a list of input devices.
|
|
|
DIR *dir = opendir("/dev/input");
|
|
DIR *dir = opendir("/dev/input");
|
|
|
if (dir) {
|
|
if (dir) {
|
|
|
|
|
+ vector_int indices;
|
|
|
dirent *entry = readdir(dir);
|
|
dirent *entry = readdir(dir);
|
|
|
while (entry != NULL) {
|
|
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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ int index = -1;
|
|
|
|
|
+ if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) {
|
|
|
|
|
+ indices.push_back(index);
|
|
|
}
|
|
}
|
|
|
entry = readdir(dir);
|
|
entry = readdir(dir);
|
|
|
}
|
|
}
|
|
|
closedir(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 {
|
|
} else {
|
|
|
device_cat.error()
|
|
device_cat.error()
|
|
|
<< "Error opening directory /dev/input: " << strerror(errno) << "\n";
|
|
<< "Error opening directory /dev/input: " << strerror(errno) << "\n";
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // Use inotify to watch /dev/input for hotplugging of devices.
|
|
|
|
|
- _inotify_fd = inotify_init1(O_NONBLOCK);
|
|
|
|
|
-
|
|
|
|
|
- if (_inotify_fd < 0) {
|
|
|
|
|
- device_cat.error()
|
|
|
|
|
- << "Error initializing inotify: " << strerror(errno) << "\n";
|
|
|
|
|
-
|
|
|
|
|
- } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) {
|
|
|
|
|
- device_cat.error()
|
|
|
|
|
- << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n";
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
#elif defined(_WIN32)
|
|
#elif defined(_WIN32)
|
|
|
InputDeviceManager::
|
|
InputDeviceManager::
|
|
@@ -129,41 +131,124 @@ InputDeviceManager::
|
|
|
|
|
|
|
|
#ifdef PHAVE_LINUX_INPUT_H
|
|
#ifdef PHAVE_LINUX_INPUT_H
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-// Function: InputDeviceManager::consider_add_linux_device
|
|
|
|
|
|
|
+// Function: InputDeviceManager::consider_add_evdev_device
|
|
|
// Access: Private
|
|
// 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);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if we can directly read the event device.
|
|
|
|
|
+ char path[64];
|
|
|
|
|
+ sprintf(path, "/dev/input/event%d", ev_index);
|
|
|
|
|
+
|
|
|
|
|
+ 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";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _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];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (_devices_by_path.count(name)) {
|
|
|
|
|
- // We already have this device.
|
|
|
|
|
- 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;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 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";
|
|
|
|
|
|
|
+ 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();
|
|
|
|
|
|
|
|
- _devices_by_path[name] = device;
|
|
|
|
|
- _connected_devices.add_device(MOVE(device));
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ 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 true;
|
|
|
|
|
|
|
+ return NULL;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
@@ -199,6 +284,12 @@ add_device(InputDevice *device) {
|
|
|
{
|
|
{
|
|
|
LightMutexHolder holder(_lock);
|
|
LightMutexHolder holder(_lock);
|
|
|
_connected_devices.add_device(device);
|
|
_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);
|
|
throw_event("connect-device", device);
|
|
|
}
|
|
}
|
|
@@ -229,9 +320,23 @@ remove_device(InputDevice *device) {
|
|
|
void InputDeviceManager::
|
|
void InputDeviceManager::
|
|
|
update() {
|
|
update() {
|
|
|
#ifdef PHAVE_LINUX_INPUT_H
|
|
#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,
|
|
// We use inotify to tell us whether a device was added, removed,
|
|
|
// or has changed permissions to allow us to access it.
|
|
// or has changed permissions to allow us to access it.
|
|
|
- unsigned int avail;
|
|
|
|
|
|
|
+ unsigned int avail = 0;
|
|
|
ioctl(_inotify_fd, FIONREAD, &avail);
|
|
ioctl(_inotify_fd, FIONREAD, &avail);
|
|
|
if (avail == 0) {
|
|
if (avail == 0) {
|
|
|
return;
|
|
return;
|
|
@@ -261,34 +366,39 @@ update() {
|
|
|
|
|
|
|
|
if (event->mask & IN_DELETE) {
|
|
if (event->mask & IN_DELETE) {
|
|
|
// The device was deleted. If we have it, remove it.
|
|
// 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);
|
|
|
|
|
-
|
|
|
|
|
- if (device_cat.is_info()) {
|
|
|
|
|
- device_cat.info()
|
|
|
|
|
- << "Removed input device " << *device << "\n";
|
|
|
|
|
|
|
+ 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_debug()) {
|
|
|
|
|
+ device_cat.debug()
|
|
|
|
|
+ << "Removed input device " << *device << "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- throw_event("disconnect-device", device.p());
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} else if (event->mask & (IN_CREATE | IN_ATTRIB)) {
|
|
} 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
|
|
// check if it's connected every so often. Perhaps we can switch to
|
|
|
// using RegisterDeviceNotification in the future.
|
|
// using RegisterDeviceNotification in the future.
|
|
|
double time_now = ClockObject::get_global_clock()->get_real_time();
|
|
double time_now = ClockObject::get_global_clock()->get_real_time();
|
|
|
|
|
+ LightMutexHolder holder(_update_lock);
|
|
|
|
|
+
|
|
|
if (time_now - _last_detection > xinput_detection_delay.get_value()) {
|
|
if (time_now - _last_detection > xinput_detection_delay.get_value()) {
|
|
|
// I've heard this can be quite slow if no device is detected. We
|
|
// I've heard this can be quite slow if no device is detected. We
|
|
|
// should probably move it to a thread.
|
|
// should probably move it to a thread.
|