1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17  * All rights reserved.
18  * Part of this code has been taken from Qt, under LGPL license
19  * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
20  */
21 
22 /** \file
23  * \ingroup GHOST
24  */
25 
26 #include <X11/XKBlib.h> /* allow detectable autorepeate */
27 #include <X11/Xatom.h>
28 #include <X11/Xutil.h>
29 #include <X11/keysym.h>
30 
31 #include "GHOST_DisplayManagerX11.h"
32 #include "GHOST_EventButton.h"
33 #include "GHOST_EventCursor.h"
34 #include "GHOST_EventDragnDrop.h"
35 #include "GHOST_EventKey.h"
36 #include "GHOST_EventWheel.h"
37 #include "GHOST_SystemX11.h"
38 #include "GHOST_TimerManager.h"
39 #include "GHOST_WindowManager.h"
40 #include "GHOST_WindowX11.h"
41 #ifdef WITH_INPUT_NDOF
42 #  include "GHOST_NDOFManagerUnix.h"
43 #endif
44 
45 #ifdef WITH_XDND
46 #  include "GHOST_DropTargetX11.h"
47 #endif
48 
49 #include "GHOST_Debug.h"
50 
51 #if defined(WITH_GL_EGL)
52 #  include "GHOST_ContextEGL.h"
53 #  include <EGL/eglext.h>
54 #else
55 #  include "GHOST_ContextGLX.h"
56 #endif
57 
58 #ifdef WITH_XF86KEYSYM
59 #  include <X11/XF86keysym.h>
60 #endif
61 
62 #ifdef WITH_X11_XFIXES
63 #  include <X11/extensions/Xfixes.h>
64 /* Workaround for XWayland grab glitch: T53004. */
65 #  define WITH_XWAYLAND_HACK
66 #endif
67 
68 /* for XIWarpPointer */
69 #ifdef WITH_X11_XINPUT
70 #  include <X11/extensions/XInput2.h>
71 #endif
72 
73 /* For timing */
74 #include <sys/time.h>
75 #include <unistd.h>
76 
77 #include <cstdlib> /* for exit */
78 #include <iostream>
79 #include <stdio.h> /* for fprintf only */
80 #include <vector>
81 
82 /* for debugging - so we can breakpoint X11 errors */
83 // #define USE_X11_ERROR_HANDLERS
84 
85 #ifdef WITH_X11_XINPUT
86 #  define USE_XINPUT_HOTPLUG
87 #endif
88 
89 /* see T34039 Fix Alt key glitch on Unity desktop */
90 #define USE_UNITY_WORKAROUND
91 
92 /* Fix 'shortcut' part of keyboard reading code only ever using first defined keymap
93  * instead of active one. See T47228 and D1746 */
94 #define USE_NON_LATIN_KB_WORKAROUND
95 
bit_is_on(const uchar * ptr,int bit)96 static uchar bit_is_on(const uchar *ptr, int bit)
97 {
98   return ptr[bit >> 3] & (1 << (bit & 7));
99 }
100 
101 static GHOST_TKey ghost_key_from_keysym(const KeySym key);
102 static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode);
103 static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key,
104                                                    const XkbDescPtr xkb_descr,
105                                                    const KeyCode keycode);
106 
107 /* these are for copy and select copy */
108 static char *txt_cut_buffer = NULL;
109 static char *txt_select_buffer = NULL;
110 
111 #ifdef WITH_XWAYLAND_HACK
112 static bool use_xwayland_hack = false;
113 #endif
114 
115 using namespace std;
116 
GHOST_SystemX11()117 GHOST_SystemX11::GHOST_SystemX11() : GHOST_System(), m_xkb_descr(NULL), m_start_time(0)
118 {
119   XInitThreads();
120   m_display = XOpenDisplay(NULL);
121 
122   if (!m_display) {
123     std::cerr << "Unable to open a display" << std::endl;
124     abort(); /* was return before, but this would just mean it will crash later */
125   }
126 
127 #ifdef USE_X11_ERROR_HANDLERS
128   (void)XSetErrorHandler(GHOST_X11_ApplicationErrorHandler);
129   (void)XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler);
130 #endif
131 
132 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
133   /* note -- don't open connection to XIM server here, because the locale
134    * has to be set before opening the connection but setlocale() has not
135    * been called yet.  the connection will be opened after entering
136    * the event loop. */
137   m_xim = NULL;
138 #endif
139 
140 #define GHOST_INTERN_ATOM_IF_EXISTS(atom) \
141   { \
142     m_atom.atom = XInternAtom(m_display, #atom, True); \
143   } \
144   (void)0
145 #define GHOST_INTERN_ATOM(atom) \
146   { \
147     m_atom.atom = XInternAtom(m_display, #atom, False); \
148   } \
149   (void)0
150 
151   GHOST_INTERN_ATOM_IF_EXISTS(WM_DELETE_WINDOW);
152   GHOST_INTERN_ATOM(WM_PROTOCOLS);
153   GHOST_INTERN_ATOM(WM_TAKE_FOCUS);
154   GHOST_INTERN_ATOM(WM_STATE);
155   GHOST_INTERN_ATOM(WM_CHANGE_STATE);
156   GHOST_INTERN_ATOM(_NET_WM_STATE);
157   GHOST_INTERN_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
158   GHOST_INTERN_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
159 
160   GHOST_INTERN_ATOM(_NET_WM_STATE_FULLSCREEN);
161   GHOST_INTERN_ATOM(_MOTIF_WM_HINTS);
162   GHOST_INTERN_ATOM(TARGETS);
163   GHOST_INTERN_ATOM(STRING);
164   GHOST_INTERN_ATOM(COMPOUND_TEXT);
165   GHOST_INTERN_ATOM(TEXT);
166   GHOST_INTERN_ATOM(CLIPBOARD);
167   GHOST_INTERN_ATOM(PRIMARY);
168   GHOST_INTERN_ATOM(XCLIP_OUT);
169   GHOST_INTERN_ATOM(INCR);
170   GHOST_INTERN_ATOM(UTF8_STRING);
171 #ifdef WITH_X11_XINPUT
172   m_atom.TABLET = XInternAtom(m_display, XI_TABLET, False);
173 #endif
174 
175 #undef GHOST_INTERN_ATOM_IF_EXISTS
176 #undef GHOST_INTERN_ATOM
177 
178   m_last_warp_x = 0;
179   m_last_warp_y = 0;
180   m_last_release_keycode = 0;
181   m_last_release_time = 0;
182 
183   /* compute the initial time */
184   timeval tv;
185   if (gettimeofday(&tv, NULL) == -1) {
186     GHOST_ASSERT(false, "Could not instantiate timer!");
187   }
188 
189   /* Taking care not to overflow the tv.tv_sec * 1000 */
190   m_start_time = GHOST_TUns64(tv.tv_sec) * 1000 + tv.tv_usec / 1000;
191 
192   /* use detectable autorepeate, mac and windows also do this */
193   int use_xkb;
194   int xkb_opcode, xkb_event, xkb_error;
195   int xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion;
196 
197   use_xkb = XkbQueryExtension(
198       m_display, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, &xkb_minor);
199   if (use_xkb) {
200     XkbSetDetectableAutoRepeat(m_display, true, NULL);
201 
202     m_xkb_descr = XkbGetMap(m_display, 0, XkbUseCoreKbd);
203     if (m_xkb_descr) {
204       XkbGetNames(m_display, XkbKeyNamesMask, m_xkb_descr);
205       XkbGetControls(m_display, XkbPerKeyRepeatMask | XkbRepeatKeysMask, m_xkb_descr);
206     }
207   }
208 
209 #ifdef WITH_XWAYLAND_HACK
210   use_xwayland_hack = getenv("WAYLAND_DISPLAY") != NULL;
211 #endif
212 
213 #ifdef WITH_X11_XINPUT
214   /* detect if we have xinput (for reuse) */
215   {
216     memset(&m_xinput_version, 0, sizeof(m_xinput_version));
217     XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
218     if (version && (version != (XExtensionVersion *)NoSuchExtension)) {
219       if (version->present) {
220         m_xinput_version = *version;
221       }
222       XFree(version);
223     }
224   }
225 
226 #  ifdef USE_XINPUT_HOTPLUG
227   if (m_xinput_version.present) {
228     XEventClass class_presence;
229     int xi_presence;
230     DevicePresence(m_display, xi_presence, class_presence);
231     XSelectExtensionEvent(
232         m_display, RootWindow(m_display, DefaultScreen(m_display)), &class_presence, 1);
233     (void)xi_presence;
234   }
235 #  endif /* USE_XINPUT_HOTPLUG */
236 
237   refreshXInputDevices();
238 #endif /* WITH_X11_XINPUT */
239 }
240 
~GHOST_SystemX11()241 GHOST_SystemX11::~GHOST_SystemX11()
242 {
243 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
244   if (m_xim) {
245     XCloseIM(m_xim);
246   }
247 #endif
248 
249 #ifdef WITH_X11_XINPUT
250   /* Close tablet devices. */
251   clearXInputDevices();
252 #endif /* WITH_X11_XINPUT */
253 
254 #ifdef WITH_GL_EGL
255   ::eglTerminate(::eglGetDisplay(m_display));
256 #endif
257 
258   if (m_xkb_descr) {
259     XkbFreeKeyboard(m_xkb_descr, XkbAllComponentsMask, true);
260   }
261 
262   XCloseDisplay(m_display);
263 }
264 
init()265 GHOST_TSuccess GHOST_SystemX11::init()
266 {
267   GHOST_TSuccess success = GHOST_System::init();
268 
269   if (success) {
270 #ifdef WITH_INPUT_NDOF
271     m_ndofManager = new GHOST_NDOFManagerUnix(*this);
272 #endif
273     m_displayManager = new GHOST_DisplayManagerX11(this);
274 
275     if (m_displayManager) {
276       return GHOST_kSuccess;
277     }
278   }
279 
280   return GHOST_kFailure;
281 }
282 
getMilliSeconds() const283 GHOST_TUns64 GHOST_SystemX11::getMilliSeconds() const
284 {
285   timeval tv;
286   if (gettimeofday(&tv, NULL) == -1) {
287     GHOST_ASSERT(false, "Could not compute time!");
288   }
289 
290   /* Taking care not to overflow the tv.tv_sec * 1000 */
291   return GHOST_TUns64(tv.tv_sec) * 1000 + tv.tv_usec / 1000 - m_start_time;
292 }
293 
getNumDisplays() const294 GHOST_TUns8 GHOST_SystemX11::getNumDisplays() const
295 {
296   return GHOST_TUns8(1);
297 }
298 
299 /**
300  * Returns the dimensions of the main display on this system.
301  * \return The dimension of the main display.
302  */
getMainDisplayDimensions(GHOST_TUns32 & width,GHOST_TUns32 & height) const303 void GHOST_SystemX11::getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
304 {
305   if (m_display) {
306     /* note, for this to work as documented,
307      * we would need to use Xinerama check r54370 for code that did this,
308      * we've since removed since its not worth the extra dep - campbell */
309     getAllDisplayDimensions(width, height);
310   }
311 }
312 
313 /**
314  * Returns the dimensions of the main display on this system.
315  * \return The dimension of the main display.
316  */
getAllDisplayDimensions(GHOST_TUns32 & width,GHOST_TUns32 & height) const317 void GHOST_SystemX11::getAllDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
318 {
319   if (m_display) {
320     width = DisplayWidth(m_display, DefaultScreen(m_display));
321     height = DisplayHeight(m_display, DefaultScreen(m_display));
322   }
323 }
324 
325 /**
326  * Create a new window.
327  * The new window is added to the list of windows managed.
328  * Never explicitly delete the window, use disposeWindow() instead.
329  * \param   title   The name of the window
330  * (displayed in the title bar of the window if the OS supports it).
331  * \param   left    The coordinate of the left edge of the window.
332  * \param   top     The coordinate of the top edge of the window.
333  * \param   width   The width the window.
334  * \param   height  The height the window.
335  * \param   state   The state of the window when opened.
336  * \param   type    The type of drawing context installed in this window.
337  * \param glSettings: Misc OpenGL settings.
338  * \param exclusive: Use to show the window ontop and ignore others (used fullscreen).
339  * \param   parentWindow    Parent window
340  * \return  The new window (or 0 if creation failed).
341  */
createWindow(const char * title,GHOST_TInt32 left,GHOST_TInt32 top,GHOST_TUns32 width,GHOST_TUns32 height,GHOST_TWindowState state,GHOST_TDrawingContextType type,GHOST_GLSettings glSettings,const bool exclusive,const bool is_dialog,const GHOST_IWindow * parentWindow)342 GHOST_IWindow *GHOST_SystemX11::createWindow(const char *title,
343                                              GHOST_TInt32 left,
344                                              GHOST_TInt32 top,
345                                              GHOST_TUns32 width,
346                                              GHOST_TUns32 height,
347                                              GHOST_TWindowState state,
348                                              GHOST_TDrawingContextType type,
349                                              GHOST_GLSettings glSettings,
350                                              const bool exclusive,
351                                              const bool is_dialog,
352                                              const GHOST_IWindow *parentWindow)
353 {
354   GHOST_WindowX11 *window = NULL;
355 
356   if (!m_display)
357     return 0;
358 
359   window = new GHOST_WindowX11(this,
360                                m_display,
361                                title,
362                                left,
363                                top,
364                                width,
365                                height,
366                                state,
367                                (GHOST_WindowX11 *)parentWindow,
368                                type,
369                                is_dialog,
370                                ((glSettings.flags & GHOST_glStereoVisual) != 0),
371                                exclusive,
372                                ((glSettings.flags & GHOST_glAlphaBackground) != 0),
373                                (glSettings.flags & GHOST_glDebugContext) != 0);
374 
375   if (window) {
376     /* Both are now handle in GHOST_WindowX11.cpp
377      * Focus and Delete atoms. */
378 
379     if (window->getValid()) {
380       /* Store the pointer to the window */
381       m_windowManager->addWindow(window);
382       m_windowManager->setActiveWindow(window);
383       pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
384     }
385     else {
386       delete window;
387       window = NULL;
388     }
389   }
390   return window;
391 }
392 
393 /**
394  * Create a new offscreen context.
395  * Never explicitly delete the context, use disposeContext() instead.
396  * \return  The new context (or 0 if creation failed).
397  */
createOffscreenContext(GHOST_GLSettings glSettings)398 GHOST_IContext *GHOST_SystemX11::createOffscreenContext(GHOST_GLSettings glSettings)
399 {
400   // During development:
401   //   try 4.x compatibility profile
402   //   try 3.3 compatibility profile
403   //   fall back to 3.0 if needed
404   //
405   // Final Blender 2.8:
406   //   try 4.x core profile
407   //   try 3.3 core profile
408   //   no fallbacks
409 
410   const bool debug_context = (glSettings.flags & GHOST_glDebugContext) != 0;
411 
412 #if defined(WITH_GL_PROFILE_CORE)
413   {
414     const char *version_major = (char *)glewGetString(GLEW_VERSION_MAJOR);
415     if (version_major != NULL && version_major[0] == '1') {
416       fprintf(stderr, "Error: GLEW version 2.0 and above is required.\n");
417       abort();
418     }
419   }
420 #endif
421 
422   const int profile_mask =
423 #ifdef WITH_GL_EGL
424 #  if defined(WITH_GL_PROFILE_CORE)
425       EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT;
426 #  elif defined(WITH_GL_PROFILE_COMPAT)
427       EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT;
428 #  else
429 #    error  // must specify either core or compat at build time
430 #  endif
431 #else
432 #  if defined(WITH_GL_PROFILE_CORE)
433       GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
434 #  elif defined(WITH_GL_PROFILE_COMPAT)
435       GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
436 #  else
437 #    error  // must specify either core or compat at build time
438 #  endif
439 #endif
440 
441   GHOST_Context *context;
442 
443   for (int minor = 5; minor >= 0; --minor) {
444 #if defined(WITH_GL_EGL)
445     context = new GHOST_ContextEGL(false,
446                                    EGLNativeWindowType(nullptr),
447                                    EGLNativeDisplayType(m_display),
448                                    profile_mask,
449                                    4,
450                                    minor,
451                                    GHOST_OPENGL_EGL_CONTEXT_FLAGS |
452                                        (debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
453                                    GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
454                                    EGL_OPENGL_API);
455 #else
456     context = new GHOST_ContextGLX(false,
457                                    (Window)NULL,
458                                    m_display,
459                                    (GLXFBConfig)NULL,
460                                    profile_mask,
461                                    4,
462                                    minor,
463                                    GHOST_OPENGL_GLX_CONTEXT_FLAGS |
464                                        (debug_context ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
465                                    GHOST_OPENGL_GLX_RESET_NOTIFICATION_STRATEGY);
466 #endif
467 
468     if (context->initializeDrawingContext())
469       return context;
470     else
471       delete context;
472   }
473 
474 #if defined(WITH_GL_EGL)
475   context = new GHOST_ContextEGL(false,
476                                  EGLNativeWindowType(nullptr),
477                                  EGLNativeDisplayType(m_display),
478                                  profile_mask,
479                                  3,
480                                  3,
481                                  GHOST_OPENGL_EGL_CONTEXT_FLAGS |
482                                      (debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
483                                  GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
484                                  EGL_OPENGL_API);
485 #else
486   context = new GHOST_ContextGLX(false,
487                                  (Window)NULL,
488                                  m_display,
489                                  (GLXFBConfig)NULL,
490                                  profile_mask,
491                                  3,
492                                  3,
493                                  GHOST_OPENGL_GLX_CONTEXT_FLAGS |
494                                      (debug_context ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
495                                  GHOST_OPENGL_GLX_RESET_NOTIFICATION_STRATEGY);
496 #endif
497 
498   if (context->initializeDrawingContext())
499     return context;
500   else
501     delete context;
502 
503   return NULL;
504 }
505 
506 /**
507  * Dispose of a context.
508  * \param   context Pointer to the context to be disposed.
509  * \return  Indication of success.
510  */
disposeContext(GHOST_IContext * context)511 GHOST_TSuccess GHOST_SystemX11::disposeContext(GHOST_IContext *context)
512 {
513   delete context;
514 
515   return GHOST_kSuccess;
516 }
517 
518 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
destroyIMCallback(XIM,XPointer ptr,XPointer)519 static void destroyIMCallback(XIM /*xim*/, XPointer ptr, XPointer /*data*/)
520 {
521   GHOST_PRINT("XIM server died\n");
522 
523   if (ptr)
524     *(XIM *)ptr = NULL;
525 }
526 
openX11_IM()527 bool GHOST_SystemX11::openX11_IM()
528 {
529   if (!m_display)
530     return false;
531 
532   /* set locale modifiers such as "@im=ibus" specified by XMODIFIERS */
533   XSetLocaleModifiers("");
534 
535   m_xim = XOpenIM(m_display, NULL, (char *)GHOST_X11_RES_NAME, (char *)GHOST_X11_RES_CLASS);
536   if (!m_xim)
537     return false;
538 
539   XIMCallback destroy;
540   destroy.callback = (XIMProc)destroyIMCallback;
541   destroy.client_data = (XPointer)&m_xim;
542   XSetIMValues(m_xim, XNDestroyCallback, &destroy, NULL);
543   return true;
544 }
545 #endif
546 
findGhostWindow(Window xwind) const547 GHOST_WindowX11 *GHOST_SystemX11::findGhostWindow(Window xwind) const
548 {
549 
550   if (xwind == 0)
551     return NULL;
552 
553   /* It is not entirely safe to do this as the backptr may point
554    * to a window that has recently been removed.
555    * We should always check the window manager's list of windows
556    * and only process events on these windows. */
557 
558   const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
559 
560   vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
561   vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
562 
563   for (; win_it != win_end; ++win_it) {
564     GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
565     if (window->getXWindow() == xwind) {
566       return window;
567     }
568   }
569   return NULL;
570 }
571 
SleepTillEvent(Display * display,GHOST_TInt64 maxSleep)572 static void SleepTillEvent(Display *display, GHOST_TInt64 maxSleep)
573 {
574   int fd = ConnectionNumber(display);
575   fd_set fds;
576 
577   FD_ZERO(&fds);
578   FD_SET(fd, &fds);
579 
580   if (maxSleep == -1) {
581     select(fd + 1, &fds, NULL, NULL, NULL);
582   }
583   else {
584     timeval tv;
585 
586     tv.tv_sec = maxSleep / 1000;
587     tv.tv_usec = (maxSleep - tv.tv_sec * 1000) * 1000;
588 
589     select(fd + 1, &fds, NULL, NULL, &tv);
590   }
591 }
592 
593 /* This function borrowed from Qt's X11 support
594  * qclipboard_x11.cpp
595  *  */
596 struct init_timestamp_data {
597   Time timestamp;
598 };
599 
init_timestamp_scanner(Display *,XEvent * event,XPointer arg)600 static Bool init_timestamp_scanner(Display *, XEvent *event, XPointer arg)
601 {
602   init_timestamp_data *data = reinterpret_cast<init_timestamp_data *>(arg);
603   switch (event->type) {
604     case ButtonPress:
605     case ButtonRelease:
606       data->timestamp = event->xbutton.time;
607       break;
608     case MotionNotify:
609       data->timestamp = event->xmotion.time;
610       break;
611     case KeyPress:
612     case KeyRelease:
613       data->timestamp = event->xkey.time;
614       break;
615     case PropertyNotify:
616       data->timestamp = event->xproperty.time;
617       break;
618     case EnterNotify:
619     case LeaveNotify:
620       data->timestamp = event->xcrossing.time;
621       break;
622     case SelectionClear:
623       data->timestamp = event->xselectionclear.time;
624       break;
625     default:
626       break;
627   }
628 
629   return false;
630 }
631 
lastEventTime(Time default_time)632 Time GHOST_SystemX11::lastEventTime(Time default_time)
633 {
634   init_timestamp_data data;
635   data.timestamp = default_time;
636   XEvent ev;
637   XCheckIfEvent(m_display, &ev, &init_timestamp_scanner, (XPointer)&data);
638 
639   return data.timestamp;
640 }
641 
processEvents(bool waitForEvent)642 bool GHOST_SystemX11::processEvents(bool waitForEvent)
643 {
644   /* Get all the current events -- translate them into
645    * ghost events and call base class pushEvent() method. */
646 
647   bool anyProcessed = false;
648 
649   do {
650     GHOST_TimerManager *timerMgr = getTimerManager();
651 
652     if (waitForEvent && m_dirty_windows.empty() && !XPending(m_display)) {
653       GHOST_TUns64 next = timerMgr->nextFireTime();
654 
655       if (next == GHOST_kFireTimeNever) {
656         SleepTillEvent(m_display, -1);
657       }
658       else {
659         GHOST_TInt64 maxSleep = next - getMilliSeconds();
660 
661         if (maxSleep >= 0)
662           SleepTillEvent(m_display, next - getMilliSeconds());
663       }
664     }
665 
666     if (timerMgr->fireTimers(getMilliSeconds())) {
667       anyProcessed = true;
668     }
669 
670     while (XPending(m_display)) {
671       XEvent xevent;
672       XNextEvent(m_display, &xevent);
673 
674 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
675       /* open connection to XIM server and create input context (XIC)
676        * when receiving the first FocusIn or KeyPress event after startup,
677        * or recover XIM and XIC when the XIM server has been restarted */
678       if (xevent.type == FocusIn || xevent.type == KeyPress) {
679         if (!m_xim && openX11_IM()) {
680           GHOST_PRINT("Connected to XIM server\n");
681         }
682 
683         if (m_xim) {
684           GHOST_WindowX11 *window = findGhostWindow(xevent.xany.window);
685           if (window && !window->getX11_XIC() && window->createX11_XIC()) {
686             GHOST_PRINT("XIM input context created\n");
687             if (xevent.type == KeyPress)
688               /* we can assume the window has input focus
689                * here, because key events are received only
690                * when the window is focused. */
691               XSetICFocus(window->getX11_XIC());
692           }
693         }
694       }
695 
696       /* dispatch event to XIM server */
697       if ((XFilterEvent(&xevent, (Window)NULL) == True)) {
698         /* do nothing now, the event is consumed by XIM. */
699         continue;
700       }
701 #endif
702       /* When using auto-repeat, some key-press events can actually come *after* the
703        * last key-release. The next code takes care of that. */
704       if (xevent.type == KeyRelease) {
705         m_last_release_keycode = xevent.xkey.keycode;
706         m_last_release_time = xevent.xkey.time;
707       }
708       else if (xevent.type == KeyPress) {
709         if ((xevent.xkey.keycode == m_last_release_keycode) &&
710             ((xevent.xkey.time <= m_last_release_time)))
711           continue;
712       }
713 
714       processEvent(&xevent);
715       anyProcessed = true;
716 
717 #ifdef USE_UNITY_WORKAROUND
718       /* note: processEvent() can't include this code because
719        * KeymapNotify event have no valid window information. */
720 
721       /* the X server generates KeymapNotify event immediately after
722        * every EnterNotify and FocusIn event.  we handle this event
723        * to correct modifier states. */
724       if (xevent.type == FocusIn) {
725         /* use previous event's window, because KeymapNotify event
726          * has no window information. */
727         GHOST_WindowX11 *window = findGhostWindow(xevent.xany.window);
728         if (window && XPending(m_display) >= 2) {
729           XNextEvent(m_display, &xevent);
730 
731           if (xevent.type == KeymapNotify) {
732             XEvent xev_next;
733 
734             /* check if KeyPress or KeyRelease event was generated
735              * in order to confirm the window is active. */
736             XPeekEvent(m_display, &xev_next);
737 
738             if (xev_next.type == KeyPress || xev_next.type == KeyRelease) {
739               /* XK_Hyper_L/R currently unused */
740               const static KeySym modifiers[8] = {
741                   XK_Shift_L,
742                   XK_Shift_R,
743                   XK_Control_L,
744                   XK_Control_R,
745                   XK_Alt_L,
746                   XK_Alt_R,
747                   XK_Super_L,
748                   XK_Super_R,
749               };
750 
751               for (int i = 0; i < (sizeof(modifiers) / sizeof(*modifiers)); i++) {
752                 KeyCode kc = XKeysymToKeycode(m_display, modifiers[i]);
753                 if (kc != 0 && ((xevent.xkeymap.key_vector[kc >> 3] >> (kc & 7)) & 1) != 0) {
754                   pushEvent(new GHOST_EventKey(getMilliSeconds(),
755                                                GHOST_kEventKeyDown,
756                                                window,
757                                                ghost_key_from_keysym(modifiers[i]),
758                                                '\0',
759                                                NULL,
760                                                false));
761                 }
762               }
763             }
764           }
765         }
766       }
767 #endif /* USE_UNITY_WORKAROUND */
768     }
769 
770     if (generateWindowExposeEvents()) {
771       anyProcessed = true;
772     }
773 
774 #ifdef WITH_INPUT_NDOF
775     if (static_cast<GHOST_NDOFManagerUnix *>(m_ndofManager)->processEvents()) {
776       anyProcessed = true;
777     }
778 #endif
779 
780   } while (waitForEvent && !anyProcessed);
781 
782   return anyProcessed;
783 }
784 
785 #ifdef WITH_X11_XINPUT
checkTabletProximity(Display * display,XDevice * device)786 static bool checkTabletProximity(Display *display, XDevice *device)
787 {
788   /* we could have true/false/not-found return value, but for now false is OK */
789 
790   /* see: state.c from xinput, to get more data out of the device */
791   XDeviceState *state;
792 
793   if (device == NULL) {
794     return false;
795   }
796 
797   /* needed since unplugging will abort() without this */
798   GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
799 
800   state = XQueryDeviceState(display, device);
801 
802   GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
803 
804   if (state) {
805     XInputClass *cls = state->data;
806     // printf("%d class%s :\n", state->num_classes,
807     //       (state->num_classes > 1) ? "es" : "");
808     for (int loop = 0; loop < state->num_classes; loop++) {
809       switch (cls->c_class) {
810         case ValuatorClass:
811           XValuatorState *val_state = (XValuatorState *)cls;
812           // printf("ValuatorClass Mode=%s Proximity=%s\n",
813           //        val_state->mode & 1 ? "Absolute" : "Relative",
814           //        val_state->mode & 2 ? "Out" : "In");
815 
816           if ((val_state->mode & 2) == 0) {
817             XFreeDeviceState(state);
818             return true;
819           }
820           break;
821       }
822       cls = (XInputClass *)((char *)cls + cls->length);
823     }
824     XFreeDeviceState(state);
825   }
826   return false;
827 }
828 #endif /* WITH_X11_XINPUT */
829 
processEvent(XEvent * xe)830 void GHOST_SystemX11::processEvent(XEvent *xe)
831 {
832   GHOST_WindowX11 *window = findGhostWindow(xe->xany.window);
833   GHOST_Event *g_event = NULL;
834 
835   /* Detect auto-repeat. */
836   bool is_repeat = false;
837   if (xe->type == KeyPress || xe->type == KeyRelease) {
838     XKeyEvent *xke = &(xe->xkey);
839 
840     /* Set to true if this key will repeat. */
841     bool is_repeat_keycode = false;
842 
843     if (m_xkb_descr != NULL) {
844       /* Use XKB support. */
845       is_repeat_keycode = (
846           /* Should always be true, check just in case. */
847           (xke->keycode < (XkbPerKeyBitArraySize << 3)) &&
848           bit_is_on(m_xkb_descr->ctrls->per_key_repeat, xke->keycode));
849     }
850     else {
851       /* No XKB support (filter by modifier). */
852       switch (XLookupKeysym(xke, 0)) {
853         case XK_Shift_L:
854         case XK_Shift_R:
855         case XK_Control_L:
856         case XK_Control_R:
857         case XK_Alt_L:
858         case XK_Alt_R:
859         case XK_Super_L:
860         case XK_Super_R:
861         case XK_Hyper_L:
862         case XK_Hyper_R:
863         case XK_Caps_Lock:
864         case XK_Scroll_Lock:
865         case XK_Num_Lock: {
866           break;
867         }
868         default: {
869           is_repeat_keycode = true;
870         }
871       }
872     }
873 
874     if (is_repeat_keycode) {
875       if (xe->type == KeyPress) {
876         if (m_keycode_last_repeat_key == xke->keycode) {
877           is_repeat = true;
878         }
879         m_keycode_last_repeat_key = xke->keycode;
880       }
881       else {
882         if (m_keycode_last_repeat_key == xke->keycode) {
883           m_keycode_last_repeat_key = (uint)-1;
884         }
885       }
886     }
887   }
888   else if (xe->type == EnterNotify) {
889     /* We can't tell how the key state changed, clear it to avoid stuck keys. */
890     m_keycode_last_repeat_key = (uint)-1;
891   }
892 
893 #ifdef USE_XINPUT_HOTPLUG
894   /* Hot-Plug support */
895   if (m_xinput_version.present) {
896     XEventClass class_presence;
897     int xi_presence;
898 
899     DevicePresence(m_display, xi_presence, class_presence);
900     (void)class_presence;
901 
902     if (xe->type == xi_presence) {
903       XDevicePresenceNotifyEvent *notify_event = (XDevicePresenceNotifyEvent *)xe;
904       if ((notify_event->devchange == DeviceEnabled) ||
905           (notify_event->devchange == DeviceDisabled) ||
906           (notify_event->devchange == DeviceAdded) || (notify_event->devchange == DeviceRemoved)) {
907         refreshXInputDevices();
908 
909         /* update all window events */
910         {
911           const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
912           vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
913           vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
914 
915           for (; win_it != win_end; ++win_it) {
916             GHOST_WindowX11 *window_xinput = static_cast<GHOST_WindowX11 *>(*win_it);
917             window_xinput->refreshXInputDevices();
918           }
919         }
920       }
921     }
922   }
923 #endif /* USE_XINPUT_HOTPLUG */
924 
925   if (!window) {
926     return;
927   }
928 
929 #ifdef WITH_X11_XINPUT
930   /* Proximity-Out Events are not reliable, if the tablet is active - check on each event
931    * this adds a little overhead but only while the tablet is in use.
932    * in the future we could have a ghost call window->CheckTabletProximity()
933    * but for now enough parts of the code are checking 'Active'
934    * - campbell */
935   if (window->GetTabletData().Active != GHOST_kTabletModeNone) {
936     bool any_proximity = false;
937 
938     for (GHOST_TabletX11 &xtablet : m_xtablets) {
939       if (checkTabletProximity(xe->xany.display, xtablet.Device)) {
940         any_proximity = true;
941       }
942     }
943 
944     if (!any_proximity) {
945       // printf("proximity disable\n");
946       window->GetTabletData().Active = GHOST_kTabletModeNone;
947     }
948   }
949 #endif /* WITH_X11_XINPUT */
950   switch (xe->type) {
951     case Expose: {
952       XExposeEvent &xee = xe->xexpose;
953 
954       if (xee.count == 0) {
955         /* Only generate a single expose event
956          * per read of the event queue. */
957 
958         g_event = new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window);
959       }
960       break;
961     }
962 
963     case MotionNotify: {
964       XMotionEvent &xme = xe->xmotion;
965 
966       bool is_tablet = window->GetTabletData().Active != GHOST_kTabletModeNone;
967 
968       if (is_tablet == false && window->getCursorGrabModeIsWarp()) {
969         GHOST_TInt32 x_new = xme.x_root;
970         GHOST_TInt32 y_new = xme.y_root;
971         GHOST_TInt32 x_accum, y_accum;
972         GHOST_Rect bounds;
973 
974         /* fallback to window bounds */
975         if (window->getCursorGrabBounds(bounds) == GHOST_kFailure)
976           window->getClientBounds(bounds);
977 
978         /* Could also clamp to screen bounds wrap with a window outside the view will fail atm.
979          * Use offset of 8 in case the window is at screen bounds. */
980         bounds.wrapPoint(x_new, y_new, 8, window->getCursorGrabAxis());
981 
982         window->getCursorGrabAccum(x_accum, y_accum);
983 
984         if (x_new != xme.x_root || y_new != xme.y_root) {
985           /* Use time of last event to avoid wrapping several times on the 'same' actual wrap.
986            * Note that we need to deal with X and Y separately as those might wrap at the same time
987            * but still in two different events (corner case, see T74918).
988            * We also have to add a few extra milliseconds of 'padding', as sometimes we get two
989            * close events that will generate extra wrap on the same axis within those few
990            * milliseconds. */
991           if (x_new != xme.x_root && xme.time > m_last_warp_x) {
992             x_accum += (xme.x_root - x_new);
993             m_last_warp_x = lastEventTime(xme.time) + 25;
994           }
995           if (y_new != xme.y_root && xme.time > m_last_warp_y) {
996             y_accum += (xme.y_root - y_new);
997             m_last_warp_y = lastEventTime(xme.time) + 25;
998           }
999           window->setCursorGrabAccum(x_accum, y_accum);
1000           /* When wrapping we don't need to add an event because the
1001            * #setCursorPosition call will cause a new event after. */
1002           setCursorPosition(x_new, y_new); /* wrap */
1003         }
1004         else {
1005           g_event = new GHOST_EventCursor(getMilliSeconds(),
1006                                           GHOST_kEventCursorMove,
1007                                           window,
1008                                           xme.x_root + x_accum,
1009                                           xme.y_root + y_accum,
1010                                           window->GetTabletData());
1011         }
1012       }
1013       else {
1014         g_event = new GHOST_EventCursor(getMilliSeconds(),
1015                                         GHOST_kEventCursorMove,
1016                                         window,
1017                                         xme.x_root,
1018                                         xme.y_root,
1019                                         window->GetTabletData());
1020       }
1021       break;
1022     }
1023 
1024     case KeyPress:
1025     case KeyRelease: {
1026       XKeyEvent *xke = &(xe->xkey);
1027       KeySym key_sym;
1028       char ascii;
1029 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
1030       /* utf8_array[] is initial buffer used for Xutf8LookupString().
1031        * if the length of the utf8 string exceeds this array, allocate
1032        * another memory area and call Xutf8LookupString() again.
1033        * the last 5 bytes are used to avoid segfault that might happen
1034        * at the end of this buffer when the constructor of GHOST_EventKey
1035        * reads 6 bytes regardless of the effective data length. */
1036       char utf8_array[16 * 6 + 5]; /* 16 utf8 characters */
1037       char *utf8_buf = utf8_array;
1038       int len = 1; /* at least one null character will be stored */
1039 #else
1040       char *utf8_buf = NULL;
1041 #endif
1042 
1043       GHOST_TEventType type = (xke->type == KeyPress) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
1044 
1045       GHOST_TKey gkey;
1046 
1047 #ifdef USE_NON_LATIN_KB_WORKAROUND
1048       /* XXX Code below is kinda awfully convoluted... Issues are:
1049        *
1050        *     - In keyboards like latin ones, numbers need a 'Shift' to be accessed but key_sym
1051        *       is unmodified (or anyone swapping the keys with xmodmap).
1052        *
1053        *     - XLookupKeysym seems to always use first defined keymap (see T47228), which generates
1054        *       keycodes unusable by ghost_key_from_keysym for non-Latin-compatible keymaps.
1055        *
1056        * To address this, we:
1057        *
1058        *     - Try to get a 'number' key_sym using XLookupKeysym (with virtual shift modifier),
1059        *       in a very restrictive set of cases.
1060        *     - Fallback to XLookupString to get a key_sym from active user-defined keymap.
1061        *
1062        * Note that:
1063        *     - This effectively 'lock' main number keys to always output number events
1064        *       (except when using alt-gr).
1065        *     - This enforces users to use an ascii-compatible keymap with Blender -
1066        *       but at least it gives predictable and consistent results.
1067        *
1068        * Also, note that nothing in XLib sources [1] makes it obvious why those two functions give
1069        * different key_sym results...
1070        *
1071        * [1] http://cgit.freedesktop.org/xorg/lib/libX11/tree/src/KeyBind.c
1072        */
1073       KeySym key_sym_str;
1074       /* Mode_switch 'modifier' is AltGr - when this one or Shift are enabled,
1075        * we do not want to apply that 'forced number' hack. */
1076       const unsigned int mode_switch_mask = XkbKeysymToModifiers(xke->display, XK_Mode_switch);
1077       const unsigned int number_hack_forbidden_kmods_mask = mode_switch_mask | ShiftMask;
1078       if ((xke->keycode >= 10 && xke->keycode < 20) &&
1079           ((xke->state & number_hack_forbidden_kmods_mask) == 0)) {
1080         key_sym = XLookupKeysym(xke, ShiftMask);
1081         if (!((key_sym >= XK_0) && (key_sym <= XK_9))) {
1082           key_sym = XLookupKeysym(xke, 0);
1083         }
1084       }
1085       else {
1086         key_sym = XLookupKeysym(xke, 0);
1087       }
1088 
1089       if (!XLookupString(xke, &ascii, 1, &key_sym_str, NULL)) {
1090         ascii = '\0';
1091       }
1092 
1093       /* Only allow a limited set of keys from XLookupKeysym,
1094        * all others we take from XLookupString, unless it gives unknown key... */
1095       gkey = ghost_key_from_keysym_or_keycode(key_sym, m_xkb_descr, xke->keycode);
1096       switch (gkey) {
1097         case GHOST_kKeyRightAlt:
1098         case GHOST_kKeyLeftAlt:
1099         case GHOST_kKeyRightShift:
1100         case GHOST_kKeyLeftShift:
1101         case GHOST_kKeyRightControl:
1102         case GHOST_kKeyLeftControl:
1103         case GHOST_kKeyOS:
1104         case GHOST_kKey0:
1105         case GHOST_kKey1:
1106         case GHOST_kKey2:
1107         case GHOST_kKey3:
1108         case GHOST_kKey4:
1109         case GHOST_kKey5:
1110         case GHOST_kKey6:
1111         case GHOST_kKey7:
1112         case GHOST_kKey8:
1113         case GHOST_kKey9:
1114         case GHOST_kKeyNumpad0:
1115         case GHOST_kKeyNumpad1:
1116         case GHOST_kKeyNumpad2:
1117         case GHOST_kKeyNumpad3:
1118         case GHOST_kKeyNumpad4:
1119         case GHOST_kKeyNumpad5:
1120         case GHOST_kKeyNumpad6:
1121         case GHOST_kKeyNumpad7:
1122         case GHOST_kKeyNumpad8:
1123         case GHOST_kKeyNumpad9:
1124         case GHOST_kKeyNumpadPeriod:
1125         case GHOST_kKeyNumpadEnter:
1126         case GHOST_kKeyNumpadPlus:
1127         case GHOST_kKeyNumpadMinus:
1128         case GHOST_kKeyNumpadAsterisk:
1129         case GHOST_kKeyNumpadSlash:
1130           break;
1131         default: {
1132           GHOST_TKey gkey_str = ghost_key_from_keysym(key_sym_str);
1133           if (gkey_str != GHOST_kKeyUnknown) {
1134             gkey = gkey_str;
1135           }
1136         }
1137       }
1138 #else
1139       /* In keyboards like latin ones,
1140        * numbers needs a 'Shift' to be accessed but key_sym
1141        * is unmodified (or anyone swapping the keys with xmodmap).
1142        *
1143        * Here we look at the 'Shifted' version of the key.
1144        * If it is a number, then we take it instead of the normal key.
1145        *
1146        * The modified key is sent in the 'ascii's variable anyway.
1147        */
1148       if ((xke->keycode >= 10 && xke->keycode < 20) &&
1149           ((key_sym = XLookupKeysym(xke, ShiftMask)) >= XK_0) && (key_sym <= XK_9)) {
1150         /* pass (keep shift'ed key_sym) */
1151       }
1152       else {
1153         /* regular case */
1154         key_sym = XLookupKeysym(xke, 0);
1155       }
1156 
1157       gkey = ghost_key_from_keysym_or_keycode(key_sym, m_xkb_descr, xke->keycode);
1158 
1159       if (!XLookupString(xke, &ascii, 1, NULL, NULL)) {
1160         ascii = '\0';
1161       }
1162 #endif
1163 
1164 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
1165       /* getting unicode on key-up events gives XLookupNone status */
1166       XIC xic = window->getX11_XIC();
1167       if (xic && xke->type == KeyPress) {
1168         Status status;
1169 
1170         /* use utf8 because its not locale depentant, from xorg docs */
1171         if (!(len = Xutf8LookupString(
1172                   xic, xke, utf8_buf, sizeof(utf8_array) - 5, &key_sym, &status))) {
1173           utf8_buf[0] = '\0';
1174         }
1175 
1176         if (status == XBufferOverflow) {
1177           utf8_buf = (char *)malloc(len + 5);
1178           len = Xutf8LookupString(xic, xke, utf8_buf, len, &key_sym, &status);
1179         }
1180 
1181         if ((status == XLookupChars || status == XLookupBoth)) {
1182           if ((unsigned char)utf8_buf[0] >= 32) { /* not an ascii control character */
1183             /* do nothing for now, this is valid utf8 */
1184           }
1185           else {
1186             utf8_buf[0] = '\0';
1187           }
1188         }
1189         else if (status == XLookupKeySym) {
1190           /* this key doesn't have a text representation, it is a command
1191            * key of some sort */
1192         }
1193         else {
1194           printf("Bad keycode lookup. Keysym 0x%x Status: %s\n",
1195                  (unsigned int)key_sym,
1196                  (status == XLookupNone ?
1197                       "XLookupNone" :
1198                       status == XLookupKeySym ? "XLookupKeySym" : "Unknown status"));
1199 
1200           printf("'%.*s' %p %p\n", len, utf8_buf, xic, m_xim);
1201         }
1202       }
1203       else {
1204         utf8_buf[0] = '\0';
1205       }
1206 #endif
1207 
1208       g_event = new GHOST_EventKey(
1209           getMilliSeconds(), type, window, gkey, ascii, utf8_buf, is_repeat);
1210 
1211 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
1212       /* when using IM for some languages such as Japanese,
1213        * one event inserts multiple utf8 characters */
1214       if (xic && xke->type == KeyPress) {
1215         unsigned char c;
1216         int i = 0;
1217         while (1) {
1218           /* search character boundary */
1219           if ((unsigned char)utf8_buf[i++] > 0x7f) {
1220             for (; i < len; ++i) {
1221               c = utf8_buf[i];
1222               if (c < 0x80 || c > 0xbf)
1223                 break;
1224             }
1225           }
1226 
1227           if (i >= len)
1228             break;
1229 
1230           /* enqueue previous character */
1231           pushEvent(g_event);
1232 
1233           g_event = new GHOST_EventKey(
1234               getMilliSeconds(), type, window, gkey, '\0', &utf8_buf[i], is_repeat);
1235         }
1236       }
1237 
1238       if (utf8_buf != utf8_array)
1239         free(utf8_buf);
1240 #endif
1241 
1242       break;
1243     }
1244 
1245     case ButtonPress:
1246     case ButtonRelease: {
1247       XButtonEvent &xbe = xe->xbutton;
1248       GHOST_TButtonMask gbmask = GHOST_kButtonMaskLeft;
1249       GHOST_TEventType type = (xbe.type == ButtonPress) ? GHOST_kEventButtonDown :
1250                                                           GHOST_kEventButtonUp;
1251 
1252       /* process wheel mouse events and break, only pass on press events */
1253       if (xbe.button == Button4) {
1254         if (xbe.type == ButtonPress)
1255           g_event = new GHOST_EventWheel(getMilliSeconds(), window, 1);
1256         break;
1257       }
1258       else if (xbe.button == Button5) {
1259         if (xbe.type == ButtonPress)
1260           g_event = new GHOST_EventWheel(getMilliSeconds(), window, -1);
1261         break;
1262       }
1263 
1264       /* process rest of normal mouse buttons */
1265       if (xbe.button == Button1)
1266         gbmask = GHOST_kButtonMaskLeft;
1267       else if (xbe.button == Button2)
1268         gbmask = GHOST_kButtonMaskMiddle;
1269       else if (xbe.button == Button3)
1270         gbmask = GHOST_kButtonMaskRight;
1271       /* It seems events 6 and 7 are for horizontal scrolling.
1272        * you can re-order button mapping like this... (swaps 6,7 with 8,9)
1273        *   xmodmap -e "pointer = 1 2 3 4 5 8 9 6 7"
1274        */
1275       else if (xbe.button == 6)
1276         gbmask = GHOST_kButtonMaskButton6;
1277       else if (xbe.button == 7)
1278         gbmask = GHOST_kButtonMaskButton7;
1279       else if (xbe.button == 8)
1280         gbmask = GHOST_kButtonMaskButton4;
1281       else if (xbe.button == 9)
1282         gbmask = GHOST_kButtonMaskButton5;
1283       else
1284         break;
1285 
1286       g_event = new GHOST_EventButton(
1287           getMilliSeconds(), type, window, gbmask, window->GetTabletData());
1288       break;
1289     }
1290 
1291     /* change of size, border, layer etc. */
1292     case ConfigureNotify: {
1293       /* XConfigureEvent & xce = xe->xconfigure; */
1294 
1295       g_event = new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window);
1296       break;
1297     }
1298 
1299     case FocusIn:
1300     case FocusOut: {
1301       XFocusChangeEvent &xfe = xe->xfocus;
1302 
1303       /* TODO: make sure this is the correct place for activate/deactivate */
1304       // printf("X: focus %s for window %d\n",
1305       //        xfe.type == FocusIn ? "in" : "out", (int) xfe.window);
1306 
1307       /* May have to look at the type of event and filter some out. */
1308 
1309       GHOST_TEventType gtype = (xfe.type == FocusIn) ? GHOST_kEventWindowActivate :
1310                                                        GHOST_kEventWindowDeactivate;
1311 
1312 #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
1313       XIC xic = window->getX11_XIC();
1314       if (xic) {
1315         if (xe->type == FocusIn)
1316           XSetICFocus(xic);
1317         else
1318           XUnsetICFocus(xic);
1319       }
1320 #endif
1321 
1322       g_event = new GHOST_Event(getMilliSeconds(), gtype, window);
1323       break;
1324     }
1325     case ClientMessage: {
1326       XClientMessageEvent &xcme = xe->xclient;
1327 
1328       if (((Atom)xcme.data.l[0]) == m_atom.WM_DELETE_WINDOW) {
1329         g_event = new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window);
1330       }
1331       else if (((Atom)xcme.data.l[0]) == m_atom.WM_TAKE_FOCUS) {
1332         XWindowAttributes attr;
1333         Window fwin;
1334         int revert_to;
1335 
1336         /* as ICCCM say, we need reply this event
1337          * with a #SetInputFocus, the data[1] have
1338          * the valid timestamp (send by the wm).
1339          *
1340          * Some WM send this event before the
1341          * window is really mapped (for example
1342          * change from virtual desktop), so we need
1343          * to be sure that our windows is mapped
1344          * or this call fail and close blender.
1345          */
1346         if (XGetWindowAttributes(m_display, xcme.window, &attr) == True) {
1347           if (XGetInputFocus(m_display, &fwin, &revert_to) == True) {
1348             if (attr.map_state == IsViewable) {
1349               if (fwin != xcme.window)
1350                 XSetInputFocus(m_display, xcme.window, RevertToParent, xcme.data.l[1]);
1351             }
1352           }
1353         }
1354       }
1355       else {
1356 #ifdef WITH_XDND
1357         /* try to handle drag event
1358          * (if there's no such events, #GHOST_HandleClientMessage will return zero) */
1359         if (window->getDropTarget()->GHOST_HandleClientMessage(xe) == false) {
1360           /* Unknown client message, ignore */
1361         }
1362 #else
1363         /* Unknown client message, ignore */
1364 #endif
1365       }
1366 
1367       break;
1368     }
1369 
1370     case DestroyNotify:
1371       ::exit(-1);
1372     /* We're not interested in the following things.(yet...) */
1373     case NoExpose:
1374     case GraphicsExpose:
1375       break;
1376 
1377     case EnterNotify:
1378     case LeaveNotify: {
1379       /* #XCrossingEvents pointer leave enter window.
1380        * also do cursor move here, #MotionNotify only
1381        * happens when motion starts & ends inside window.
1382        * we only do moves when the crossing mode is 'normal'
1383        * (really crossing between windows) since some window-managers
1384        * also send grab/un-grab crossings for mouse-wheel events.
1385        */
1386       XCrossingEvent &xce = xe->xcrossing;
1387       if (xce.mode == NotifyNormal) {
1388         g_event = new GHOST_EventCursor(getMilliSeconds(),
1389                                         GHOST_kEventCursorMove,
1390                                         window,
1391                                         xce.x_root,
1392                                         xce.y_root,
1393                                         window->GetTabletData());
1394       }
1395 
1396       // printf("X: %s window %d\n",
1397       //        xce.type == EnterNotify ? "entering" : "leaving", (int) xce.window);
1398 
1399       if (xce.type == EnterNotify)
1400         m_windowManager->setActiveWindow(window);
1401       else
1402         m_windowManager->setWindowInactive(window);
1403 
1404       break;
1405     }
1406     case MapNotify:
1407       /*
1408        * From ICCCM:
1409        * [ Clients can select for #StructureNotify on their
1410        *   top-level windows to track transition between
1411        *   Normal and Iconic states. Receipt of a #MapNotify
1412        *   event will indicate a transition to the Normal
1413        *   state, and receipt of an #UnmapNotify event will
1414        *   indicate a transition to the Iconic state. ]
1415        */
1416       if (window->m_post_init == True) {
1417         /*
1418          * Now we are sure that the window is
1419          * mapped, so only need change the state.
1420          */
1421         window->setState(window->m_post_state);
1422         window->m_post_init = False;
1423       }
1424       break;
1425     case UnmapNotify:
1426       break;
1427     case MappingNotify:
1428     case ReparentNotify:
1429       break;
1430     case SelectionRequest: {
1431       XEvent nxe;
1432       Atom target, utf8_string, string, compound_text, c_string;
1433       XSelectionRequestEvent *xse = &xe->xselectionrequest;
1434 
1435       target = XInternAtom(m_display, "TARGETS", False);
1436       utf8_string = XInternAtom(m_display, "UTF8_STRING", False);
1437       string = XInternAtom(m_display, "STRING", False);
1438       compound_text = XInternAtom(m_display, "COMPOUND_TEXT", False);
1439       c_string = XInternAtom(m_display, "C_STRING", False);
1440 
1441       /* support obsolete clients */
1442       if (xse->property == None) {
1443         xse->property = xse->target;
1444       }
1445 
1446       nxe.xselection.type = SelectionNotify;
1447       nxe.xselection.requestor = xse->requestor;
1448       nxe.xselection.property = xse->property;
1449       nxe.xselection.display = xse->display;
1450       nxe.xselection.selection = xse->selection;
1451       nxe.xselection.target = xse->target;
1452       nxe.xselection.time = xse->time;
1453 
1454       /* Check to see if the requester is asking for String */
1455       if (xse->target == utf8_string || xse->target == string || xse->target == compound_text ||
1456           xse->target == c_string) {
1457         if (xse->selection == XInternAtom(m_display, "PRIMARY", False)) {
1458           XChangeProperty(m_display,
1459                           xse->requestor,
1460                           xse->property,
1461                           xse->target,
1462                           8,
1463                           PropModeReplace,
1464                           (unsigned char *)txt_select_buffer,
1465                           strlen(txt_select_buffer));
1466         }
1467         else if (xse->selection == XInternAtom(m_display, "CLIPBOARD", False)) {
1468           XChangeProperty(m_display,
1469                           xse->requestor,
1470                           xse->property,
1471                           xse->target,
1472                           8,
1473                           PropModeReplace,
1474                           (unsigned char *)txt_cut_buffer,
1475                           strlen(txt_cut_buffer));
1476         }
1477       }
1478       else if (xse->target == target) {
1479         Atom alist[5];
1480         alist[0] = target;
1481         alist[1] = utf8_string;
1482         alist[2] = string;
1483         alist[3] = compound_text;
1484         alist[4] = c_string;
1485         XChangeProperty(m_display,
1486                         xse->requestor,
1487                         xse->property,
1488                         xse->target,
1489                         32,
1490                         PropModeReplace,
1491                         (unsigned char *)alist,
1492                         5);
1493         XFlush(m_display);
1494       }
1495       else {
1496         /* Change property to None because we do not support anything but STRING */
1497         nxe.xselection.property = None;
1498       }
1499 
1500       /* Send the event to the client 0 0 == False, #SelectionNotify */
1501       XSendEvent(m_display, xse->requestor, 0, 0, &nxe);
1502       XFlush(m_display);
1503       break;
1504     }
1505 
1506     default: {
1507 #ifdef WITH_X11_XINPUT
1508       for (GHOST_TabletX11 &xtablet : m_xtablets) {
1509         if (xe->type == xtablet.MotionEvent || xe->type == xtablet.PressEvent) {
1510           XDeviceMotionEvent *data = (XDeviceMotionEvent *)xe;
1511           if (data->deviceid != xtablet.ID) {
1512             continue;
1513           }
1514 
1515           const unsigned char axis_first = data->first_axis;
1516           const unsigned char axes_end = axis_first + data->axes_count; /* after the last */
1517           int axis_value;
1518 
1519           /* stroke might begin without leading ProxyIn event,
1520            * this happens when window is opened when stylus is already hovering
1521            * around tablet surface */
1522           window->GetTabletData().Active = xtablet.mode;
1523 
1524           /* Note: This event might be generated with incomplete data-set
1525            * (don't exactly know why, looks like in some cases, if the value does not change,
1526            * it is not included in subsequent #XDeviceMotionEvent events).
1527            * So we have to check which values this event actually contains!
1528            */
1529 
1530 #  define AXIS_VALUE_GET(axis, val) \
1531     ((axis_first <= axis && axes_end > axis) && \
1532      ((void)(val = data->axis_data[axis - axis_first]), true))
1533 
1534           if (AXIS_VALUE_GET(2, axis_value)) {
1535             window->GetTabletData().Pressure = axis_value / ((float)xtablet.PressureLevels);
1536           }
1537 
1538           /* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
1539            * but I got garbage data without it. Found it in the xidump.c source --matt
1540            *
1541            * The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
1542            * some drivers do not properly set the whole int value? Since we convert to float
1543            * afterward, I don't think we need to cast to short here, but do not have a device to
1544            * check this. --mont29
1545            */
1546           if (AXIS_VALUE_GET(3, axis_value)) {
1547             window->GetTabletData().Xtilt = (short)(axis_value & 0xffff) /
1548                                             ((float)xtablet.XtiltLevels);
1549           }
1550           if (AXIS_VALUE_GET(4, axis_value)) {
1551             window->GetTabletData().Ytilt = (short)(axis_value & 0xffff) /
1552                                             ((float)xtablet.YtiltLevels);
1553           }
1554 
1555 #  undef AXIS_VALUE_GET
1556         }
1557         else if (xe->type == xtablet.ProxInEvent) {
1558           XProximityNotifyEvent *data = (XProximityNotifyEvent *)xe;
1559           if (data->deviceid != xtablet.ID) {
1560             continue;
1561           }
1562 
1563           window->GetTabletData().Active = xtablet.mode;
1564         }
1565         else if (xe->type == xtablet.ProxOutEvent) {
1566           window->GetTabletData().Active = GHOST_kTabletModeNone;
1567         }
1568       }
1569 #endif  // WITH_X11_XINPUT
1570       break;
1571     }
1572   }
1573 
1574   if (g_event) {
1575     pushEvent(g_event);
1576   }
1577 }
1578 
getModifierKeys(GHOST_ModifierKeys & keys) const1579 GHOST_TSuccess GHOST_SystemX11::getModifierKeys(GHOST_ModifierKeys &keys) const
1580 {
1581 
1582   /* Analyze the masks returned from #XQueryPointer. */
1583 
1584   memset((void *)m_keyboard_vector, 0, sizeof(m_keyboard_vector));
1585 
1586   XQueryKeymap(m_display, (char *)m_keyboard_vector);
1587 
1588   /* Now translate key symbols into key-codes and test with vector. */
1589 
1590   const static KeyCode shift_l = XKeysymToKeycode(m_display, XK_Shift_L);
1591   const static KeyCode shift_r = XKeysymToKeycode(m_display, XK_Shift_R);
1592   const static KeyCode control_l = XKeysymToKeycode(m_display, XK_Control_L);
1593   const static KeyCode control_r = XKeysymToKeycode(m_display, XK_Control_R);
1594   const static KeyCode alt_l = XKeysymToKeycode(m_display, XK_Alt_L);
1595   const static KeyCode alt_r = XKeysymToKeycode(m_display, XK_Alt_R);
1596   const static KeyCode super_l = XKeysymToKeycode(m_display, XK_Super_L);
1597   const static KeyCode super_r = XKeysymToKeycode(m_display, XK_Super_R);
1598 
1599   /* shift */
1600   keys.set(GHOST_kModifierKeyLeftShift,
1601            ((m_keyboard_vector[shift_l >> 3] >> (shift_l & 7)) & 1) != 0);
1602   keys.set(GHOST_kModifierKeyRightShift,
1603            ((m_keyboard_vector[shift_r >> 3] >> (shift_r & 7)) & 1) != 0);
1604   /* control */
1605   keys.set(GHOST_kModifierKeyLeftControl,
1606            ((m_keyboard_vector[control_l >> 3] >> (control_l & 7)) & 1) != 0);
1607   keys.set(GHOST_kModifierKeyRightControl,
1608            ((m_keyboard_vector[control_r >> 3] >> (control_r & 7)) & 1) != 0);
1609   /* alt */
1610   keys.set(GHOST_kModifierKeyLeftAlt, ((m_keyboard_vector[alt_l >> 3] >> (alt_l & 7)) & 1) != 0);
1611   keys.set(GHOST_kModifierKeyRightAlt, ((m_keyboard_vector[alt_r >> 3] >> (alt_r & 7)) & 1) != 0);
1612   /* super (windows) - only one GHOST-kModifierKeyOS, so mapping to either */
1613   keys.set(GHOST_kModifierKeyOS,
1614            (((m_keyboard_vector[super_l >> 3] >> (super_l & 7)) & 1) ||
1615             ((m_keyboard_vector[super_r >> 3] >> (super_r & 7)) & 1)) != 0);
1616 
1617   return GHOST_kSuccess;
1618 }
1619 
getButtons(GHOST_Buttons & buttons) const1620 GHOST_TSuccess GHOST_SystemX11::getButtons(GHOST_Buttons &buttons) const
1621 {
1622   Window root_return, child_return;
1623   int rx, ry, wx, wy;
1624   unsigned int mask_return;
1625 
1626   if (XQueryPointer(m_display,
1627                     RootWindow(m_display, DefaultScreen(m_display)),
1628                     &root_return,
1629                     &child_return,
1630                     &rx,
1631                     &ry,
1632                     &wx,
1633                     &wy,
1634                     &mask_return) == True) {
1635     buttons.set(GHOST_kButtonMaskLeft, (mask_return & Button1Mask) != 0);
1636     buttons.set(GHOST_kButtonMaskMiddle, (mask_return & Button2Mask) != 0);
1637     buttons.set(GHOST_kButtonMaskRight, (mask_return & Button3Mask) != 0);
1638   }
1639   else {
1640     return GHOST_kFailure;
1641   }
1642 
1643   return GHOST_kSuccess;
1644 }
1645 
getCursorPosition_impl(Display * display,GHOST_TInt32 & x,GHOST_TInt32 & y,Window * child_return)1646 static GHOST_TSuccess getCursorPosition_impl(Display *display,
1647                                              GHOST_TInt32 &x,
1648                                              GHOST_TInt32 &y,
1649                                              Window *child_return)
1650 {
1651   int rx, ry, wx, wy;
1652   unsigned int mask_return;
1653   Window root_return;
1654 
1655   if (XQueryPointer(display,
1656                     RootWindow(display, DefaultScreen(display)),
1657                     &root_return,
1658                     child_return,
1659                     &rx,
1660                     &ry,
1661                     &wx,
1662                     &wy,
1663                     &mask_return) == False) {
1664     return GHOST_kFailure;
1665   }
1666   else {
1667     x = rx;
1668     y = ry;
1669   }
1670   return GHOST_kSuccess;
1671 }
1672 
getCursorPosition(GHOST_TInt32 & x,GHOST_TInt32 & y) const1673 GHOST_TSuccess GHOST_SystemX11::getCursorPosition(GHOST_TInt32 &x, GHOST_TInt32 &y) const
1674 {
1675   Window child_return;
1676   return getCursorPosition_impl(m_display, x, y, &child_return);
1677 }
1678 
setCursorPosition(GHOST_TInt32 x,GHOST_TInt32 y)1679 GHOST_TSuccess GHOST_SystemX11::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
1680 {
1681 
1682   /* This is a brute force move in screen coordinates
1683    * #XWarpPointer does relative moves so first determine the
1684    * current pointer position. */
1685 
1686   int cx, cy;
1687 
1688 #ifdef WITH_XWAYLAND_HACK
1689   Window child_return = None;
1690   if (getCursorPosition_impl(m_display, cx, cy, &child_return) == GHOST_kFailure) {
1691     return GHOST_kFailure;
1692   }
1693 #else
1694   if (getCursorPosition(cx, cy) == GHOST_kFailure) {
1695     return GHOST_kFailure;
1696   }
1697 #endif
1698 
1699   int relx = x - cx;
1700   int rely = y - cy;
1701 
1702 #ifdef WITH_XWAYLAND_HACK
1703   if (use_xwayland_hack) {
1704     if (child_return != None) {
1705       XFixesHideCursor(m_display, child_return);
1706     }
1707   }
1708 #endif
1709 
1710 #if defined(WITH_X11_XINPUT) && defined(USE_X11_XINPUT_WARP)
1711   if ((m_xinput_version.present) && (m_xinput_version.major_version >= 2)) {
1712     /* Needed to account for XInput "Coordinate Transformation Matrix", see T48901 */
1713     int device_id;
1714     if (XIGetClientPointer(m_display, None, &device_id) != False) {
1715       XIWarpPointer(m_display, device_id, None, None, 0, 0, 0, 0, relx, rely);
1716     }
1717   }
1718   else
1719 #endif
1720   {
1721     XWarpPointer(m_display, None, None, 0, 0, 0, 0, relx, rely);
1722   }
1723 
1724 #ifdef WITH_XWAYLAND_HACK
1725   if (use_xwayland_hack) {
1726     if (child_return != None) {
1727       XFixesShowCursor(m_display, child_return);
1728     }
1729   }
1730 #endif
1731 
1732   XSync(m_display, 0); /* Sync to process all requests */
1733 
1734   return GHOST_kSuccess;
1735 }
1736 
addDirtyWindow(GHOST_WindowX11 * bad_wind)1737 void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind)
1738 {
1739   GHOST_ASSERT((bad_wind != NULL), "addDirtyWindow() NULL ptr trapped (window)");
1740 
1741   m_dirty_windows.push_back(bad_wind);
1742 }
1743 
generateWindowExposeEvents()1744 bool GHOST_SystemX11::generateWindowExposeEvents()
1745 {
1746   vector<GHOST_WindowX11 *>::const_iterator w_start = m_dirty_windows.begin();
1747   vector<GHOST_WindowX11 *>::const_iterator w_end = m_dirty_windows.end();
1748   bool anyProcessed = false;
1749 
1750   for (; w_start != w_end; ++w_start) {
1751     GHOST_Event *g_event = new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, *w_start);
1752 
1753     (*w_start)->validate();
1754 
1755     if (g_event) {
1756       pushEvent(g_event);
1757       anyProcessed = true;
1758     }
1759   }
1760 
1761   m_dirty_windows.clear();
1762   return anyProcessed;
1763 }
1764 
ghost_key_from_keysym_or_keycode(const KeySym keysym,XkbDescPtr xkb_descr,const KeyCode keycode)1765 static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym keysym,
1766                                                    XkbDescPtr xkb_descr,
1767                                                    const KeyCode keycode)
1768 {
1769   GHOST_TKey type = ghost_key_from_keysym(keysym);
1770   if (type == GHOST_kKeyUnknown) {
1771     if (xkb_descr) {
1772       type = ghost_key_from_keycode(xkb_descr, keycode);
1773     }
1774   }
1775   return type;
1776 }
1777 
1778 #define GXMAP(k, x, y) \
1779   case x: \
1780     k = y; \
1781     break
1782 
ghost_key_from_keysym(const KeySym key)1783 static GHOST_TKey ghost_key_from_keysym(const KeySym key)
1784 {
1785   GHOST_TKey type;
1786 
1787   if ((key >= XK_A) && (key <= XK_Z)) {
1788     type = GHOST_TKey(key - XK_A + int(GHOST_kKeyA));
1789   }
1790   else if ((key >= XK_a) && (key <= XK_z)) {
1791     type = GHOST_TKey(key - XK_a + int(GHOST_kKeyA));
1792   }
1793   else if ((key >= XK_0) && (key <= XK_9)) {
1794     type = GHOST_TKey(key - XK_0 + int(GHOST_kKey0));
1795   }
1796   else if ((key >= XK_F1) && (key <= XK_F24)) {
1797     type = GHOST_TKey(key - XK_F1 + int(GHOST_kKeyF1));
1798   }
1799   else {
1800     switch (key) {
1801       GXMAP(type, XK_BackSpace, GHOST_kKeyBackSpace);
1802       GXMAP(type, XK_Tab, GHOST_kKeyTab);
1803       GXMAP(type, XK_ISO_Left_Tab, GHOST_kKeyTab);
1804       GXMAP(type, XK_Return, GHOST_kKeyEnter);
1805       GXMAP(type, XK_Escape, GHOST_kKeyEsc);
1806       GXMAP(type, XK_space, GHOST_kKeySpace);
1807 
1808       GXMAP(type, XK_Linefeed, GHOST_kKeyLinefeed);
1809       GXMAP(type, XK_semicolon, GHOST_kKeySemicolon);
1810       GXMAP(type, XK_period, GHOST_kKeyPeriod);
1811       GXMAP(type, XK_comma, GHOST_kKeyComma);
1812       GXMAP(type, XK_quoteright, GHOST_kKeyQuote);
1813       GXMAP(type, XK_quoteleft, GHOST_kKeyAccentGrave);
1814       GXMAP(type, XK_minus, GHOST_kKeyMinus);
1815       GXMAP(type, XK_plus, GHOST_kKeyPlus);
1816       GXMAP(type, XK_slash, GHOST_kKeySlash);
1817       GXMAP(type, XK_backslash, GHOST_kKeyBackslash);
1818       GXMAP(type, XK_equal, GHOST_kKeyEqual);
1819       GXMAP(type, XK_bracketleft, GHOST_kKeyLeftBracket);
1820       GXMAP(type, XK_bracketright, GHOST_kKeyRightBracket);
1821       GXMAP(type, XK_Pause, GHOST_kKeyPause);
1822 
1823       GXMAP(type, XK_Shift_L, GHOST_kKeyLeftShift);
1824       GXMAP(type, XK_Shift_R, GHOST_kKeyRightShift);
1825       GXMAP(type, XK_Control_L, GHOST_kKeyLeftControl);
1826       GXMAP(type, XK_Control_R, GHOST_kKeyRightControl);
1827       GXMAP(type, XK_Alt_L, GHOST_kKeyLeftAlt);
1828       GXMAP(type, XK_Alt_R, GHOST_kKeyRightAlt);
1829       GXMAP(type, XK_Super_L, GHOST_kKeyOS);
1830       GXMAP(type, XK_Super_R, GHOST_kKeyOS);
1831 
1832       GXMAP(type, XK_Insert, GHOST_kKeyInsert);
1833       GXMAP(type, XK_Delete, GHOST_kKeyDelete);
1834       GXMAP(type, XK_Home, GHOST_kKeyHome);
1835       GXMAP(type, XK_End, GHOST_kKeyEnd);
1836       GXMAP(type, XK_Page_Up, GHOST_kKeyUpPage);
1837       GXMAP(type, XK_Page_Down, GHOST_kKeyDownPage);
1838 
1839       GXMAP(type, XK_Left, GHOST_kKeyLeftArrow);
1840       GXMAP(type, XK_Right, GHOST_kKeyRightArrow);
1841       GXMAP(type, XK_Up, GHOST_kKeyUpArrow);
1842       GXMAP(type, XK_Down, GHOST_kKeyDownArrow);
1843 
1844       GXMAP(type, XK_Caps_Lock, GHOST_kKeyCapsLock);
1845       GXMAP(type, XK_Scroll_Lock, GHOST_kKeyScrollLock);
1846       GXMAP(type, XK_Num_Lock, GHOST_kKeyNumLock);
1847       GXMAP(type, XK_Menu, GHOST_kKeyApp);
1848 
1849       /* keypad events */
1850 
1851       GXMAP(type, XK_KP_0, GHOST_kKeyNumpad0);
1852       GXMAP(type, XK_KP_1, GHOST_kKeyNumpad1);
1853       GXMAP(type, XK_KP_2, GHOST_kKeyNumpad2);
1854       GXMAP(type, XK_KP_3, GHOST_kKeyNumpad3);
1855       GXMAP(type, XK_KP_4, GHOST_kKeyNumpad4);
1856       GXMAP(type, XK_KP_5, GHOST_kKeyNumpad5);
1857       GXMAP(type, XK_KP_6, GHOST_kKeyNumpad6);
1858       GXMAP(type, XK_KP_7, GHOST_kKeyNumpad7);
1859       GXMAP(type, XK_KP_8, GHOST_kKeyNumpad8);
1860       GXMAP(type, XK_KP_9, GHOST_kKeyNumpad9);
1861       GXMAP(type, XK_KP_Decimal, GHOST_kKeyNumpadPeriod);
1862 
1863       GXMAP(type, XK_KP_Insert, GHOST_kKeyNumpad0);
1864       GXMAP(type, XK_KP_End, GHOST_kKeyNumpad1);
1865       GXMAP(type, XK_KP_Down, GHOST_kKeyNumpad2);
1866       GXMAP(type, XK_KP_Page_Down, GHOST_kKeyNumpad3);
1867       GXMAP(type, XK_KP_Left, GHOST_kKeyNumpad4);
1868       GXMAP(type, XK_KP_Begin, GHOST_kKeyNumpad5);
1869       GXMAP(type, XK_KP_Right, GHOST_kKeyNumpad6);
1870       GXMAP(type, XK_KP_Home, GHOST_kKeyNumpad7);
1871       GXMAP(type, XK_KP_Up, GHOST_kKeyNumpad8);
1872       GXMAP(type, XK_KP_Page_Up, GHOST_kKeyNumpad9);
1873       GXMAP(type, XK_KP_Delete, GHOST_kKeyNumpadPeriod);
1874 
1875       GXMAP(type, XK_KP_Enter, GHOST_kKeyNumpadEnter);
1876       GXMAP(type, XK_KP_Add, GHOST_kKeyNumpadPlus);
1877       GXMAP(type, XK_KP_Subtract, GHOST_kKeyNumpadMinus);
1878       GXMAP(type, XK_KP_Multiply, GHOST_kKeyNumpadAsterisk);
1879       GXMAP(type, XK_KP_Divide, GHOST_kKeyNumpadSlash);
1880 
1881       /* Media keys in some keyboards and laptops with XFree86/Xorg */
1882 #ifdef WITH_XF86KEYSYM
1883       GXMAP(type, XF86XK_AudioPlay, GHOST_kKeyMediaPlay);
1884       GXMAP(type, XF86XK_AudioStop, GHOST_kKeyMediaStop);
1885       GXMAP(type, XF86XK_AudioPrev, GHOST_kKeyMediaFirst);
1886       GXMAP(type, XF86XK_AudioRewind, GHOST_kKeyMediaFirst);
1887       GXMAP(type, XF86XK_AudioNext, GHOST_kKeyMediaLast);
1888 #  ifdef XF86XK_AudioForward /* Debian lenny's XF86keysym.h has no XF86XK_AudioForward define */
1889       GXMAP(type, XF86XK_AudioForward, GHOST_kKeyMediaLast);
1890 #  endif
1891 #endif
1892       default:
1893 #ifdef WITH_GHOST_DEBUG
1894         printf("%s: unknown key: %lu / 0x%lx\n", __func__, key, key);
1895 #endif
1896         type = GHOST_kKeyUnknown;
1897         break;
1898     }
1899   }
1900 
1901   return type;
1902 }
1903 
1904 #undef GXMAP
1905 
1906 #define MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a))
1907 
ghost_key_from_keycode(const XkbDescPtr xkb_descr,const KeyCode keycode)1908 static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode)
1909 {
1910   GHOST_ASSERT(XkbKeyNameLength == 4, "Name length is invalid!");
1911   if (keycode >= xkb_descr->min_key_code && keycode <= xkb_descr->max_key_code) {
1912     const char *id_str = xkb_descr->names->keys[keycode].name;
1913     const uint32_t id = MAKE_ID(id_str[0], id_str[1], id_str[2], id_str[3]);
1914     switch (id) {
1915       case MAKE_ID('T', 'L', 'D', 'E'):
1916         return GHOST_kKeyAccentGrave;
1917 #ifdef WITH_GHOST_DEBUG
1918       default:
1919         printf("%s unhandled keycode: %.*s\n", __func__, XkbKeyNameLength, id_str);
1920         break;
1921 #endif
1922     }
1923   }
1924   else if (keycode != 0) {
1925     GHOST_ASSERT(false, "KeyCode out of range!");
1926   }
1927   return GHOST_kKeyUnknown;
1928 }
1929 
1930 #undef MAKE_ID
1931 
1932 /* from xclip.c xcout() v0.11 */
1933 
1934 #define XCLIB_XCOUT_NONE 0          /* no context */
1935 #define XCLIB_XCOUT_SENTCONVSEL 1   /* sent a request */
1936 #define XCLIB_XCOUT_INCR 2          /* in an incr loop */
1937 #define XCLIB_XCOUT_FALLBACK 3      /* STRING failed, need fallback to UTF8 */
1938 #define XCLIB_XCOUT_FALLBACK_UTF8 4 /* UTF8 failed, move to compouned */
1939 #define XCLIB_XCOUT_FALLBACK_COMP 5 /* compouned failed, move to text. */
1940 #define XCLIB_XCOUT_FALLBACK_TEXT 6
1941 
1942 /* Retrieves the contents of a selections. */
getClipboard_xcout(const XEvent * evt,Atom sel,Atom target,unsigned char ** txt,unsigned long * len,unsigned int * context) const1943 void GHOST_SystemX11::getClipboard_xcout(const XEvent *evt,
1944                                          Atom sel,
1945                                          Atom target,
1946                                          unsigned char **txt,
1947                                          unsigned long *len,
1948                                          unsigned int *context) const
1949 {
1950   Atom pty_type;
1951   int pty_format;
1952   unsigned char *buffer;
1953   unsigned long pty_size, pty_items;
1954   unsigned char *ltxt = *txt;
1955 
1956   const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
1957   vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
1958   GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
1959   Window win = window->getXWindow();
1960 
1961   switch (*context) {
1962     /* There is no context, do an XConvertSelection() */
1963     case XCLIB_XCOUT_NONE:
1964       /* Initialize return length to 0. */
1965       if (*len > 0) {
1966         free(*txt);
1967         *len = 0;
1968       }
1969 
1970       /* Send a selection request */
1971       XConvertSelection(m_display, sel, target, m_atom.XCLIP_OUT, win, CurrentTime);
1972       *context = XCLIB_XCOUT_SENTCONVSEL;
1973       return;
1974 
1975     case XCLIB_XCOUT_SENTCONVSEL:
1976       if (evt->type != SelectionNotify)
1977         return;
1978 
1979       if (target == m_atom.UTF8_STRING && evt->xselection.property == None) {
1980         *context = XCLIB_XCOUT_FALLBACK_UTF8;
1981         return;
1982       }
1983       else if (target == m_atom.COMPOUND_TEXT && evt->xselection.property == None) {
1984         *context = XCLIB_XCOUT_FALLBACK_COMP;
1985         return;
1986       }
1987       else if (target == m_atom.TEXT && evt->xselection.property == None) {
1988         *context = XCLIB_XCOUT_FALLBACK_TEXT;
1989         return;
1990       }
1991 
1992       /* find the size and format of the data in property */
1993       XGetWindowProperty(m_display,
1994                          win,
1995                          m_atom.XCLIP_OUT,
1996                          0,
1997                          0,
1998                          False,
1999                          AnyPropertyType,
2000                          &pty_type,
2001                          &pty_format,
2002                          &pty_items,
2003                          &pty_size,
2004                          &buffer);
2005       XFree(buffer);
2006 
2007       if (pty_type == m_atom.INCR) {
2008         /* start INCR mechanism by deleting property */
2009         XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2010         XFlush(m_display);
2011         *context = XCLIB_XCOUT_INCR;
2012         return;
2013       }
2014 
2015       /* if it's not incr, and not format == 8, then there's
2016        * nothing in the selection (that xclip understands, anyway) */
2017 
2018       if (pty_format != 8) {
2019         *context = XCLIB_XCOUT_NONE;
2020         return;
2021       }
2022 
2023       // not using INCR mechanism, just read the property
2024       XGetWindowProperty(m_display,
2025                          win,
2026                          m_atom.XCLIP_OUT,
2027                          0,
2028                          (long)pty_size,
2029                          False,
2030                          AnyPropertyType,
2031                          &pty_type,
2032                          &pty_format,
2033                          &pty_items,
2034                          &pty_size,
2035                          &buffer);
2036 
2037       /* finished with property, delete it */
2038       XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2039 
2040       /* copy the buffer to the pointer for returned data */
2041       ltxt = (unsigned char *)malloc(pty_items);
2042       memcpy(ltxt, buffer, pty_items);
2043 
2044       /* set the length of the returned data */
2045       *len = pty_items;
2046       *txt = ltxt;
2047 
2048       /* free the buffer */
2049       XFree(buffer);
2050 
2051       *context = XCLIB_XCOUT_NONE;
2052 
2053       /* complete contents of selection fetched, return 1 */
2054       return;
2055 
2056     case XCLIB_XCOUT_INCR:
2057       /* To use the INCR method, we basically delete the
2058        * property with the selection in it, wait for an
2059        * event indicating that the property has been created,
2060        * then read it, delete it, etc. */
2061 
2062       /* make sure that the event is relevant */
2063       if (evt->type != PropertyNotify)
2064         return;
2065 
2066       /* skip unless the property has a new value */
2067       if (evt->xproperty.state != PropertyNewValue)
2068         return;
2069 
2070       /* check size and format of the property */
2071       XGetWindowProperty(m_display,
2072                          win,
2073                          m_atom.XCLIP_OUT,
2074                          0,
2075                          0,
2076                          False,
2077                          AnyPropertyType,
2078                          &pty_type,
2079                          &pty_format,
2080                          &pty_items,
2081                          &pty_size,
2082                          &buffer);
2083 
2084       if (pty_format != 8) {
2085         /* property does not contain text, delete it
2086          * to tell the other X client that we have read
2087          * it and to send the next property */
2088         XFree(buffer);
2089         XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2090         return;
2091       }
2092 
2093       if (pty_size == 0) {
2094         /* no more data, exit from loop */
2095         XFree(buffer);
2096         XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2097         *context = XCLIB_XCOUT_NONE;
2098 
2099         /* this means that an INCR transfer is now
2100          * complete, return 1 */
2101         return;
2102       }
2103 
2104       XFree(buffer);
2105 
2106       /* if we have come this far, the property contains
2107        * text, we know the size. */
2108       XGetWindowProperty(m_display,
2109                          win,
2110                          m_atom.XCLIP_OUT,
2111                          0,
2112                          (long)pty_size,
2113                          False,
2114                          AnyPropertyType,
2115                          &pty_type,
2116                          &pty_format,
2117                          &pty_items,
2118                          &pty_size,
2119                          &buffer);
2120 
2121       /* allocate memory to accommodate data in *txt */
2122       if (*len == 0) {
2123         *len = pty_items;
2124         ltxt = (unsigned char *)malloc(*len);
2125       }
2126       else {
2127         *len += pty_items;
2128         ltxt = (unsigned char *)realloc(ltxt, *len);
2129       }
2130 
2131       /* add data to ltxt */
2132       memcpy(&ltxt[*len - pty_items], buffer, pty_items);
2133 
2134       *txt = ltxt;
2135       XFree(buffer);
2136 
2137       /* delete property to get the next item */
2138       XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2139       XFlush(m_display);
2140       return;
2141   }
2142   return;
2143 }
2144 
getClipboard(bool selection) const2145 GHOST_TUns8 *GHOST_SystemX11::getClipboard(bool selection) const
2146 {
2147   Atom sseln;
2148   Atom target = m_atom.UTF8_STRING;
2149   Window owner;
2150 
2151   /* from xclip.c doOut() v0.11 */
2152   unsigned char *sel_buf;
2153   unsigned long sel_len = 0;
2154   XEvent evt;
2155   unsigned int context = XCLIB_XCOUT_NONE;
2156 
2157   if (selection == True)
2158     sseln = m_atom.PRIMARY;
2159   else
2160     sseln = m_atom.CLIPBOARD;
2161 
2162   const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
2163   vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
2164   GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
2165   Window win = window->getXWindow();
2166 
2167   /* check if we are the owner. */
2168   owner = XGetSelectionOwner(m_display, sseln);
2169   if (owner == win) {
2170     if (sseln == m_atom.CLIPBOARD) {
2171       sel_buf = (unsigned char *)malloc(strlen(txt_cut_buffer) + 1);
2172       strcpy((char *)sel_buf, txt_cut_buffer);
2173       return sel_buf;
2174     }
2175     else {
2176       sel_buf = (unsigned char *)malloc(strlen(txt_select_buffer) + 1);
2177       strcpy((char *)sel_buf, txt_select_buffer);
2178       return sel_buf;
2179     }
2180   }
2181   else if (owner == None)
2182     return NULL;
2183 
2184   /* Restore events so copy doesn't swallow other event types (keyboard/mouse). */
2185   vector<XEvent> restore_events;
2186 
2187   while (1) {
2188     /* only get an event if xcout() is doing something */
2189     bool restore_this_event = false;
2190     if (context != XCLIB_XCOUT_NONE) {
2191       XNextEvent(m_display, &evt);
2192       restore_this_event = (evt.type != SelectionNotify);
2193     }
2194 
2195     /* fetch the selection, or part of it */
2196     getClipboard_xcout(&evt, sseln, target, &sel_buf, &sel_len, &context);
2197 
2198     if (restore_this_event) {
2199       restore_events.push_back(evt);
2200     }
2201 
2202     /* fallback is needed. set XA_STRING to target and restart the loop. */
2203     if (context == XCLIB_XCOUT_FALLBACK) {
2204       context = XCLIB_XCOUT_NONE;
2205       target = m_atom.STRING;
2206       continue;
2207     }
2208     else if (context == XCLIB_XCOUT_FALLBACK_UTF8) {
2209       /* utf8 fail, move to compouned text. */
2210       context = XCLIB_XCOUT_NONE;
2211       target = m_atom.COMPOUND_TEXT;
2212       continue;
2213     }
2214     else if (context == XCLIB_XCOUT_FALLBACK_COMP) {
2215       /* compouned text fail, move to text. */
2216       context = XCLIB_XCOUT_NONE;
2217       target = m_atom.TEXT;
2218       continue;
2219     }
2220     else if (context == XCLIB_XCOUT_FALLBACK_TEXT) {
2221       /* text fail, nothing else to try, break. */
2222       context = XCLIB_XCOUT_NONE;
2223     }
2224 
2225     /* only continue if xcout() is doing something */
2226     if (context == XCLIB_XCOUT_NONE)
2227       break;
2228   }
2229 
2230   while (!restore_events.empty()) {
2231     XPutBackEvent(m_display, &restore_events.back());
2232     restore_events.pop_back();
2233   }
2234 
2235   if (sel_len) {
2236     /* only print the buffer out, and free it, if it's not
2237      * empty
2238      */
2239     unsigned char *tmp_data = (unsigned char *)malloc(sel_len + 1);
2240     memcpy((char *)tmp_data, (char *)sel_buf, sel_len);
2241     tmp_data[sel_len] = '\0';
2242 
2243     if (sseln == m_atom.STRING)
2244       XFree(sel_buf);
2245     else
2246       free(sel_buf);
2247 
2248     return tmp_data;
2249   }
2250   return NULL;
2251 }
2252 
putClipboard(GHOST_TInt8 * buffer,bool selection) const2253 void GHOST_SystemX11::putClipboard(GHOST_TInt8 *buffer, bool selection) const
2254 {
2255   Window m_window, owner;
2256 
2257   const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
2258   vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
2259   GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
2260   m_window = window->getXWindow();
2261 
2262   if (buffer) {
2263     if (selection == False) {
2264       XSetSelectionOwner(m_display, m_atom.CLIPBOARD, m_window, CurrentTime);
2265       owner = XGetSelectionOwner(m_display, m_atom.CLIPBOARD);
2266       if (txt_cut_buffer)
2267         free((void *)txt_cut_buffer);
2268 
2269       txt_cut_buffer = (char *)malloc(strlen(buffer) + 1);
2270       strcpy(txt_cut_buffer, buffer);
2271     }
2272     else {
2273       XSetSelectionOwner(m_display, m_atom.PRIMARY, m_window, CurrentTime);
2274       owner = XGetSelectionOwner(m_display, m_atom.PRIMARY);
2275       if (txt_select_buffer)
2276         free((void *)txt_select_buffer);
2277 
2278       txt_select_buffer = (char *)malloc(strlen(buffer) + 1);
2279       strcpy(txt_select_buffer, buffer);
2280     }
2281 
2282     if (owner != m_window)
2283       fprintf(stderr, "failed to own primary\n");
2284   }
2285 }
2286 
2287 /** \name Message Box
2288  * \{ */
2289 class DialogData {
2290  public:
2291   /* Width of the dialog */
2292   uint width;
2293   /* Heigth of the dialog */
2294   uint height;
2295   /* Default padding (x direction) between controls and edge of dialog */
2296   uint padding_x;
2297   /* Default padding (y direction) between controls and edge of dialog */
2298   uint padding_y;
2299   /* Width of a single button */
2300   uint button_width;
2301   /* Height of a single button */
2302   uint button_height;
2303   /* Inset of a button to its text */
2304   uint button_inset_x;
2305   /* Size of the border of the button */
2306   uint button_border_size;
2307   /* Height of a line of text */
2308   uint line_height;
2309   /* offset of the text inside the button */
2310   uint button_text_offset_y;
2311 
2312   /* Construct a new DialogData with the default settings */
DialogData()2313   DialogData()
2314       : width(640),
2315         height(175),
2316         padding_x(10),
2317         padding_y(5),
2318         button_width(130),
2319         button_height(24),
2320         button_inset_x(10),
2321         button_border_size(1),
2322         line_height(16)
2323   {
2324     button_text_offset_y = button_height - line_height;
2325   }
2326 
drawButton(Display * display,Window & window,GC & borderGC,GC & buttonGC,uint button_num,const char * label)2327   void drawButton(Display *display,
2328                   Window &window,
2329                   GC &borderGC,
2330                   GC &buttonGC,
2331                   uint button_num,
2332                   const char *label)
2333   {
2334     XFillRectangle(display,
2335                    window,
2336                    borderGC,
2337                    width - (padding_x + button_width) * button_num,
2338                    height - padding_y - button_height,
2339                    button_width,
2340                    button_height);
2341 
2342     XFillRectangle(display,
2343                    window,
2344                    buttonGC,
2345                    width - (padding_x + button_width) * button_num + button_border_size,
2346                    height - padding_y - button_height + button_border_size,
2347                    button_width - button_border_size * 2,
2348                    button_height - button_border_size * 2);
2349 
2350     XDrawString(display,
2351                 window,
2352                 borderGC,
2353                 width - (padding_x + button_width) * button_num + button_inset_x,
2354                 height - padding_y - button_text_offset_y,
2355                 label,
2356                 strlen(label));
2357   }
2358 
2359   /* Is the mouse inside the given button */
isInsideButton(XEvent & e,uint button_num)2360   bool isInsideButton(XEvent &e, uint button_num)
2361   {
2362     return ((e.xmotion.y > height - padding_y - button_height) &&
2363             (e.xmotion.y < height - padding_y) &&
2364             (e.xmotion.x > width - (padding_x + button_width) * button_num) &&
2365             (e.xmotion.x < width - padding_x - (padding_x + button_width) * (button_num - 1)));
2366   }
2367 };
2368 
split(const char * text,const char * seps,char *** str,int * count)2369 static void split(const char *text, const char *seps, char ***str, int *count)
2370 {
2371   char *tok, *data;
2372   int i;
2373   *count = 0;
2374 
2375   data = strdup(text);
2376   for (tok = strtok(data, seps); tok != NULL; tok = strtok(NULL, seps))
2377     (*count)++;
2378   free(data);
2379 
2380   data = strdup(text);
2381   *str = (char **)malloc((size_t)(*count) * sizeof(char *));
2382   for (i = 0, tok = strtok(data, seps); tok != NULL; tok = strtok(NULL, seps), i++)
2383     (*str)[i] = strdup(tok);
2384   free(data);
2385 }
2386 
showMessageBox(const char * title,const char * message,const char * help_label,const char * continue_label,const char * link,GHOST_DialogOptions) const2387 GHOST_TSuccess GHOST_SystemX11::showMessageBox(const char *title,
2388                                                const char *message,
2389                                                const char *help_label,
2390                                                const char *continue_label,
2391                                                const char *link,
2392                                                GHOST_DialogOptions) const
2393 {
2394   char **text_splitted = NULL;
2395   int textLines = 0;
2396   split(message, "\n", &text_splitted, &textLines);
2397 
2398   DialogData dialog_data;
2399   XSizeHints hints;
2400 
2401   Window window;
2402   XEvent e;
2403   int screen = DefaultScreen(m_display);
2404   window = XCreateSimpleWindow(m_display,
2405                                RootWindow(m_display, screen),
2406                                0,
2407                                0,
2408                                dialog_data.width,
2409                                dialog_data.height,
2410                                1,
2411                                BlackPixel(m_display, screen),
2412                                WhitePixel(m_display, screen));
2413 
2414   /* Window Should not be resizable */
2415   {
2416     hints.flags = PSize | PMinSize | PMaxSize;
2417     hints.min_width = hints.max_width = hints.base_width = dialog_data.width;
2418     hints.min_height = hints.max_height = hints.base_height = dialog_data.height;
2419     XSetWMNormalHints(m_display, window, &hints);
2420   }
2421 
2422   /* Set title */
2423   {
2424     Atom wm_Name = XInternAtom(m_display, "_NET_WM_NAME", False);
2425     Atom utf8Str = XInternAtom(m_display, "UTF8_STRING", False);
2426 
2427     Atom winType = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE", False);
2428     Atom typeDialog = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
2429 
2430     XChangeProperty(m_display,
2431                     window,
2432                     wm_Name,
2433                     utf8Str,
2434                     8,
2435                     PropModeReplace,
2436                     (const unsigned char *)title,
2437                     (int)strlen(title));
2438 
2439     XChangeProperty(
2440         m_display, window, winType, XA_ATOM, 32, PropModeReplace, (unsigned char *)&typeDialog, 1);
2441   }
2442 
2443   /* Create buttons GC */
2444   XGCValues buttonBorderGCValues;
2445   buttonBorderGCValues.foreground = BlackPixel(m_display, screen);
2446   buttonBorderGCValues.background = WhitePixel(m_display, screen);
2447   XGCValues buttonGCValues;
2448   buttonGCValues.foreground = WhitePixel(m_display, screen);
2449   buttonGCValues.background = BlackPixel(m_display, screen);
2450 
2451   GC buttonBorderGC = XCreateGC(m_display, window, GCForeground, &buttonBorderGCValues);
2452   GC buttonGC = XCreateGC(m_display, window, GCForeground, &buttonGCValues);
2453 
2454   XSelectInput(m_display, window, ExposureMask | ButtonPressMask | ButtonReleaseMask);
2455   XMapWindow(m_display, window);
2456 
2457   while (1) {
2458     XNextEvent(m_display, &e);
2459     if (e.type == Expose) {
2460       for (int i = 0; i < textLines; i++) {
2461         XDrawString(m_display,
2462                     window,
2463                     DefaultGC(m_display, screen),
2464                     dialog_data.padding_x,
2465                     dialog_data.padding_x + (i + 1) * dialog_data.line_height,
2466                     text_splitted[i],
2467                     (int)strlen(text_splitted[i]));
2468       }
2469       dialog_data.drawButton(m_display, window, buttonBorderGC, buttonGC, 1, continue_label);
2470       if (strlen(link)) {
2471         dialog_data.drawButton(m_display, window, buttonBorderGC, buttonGC, 2, help_label);
2472       }
2473     }
2474     else if (e.type == ButtonRelease) {
2475       if (dialog_data.isInsideButton(e, 1)) {
2476         break;
2477       }
2478       else if (dialog_data.isInsideButton(e, 2)) {
2479         if (strlen(link)) {
2480           string cmd = "xdg-open \"" + string(link) + "\"";
2481           if (system(cmd.c_str()) != 0) {
2482             GHOST_PRINTF("GHOST_SystemX11::showMessageBox: Unable to run system command [%s]",
2483                          cmd.c_str());
2484           }
2485         }
2486         break;
2487       }
2488     }
2489   }
2490 
2491   for (int i = 0; i < textLines; i++) {
2492     free(text_splitted[i]);
2493   }
2494   free(text_splitted);
2495 
2496   XDestroyWindow(m_display, window);
2497   XFreeGC(m_display, buttonBorderGC);
2498   XFreeGC(m_display, buttonGC);
2499   return GHOST_kSuccess;
2500 }
2501 /* \} */
2502 
2503 #ifdef WITH_XDND
pushDragDropEvent(GHOST_TEventType eventType,GHOST_TDragnDropTypes draggedObjectType,GHOST_IWindow * window,int mouseX,int mouseY,void * data)2504 GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType,
2505                                                   GHOST_TDragnDropTypes draggedObjectType,
2506                                                   GHOST_IWindow *window,
2507                                                   int mouseX,
2508                                                   int mouseY,
2509                                                   void *data)
2510 {
2511   GHOST_SystemX11 *system = ((GHOST_SystemX11 *)getSystem());
2512   return system->pushEvent(new GHOST_EventDragnDrop(
2513       system->getMilliSeconds(), eventType, draggedObjectType, window, mouseX, mouseY, data));
2514 }
2515 #endif
2516 /**
2517  * These callbacks can be used for debugging, so we can breakpoint on an X11 error.
2518  *
2519  * Dummy function to get around IO Handler exiting if device invalid
2520  * Basically it will not crash blender now if you have a X device that
2521  * is configured but not plugged in.
2522  */
GHOST_X11_ApplicationErrorHandler(Display * display,XErrorEvent * event)2523 int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *event)
2524 {
2525   GHOST_ISystem *system = GHOST_ISystem::getSystem();
2526   if (!system->isDebugEnabled()) {
2527     return 0;
2528   }
2529 
2530   char error_code_str[512];
2531 
2532   XGetErrorText(display, event->error_code, error_code_str, sizeof(error_code_str));
2533 
2534   fprintf(stderr,
2535           "Received X11 Error:\n"
2536           "\terror code:   %d\n"
2537           "\trequest code: %d\n"
2538           "\tminor code:   %d\n"
2539           "\terror text:   %s\n",
2540           event->error_code,
2541           event->request_code,
2542           event->minor_code,
2543           error_code_str);
2544 
2545   /* No exit! - but keep lint happy */
2546   return 0;
2547 }
2548 
GHOST_X11_ApplicationIOErrorHandler(Display *)2549 int GHOST_X11_ApplicationIOErrorHandler(Display * /*display*/)
2550 {
2551   GHOST_ISystem *system = GHOST_ISystem::getSystem();
2552   if (!system->isDebugEnabled()) {
2553     return 0;
2554   }
2555 
2556   fprintf(stderr, "Ignoring Xlib error: error IO\n");
2557 
2558   /* No exit! - but keep lint happy */
2559   return 0;
2560 }
2561 
2562 #ifdef WITH_X11_XINPUT
2563 
is_filler_char(char c)2564 static bool is_filler_char(char c)
2565 {
2566   return isspace(c) || c == '_' || c == '-' || c == ';' || c == ':';
2567 }
2568 
2569 /* These C functions are copied from Wine 3.12's wintab.c */
match_token(const char * haystack,const char * needle)2570 static bool match_token(const char *haystack, const char *needle)
2571 {
2572   const char *h, *n;
2573   for (h = haystack; *h;) {
2574     while (*h && is_filler_char(*h))
2575       h++;
2576     if (!*h)
2577       break;
2578 
2579     for (n = needle; *n && *h && tolower(*h) == tolower(*n); n++)
2580       h++;
2581     if (!*n && (is_filler_char(*h) || !*h))
2582       return true;
2583 
2584     while (*h && !is_filler_char(*h))
2585       h++;
2586   }
2587   return false;
2588 }
2589 
2590 /* Determining if an X device is a Tablet style device is an imperfect science.
2591  * We rely on common conventions around device names as well as the type reported
2592  * by Wacom tablets.  This code will likely need to be expanded for alternate tablet types
2593  *
2594  * Wintab refers to any device that interacts with the tablet as a cursor,
2595  * (stylus, eraser, tablet mouse, airbrush, etc)
2596  * this is not to be confused with wacom x11 configuration "cursor" device.
2597  * Wacoms x11 config "cursor" refers to its device slot (which we mirror with
2598  * our gSysCursors) for puck like devices (tablet mice essentially).
2599  */
tablet_mode_from_name(const char * name,const char * type)2600 static GHOST_TTabletMode tablet_mode_from_name(const char *name, const char *type)
2601 {
2602   int i;
2603   static const char *tablet_stylus_whitelist[] = {"stylus", "wizardpen", "acecad", "pen", NULL};
2604 
2605   static const char *type_blacklist[] = {"pad", "cursor", "touch", NULL};
2606 
2607   /* Skip some known unsupported types. */
2608   for (i = 0; type_blacklist[i] != NULL; i++) {
2609     if (type && (strcasecmp(type, type_blacklist[i]) == 0)) {
2610       return GHOST_kTabletModeNone;
2611     }
2612   }
2613 
2614   /* First check device type to avoid cases where name is "Pen and Eraser" and type is "ERASER" */
2615   for (i = 0; tablet_stylus_whitelist[i] != NULL; i++) {
2616     if (type && match_token(type, tablet_stylus_whitelist[i])) {
2617       return GHOST_kTabletModeStylus;
2618     }
2619   }
2620   if (type && match_token(type, "eraser")) {
2621     return GHOST_kTabletModeEraser;
2622   }
2623   for (i = 0; tablet_stylus_whitelist[i] != NULL; i++) {
2624     if (name && match_token(name, tablet_stylus_whitelist[i])) {
2625       return GHOST_kTabletModeStylus;
2626     }
2627   }
2628   if (name && match_token(name, "eraser")) {
2629     return GHOST_kTabletModeEraser;
2630   }
2631 
2632   return GHOST_kTabletModeNone;
2633 }
2634 
2635 /* End code copied from Wine. */
2636 
refreshXInputDevices()2637 void GHOST_SystemX11::refreshXInputDevices()
2638 {
2639   if (m_xinput_version.present) {
2640     /* Close tablet devices. */
2641     clearXInputDevices();
2642 
2643     /* Install our error handler to override Xlib's termination behavior */
2644     GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
2645 
2646     {
2647       int device_count;
2648       XDeviceInfo *device_info = XListInputDevices(m_display, &device_count);
2649 
2650       for (int i = 0; i < device_count; ++i) {
2651         char *device_type = device_info[i].type ? XGetAtomName(m_display, device_info[i].type) :
2652                                                   NULL;
2653         GHOST_TTabletMode tablet_mode = tablet_mode_from_name(device_info[i].name, device_type);
2654 
2655         // printf("Tablet type:'%s', name:'%s', index:%d\n", device_type, device_info[i].name, i);
2656 
2657         if (device_type) {
2658           XFree((void *)device_type);
2659         }
2660 
2661         if (!(tablet_mode == GHOST_kTabletModeStylus || tablet_mode == GHOST_kTabletModeEraser)) {
2662           continue;
2663         }
2664 
2665         GHOST_TabletX11 xtablet = {tablet_mode};
2666         xtablet.ID = device_info[i].id;
2667         xtablet.Device = XOpenDevice(m_display, xtablet.ID);
2668 
2669         if (xtablet.Device != NULL) {
2670           /* Find how many pressure levels tablet has */
2671           XAnyClassPtr ici = device_info[i].inputclassinfo;
2672 
2673           if (ici != NULL) {
2674             for (int j = 0; j < device_info[i].num_classes; ++j) {
2675               if (ici->c_class == ValuatorClass) {
2676                 XValuatorInfo *xvi = (XValuatorInfo *)ici;
2677                 if (xvi->axes != NULL) {
2678                   xtablet.PressureLevels = xvi->axes[2].max_value;
2679 
2680                   if (xvi->num_axes > 3) {
2681                     /* this is assuming that the tablet has the same tilt resolution in both
2682                      * positive and negative directions. It would be rather weird if it didn't.. */
2683                     xtablet.XtiltLevels = xvi->axes[3].max_value;
2684                     xtablet.YtiltLevels = xvi->axes[4].max_value;
2685                   }
2686                   else {
2687                     xtablet.XtiltLevels = 0;
2688                     xtablet.YtiltLevels = 0;
2689                   }
2690 
2691                   break;
2692                 }
2693               }
2694 
2695               ici = (XAnyClassPtr)(((char *)ici) + ici->length);
2696             }
2697           }
2698 
2699           m_xtablets.push_back(xtablet);
2700         }
2701       }
2702 
2703       XFreeDeviceList(device_info);
2704     }
2705 
2706     GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
2707   }
2708 }
2709 
clearXInputDevices()2710 void GHOST_SystemX11::clearXInputDevices()
2711 {
2712   for (GHOST_TabletX11 &xtablet : m_xtablets) {
2713     if (xtablet.Device)
2714       XCloseDevice(m_display, xtablet.Device);
2715   }
2716 
2717   m_xtablets.clear();
2718 }
2719 
2720 #endif /* WITH_X11_XINPUT */
2721