LogitechG27: mirror mapped steering device presence on the usb bus

The emulated G27 was always attached while enabled, so games kept
reading a phantom wheel (centered steering after the previous commit,
formerly hard-left) and ignored pad steering input. Attach the emulated
device only while the mapped steering device is connected: games now
see the wheel plug in and out exactly like on real hardware, and pad
play works without disabling the option and restarting the game.

Also guard the personality reconnect against the device having been
detached between the game's mode-change request and the housekeeping
tick.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Mohamed
2026-09-15 21:10:22 +02:00
committed by Megamouse
co-authored by Claude Fable 5
parent b7056bc88a
commit 1b3e02044f
5 changed files with 81 additions and 1 deletions
+34
View File
@@ -147,6 +147,7 @@ public:
void connect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices = false);
void disconnect_usb_device(std::shared_ptr<usb_device> dev, bool update_usb_devices = false);
void reconnect_usb_device(u32 assigned_number);
void set_usb_device_attached(usb_device* raw_dev, bool attached);
// Map of devices actively handled by the ps3(device_id, device)
std::map<u32, std::pair<UsbInternalDevice, std::shared_ptr<usb_device>>> handled_devices;
@@ -962,6 +963,12 @@ void usb_handler_thread::connect_usb_device(std::shared_ptr<usb_device> dev, boo
if (update_usb_devices)
usb_devices.push_back(dev);
if (!dev->is_attachable())
{
sys_usbd.trace("USB device(VID=0x%04x, PID=0x%04x) is not attachable, skipping connection", dev->device._device.idVendor, dev->device._device.idProduct);
return;
}
for (const auto& [name, ldd] : ldds)
{
if (dev->device._device.idVendor == ldd.id_vendor && dev->device._device.idProduct >= ldd.id_product_min && dev->device._device.idProduct <= ldd.id_product_max)
@@ -1019,6 +1026,23 @@ void usb_handler_thread::reconnect_usb_device(u32 assigned_number)
}
}
void usb_handler_thread::set_usb_device_attached(usb_device* raw_dev, bool attached)
{
std::lock_guard lock(mutex);
for (const auto& dev : usb_devices)
{
if (dev.get() != raw_dev)
continue;
if (attached && !dev->assigned_number)
connect_usb_device(dev, false);
else if (!attached && dev->assigned_number)
disconnect_usb_device(dev, false);
break;
}
}
void connect_usb_controller(u8 index, input::product_type type)
{
auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>();
@@ -1115,6 +1139,16 @@ void reconnect_usb(u32 assigned_number)
usbh->reconnect_usb_device(assigned_number);
}
void set_usb_device_attached(usb_device* dev, bool attached)
{
auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>();
if (!usbh)
{
return;
}
usbh->set_usb_device_attached(dev, attached);
}
void handle_hotplug_event(bool connected, bool source_is_libusb)
{
if (auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>())
+3
View File
@@ -88,6 +88,9 @@ error_code sys_usbd_get_device_speed(ppu_thread& ppu);
error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max);
error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product);
class usb_device;
void connect_usb_controller(u8 index, input::product_type);
void reconnect_usb(u32 assigned_number);
void set_usb_device_attached(usb_device* dev, bool attached);
void handle_hotplug_event(bool connected, bool source_is_libusb);
+34 -1
View File
@@ -303,8 +303,10 @@ void usb_device_logitech_g27::set_personality(logitech_personality personality,
index += raw_config[index];
}
if (reconnect)
if (reconnect && assigned_number)
{
// assigned_number can be 0 if the device was detached (mapped steering device unplugged)
// after the game requested the personality change
reconnect_usb(assigned_number);
}
}
@@ -332,11 +334,30 @@ usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std
if (!m_enabled)
return;
// Initial refresh before the usb handler connects the device, so is_attachable() already reflects the physical state
sdl_refresh();
m_house_keeping_thread = std::make_unique<named_thread<std::function<void()>>>("Logitech G27", [this]()
{
bool last_steering_device_present = m_steering_device_present;
while (thread_ctrl::state() != thread_state::aborting)
{
// Pump SDL events here as well: while the device is detached no interrupt transfers run,
// so this is the only pump that lets SDL notice the wheel being plugged back in
sdl_instance::get_instance().pump_events();
sdl_refresh();
// Mirror the physical presence of the mapped steering device on the emulated usb bus, so games
// fall back to the pad when the real wheel is not connected instead of reading a phantom wheel
const bool steering_device_present = m_steering_device_present;
if (steering_device_present != last_steering_device_present)
{
set_usb_device_attached(this, steering_device_present);
last_steering_device_present = steering_device_present;
}
thread_ctrl::wait_for(1'000'000);
std::unique_lock lock(g_cfg_logitech_g27.m_mutex);
@@ -354,6 +375,11 @@ bool usb_device_logitech_g27::open_device()
return m_enabled;
}
bool usb_device_logitech_g27::is_attachable() const
{
return m_enabled && m_steering_device_present;
}
static void clear_sdl_joysticks(std::map<u64, std::vector<SDL_Joystick*>>& joystick_map)
{
for (auto& [type, joysticks] : joystick_map)
@@ -612,6 +638,13 @@ void usb_device_logitech_g27::sdl_refresh()
if (new_haptic_handle)
SDL_CloseHaptic(new_haptic_handle);
}
{
// Cache the presence of the mapped steering device for is_attachable()
const std::lock_guard<std::mutex> handles_lock(m_sdl_handles_mutex);
const auto steering_joysticks = m_joysticks.find(m_mapping.steering.device_type_id);
m_steering_device_present = steering_joysticks != m_joysticks.end() && !steering_joysticks->second.empty();
}
}
static inline s16 logitech_g27_force_to_level(u8 force, bool reverse = false)
+4
View File
@@ -117,6 +117,7 @@ public:
void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override;
void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override;
bool open_device() override;
bool is_attachable() const override;
private:
void sdl_refresh();
@@ -149,5 +150,8 @@ private:
bool m_enabled = false;
// Updated by sdl_refresh, read by is_attachable which cannot take m_sdl_handles_mutex (called from the usb handler)
atomic_t<bool> m_steering_device_present = false;
std::unique_ptr<named_thread<std::function<void()>>> m_house_keeping_thread;
};
+6
View File
@@ -226,6 +226,12 @@ public:
virtual bool open_device() = 0;
// Emulated devices can decline attachment to the bus, e.g. while the real hardware they are backed by is not connected
virtual bool is_attachable() const
{
return true;
}
void get_location(u8* location) const;
virtual void read_descriptors();