Skip to content

Commit c365bea

Browse files
committed
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.
1 parent dccf807 commit c365bea

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

panda/src/device/evdevInputDevice.cxx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ enum QuirkBits {
5959
QB_rudder_from_throttle = 16,
6060

6161
// Special handling for Steam Controller, which has many peculiarities.
62+
// We only connect it if it is reporting any events, because when Steam is
63+
// running, the Steam controller is muted in favour of a dummy Xbox device.
6264
QB_steam_controller = 32,
6365
};
6466

@@ -119,7 +121,8 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) :
119121
_dpad_left_button(-1),
120122
_dpad_up_button(-1),
121123
_ltrigger_code(-1),
122-
_rtrigger_code(-1) {
124+
_rtrigger_code(-1),
125+
_quirks(0) {
123126

124127
char path[64];
125128
sprintf(path, "/dev/input/event%zd", index);
@@ -217,6 +220,26 @@ do_set_vibration(double strong, double weak) {
217220
}
218221
}
219222

223+
/**
224+
* Special case for Steam controllers; called if a Steam virtual device has
225+
* just been disconnected, and this is currently an inactive Steam Controller
226+
* previously blocked by Steam, waiting to be reactivated.
227+
* Returns true if the device has just been reconnected.
228+
*/
229+
bool EvdevInputDevice::
230+
reactivate_steam_controller() {
231+
LightMutexHolder holder(_lock);
232+
if (!_is_connected && (_quirks & QB_steam_controller) != 0) {
233+
// Just check to make sure the device is still readable.
234+
process_events();
235+
if (_fd != -1) {
236+
_is_connected = true;
237+
return true;
238+
}
239+
}
240+
return false;
241+
}
242+
220243
/**
221244
* Polls the input device for new activity, to ensure it contains the latest
222245
* events. This will only have any effect for some types of input devices;
@@ -228,7 +251,7 @@ do_poll() {
228251
while (process_events()) {}
229252

230253
// If we got events, we are obviously connected. Mark us so.
231-
if (!_is_connected) {
254+
if (!_is_connected && _fd != -1) {
232255
_is_connected = true;
233256
if (_manager != nullptr) {
234257
_manager->add_device(this);
@@ -310,8 +333,19 @@ init_device() {
310333
if (quirks & QB_steam_controller) {
311334
if (test_bit(BTN_GAMEPAD, keys)) {
312335
_device_class = DeviceClass::gamepad;
336+
337+
// If we have a virtual gamepad on the system, then careful: if Steam is
338+
// running, it may disable its own gamepad in favour of the virtual
339+
// device it registers. If the virtual device is present, we will only
340+
// register this gamepad as connected when it registers input.
341+
if (_manager->has_virtual_device(0x28de, 0x11ff)) {
342+
device_cat.debug()
343+
<< "Detected Steam virtual gamepad, disabling Steam Controller\n";
344+
quirks |= QB_connect_if_nonzero;
345+
}
313346
}
314347
}
348+
_quirks = quirks;
315349

316350
// Try to detect which type of device we have here
317351
if (_device_class == DeviceClass::unknown) {

panda/src/device/evdevInputDevice.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice {
3030
EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index);
3131
virtual ~EvdevInputDevice();
3232

33+
bool reactivate_steam_controller();
34+
3335
private:
3436
virtual void do_set_vibration(double strong, double weak);
3537
virtual void do_poll();
@@ -41,6 +43,7 @@ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice {
4143
LinuxInputDeviceManager *_manager;
4244

4345
int _fd;
46+
int _quirks;
4447
size_t _index;
4548

4649
bool _can_write;
@@ -77,6 +80,10 @@ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice {
7780
register_type(_type_handle, "EvdevInputDevice",
7881
InputDevice::get_class_type());
7982
}
83+
virtual TypeHandle get_type() const {
84+
return get_class_type();
85+
}
86+
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
8087

8188
private:
8289
static TypeHandle _type_handle;

panda/src/device/linuxInputDeviceManager.cxx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,55 @@ consider_add_js_device(size_t js_index) {
200200
return nullptr;
201201
}
202202

203+
/**
204+
* Scans the "virtual" input devices on the system to check whether one with
205+
* the given vendor and product ID exists.
206+
*/
207+
bool LinuxInputDeviceManager::
208+
has_virtual_device(unsigned short vendor_id, unsigned short product_id) const {
209+
char path[294];
210+
sprintf(path, "/sys/devices/virtual/input");
211+
212+
DIR *dir = opendir(path);
213+
if (dir != nullptr) {
214+
dirent *entry = readdir(dir);
215+
while (entry != nullptr) {
216+
if (entry->d_name[0] != 'i') {
217+
entry = readdir(dir);
218+
continue;
219+
}
220+
FILE *f;
221+
222+
char vendor[5] = {0};
223+
sprintf(path, "/sys/devices/virtual/input/%s/id/vendor", entry->d_name);
224+
f = fopen(path, "r");
225+
if (f) {
226+
fgets(vendor, sizeof(vendor), f);
227+
fclose(f);
228+
}
229+
230+
char product[5] = {0};
231+
sprintf(path, "/sys/devices/virtual/input/%s/id/product", entry->d_name);
232+
f = fopen(path, "r");
233+
if (f) {
234+
fgets(product, sizeof(product), f);
235+
fclose(f);
236+
}
237+
238+
if (vendor[0] && std::stoi(std::string(vendor), nullptr, 16) == (int)vendor_id &&
239+
product[0] && std::stoi(std::string(product), nullptr, 16) == (int)product_id) {
240+
closedir(dir);
241+
return true;
242+
}
243+
244+
entry = readdir(dir);
245+
}
246+
closedir(dir);
247+
}
248+
249+
return false;
250+
}
251+
203252
/**
204253
* Polls the system to see if there are any new devices. In some
205254
* implementations this is a no-op.
@@ -243,6 +292,7 @@ update() {
243292
LightMutexHolder holder(_lock);
244293

245294
// Iterate over the events in the buffer.
295+
bool removed_steam_virtual_device = false;
246296
char *ptr = buffer;
247297
char *end = buffer + avail;
248298
while (ptr < end) {
@@ -270,6 +320,12 @@ update() {
270320
device_cat.debug()
271321
<< "Removed input device " << *device << "\n";
272322
}
323+
324+
// Check for Steam virtual device; see comment below.
325+
if (device->get_vendor_id() == 0x28de &&
326+
device->get_product_id() == 0x11ff) {
327+
removed_steam_virtual_device = true;
328+
}
273329
}
274330
}
275331
}
@@ -290,6 +346,25 @@ update() {
290346

291347
ptr += sizeof(inotify_event) + event->len;
292348
}
349+
350+
// If the Steam virtual device was just disconnected, the user may have just
351+
// shut down Steam, and we need to reactivate the real Steam Controller
352+
// device that was previously suppressed by Steam.
353+
if (removed_steam_virtual_device) {
354+
inactive_devices = _inactive_devices;
355+
356+
for (size_t i = 0; i < inactive_devices.size(); ++i) {
357+
InputDevice *device = inactive_devices[i];
358+
if (device != nullptr && device->is_of_type(EvdevInputDevice::get_class_type())) {
359+
PT(EvdevInputDevice) evdev_device = (EvdevInputDevice *)device;
360+
if (evdev_device->reactivate_steam_controller()) {
361+
_inactive_devices.remove_device(device);
362+
_connected_devices.add_device(device);
363+
throw_event("connect-device", device);
364+
}
365+
}
366+
}
367+
}
293368
}
294369

295370
#endif // PHAVE_LINUX_INPUT_H

panda/src/device/linuxInputDeviceManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class EXPCL_PANDA_DEVICE LinuxInputDeviceManager final : public InputDeviceManag
3030
InputDevice *consider_add_evdev_device(size_t index);
3131
InputDevice *consider_add_js_device(size_t index);
3232

33+
public:
34+
bool has_virtual_device(unsigned short vendor_id, unsigned short product_id) const;
35+
3336
virtual void update();
3437

3538
protected:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy