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