diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 057d9b14ff..b137888d6d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -59,6 +59,8 @@ enum QuirkBits { QB_rudder_from_throttle = 16, // Special handling for Steam Controller, which has many peculiarities. + // We only connect it if it is reporting any events, because when Steam is + // running, the Steam controller is muted in favour of a dummy Xbox device. QB_steam_controller = 32, }; @@ -119,7 +121,8 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) : _dpad_left_button(-1), _dpad_up_button(-1), _ltrigger_code(-1), - _rtrigger_code(-1) { + _rtrigger_code(-1), + _quirks(0) { char path[64]; sprintf(path, "/dev/input/event%zd", index); @@ -217,6 +220,26 @@ do_set_vibration(double strong, double weak) { } } +/** + * Special case for Steam controllers; called if a Steam virtual device has + * just been disconnected, and this is currently an inactive Steam Controller + * previously blocked by Steam, waiting to be reactivated. + * Returns true if the device has just been reconnected. + */ +bool EvdevInputDevice:: +reactivate_steam_controller() { + LightMutexHolder holder(_lock); + if (!_is_connected && (_quirks & QB_steam_controller) != 0) { + // Just check to make sure the device is still readable. + process_events(); + if (_fd != -1) { + _is_connected = true; + return true; + } + } + return false; +} + /** * Polls the input device for new activity, to ensure it contains the latest * events. This will only have any effect for some types of input devices; @@ -228,7 +251,7 @@ do_poll() { while (process_events()) {} // If we got events, we are obviously connected. Mark us so. - if (!_is_connected) { + if (!_is_connected && _fd != -1) { _is_connected = true; if (_manager != nullptr) { _manager->add_device(this); @@ -310,8 +333,19 @@ init_device() { if (quirks & QB_steam_controller) { if (test_bit(BTN_GAMEPAD, keys)) { _device_class = DeviceClass::gamepad; + + // If we have a virtual gamepad on the system, then careful: if Steam is + // running, it may disable its own gamepad in favour of the virtual + // device it registers. If the virtual device is present, we will only + // register this gamepad as connected when it registers input. + if (_manager->has_virtual_device(0x28de, 0x11ff)) { + device_cat.debug() + << "Detected Steam virtual gamepad, disabling Steam Controller\n"; + quirks |= QB_connect_if_nonzero; + } } } + _quirks = quirks; // Try to detect which type of device we have here if (_device_class == DeviceClass::unknown) { diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 994915e40b..ffc3503ff0 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -30,6 +30,8 @@ public: EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~EvdevInputDevice(); + bool reactivate_steam_controller(); + private: virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); @@ -41,6 +43,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; + int _quirks; size_t _index; bool _can_write; @@ -77,6 +80,10 @@ public: register_type(_type_handle, "EvdevInputDevice", InputDevice::get_class_type()); } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} private: static TypeHandle _type_handle; diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 45a83bfdb3..8eed3e1cc3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -200,6 +200,55 @@ consider_add_js_device(size_t js_index) { return nullptr; } +/** + * Scans the "virtual" input devices on the system to check whether one with + * the given vendor and product ID exists. + */ +bool LinuxInputDeviceManager:: +has_virtual_device(unsigned short vendor_id, unsigned short product_id) const { + char path[294]; + sprintf(path, "/sys/devices/virtual/input"); + + DIR *dir = opendir(path); + if (dir != nullptr) { + dirent *entry = readdir(dir); + while (entry != nullptr) { + if (entry->d_name[0] != 'i') { + entry = readdir(dir); + continue; + } + FILE *f; + + char vendor[5] = {0}; + sprintf(path, "/sys/devices/virtual/input/%s/id/vendor", entry->d_name); + f = fopen(path, "r"); + if (f) { + fgets(vendor, sizeof(vendor), f); + fclose(f); + } + + char product[5] = {0}; + sprintf(path, "/sys/devices/virtual/input/%s/id/product", entry->d_name); + f = fopen(path, "r"); + if (f) { + fgets(product, sizeof(product), f); + fclose(f); + } + + if (vendor[0] && std::stoi(std::string(vendor), nullptr, 16) == (int)vendor_id && + product[0] && std::stoi(std::string(product), nullptr, 16) == (int)product_id) { + closedir(dir); + return true; + } + + entry = readdir(dir); + } + closedir(dir); + } + + return false; +} + /** * Polls the system to see if there are any new devices. In some * implementations this is a no-op. @@ -243,6 +292,7 @@ update() { LightMutexHolder holder(_lock); // Iterate over the events in the buffer. + bool removed_steam_virtual_device = false; char *ptr = buffer; char *end = buffer + avail; while (ptr < end) { @@ -270,6 +320,12 @@ update() { device_cat.debug() << "Removed input device " << *device << "\n"; } + + // Check for Steam virtual device; see comment below. + if (device->get_vendor_id() == 0x28de && + device->get_product_id() == 0x11ff) { + removed_steam_virtual_device = true; + } } } } @@ -290,6 +346,25 @@ update() { ptr += sizeof(inotify_event) + event->len; } + + // If the Steam virtual device was just disconnected, the user may have just + // shut down Steam, and we need to reactivate the real Steam Controller + // device that was previously suppressed by Steam. + if (removed_steam_virtual_device) { + inactive_devices = _inactive_devices; + + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != nullptr && device->is_of_type(EvdevInputDevice::get_class_type())) { + PT(EvdevInputDevice) evdev_device = (EvdevInputDevice *)device; + if (evdev_device->reactivate_steam_controller()) { + _inactive_devices.remove_device(device); + _connected_devices.add_device(device); + throw_event("connect-device", device); + } + } + } + } } #endif // PHAVE_LINUX_INPUT_H diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h index c5ad6a013f..36fce94842 100644 --- a/panda/src/device/linuxInputDeviceManager.h +++ b/panda/src/device/linuxInputDeviceManager.h @@ -30,6 +30,9 @@ private: InputDevice *consider_add_evdev_device(size_t index); InputDevice *consider_add_js_device(size_t index); +public: + bool has_virtual_device(unsigned short vendor_id, unsigned short product_id) const; + virtual void update(); protected: