GHOST/X11: Hotplug support for xinput

Allows to plug/unplug different tablets while Blender is running.

Also fixes crash unplugging tablet while Blender runs (T48750).
This commit is contained in:
Campbell Barton 2016-06-28 13:34:53 +10:00
parent e5a1f3142e
commit 6ce0ddae96
Notes: blender-bot 2023-02-14 07:47:52 +01:00
Referenced by issue #48750, Blender crashes when I unplug my genius tablet
5 changed files with 145 additions and 77 deletions

View File

@ -155,15 +155,7 @@ void GHOST_ContextGLX::initContextGLXEW()
GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext()
{
#ifdef WITH_X11_XINPUT
/* use our own event handlers to avoid exiting blender,
* this would happen for eg:
* if you open blender, unplug a tablet, then open a new window. */
XErrorHandler old_handler = XSetErrorHandler (GHOST_X11_ApplicationErrorHandler );
XIOErrorHandler old_handler_io = XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler);
#endif
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
/* -------------------------------------------------------------------- */
/* Begin Inline Glew */
@ -350,11 +342,8 @@ const bool GLXEW_ARB_create_context_robustness =
success = GHOST_kFailure;
}
#ifdef WITH_X11_XINPUT
/* Restore handler */
XSetErrorHandler (old_handler);
XSetIOErrorHandler(old_handler_io);
#endif
GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
return success;
}

View File

