device: hide Steam Controller on Linux while Steam is running

When Steam runs, it mutes all input from the Steam Controller and instead registers a virtual gamepad that obeys the Big Picture controller remapping.  We want to make sure to pick that one, since the "real" Steam Controller doesn't report any events while the virtual device is active.  So we carefully consider the "real" Steam Controller "inactive" if the presence of the virtual device is detected, until the virtual device is disconnected (ie. when Steam is closed) or if it turns out that it is generating events after all, in which case it gets automatically reactivated.

This is a bit of a hack, maybe there is a cleaner way of doing this.
This commit is contained in:
rdb 2019-01-01 22:07:46 +01:00
parent dccf8074fd
commit c365beaf11
4 changed files with 121 additions and 2 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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: