Skip to content

Commit cbd64e4

Browse files
committed
fix: prevent hangs during game launch
- Linux X11 remapper: open/close display per-call instead of caching to handle game fullscreen/X11 focus grabs gracefully - Add X11 error handler to catch async errors without crashing - GamepadManager: use timed_mutex with try_lock_for to prevent hotplug thread from blocking main thread during SDL enumeration Fixes UnionCrax.Direct hangs during game launch
1 parent 2f2bf02 commit cbd64e4

2 files changed

Lines changed: 88 additions & 20 deletions

File tree

GCPad_Lib/src/GamepadManager.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <thread>
1414
#include <atomic>
15+
#include <condition_variable>
1516
#include <mutex>
1617
#include <algorithm>
1718
#include <set>
@@ -54,7 +55,7 @@ class GamepadManagerImpl : public GamepadManager {
5455

5556
std::thread hotplug_thread_;
5657
std::atomic<bool> hotplug_running_;
57-
mutable std::mutex mutex_;
58+
mutable std::timed_mutex mutex_;
5859

5960
GamepadConnectedCallback connected_callback_;
6061
GamepadDisconnectedCallback disconnected_callback_;
@@ -103,7 +104,7 @@ GamepadManagerImpl::~GamepadManagerImpl() {
103104
}
104105

105106
bool GamepadManagerImpl::initialize() {
106-
std::lock_guard<std::mutex> lock(mutex_);
107+
std::lock_guard<std::timed_mutex> lock(mutex_);
107108

108109
// Initialize SDL2 first so we know which devices SDL has already
109110
// claimed before we try to open them via raw HID. On Windows, both
@@ -197,7 +198,7 @@ void GamepadManagerImpl::shutdown() {
197198
}
198199

199200
// Now safe to lock — hotplug thread is dead
200-
std::lock_guard<std::mutex> lock(mutex_);
201+
std::lock_guard<std::timed_mutex> lock(mutex_);
201202

202203
for (auto& gamepad : gamepads_) {
203204
gamepad.reset();
@@ -223,7 +224,7 @@ void GamepadManagerImpl::shutdown() {
223224
}
224225

225226
int GamepadManagerImpl::getConnectedGamepadCount() const {
226-
std::lock_guard<std::mutex> lock(mutex_);
227+
std::lock_guard<std::timed_mutex> lock(mutex_);
227228

228229
int count = 0;
229230
for (const auto& gamepad : gamepads_) {
@@ -235,7 +236,7 @@ int GamepadManagerImpl::getConnectedGamepadCount() const {
235236
}
236237

237238
std::vector<int> GamepadManagerImpl::getConnectedGamepadIndices() const {
238-
std::lock_guard<std::mutex> lock(mutex_);
239+
std::lock_guard<std::timed_mutex> lock(mutex_);
239240

240241
std::vector<int> indices;
241242
for (int i = 0; i < MAX_GAMEPADS; ++i) {
@@ -247,7 +248,7 @@ std::vector<int> GamepadManagerImpl::getConnectedGamepadIndices() const {
247248
}
248249

249250
GamepadDevice* GamepadManagerImpl::getGamepad(int index) {
250-
std::lock_guard<std::mutex> lock(mutex_);
251+
std::lock_guard<std::timed_mutex> lock(mutex_);
251252

252253
if (index >= 0 && index < MAX_GAMEPADS && gamepads_[index]) {
253254
return gamepads_[index].get();
@@ -256,7 +257,7 @@ GamepadDevice* GamepadManagerImpl::getGamepad(int index) {
256257
}
257258

258259
const GamepadDevice* GamepadManagerImpl::getGamepad(int index) const {
259-
std::lock_guard<std::mutex> lock(mutex_);
260+
std::lock_guard<std::timed_mutex> lock(mutex_);
260261

261262
if (index >= 0 && index < MAX_GAMEPADS && gamepads_[index]) {
262263
return gamepads_[index].get();
@@ -265,12 +266,12 @@ const GamepadDevice* GamepadManagerImpl::getGamepad(int index) const {
265266
}
266267

267268
void GamepadManagerImpl::setGamepadConnectedCallback(GamepadConnectedCallback callback) {
268-
std::lock_guard<std::mutex> lock(mutex_);
269+
std::lock_guard<std::timed_mutex> lock(mutex_);
269270
connected_callback_ = callback;
270271
}
271272

272273
void GamepadManagerImpl::setGamepadDisconnectedCallback(GamepadDisconnectedCallback callback) {
273-
std::lock_guard<std::mutex> lock(mutex_);
274+
std::lock_guard<std::timed_mutex> lock(mutex_);
274275
disconnected_callback_ = callback;
275276
}
276277

@@ -284,13 +285,13 @@ void GamepadManagerImpl::updateAll() {
284285
}
285286

286287
void GamepadManagerImpl::setRemapper(std::shared_ptr<Remapper> remapper) {
287-
std::lock_guard<std::mutex> lock(mutex_);
288+
std::lock_guard<std::timed_mutex> lock(mutex_);
288289
global_remapper_ = std::move(remapper);
289290
apply_remapper_to_all();
290291
}
291292

292293
std::shared_ptr<Remapper> GamepadManagerImpl::getRemapper() const {
293-
std::lock_guard<std::mutex> lock(mutex_);
294+
std::lock_guard<std::timed_mutex> lock(mutex_);
294295
return global_remapper_;
295296
}
296297

@@ -306,7 +307,13 @@ void GamepadManagerImpl::hotplug_detection_loop() {
306307
while (hotplug_running_) {
307308
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
308309

309-
std::lock_guard<std::mutex> lock(mutex_);
310+
// FIX: Use try_lock with timeout to avoid blocking game launch.
311+
// SDL enumeration can freeze momentarily when games are starting.
312+
std::unique_lock<std::timed_mutex> lock(mutex_, std::defer_lock);
313+
if (!lock.try_lock_for(std::chrono::milliseconds(100))) {
314+
// Skip this cycle if mutex is held by another thread
315+
continue;
316+
}
310317

311318
#ifdef _WIN32
312319
check_for_new_devices();

GCPad_Remap/src/gamepad_input_remapper.cpp

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cmath>
44
#include <algorithm>
55
#include <cstring>
6+
#include <atomic>
67

78
#ifdef _WIN32
89
#define WIN32_LEAN_AND_MEAN
@@ -69,6 +70,17 @@ static float axisToMouseMotionF(float value, const AxisMouseMapping& mapping) {
6970
return processed * mapping.sensitivity;
7071
}
7172

73+
#ifdef __linux__
74+
// Global error handler state for X11 async error handling
75+
static std::atomic<int> g_x11_error_occurred{0};
76+
77+
static int x11_error_handler(Display* /*display*/, XErrorEvent* /*event*/) {
78+
// Don't crash on X11 errors - game may have grabbed focus
79+
g_x11_error_occurred.store(1, std::memory_order_relaxed);
80+
return 0;
81+
}
82+
#endif
83+
7284
// ── Construction / reset ─────────────────────────────────────────────────────
7385

7486
GamepadInputRemapper::GamepadInputRemapper() {
@@ -459,17 +471,46 @@ bool GamepadInputRemapper::sendInput(const GamepadState& current,
459471

460472
return true;
461473
#elif defined(__linux__)
462-
static Display* display = XOpenDisplay(nullptr);
474+
// FIX: Open and close X11 display per-call instead of caching.
475+
// When a game launches fullscreen, it takes control of X11 and the
476+
// cached display handle becomes invalid, causing X11 calls to block.
477+
// Also set up error handler to prevent crashes on X11 errors.
478+
g_x11_error_occurred.store(0, std::memory_order_relaxed);
479+
XSetErrorHandler(x11_error_handler);
480+
481+
Display* display = XOpenDisplay(nullptr);
463482
if (!display) {
483+
XSetErrorHandler(nullptr);
464484
return false;
465485
}
466486

487+
// Check if error occurred during display open
488+
if (g_x11_error_occurred.load(std::memory_order_relaxed)) {
489+
XCloseDisplay(display);
490+
XSetErrorHandler(nullptr);
491+
return false;
492+
}
493+
494+
// Try to get input focus - this may fail during game launch
467495
Window root = DefaultRootWindow(display);
468-
Window focus;
469-
int revert_to;
496+
Window focus = 0;
497+
int revert_to = 0;
498+
499+
// Use XGetInputFocus with error checking
470500
XGetInputFocus(display, &focus, &revert_to);
501+
int focus_error = XSync(display, False);
502+
if (g_x11_error_occurred.load(std::memory_order_relaxed)) {
503+
XCloseDisplay(display);
504+
XSetErrorHandler(nullptr);
505+
return false;
506+
}
471507

472508
for (const auto& e : events) {
509+
// Skip further processing if error occurred
510+
if (g_x11_error_occurred.load(std::memory_order_relaxed)) {
511+
break;
512+
}
513+
473514
switch (e.type) {
474515
case GamepadInputEvent::Type::Keyboard: {
475516
KeyCode keycode = static_cast<KeyCode>(e.keyboard.virtual_key);
@@ -511,7 +552,9 @@ bool GamepadInputRemapper::sendInput(const GamepadState& current,
511552
}
512553

513554
XFlush(display);
514-
return true;
555+
XCloseDisplay(display);
556+
XSetErrorHandler(nullptr);
557+
return !g_x11_error_occurred.load(std::memory_order_relaxed);
515558
#else
516559
(void)events;
517560
return false;
@@ -541,16 +584,34 @@ std::string GamepadInputRemapper::virtualKeyName(uint16_t vk) {
541584
snprintf(buf, sizeof(buf), "VK_0x%02X", vk);
542585
return buf;
543586
#elif defined(__linux__)
544-
static Display* display = XOpenDisplay(nullptr);
587+
// FIX: Open and close display per-call instead of caching.
588+
// This ensures we get a fresh connection that works even after
589+
// a game has taken control of X11.
590+
Display* display = XOpenDisplay(nullptr);
545591
if (!display) {
546592
char buf[32];
547593
snprintf(buf, sizeof(buf), "VK_0x%02X", vk);
548594
return buf;
549595
}
596+
597+
g_x11_error_occurred.store(0, std::memory_order_relaxed);
598+
XSetErrorHandler(x11_error_handler);
599+
550600
KeyCode keycode = static_cast<KeyCode>(vk);
551-
KeySym keysym = XKeycodeToKeysym(display, keycode, 0);
601+
// Use XGetKeyboardMapping as fallback for XKeycodeToKeysym deprecation
602+
KeySym keysym = 0;
603+
int keysyms_per_keycode_return = 0;
604+
KeySym* keysym_map = XGetKeyboardMapping(display, keycode, 1, &keysyms_per_keycode_return);
605+
if (keysym_map && keysyms_per_keycode_return > 0) {
606+
keysym = keysym_map[0];
607+
XFree(keysym_map);
608+
}
552609
const char* name = XKeysymToString(keysym);
553-
if (name) {
610+
611+
XSetErrorHandler(nullptr);
612+
XCloseDisplay(display);
613+
614+
if (name && !g_x11_error_occurred.load(std::memory_order_relaxed)) {
554615
return std::string(name);
555616
}
556617
char buf[32];
@@ -563,4 +624,4 @@ std::string GamepadInputRemapper::virtualKeyName(uint16_t vk) {
563624
#endif
564625
}
565626

566-
} // namespace gcpad
627+
} // namespace gcpad

0 commit comments

Comments
 (0)