[client] evdev: split teardown into stop and free phases

evdev_stop was never called, and calling it revealed unsafe teardown:
the device list was freed before the thread was joined, a zero epoll
descriptor was mistaken for a valid one, and the device loops walked
past the end of the array through a sentinel that does not exist.

Join the thread in evdev_stop and defer the rest to the new evdev_free.
The display server grab hooks route into evdev until its event thread
is joined, so the device state must stay valid until the display
server has been freed; evdev_free then restores the hooks and releases
the state.
This commit is contained in:
Amit Mendapara
2026-08-11 20:06:04 +05:30
committed by Geoffrey McRae
parent c66a974f1d
commit 75f7d614a7
2 changed files with 45 additions and 17 deletions

View File

@@ -31,10 +31,16 @@ void evdev_earlyInit(void);
bool evdev_start(void); bool evdev_start(void);
/** /**
* stop the evdev layer * join the evdev thread; device state stays valid for callbacks
*/ */
void evdev_stop(void); void evdev_stop(void);
/**
* restore the display server hooks and free the device state; only
* call once display server callbacks have stopped
*/
void evdev_free(void);
/** /**
* grab the keyboard for exclusive access * grab the keyboard for exclusive access
*/ */

View File

@@ -68,7 +68,7 @@ struct EvdevState
pending; pending;
}; };
static struct EvdevState state = {}; static struct EvdevState state = { .epoll = -1 };
static struct Option options[] = static struct Option options[] =
{ {
@@ -351,31 +351,51 @@ bool evdev_start(void)
void evdev_stop(void) void evdev_stop(void)
{ {
if (state.thread)
{
lgJoinThread(state.thread, NULL);
state.thread = NULL;
}
}
void evdev_free(void)
{
evdev_stop();
// restore the display server grab methods
if (state.dsGrabKeyboard)
{
g_state.ds->grabKeyboard = state.dsGrabKeyboard;
g_state.ds->ungrabKeyboard = state.dsUngrabKeyboard;
state.dsGrabKeyboard = NULL;
state.dsUngrabKeyboard = NULL;
}
state.grabbed = false;
// the thread and grab callbacks reference the device state
if (state.deviceList) if (state.deviceList)
{ {
free(state.deviceList); free(state.deviceList);
state.deviceList = NULL; state.deviceList = NULL;
} }
if (state.thread)
{
lgJoinThread(state.thread, NULL);
state.thread = NULL;
}
if (state.epoll >= 0) if (state.epoll >= 0)
{ {
close(state.epoll); close(state.epoll);
state.epoll = 0; state.epoll = -1;
} }
for(EvdevDevice * device = state.devices; device->path; ++device) if (state.devices)
{ {
if (device->fd <= 0) for(int i = 0; i < state.deviceCount; ++i)
continue; {
EvdevDevice * device = &state.devices[i];
if (device->fd > 0)
close(device->fd); close(device->fd);
device->fd = 0; }
free(state.devices);
state.devices = NULL;
state.deviceCount = 0;
} }
} }
@@ -395,8 +415,9 @@ void evdev_grabKeyboard(void)
// state.dsGrabKeyboard(); // state.dsGrabKeyboard();
for(EvdevDevice * device = state.devices; device->path; ++device) for(int i = 0; i < state.deviceCount; ++i)
{ {
EvdevDevice * device = &state.devices[i];
if (device->fd > 0) if (device->fd > 0)
evdev_grabDevice(device); evdev_grabDevice(device);
} }
@@ -418,8 +439,9 @@ void evdev_ungrabKeyboard(void)
return; return;
} }
for(EvdevDevice * device = state.devices; device->path; ++device) for(int i = 0; i < state.deviceCount; ++i)
{ {
EvdevDevice * device = &state.devices[i];
if (device->fd <= 0 || !device->grabbed) if (device->fd <= 0 || !device->grabbed)
continue; continue;