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(<xt[*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