@ -73,6 +73,10 @@
/* for debugging - so we can breakpoint X11 errors */
// #define USE_X11_ERROR_HANDLERS
#ifdef WITH_X11_XINPUT
# define USE_XINPUT_HOTPLUG
#endif
/* see [#34039] Fix Alt key glitch on Unity desktop */
#define USE_UNITY_WORKAROUND
@ -169,11 +173,36 @@ GHOST_SystemX11(
}
#ifdef WITH_X11_XINPUT
/* detect if we have xinput (for reuse) */
{
memset(&m_xinput_version, 0, sizeof(m_xinput_version));
XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
if (version && (version != (XExtensionVersion *)NoSuchExtension)) {
if (version->present) {
m_xinput_version = *version;
}
XFree(version);
}
}
#ifdef USE_XINPUT_HOTPLUG
if (m_xinput_version.present) {
XEventClass class_presence;
int xi_presence;
DevicePresence(m_display, xi_presence, class_presence);
XSelectExtensionEvent(
m_display,
RootWindow(m_display, DefaultScreen(m_display)),
&class_presence, 1);
(void)xi_presence;
}
#endif /* USE_XINPUT_HOTPLUG */
/* initialize incase X11 fails to load */
memset(&m_xtablet, 0, sizeof(m_xtablet));
initXInputDevices();
#endif
refreshXInputDevices();
#endif /* WITH_X11_XINPUT */
}
GHOST_SystemX11::
@ -627,8 +656,13 @@ static bool checkTabletProximity(Display *display, XDevice *device)
return false;
}
/* needed since unplugging will abort() without this */
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
state = XQueryDeviceState(display, device);
GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
if (state) {
XInputClass *cls = state->data;
// printf("%d class%s :\n", state->num_classes,
@ -661,6 +695,41 @@ GHOST_SystemX11::processEvent(XEvent *xe)
GHOST_WindowX11 *window = findGhostWindow(xe->xany.window);
GHOST_Event *g_event = NULL;
#ifdef USE_XINPUT_HOTPLUG
/* Hot-Plug support */
if (m_xinput_version.present) {
XEventClass class_presence;
int xi_presence;
DevicePresence(m_display, xi_presence, class_presence);
(void)class_presence;
if (xe->type == xi_presence) {
XDevicePresenceNotifyEvent *notify_event = (XDevicePresenceNotifyEvent *)xe;
if ((notify_event->devchange == DeviceEnabled) ||
(notify_event->devchange == DeviceDisabled) ||
(notify_event->devchange == DeviceAdded) ||
(notify_event->devchange == DeviceRemoved))
{
refreshXInputDevices();
/* update all window events */
{
vector<GHOST_IWindow *> & win_vec = m_windowManager->getWindows();
vector<GHOST_IWindow *>::iterator win_it = win_vec.begin();
vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
for (; win_it != win_end; ++win_it) {
GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
window->refreshXInputDevices();
}
}
}
}
}
#endif /* USE_XINPUT_HOTPLUG */
if (!window) {
return;
}
@ -680,7 +749,6 @@ GHOST_SystemX11::processEvent(XEvent *xe)
}
}
#endif /* WITH_X11_XINPUT */
switch (xe->type) {
case Expose:
{
@ -1917,8 +1985,6 @@ GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType,
);
}
#endif
#if defined(USE_X11_ERROR_HANDLERS) || defined(WITH_X11_XINPUT)
/*
* These callbacks can be used for debugging, so we can breakpoint on an X11 error.
@ -1952,7 +2018,6 @@ int GHOST_X11_ApplicationIOErrorHandler(Display * /*display*/)
/* No exit! - but keep lint happy */
return 0;
}
#endif
#ifdef WITH_X11_XINPUT
/* These C functions are copied from Wine 1.1.13's wintab.c */
@ -2049,23 +2114,27 @@ static BOOL is_eraser(const char *name, const char *type)
#undef FALSE
/* end code copied from wine */
void GHOST_SystemX11::initXInputDevices()
void GHOST_SystemX11::refreshXInputDevices()
{
static XErrorHandler old_handler = (XErrorHandler) 0;
static XIOErrorHandler old_handler_io = (XIOErrorHandler) 0;
if (m_xinput_version.present) {
XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
if (m_xtablet.StylusDevice) {
XCloseDevice(m_display, m_xtablet.StylusDevice);
m_xtablet.StylusDevice = NULL;
}
if (version && (version != (XExtensionVersion *)NoSuchExtension)) {
if (version->present) {
if (m_xtablet.EraserDevice) {
XCloseDevice(m_display, m_xtablet.EraserDevice);
m_xtablet.EraserDevice = NULL;
}
/* Install our error handler to override Xlib's termination behavior */
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
{
int device_count;
XDeviceInfo *device_info = XListInputDevices(m_display, &device_count);
m_xtablet.StylusDevice = NULL;
m_xtablet.EraserDevice = NULL;
/* Install our error handler to override Xlib's termination behavior */
old_handler = XSetErrorHandler(GHOST_X11_ApplicationErrorHandler);
old_handler_io = XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler);
for (int i = 0; i < device_count; ++i) {
char *device_type = device_info[i].type ? XGetAtomName(m_display, device_info[i].type) : NULL;
@ -2124,13 +2193,10 @@ void GHOST_SystemX11::initXInputDevices()
}
}
/* Restore handler */
(void) XSetErrorHandler(old_handler);
(void) XSetIOErrorHandler(old_handler_io);
XFreeDeviceList(device_info);
}
XFree(version);
GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
}
}

View File

@ -52,6 +52,20 @@
int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *theEvent);
int GHOST_X11_ApplicationIOErrorHandler(Display *display);
#define GHOST_X11_ERROR_HANDLERS_OVERRIDE(var) \
struct { \
XErrorHandler handler; \
XIOErrorHandler handler_io; \
} var = { \
XSetErrorHandler(GHOST_X11_ApplicationErrorHandler), \
XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler), \
}
#define GHOST_X11_ERROR_HANDLERS_RESTORE(var) \
{ \
(void)XSetErrorHandler(var.handler); \
(void)XSetIOErrorHandler(var.handler_io); \
} ((void)0)
class GHOST_WindowX11;
@ -328,6 +342,10 @@ public:
#endif
} m_atom;
#ifdef WITH_X11_XINPUT
XExtensionVersion m_xinput_version;
#endif
private:
Display *m_display;
@ -367,7 +385,7 @@ private:
#endif
#ifdef WITH_X11_XINPUT
void initXInputDevices();
void refreshXInputDevices();
#endif
GHOST_WindowX11 *

