From 6ce0ddae9612dc093331630a9eb55e1020d845fd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 28 Jun 2016 13:34:53 +1000 Subject: [PATCH] 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). --- intern/ghost/intern/GHOST_ContextGLX.cpp | 17 +--- intern/ghost/intern/GHOST_SystemX11.cpp | 110 ++++++++++++++++++----- intern/ghost/intern/GHOST_SystemX11.h | 20 ++++- intern/ghost/intern/GHOST_WindowX11.cpp | 67 +++++++------- intern/ghost/intern/GHOST_WindowX11.h | 8 +- 5 files changed, 145 insertions(+), 77 deletions(-) diff --git a/intern/ghost/intern/GHOST_ContextGLX.cpp b/intern/ghost/intern/GHOST_ContextGLX.cpp index d4f67da1242..9ac61db4041 100644 --- a/intern/ghost/intern/GHOST_ContextGLX.cpp +++ b/intern/ghost/intern/GHOST_ContextGLX.cpp @@ -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; } diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index aeda95d5d4d..55d013f6e5f 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -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 & win_vec = m_windowManager->getWindows(); + vector::iterator win_it = win_vec.begin(); + vector::const_iterator win_end = win_vec.end(); + + for (; win_it != win_end; ++win_it) { + GHOST_WindowX11 *window = static_cast(*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); } } diff --git a/intern/ghost/intern/GHOST_SystemX11.h b/intern/ghost/intern/GHOST_SystemX11.h index a0088dbe8f0..e60cab6a194 100644 --- a/intern/ghost/intern/GHOST_SystemX11.h +++ b/intern/ghost/intern/GHOST_SystemX11.h @@ -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 * diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index a12ecec6371..fd002cec80c 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -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); } } diff --git a/intern/ghost/intern/GHOST_WindowX11.h b/intern/ghost/intern/GHOST_WindowX11.h index 0738e3d47b8..9380aa9d631 100644 --- a/intern/ghost/intern/GHOST_WindowX11.h +++ b/intern/ghost/intern/GHOST_WindowX11.h @@ -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;