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