View File

@ -566,7 +566,7 @@ GHOST_WindowX11(GHOST_SystemX11 *system,
}
#ifdef WITH_X11_XINPUT
initXInputDevices();
refreshXInputDevices();
m_tabletData.Active = GHOST_kTabletModeNone;
#endif
@ -633,45 +633,40 @@ bool GHOST_WindowX11::createX11_XIC()
#endif
#ifdef WITH_X11_XINPUT
void GHOST_WindowX11::initXInputDevices()
void GHOST_WindowX11::refreshXInputDevices()
{
XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
if (m_system->m_xinput_version.present) {
GHOST_SystemX11::GHOST_TabletX11 &xtablet = m_system->GetXTablet();
XEventClass xevents[8], ev;
int dcount = 0;
if (version && (version != (XExtensionVersion *)NoSuchExtension)) {
if (version->present) {
GHOST_SystemX11::GHOST_TabletX11 &xtablet = m_system->GetXTablet();
XEventClass xevents[8], ev;
int dcount = 0;
/* With modern XInput (xlib 1.6.2 at least and/or evdev 2.9.0) and some 'no-name' tablets
* like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
* otherwise we do not get any tablet motion event once pen is pressed... See T43367.
*/
/* With modern XInput (xlib 1.6.2 at least and/or evdev 2.9.0) and some 'no-name' tablets
* like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
* otherwise we do not get any tablet motion event once pen is pressed... See T43367.
*/
if (xtablet.StylusDevice) {
DeviceMotionNotify(xtablet.StylusDevice, xtablet.MotionEvent, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.StylusDevice, xtablet.PressEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.StylusDevice, xtablet.ProxInEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.StylusDevice, xtablet.ProxOutEvent, ev);
if (ev) xevents[dcount++] = ev;
}
if (xtablet.EraserDevice) {
DeviceMotionNotify(xtablet.EraserDevice, xtablet.MotionEventEraser, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.EraserDevice, xtablet.PressEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.EraserDevice, xtablet.ProxInEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.EraserDevice, xtablet.ProxOutEventEraser, ev);
if (ev) xevents[dcount++] = ev;
}
XSelectExtensionEvent(m_display, m_window, xevents, dcount);
if (xtablet.StylusDevice) {
DeviceMotionNotify(xtablet.StylusDevice, xtablet.MotionEvent, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.StylusDevice, xtablet.PressEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.StylusDevice, xtablet.ProxInEvent, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.StylusDevice, xtablet.ProxOutEvent, ev);
if (ev) xevents[dcount++] = ev;
}
XFree(version);
if (xtablet.EraserDevice) {
DeviceMotionNotify(xtablet.EraserDevice, xtablet.MotionEventEraser, ev);
if (ev) xevents[dcount++] = ev;
DeviceButtonPress(xtablet.EraserDevice, xtablet.PressEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityIn(xtablet.EraserDevice, xtablet.ProxInEventEraser, ev);
if (ev) xevents[dcount++] = ev;
ProximityOut(xtablet.EraserDevice, xtablet.ProxOutEventEraser, ev);
if (ev) xevents[dcount++] = ev;
}
XSelectExtensionEvent(m_display, m_window, xevents, dcount);
}
}

View File

@ -212,6 +212,10 @@ public:
bool createX11_XIC();
#endif
#ifdef WITH_X11_XINPUT
void refreshXInputDevices();
#endif
#ifdef WITH_XDND
GHOST_DropTargetX11 *getDropTarget()
{
@ -315,10 +319,6 @@ private:
Cursor
getEmptyCursor(
);
#ifdef WITH_X11_XINPUT
void initXInputDevices();
#endif
Window m_window;
Display *m_display;