Blender V4.3
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
18#include "GHOST_EventButton.hh"
19#include "GHOST_EventCursor.hh"
21#include "GHOST_EventKey.hh"
22#include "GHOST_EventWheel.hh"
23#include "GHOST_SystemX11.hh"
24#include "GHOST_TimerManager.hh"
26#include "GHOST_WindowX11.hh"
27#ifdef WITH_INPUT_NDOF
29#endif
30#include "GHOST_utildefines.hh"
31
32#ifdef WITH_XDND
33# include "GHOST_DropTargetX11.hh"
34#endif
35
36#include "GHOST_Debug.hh"
37
38#ifdef WITH_OPENGL_BACKEND
39# include "GHOST_ContextEGL.hh"
40# include "GHOST_ContextGLX.hh"
41#endif
42
43#ifdef WITH_VULKAN_BACKEND
44# include "GHOST_ContextVK.hh"
45#endif
46
47#ifdef WITH_XF86KEYSYM
48# include <X11/XF86keysym.h>
49#endif
50
51#ifdef WITH_X11_XFIXES
52# include <X11/extensions/Xfixes.h>
53/* Workaround for XWayland grab glitch: #53004. */
54# define WITH_XWAYLAND_HACK
55#endif
56
57/* for XIWarpPointer */
58#ifdef WITH_X11_XINPUT
59# include <X11/extensions/XInput2.h>
60#endif
61
62/* For timing */
63#include <sys/time.h>
64#include <unistd.h>
65
66#include <cstdio> /* for fprintf only */
67#include <cstdlib> /* for exit */
68#include <iostream>
69#include <vector>
70
71/* For debugging, so we can break-point X11 errors. */
72// #define USE_X11_ERROR_HANDLERS
73
74#ifdef WITH_X11_XINPUT
75# define USE_XINPUT_HOTPLUG
76#endif
77
78/* see #34039 Fix Alt key glitch on Unity desktop */
79#define USE_UNITY_WORKAROUND
80
81/* Fix 'shortcut' part of keyboard reading code only ever using first defined key-map
82 * instead of active one. See #47228 and D1746 */
83#define USE_NON_LATIN_KB_WORKAROUND
84
85static uchar bit_is_on(const uchar *ptr, int bit)
86{
87 return ptr[bit >> 3] & (1 << (bit & 7));
88}
89
90static GHOST_TKey ghost_key_from_keysym(const KeySym key);
91static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode);
92static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key_sym,
93 const XkbDescPtr xkb_descr,
94 const KeyCode keycode);
95
96/* these are for copy and select copy */
97static char *txt_cut_buffer = nullptr;
98static char *txt_select_buffer = nullptr;
99
100#ifdef WITH_XWAYLAND_HACK
101static bool use_xwayland_hack = false;
102#endif
103
104using namespace std;
105
107 : GHOST_System(),
108 m_xkb_descr(nullptr),
109 m_keyboard_vector{0},
110#ifdef WITH_X11_XINPUT
111 m_last_key_time(0),
112#endif
113 m_keycode_last_repeat_key(uint(-1))
114{
115 XInitThreads();
116 m_display = XOpenDisplay(nullptr);
117
118 if (!m_display) {
119 throw std::runtime_error("unable to open a display!");
120 }
121
122#ifdef USE_X11_ERROR_HANDLERS
123 (void)XSetErrorHandler(GHOST_X11_ApplicationErrorHandler);
124 (void)XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler);
125#endif
126
127#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
128 /* NOTE: Don't open connection to XIM server here, because the locale has to be
129 * set before opening the connection but `setlocale()` has not been called yet.
130 * the connection will be opened after entering the event loop. */
131 m_xim = nullptr;
132#endif
133
134#define GHOST_INTERN_ATOM_IF_EXISTS(atom) \
135 { \
136 m_atom.atom = XInternAtom(m_display, #atom, True); \
137 } \
138 (void)0
139#define GHOST_INTERN_ATOM(atom) \
140 { \
141 m_atom.atom = XInternAtom(m_display, #atom, False); \
142 } \
143 (void)0
144
153
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
255
256 if (m_displayManager) {
257 return GHOST_kSuccess;
258 }
259 }
260
261 return GHOST_kFailure;
262}
263
265{
266 timespec ts = {0, 0};
267 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
268 GHOST_ASSERT(false, "Could not instantiate monotonic timer!");
269 }
270 /* Taking care not to overflow the tv.tv_sec * 1000 */
271 const uint64_t time = (uint64_t(ts.tv_sec) * 1000) + uint64_t(ts.tv_nsec / 1000000);
272 return time;
273}
274
276{
277 /* NOTE(@ideasman42): Return a time compatible with `getMilliSeconds()`,
278 * this is needed as X11 time-stamps use monotonic time.
279 * The X11 implementation *could* use any basis, in practice though we are supporting
280 * XORG/LIBINPUT which uses time-stamps based on the monotonic time,
281 * Needed to resolve failure to detect double-clicking, see: #40009. */
282
283 /* Accumulate time rollover (as well as store the initial delta from #getMilliSeconds). */
284 static uint64_t timestamp_offset = 0;
285
286 /* The last event time (to detect rollover). */
287 static uint32_t timestamp_prev = 0;
288 /* Causes the X11 time-stamp to be zero based. */
289 static uint32_t timestamp_start = 0;
290
291 static bool is_time_init = false;
292
293#if 0
294 /* Force rollover after 2 seconds (for testing). */
295 {
296 const uint32_t timestamp_wrap_ms = 2000;
297 static uint32_t timestamp_offset_fake = 0;
298 if (!is_time_init) {
299 timestamp_offset_fake = UINT32_MAX - (timestamp + timestamp_wrap_ms);
300 }
301 timestamp = uint32_t(timestamp + timestamp_offset_fake);
302 }
303#endif
304
305 if (!is_time_init) {
306 /* Store the initial delta in the rollover. */
307 const uint64_t current_time = getMilliSeconds();
308 timestamp_offset = current_time;
309 timestamp_start = timestamp;
310 }
311
312 /* Always remove the start time.
313 * This changes the point where `uint32_t` rolls over, but that's OK. */
314 timestamp = uint32_t(timestamp) - timestamp_start;
315
316 if (!is_time_init) {
317 is_time_init = true;
318 timestamp_prev = timestamp;
319 }
320
321 if (UNLIKELY(timestamp < timestamp_prev)) {
322 /* Only rollover if this is within a reasonable range. */
323 if (UNLIKELY(timestamp_prev - timestamp > UINT32_MAX / 2)) {
324 timestamp_offset += uint64_t(UINT32_MAX) + 1;
325 }
326 }
327 timestamp_prev = timestamp;
328
329 uint64_t timestamp_final = (uint64_t(timestamp) + timestamp_offset);
330
331 return timestamp_final;
332}
333
335{
336 return uint8_t(1);
337}
338
340{
341 if (m_display) {
342 /* NOTE(@ideasman42): for this to work as documented,
343 * we would need to use Xinerama check r54370 for code that did this,
344 * we've since removed since its not worth the extra dependency. */
345 getAllDisplayDimensions(width, height);
346 }
347}
348
350{
351 if (m_display) {
352 width = DisplayWidth(m_display, DefaultScreen(m_display));
353 height = DisplayHeight(m_display, DefaultScreen(m_display));
354 }
355}
356
359 int32_t top,
360 uint32_t width,
361 uint32_t height,
363 GHOST_GPUSettings gpuSettings,
364 const bool exclusive,
365 const bool is_dialog,
366 const GHOST_IWindow *parentWindow)
367{
368 GHOST_WindowX11 *window = nullptr;
369
370 if (!m_display) {
371 return nullptr;
372 }
373
374 window = new GHOST_WindowX11(this,
375 m_display,
376 title,
377 left,
378 top,
379 width,
380 height,
381 state,
382 (GHOST_WindowX11 *)parentWindow,
383 gpuSettings.context_type,
384 is_dialog,
385 ((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
386 exclusive,
387 (gpuSettings.flags & GHOST_gpuDebugContext) != 0,
388 gpuSettings.preferred_device);
389
390 if (window) {
391 /* Both are now handle in GHOST_WindowX11.cc
392 * Focus and Delete atoms. */
393
394 if (window->getValid()) {
395 /* Store the pointer to the window */
396 m_windowManager->addWindow(window);
397 m_windowManager->setActiveWindow(window);
399 }
400 else {
401 delete window;
402 window = nullptr;
403 }
404 }
405 return window;
406}
407
409{
410 const bool debug_context = (gpuSettings.flags & GHOST_gpuDebugContext) != 0;
411 switch (gpuSettings.context_type) {
412#ifdef WITH_VULKAN_BACKEND
413 case GHOST_kDrawingContextTypeVulkan: {
414 GHOST_Context *context = new GHOST_ContextVK(false,
415 GHOST_kVulkanPlatformX11,
416 0,
417 m_display,
418 nullptr,
419 nullptr,
420 nullptr,
421 1,
422 2,
423 debug_context,
424 gpuSettings.preferred_device);
425 if (context->initializeDrawingContext()) {
426 return context;
427 }
428 delete context;
429 return nullptr;
430 }
431#endif
432
433#ifdef WITH_OPENGL_BACKEND
434 case GHOST_kDrawingContextTypeOpenGL: {
435 for (int minor = 6; minor >= 3; --minor) {
436 GHOST_Context *context = new GHOST_ContextGLX(
437 false,
438 (Window) nullptr,
439 m_display,
440 (GLXFBConfig) nullptr,
441 GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
442 4,
443 minor,
444 GHOST_OPENGL_GLX_CONTEXT_FLAGS | (debug_context ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
446 if (context->initializeDrawingContext()) {
447 return context;
448 }
449 delete context;
450 }
451 return nullptr;
452 }
453#endif
454
455 default:
456 /* Unsupported backend. */
457 return nullptr;
458 }
459}
460
462{
463 delete context;
464
465 return GHOST_kSuccess;
466}
467
468#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
469static void destroyIMCallback(XIM /*xim*/, XPointer ptr, XPointer /*data*/)
470{
471 GHOST_PRINT("XIM server died\n");
472
473 if (ptr) {
474 *(XIM *)ptr = nullptr;
475 }
476}
477
478bool GHOST_SystemX11::openX11_IM()
479{
480 if (!m_display) {
481 return false;
482 }
483
484 /* set locale modifiers such as `@im=ibus` specified by XMODIFIERS. */
485 XSetLocaleModifiers("");
486
487 m_xim = XOpenIM(m_display, nullptr, (char *)GHOST_X11_RES_NAME, (char *)GHOST_X11_RES_CLASS);
488 if (!m_xim) {
489 return false;
490 }
491
492 XIMCallback destroy;
493 destroy.callback = (XIMProc)destroyIMCallback;
494 destroy.client_data = (XPointer)&m_xim;
495 XSetIMValues(m_xim, XNDestroyCallback, &destroy, nullptr);
496 return true;
497}
498#endif
499
500GHOST_WindowX11 *GHOST_SystemX11::findGhostWindow(Window xwind) const
501{
502
503 if (xwind == 0) {
504 return nullptr;
505 }
506
507 /* It is not entirely safe to do this as the back-pointer may point
508 * to a window that has recently been removed.
509 * We should always check the window manager's list of windows
510 * and only process events on these windows. */
511
512 const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
513
514 vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
515 vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
516
517 for (; win_it != win_end; ++win_it) {
518 GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
519 if (window->getXWindow() == xwind) {
520 return window;
521 }
522 }
523 return nullptr;
524}
525
526static void SleepTillEvent(Display *display, int64_t maxSleep)
527{
528 int fd = ConnectionNumber(display);
529 fd_set fds;
530
531 FD_ZERO(&fds);
532 FD_SET(fd, &fds);
533
534 if (maxSleep == -1) {
535 select(fd + 1, &fds, nullptr, nullptr, nullptr);
536 }
537 else {
538 timeval tv;
539
540 tv.tv_sec = maxSleep / 1000;
541 tv.tv_usec = (maxSleep - tv.tv_sec * 1000) * 1000;
542
543 select(fd + 1, &fds, nullptr, nullptr, &tv);
544 }
545}
546
547/* This function borrowed from QT's X11 support `qclipboard_x11.cpp`. */
551
552static Bool init_timestamp_scanner(Display * /*display*/, XEvent *event, XPointer arg)
553{
554 init_timestamp_data *data = reinterpret_cast<init_timestamp_data *>(arg);
555 switch (event->type) {
556 case ButtonPress:
557 case ButtonRelease:
558 data->timestamp = event->xbutton.time;
559 break;
560 case MotionNotify:
561 data->timestamp = event->xmotion.time;
562 break;
563 case KeyPress:
564 case KeyRelease:
565 data->timestamp = event->xkey.time;
566 break;
567 case PropertyNotify:
568 data->timestamp = event->xproperty.time;
569 break;
570 case EnterNotify:
571 case LeaveNotify:
572 data->timestamp = event->xcrossing.time;
573 break;
574 case SelectionClear:
575 data->timestamp = event->xselectionclear.time;
576 break;
577 default:
578 break;
579 }
580
581 return false;
582}
583
584Time GHOST_SystemX11::lastEventTime(Time default_time)
585{
586 init_timestamp_data data;
587 data.timestamp = default_time;
588 XEvent ev;
589 XCheckIfEvent(m_display, &ev, &init_timestamp_scanner, (XPointer)&data);
590
591 return data.timestamp;
592}
593
594bool GHOST_SystemX11::processEvents(bool waitForEvent)
595{
596 /* Get all the current events -- translate them into
597 * ghost events and call base class pushEvent() method. */
598
599 bool anyProcessed = false;
600
601 do {
603
604 if (waitForEvent && m_dirty_windows.empty() && !XPending(m_display)) {
605 uint64_t next = timerMgr->nextFireTime();
606
607 if (next == GHOST_kFireTimeNever) {
608 SleepTillEvent(m_display, -1);
609 }
610 else {
611 const int64_t maxSleep = next - getMilliSeconds();
612
613 if (maxSleep >= 0) {
614 SleepTillEvent(m_display, next - getMilliSeconds());
615 }
616 }
617 }
618
619 if (timerMgr->fireTimers(getMilliSeconds())) {
620 anyProcessed = true;
621 }
622
623 while (XPending(m_display)) {
624 XEvent xevent;
625 XNextEvent(m_display, &xevent);
626
627#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
628 /* open connection to XIM server and create input context (XIC)
629 * when receiving the first FocusIn or KeyPress event after startup,
630 * or recover XIM and XIC when the XIM server has been restarted */
631 if (ELEM(xevent.type, FocusIn, KeyPress)) {
632 if (!m_xim && openX11_IM()) {
633 GHOST_PRINT("Connected to XIM server\n");
634 }
635
636 if (m_xim) {
637 GHOST_WindowX11 *window = findGhostWindow(xevent.xany.window);
638 if (window && !window->getX11_XIC() && window->createX11_XIC()) {
639 GHOST_PRINT("XIM input context created\n");
640 if (xevent.type == KeyPress) {
641 /* we can assume the window has input focus
642 * here, because key events are received only
643 * when the window is focused. */
644 XSetICFocus(window->getX11_XIC());
645 }
646 }
647 }
648 }
649
650 /* Ensure generated time-stamps are non-zero. */
651 if (ELEM(xevent.type, KeyPress, KeyRelease)) {
652 if (xevent.xkey.time != 0) {
653 m_last_key_time = xevent.xkey.time;
654 }
655 }
656
657 /* dispatch event to XIM server */
658 if (XFilterEvent(&xevent, (Window) nullptr) == True) {
659 /* do nothing now, the event is consumed by XIM. */
660 continue;
661 }
662#endif
663 /* When using auto-repeat, some key-press events can actually come *after* the
664 * last key-release. The next code takes care of that. */
665 if (xevent.type == KeyRelease) {
666 m_last_release_keycode = xevent.xkey.keycode;
667 m_last_release_time = xevent.xkey.time;
668 }
669 else if (xevent.type == KeyPress) {
670 if ((xevent.xkey.keycode == m_last_release_keycode) &&
671 (xevent.xkey.time <= m_last_release_time))
672 {
673 continue;
674 }
675 }
676
677 processEvent(&xevent);
678 anyProcessed = true;
679
680#ifdef USE_UNITY_WORKAROUND
681 /* NOTE: processEvent() can't include this code because
682 * KeymapNotify event have no valid window information. */
683
684 /* the X server generates KeymapNotify event immediately after
685 * every EnterNotify and FocusIn event. we handle this event
686 * to correct modifier states. */
687 if (xevent.type == FocusIn) {
688 /* use previous event's window, because KeymapNotify event
689 * has no window information. */
690 GHOST_WindowX11 *window = findGhostWindow(xevent.xany.window);
691 if (window && XPending(m_display) >= 2) {
692 XNextEvent(m_display, &xevent);
693
694 if (xevent.type == KeymapNotify) {
695 XEvent xev_next;
696
697 /* check if KeyPress or KeyRelease event was generated
698 * in order to confirm the window is active. */
699 XPeekEvent(m_display, &xev_next);
700
701 if (ELEM(xev_next.type, KeyPress, KeyRelease)) {
702 const uint64_t event_ms = ms_from_input_time(xev_next.xkey.time);
703 /* XK_Hyper_L/R currently unused. */
704 const static KeySym modifiers[] = {
705 XK_Shift_L,
706 XK_Shift_R,
707 XK_Control_L,
708 XK_Control_R,
709 XK_Alt_L,
710 XK_Alt_R,
711 XK_Super_L,
712 XK_Super_R,
713 };
714
715 for (int i = 0; i < int(ARRAY_SIZE(modifiers)); i++) {
716 KeyCode kc = XKeysymToKeycode(m_display, modifiers[i]);
717 if (kc != 0 && ((xevent.xkeymap.key_vector[kc >> 3] >> (kc & 7)) & 1) != 0) {
718 pushEvent(new GHOST_EventKey(event_ms,
720 window,
721 ghost_key_from_keysym(modifiers[i]),
722 false,
723 nullptr));
724 }
725 }
726 }
727 }
728 }
729 }
730#endif /* USE_UNITY_WORKAROUND */
731 }
732
733 if (generateWindowExposeEvents()) {
734 anyProcessed = true;
735 }
736
737#ifdef WITH_INPUT_NDOF
738 if (static_cast<GHOST_NDOFManagerUnix *>(m_ndofManager)->processEvents()) {
739 anyProcessed = true;
740 }
741#endif
742
743 } while (waitForEvent && !anyProcessed);
744
745 return anyProcessed;
746}
747
748#ifdef WITH_X11_XINPUT
749static bool checkTabletProximity(Display *display, XDevice *device)
750{
751 /* we could have true/false/not-found return value, but for now false is OK */
752
753 /* see: state.c from xinput, to get more data out of the device */
754 XDeviceState *state;
755
756 if (device == nullptr) {
757 return false;
758 }
759
760 /* needed since unplugging will abort() without this */
762
763 state = XQueryDeviceState(display, device);
764
766
767 if (state) {
768 XInputClass *cls = state->data;
769 // printf("%d class%s :\n", state->num_classes, (state->num_classes > 1) ? "es" : "");
770 for (int loop = 0; loop < state->num_classes; loop++) {
771 switch (cls->c_class) {
772 case ValuatorClass:
773 XValuatorState *val_state = (XValuatorState *)cls;
774 // printf("ValuatorClass Mode=%s Proximity=%s\n",
775 // val_state->mode & 1 ? "Absolute" : "Relative",
776 // val_state->mode & 2 ? "Out" : "In");
777
778 if ((val_state->mode & 2) == 0) {
779 XFreeDeviceState(state);
780 return true;
781 }
782 break;
783 }
784 cls = (XInputClass *)((char *)cls + cls->length);
785 }
786 XFreeDeviceState(state);
787 }
788 return false;
789}
790#endif /* WITH_X11_XINPUT */
791
792void GHOST_SystemX11::processEvent(XEvent *xe)
793{
794 GHOST_WindowX11 *window = findGhostWindow(xe->xany.window);
795 GHOST_Event *g_event = nullptr;
796
797 /* Detect auto-repeat. */
798 bool is_repeat = false;
799 if (ELEM(xe->type, KeyPress, KeyRelease)) {
800 XKeyEvent *xke = &(xe->xkey);
801
802 /* Set to true if this key will repeat. */
803 bool is_repeat_keycode = false;
804
805 if (m_xkb_descr != nullptr) {
806 /* Use XKB support. */
807 is_repeat_keycode = (
808 /* Should always be true, check just in case. */
809 (xke->keycode < (XkbPerKeyBitArraySize << 3)) &&
810 bit_is_on(m_xkb_descr->ctrls->per_key_repeat, xke->keycode));
811 }
812 else {
813 /* No XKB support (filter by modifier). */
814 switch (XLookupKeysym(xke, 0)) {
815 case XK_Shift_L:
816 case XK_Shift_R:
817 case XK_Control_L:
818 case XK_Control_R:
819 case XK_Alt_L:
820 case XK_Alt_R:
821 case XK_Super_L:
822 case XK_Super_R:
823 case XK_Hyper_L:
824 case XK_Hyper_R:
825 case XK_Caps_Lock:
826 case XK_Scroll_Lock:
827 case XK_Num_Lock: {
828 break;
829 }
830 default: {
831 is_repeat_keycode = true;
832 }
833 }
834 }
835
836 if (is_repeat_keycode) {
837 if (xe->type == KeyPress) {
838 if (m_keycode_last_repeat_key == xke->keycode) {
839 is_repeat = true;
840 }
841 m_keycode_last_repeat_key = xke->keycode;
842 }
843 else {
844 if (m_keycode_last_repeat_key == xke->keycode) {
845 m_keycode_last_repeat_key = uint(-1);
846 }
847 }
848 }
849 }
850 else if (xe->type == EnterNotify) {
851 /* We can't tell how the key state changed, clear it to avoid stuck keys. */
852 m_keycode_last_repeat_key = uint(-1);
853 }
854
855#ifdef USE_XINPUT_HOTPLUG
856 /* Hot-Plug support */
857 if (m_xinput_version.present) {
858 XEventClass class_presence;
859 int xi_presence;
860
861 DevicePresence(m_display, xi_presence, class_presence);
862 (void)class_presence;
863
864 if (xe->type == xi_presence) {
865 const XDevicePresenceNotifyEvent *notify_event = (const XDevicePresenceNotifyEvent *)xe;
866 if (ELEM(notify_event->devchange, DeviceEnabled, DeviceDisabled, DeviceAdded, DeviceRemoved))
867 {
868 refreshXInputDevices();
869
870 /* update all window events */
871 {
872 const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
873 vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
874 vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
875
876 for (; win_it != win_end; ++win_it) {
877 GHOST_WindowX11 *window_xinput = static_cast<GHOST_WindowX11 *>(*win_it);
878 window_xinput->refreshXInputDevices();
879 }
880 }
881 }
882 }
883 }
884#endif /* USE_XINPUT_HOTPLUG */
885
886 if (!window) {
887 return;
888 }
889
890#ifdef WITH_X11_XINPUT
891 /* Proximity-Out Events are not reliable, if the tablet is active - check on each event
892 * this adds a little overhead but only while the tablet is in use.
893 * in the future we could have a ghost call window->CheckTabletProximity()
894 * but for now enough parts of the code are checking 'Active'
895 * - campbell */
896 if (window->GetTabletData().Active != GHOST_kTabletModeNone) {
897 bool any_proximity = false;
898
899 for (const GHOST_TabletX11 &xtablet : m_xtablets) {
900 if (checkTabletProximity(xe->xany.display, xtablet.Device)) {
901 any_proximity = true;
902 }
903 }
904
905 if (!any_proximity) {
906 // printf("proximity disable\n");
908 }
909 }
910#endif /* WITH_X11_XINPUT */
911 switch (xe->type) {
912 case Expose: {
913 const XExposeEvent &xee = xe->xexpose;
914
915 if (xee.count == 0) {
916 /* Only generate a single expose event per read of the event queue. */
917
918 /* Event has no timestamp. */
919 const uint64_t event_ms = getMilliSeconds();
920
921 g_event = new GHOST_Event(event_ms, GHOST_kEventWindowUpdate, window);
922 }
923 break;
924 }
925
926 case MotionNotify: {
927 const XMotionEvent &xme = xe->xmotion;
928 const uint64_t event_ms = ms_from_input_time(xme.time);
929
930 bool is_tablet = window->GetTabletData().Active != GHOST_kTabletModeNone;
931
932 if (is_tablet == false && window->getCursorGrabModeIsWarp()) {
933 int32_t x_new = xme.x_root;
934 int32_t y_new = xme.y_root;
935 int32_t x_accum, y_accum;
936
937 /* Warp within bounds. */
938 {
939 GHOST_Rect bounds;
940 int32_t bounds_margin = 0;
941 GHOST_TAxisFlag bounds_axis = GHOST_kAxisNone;
942
943 if (window->getCursorGrabMode() == GHOST_kGrabHide) {
944 window->getClientBounds(bounds);
945
946 /* TODO(@ideasman42): warp the cursor to `window->getCursorGrabInitPos`,
947 * on every motion event, see: D16557 (alternative fix for #102346). */
948 const int32_t subregion_div = 4; /* One quarter of the region. */
949 const int32_t size[2] = {bounds.getWidth(), bounds.getHeight()};
950 const int32_t center[2] = {
951 (bounds.m_l + bounds.m_r) / 2,
952 (bounds.m_t + bounds.m_b) / 2,
953 };
954 /* Shrink the box to prevent the cursor escaping. */
955 bounds.m_l = center[0] - (size[0] / (subregion_div * 2));
956 bounds.m_r = center[0] + (size[0] / (subregion_div * 2));
957 bounds.m_t = center[1] - (size[1] / (subregion_div * 2));
958 bounds.m_b = center[1] + (size[1] / (subregion_div * 2));
959 bounds_margin = 0;
961 }
962 else {
963 /* Fallback to window bounds. */
964 if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) {
965 window->getClientBounds(bounds);
966 }
967 /* Could also clamp to screen bounds wrap with a window outside the view will
968 * fail at the moment. Use offset of 8 in case the window is at screen bounds. */
969 bounds_margin = 8;
970 bounds_axis = window->getCursorGrabAxis();
971 }
972
973 /* Could also clamp to screen bounds wrap with a window outside the view will
974 * fail at the moment. Use inset in case the window is at screen bounds. */
975 bounds.wrapPoint(x_new, y_new, bounds_margin, bounds_axis);
976 }
977
978 window->getCursorGrabAccum(x_accum, y_accum);
979
980 if (x_new != xme.x_root || y_new != xme.y_root) {
981 /* Use time of last event to avoid wrapping several times on the 'same' actual wrap.
982 * Note that we need to deal with X and Y separately as those might wrap at the same time
983 * but still in two different events (corner case, see #74918).
984 * We also have to add a few extra milliseconds of 'padding', as sometimes we get two
985 * close events that will generate extra wrap on the same axis within those few
986 * milliseconds. */
987 if (x_new != xme.x_root && xme.time > m_last_warp_x) {
988 x_accum += (xme.x_root - x_new);
989 m_last_warp_x = lastEventTime(xme.time) + 25;
990 }
991 if (y_new != xme.y_root && xme.time > m_last_warp_y) {
992 y_accum += (xme.y_root - y_new);
993 m_last_warp_y = lastEventTime(xme.time) + 25;
994 }
995 window->setCursorGrabAccum(x_accum, y_accum);
996 /* When wrapping we don't need to add an event because the
997 * #setCursorPosition call will cause a new event after. */
998 setCursorPosition(x_new, y_new); /* wrap */
999 }
1000 else {
1001 g_event = new GHOST_EventCursor(event_ms,
1003 window,
1004 xme.x_root + x_accum,
1005 xme.y_root + y_accum,
1006 window->GetTabletData());
1007 }
1008 }
1009 else {
1010 g_event = new GHOST_EventCursor(event_ms,
1012 window,
1013 xme.x_root,
1014 xme.y_root,
1015 window->GetTabletData());
1016 }
1017 break;
1018 }
1019
1020 case KeyPress:
1021 case KeyRelease: {
1022 XKeyEvent *xke = &(xe->xkey);
1023#ifdef WITH_X11_XINPUT
1024 /* Can be zero for XIM generated events. */
1025 const Time time = xke->time ? xke->time : m_last_key_time;
1026#else
1027 const Time time = xke->time;
1028#endif
1029 const uint64_t event_ms = ms_from_input_time(time);
1030
1031 KeySym key_sym;
1032 char *utf8_buf = nullptr;
1033 char ascii;
1034
1035#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
1036 /* utf8_array[] is initial buffer used for Xutf8LookupString().
1037 * if the length of the utf8 string exceeds this array, allocate
1038 * another memory area and call Xutf8LookupString() again.
1039 * the last 5 bytes are used to avoid segfault that might happen
1040 * at the end of this buffer when the constructor of GHOST_EventKey
1041 * reads 6 bytes regardless of the effective data length. */
1042 char utf8_array[16 * 6 + 5]; /* 16 utf8 characters */
1043 int len = 1; /* at least one null character will be stored */
1044#else
1045 char utf8_array[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
1046#endif
1047 GHOST_TEventType type = (xke->type == KeyPress) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
1048
1049 GHOST_TKey gkey;
1050
1051#ifdef USE_NON_LATIN_KB_WORKAROUND
1052 /* XXX: Code below is kinda awfully convoluted... Issues are:
1053 * - In keyboards like Latin ones, numbers need a 'Shift' to be accessed but key_sym
1054 * is unmodified (or anyone swapping the keys with `xmodmap`).
1055 * - #XLookupKeysym seems to always use first defined key-map (see #47228), which generates
1056 * key-codes unusable by ghost_key_from_keysym for non-Latin-compatible key-maps.
1057 *
1058 * To address this, we:
1059 * - Try to get a 'number' key_sym using #XLookupKeysym (with virtual shift modifier),
1060 * in a very restrictive set of cases.
1061 * - Fallback to #XLookupString to get a key_sym from active user-defined key-map.
1062 *
1063 * Note that:
1064 * - This effectively 'lock' main number keys to always output number events
1065 * (except when using alt-gr).
1066 * - This enforces users to use an ASCII-compatible key-map with Blender -
1067 * but at least it gives predictable and consistent results.
1068 *
1069 * Also, note that nothing in XLib sources [1] makes it obvious why those two functions give
1070 * different key_sym results.
1071 *
1072 * [1] http://cgit.freedesktop.org/xorg/lib/libX11/tree/src/KeyBind.c
1073 */
1074 KeySym key_sym_str;
1075 /* Mode_switch 'modifier' is `AltGr` - when this one or Shift are enabled,
1076 * we do not want to apply that 'forced number' hack. */
1077 const uint mode_switch_mask = XkbKeysymToModifiers(xke->display, XK_Mode_switch);
1078 const uint number_hack_forbidden_kmods_mask = mode_switch_mask | ShiftMask;
1079 if ((xke->keycode >= 10 && xke->keycode < 20) &&
1080 ((xke->state & number_hack_forbidden_kmods_mask) == 0))
1081 {
1082 key_sym = XLookupKeysym(xke, ShiftMask);
1083 if (!((key_sym >= XK_0) && (key_sym <= XK_9))) {
1084 key_sym = XLookupKeysym(xke, 0);
1085 }
1086 }
1087 else {
1088 key_sym = XLookupKeysym(xke, 0);
1089 }
1090
1091 if (!XLookupString(xke, &ascii, 1, &key_sym_str, nullptr)) {
1092 ascii = '\0';
1093 }
1094
1095 /* Only allow a limited set of keys from XLookupKeysym,
1096 * all others we take from XLookupString, unless it gives unknown key... */
1097 gkey = ghost_key_from_keysym_or_keycode(key_sym, m_xkb_descr, xke->keycode);
1098 switch (gkey) {
1099 case GHOST_kKeyRightAlt:
1100 case GHOST_kKeyLeftAlt:
1105 case GHOST_kKeyLeftOS:
1106 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, 1);
1273 }
1274 break;
1275 }
1276 if (xbe.button == Button5) {
1277 if (xbe.type == ButtonPress) {
1278 g_event = new GHOST_EventWheel(event_ms, window, -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 Atom target, utf8_string, string, compound_text, c_string;
1469 XSelectionRequestEvent *xse = &xe->xselectionrequest;
1470
1471 target = XInternAtom(m_display, "TARGETS", False);
1472 utf8_string = XInternAtom(m_display, "UTF8_STRING", False);
1473 string = XInternAtom(m_display, "STRING", False);
1474 compound_text = XInternAtom(m_display, "COMPOUND_TEXT", False);
1475 c_string = XInternAtom(m_display, "C_STRING", False);
1476
1477 /* support obsolete clients */
1478 if (xse->property == None) {
1479 xse->property = xse->target;
1480 }
1481
1482 nxe.xselection.type = SelectionNotify;
1483 nxe.xselection.requestor = xse->requestor;
1484 nxe.xselection.property = xse->property;
1485 nxe.xselection.display = xse->display;
1486 nxe.xselection.selection = xse->selection;
1487 nxe.xselection.target = xse->target;
1488 nxe.xselection.time = xse->time;
1489
1490 /* Check to see if the requester is asking for String */
1491 if (ELEM(xse->target, utf8_string, string, compound_text, c_string)) {
1492 if (xse->selection == XInternAtom(m_display, "PRIMARY", False)) {
1493 XChangeProperty(m_display,
1494 xse->requestor,
1495 xse->property,
1496 xse->target,
1497 8,
1498 PropModeReplace,
1500 strlen(txt_select_buffer));
1501 }
1502 else if (xse->selection == XInternAtom(m_display, "CLIPBOARD", False)) {
1503 XChangeProperty(m_display,
1504 xse->requestor,
1505 xse->property,
1506 xse->target,
1507 8,
1508 PropModeReplace,
1510 strlen(txt_cut_buffer));
1511 }
1512 }
1513 else if (xse->target == target) {
1514 Atom alist[5];
1515 alist[0] = target;
1516 alist[1] = utf8_string;
1517 alist[2] = string;
1518 alist[3] = compound_text;
1519 alist[4] = c_string;
1520 XChangeProperty(m_display,
1521 xse->requestor,
1522 xse->property,
1523 xse->target,
1524 32,
1525 PropModeReplace,
1526 (uchar *)alist,
1527 5);
1528 XFlush(m_display);
1529 }
1530 else {
1531 /* Change property to None because we do not support anything but STRING */
1532 nxe.xselection.property = None;
1533 }
1534
1535 /* Send the event to the client 0 0 == False, #SelectionNotify */
1536 XSendEvent(m_display, xse->requestor, 0, 0, &nxe);
1537 XFlush(m_display);
1538 break;
1539 }
1540
1541 default: {
1542#ifdef WITH_X11_XINPUT
1543 for (GHOST_TabletX11 &xtablet : m_xtablets) {
1544 if (ELEM(xe->type, xtablet.MotionEvent, xtablet.PressEvent)) {
1545 const XDeviceMotionEvent *data = (const XDeviceMotionEvent *)xe;
1546 if (data->deviceid != xtablet.ID) {
1547 continue;
1548 }
1549
1550 const uchar axis_first = data->first_axis;
1551 const uchar axes_end = axis_first + data->axes_count; /* after the last */
1552 int axis_value;
1553
1554 /* stroke might begin without leading ProxyIn event,
1555 * this happens when window is opened when stylus is already hovering
1556 * around tablet surface */
1557 window->GetTabletData().Active = xtablet.mode;
1558
1559 /* NOTE: This event might be generated with incomplete data-set
1560 * (don't exactly know why, looks like in some cases, if the value does not change,
1561 * it is not included in subsequent #XDeviceMotionEvent events).
1562 * So we have to check which values this event actually contains!
1563 */
1564
1565# define AXIS_VALUE_GET(axis, val) \
1566 ((axis_first <= axis && axes_end > axis) && \
1567 ((void)(val = data->axis_data[axis - axis_first]), true))
1568
1569 if (AXIS_VALUE_GET(2, axis_value)) {
1570 window->GetTabletData().Pressure = axis_value / float(xtablet.PressureLevels);
1571 }
1572
1573 /* NOTE(@broken): the `short` cast and the & 0xffff is bizarre and unexplained anywhere,
1574 * but I got garbage data without it. Found it in the `xidump.c` source.
1575 *
1576 * NOTE(@mont29): The '& 0xffff' just truncates the value to its two lowest bytes,
1577 * this probably means some drivers do not properly set the whole int value?
1578 * Since we convert to float afterward,
1579 * I don't think we need to cast to short here, but do not have a device to check this.
1580 */
1581 if (AXIS_VALUE_GET(3, axis_value)) {
1582 window->GetTabletData().Xtilt = short(axis_value & 0xffff) /
1583 float(xtablet.XtiltLevels);
1584 }
1585 if (AXIS_VALUE_GET(4, axis_value)) {
1586 window->GetTabletData().Ytilt = short(axis_value & 0xffff) /
1587 float(xtablet.YtiltLevels);
1588 }
1589
1590# undef AXIS_VALUE_GET
1591 }
1592 else if (xe->type == xtablet.ProxInEvent) {
1593 const XProximityNotifyEvent *data = (const XProximityNotifyEvent *)xe;
1594 if (data->deviceid != xtablet.ID) {
1595 continue;
1596 }
1597
1598 window->GetTabletData().Active = xtablet.mode;
1599 }
1600 else if (xe->type == xtablet.ProxOutEvent) {
1602 }
1603 }
1604#endif // WITH_X11_XINPUT
1605 break;
1606 }
1607 }
1608
1609 if (g_event) {
1610 pushEvent(g_event);
1611 }
1612}
1613
1615{
1616 /* NOTE: There are known issues/limitations at the moment:
1617 *
1618 * - Blender has no control of the cursor outside of its window, so it is
1619 * not going to be the eyedropper icon.
1620 * - GHOST does not report click events from outside of the window, so the
1621 * user needs to press Enter instead.
1622 *
1623 * Ref #111303. */
1624
1625 XColor c;
1626 int32_t x, y;
1627
1629 return GHOST_kFailure;
1630 }
1631 XImage *image = XGetImage(m_display,
1632 XRootWindow(m_display, XDefaultScreen(m_display)),
1633 x,
1634 y,
1635 1,
1636 1,
1637 AllPlanes,
1638 XYPixmap);
1639 if (image == nullptr) {
1640 return GHOST_kFailure;
1641 }
1642 c.pixel = XGetPixel(image, 0, 0);
1643 XFree(image);
1644 XQueryColor(m_display, XDefaultColormap(m_display, XDefaultScreen(m_display)), &c);
1645
1646 /* X11 returns colors in the [0, 65535] range, so we need to scale back to [0, 1]. */
1647 r_color[0] = c.red / 65535.0f;
1648 r_color[1] = c.green / 65535.0f;
1649 r_color[2] = c.blue / 65535.0f;
1650 return GHOST_kSuccess;
1651}
1652
1654{
1655
1656 /* Analyze the masks returned from #XQueryPointer. */
1657
1658 memset((void *)m_keyboard_vector, 0, sizeof(m_keyboard_vector));
1659
1660 XQueryKeymap(m_display, (char *)m_keyboard_vector);
1661
1662 /* Now translate key symbols into key-codes and test with vector. */
1663
1664 const static KeyCode shift_l = XKeysymToKeycode(m_display, XK_Shift_L);
1665 const static KeyCode shift_r = XKeysymToKeycode(m_display, XK_Shift_R);
1666 const static KeyCode control_l = XKeysymToKeycode(m_display, XK_Control_L);
1667 const static KeyCode control_r = XKeysymToKeycode(m_display, XK_Control_R);
1668 const static KeyCode alt_l = XKeysymToKeycode(m_display, XK_Alt_L);
1669 const static KeyCode alt_r = XKeysymToKeycode(m_display, XK_Alt_R);
1670 const static KeyCode super_l = XKeysymToKeycode(m_display, XK_Super_L);
1671 const static KeyCode super_r = XKeysymToKeycode(m_display, XK_Super_R);
1672
1673 /* shift */
1675 ((m_keyboard_vector[shift_l >> 3] >> (shift_l & 7)) & 1) != 0);
1677 ((m_keyboard_vector[shift_r >> 3] >> (shift_r & 7)) & 1) != 0);
1678 /* control */
1680 ((m_keyboard_vector[control_l >> 3] >> (control_l & 7)) & 1) != 0);
1682 ((m_keyboard_vector[control_r >> 3] >> (control_r & 7)) & 1) != 0);
1683 /* alt */
1684 keys.set(GHOST_kModifierKeyLeftAlt, ((m_keyboard_vector[alt_l >> 3] >> (alt_l & 7)) & 1) != 0);
1685 keys.set(GHOST_kModifierKeyRightAlt, ((m_keyboard_vector[alt_r >> 3] >> (alt_r & 7)) & 1) != 0);
1686 /* super (windows) - only one GHOST-kModifierKeyOS, so mapping to either */
1688 ((m_keyboard_vector[super_l >> 3] >> (super_l & 7)) & 1) != 0);
1690 ((m_keyboard_vector[super_r >> 3] >> (super_r & 7)) & 1) != 0);
1691
1692 return GHOST_kSuccess;
1693}
1694
1696{
1697 Window root_return, child_return;
1698 int rx, ry, wx, wy;
1699 uint mask_return;
1700
1701 if (XQueryPointer(m_display,
1702 RootWindow(m_display, DefaultScreen(m_display)),
1703 &root_return,
1704 &child_return,
1705 &rx,
1706 &ry,
1707 &wx,
1708 &wy,
1709 &mask_return) == True)
1710 {
1711 buttons.set(GHOST_kButtonMaskLeft, (mask_return & Button1Mask) != 0);
1712 buttons.set(GHOST_kButtonMaskMiddle, (mask_return & Button2Mask) != 0);
1713 buttons.set(GHOST_kButtonMaskRight, (mask_return & Button3Mask) != 0);
1714 buttons.set(GHOST_kButtonMaskButton4, (mask_return & Button4Mask) != 0);
1715 buttons.set(GHOST_kButtonMaskButton5, (mask_return & Button5Mask) != 0);
1716 }
1717 else {
1718 return GHOST_kFailure;
1719 }
1720
1721 return GHOST_kSuccess;
1722}
1723
1725 int32_t &x,
1726 int32_t &y,
1727 Window *child_return)
1728{
1729 int rx, ry, wx, wy;
1730 uint mask_return;
1731 Window root_return;
1732
1733 if (XQueryPointer(display,
1734 RootWindow(display, DefaultScreen(display)),
1735 &root_return,
1736 child_return,
1737 &rx,
1738 &ry,
1739 &wx,
1740 &wy,
1741 &mask_return) == False)
1742 {
1743 return GHOST_kFailure;
1744 }
1745
1746 x = rx;
1747 y = ry;
1748
1749 return GHOST_kSuccess;
1750}
1751
1753{
1754 Window child_return;
1755 return getCursorPosition_impl(m_display, x, y, &child_return);
1756}
1757
1759{
1760
1761 /* This is a brute force move in screen coordinates
1762 * #XWarpPointer does relative moves so first determine the
1763 * current pointer position. */
1764
1765 int cx, cy;
1766
1767#ifdef WITH_XWAYLAND_HACK
1768 Window child_return = None;
1769 if (getCursorPosition_impl(m_display, cx, cy, &child_return) == GHOST_kFailure) {
1770 return GHOST_kFailure;
1771 }
1772#else
1773 if (getCursorPosition(cx, cy) == GHOST_kFailure) {
1774 return GHOST_kFailure;
1775 }
1776#endif
1777
1778 int relx = x - cx;
1779 int rely = y - cy;
1780
1781#ifdef WITH_XWAYLAND_HACK
1782 if (use_xwayland_hack) {
1783 if (child_return != None) {
1784 XFixesHideCursor(m_display, child_return);
1785 }
1786 }
1787#endif
1788
1789#if defined(WITH_X11_XINPUT) && defined(USE_X11_XINPUT_WARP)
1790 if ((m_xinput_version.present) && (m_xinput_version.major_version >= 2)) {
1791 /* Needed to account for XInput "Coordinate Transformation Matrix", see #48901 */
1792 int device_id;
1793 if (XIGetClientPointer(m_display, None, &device_id) != False) {
1794 XIWarpPointer(m_display, device_id, None, None, 0, 0, 0, 0, relx, rely);
1795 }
1796 }
1797 else
1798#endif
1799 {
1800 XWarpPointer(m_display, None, None, 0, 0, 0, 0, relx, rely);
1801 }
1802
1803#ifdef WITH_XWAYLAND_HACK
1804 if (use_xwayland_hack) {
1805 if (child_return != None) {
1806 XFixesShowCursor(m_display, child_return);
1807 }
1808 }
1809#endif
1810
1811 XSync(m_display, 0); /* Sync to process all requests */
1812
1813 return GHOST_kSuccess;
1814}
1815
1817{
1819 ~(
1820 /* No support yet for image copy/paste. */
1822 /* No support yet for IME input methods. */
1824}
1825
1827{
1828 GHOST_ASSERT((bad_wind != nullptr), "addDirtyWindow() nullptr ptr trapped (window)");
1829
1830 m_dirty_windows.push_back(bad_wind);
1831}
1832
1833bool GHOST_SystemX11::generateWindowExposeEvents()
1834{
1835 vector<GHOST_WindowX11 *>::const_iterator w_start = m_dirty_windows.begin();
1836 vector<GHOST_WindowX11 *>::const_iterator w_end = m_dirty_windows.end();
1837 bool anyProcessed = false;
1838
1839 for (; w_start != w_end; ++w_start) {
1840 const GHOST_Event *g_event = new GHOST_Event(
1842
1843 (*w_start)->validate();
1844
1845 if (g_event) {
1846 pushEvent(g_event);
1847 anyProcessed = true;
1848 }
1849 }
1850
1851 m_dirty_windows.clear();
1852 return anyProcessed;
1853}
1854
1856 XkbDescPtr xkb_descr,
1857 const KeyCode keycode)
1858{
1859 GHOST_TKey type = ghost_key_from_keysym(key_sym);
1860 if (type == GHOST_kKeyUnknown) {
1861 if (xkb_descr) {
1862 type = ghost_key_from_keycode(xkb_descr, keycode);
1863 }
1864 }
1865 return type;
1866}
1867
1868#define GXMAP(k, x, y) \
1869 case x: \
1870 k = y; \
1871 break
1872
1873static GHOST_TKey ghost_key_from_keysym(const KeySym key)
1874{
1875 GHOST_TKey type;
1876
1877 if ((key >= XK_A) && (key <= XK_Z)) {
1878 type = GHOST_TKey(key - XK_A + int(GHOST_kKeyA));
1879 }
1880 else if ((key >= XK_a) && (key <= XK_z)) {
1881 type = GHOST_TKey(key - XK_a + int(GHOST_kKeyA));
1882 }
1883 else if ((key >= XK_0) && (key <= XK_9)) {
1884 type = GHOST_TKey(key - XK_0 + int(GHOST_kKey0));
1885 }
1886 else if ((key >= XK_F1) && (key <= XK_F24)) {
1887 type = GHOST_TKey(key - XK_F1 + int(GHOST_kKeyF1));
1888 }
1889 else {
1890 switch (key) {
1891 GXMAP(type, XK_BackSpace, GHOST_kKeyBackSpace);
1892 GXMAP(type, XK_Tab, GHOST_kKeyTab);
1893 GXMAP(type, XK_ISO_Left_Tab, GHOST_kKeyTab);
1894 GXMAP(type, XK_Return, GHOST_kKeyEnter);
1895 GXMAP(type, XK_Escape, GHOST_kKeyEsc);
1896 GXMAP(type, XK_space, GHOST_kKeySpace);
1897
1898 GXMAP(type, XK_Linefeed, GHOST_kKeyLinefeed);
1899 GXMAP(type, XK_semicolon, GHOST_kKeySemicolon);
1900 GXMAP(type, XK_period, GHOST_kKeyPeriod);
1901 GXMAP(type, XK_comma, GHOST_kKeyComma);
1902 GXMAP(type, XK_quoteright, GHOST_kKeyQuote);
1903 GXMAP(type, XK_quoteleft, GHOST_kKeyAccentGrave);
1904 GXMAP(type, XK_minus, GHOST_kKeyMinus);
1905 GXMAP(type, XK_plus, GHOST_kKeyPlus);
1906 GXMAP(type, XK_slash, GHOST_kKeySlash);
1907 GXMAP(type, XK_backslash, GHOST_kKeyBackslash);
1908 GXMAP(type, XK_equal, GHOST_kKeyEqual);
1909 GXMAP(type, XK_bracketleft, GHOST_kKeyLeftBracket);
1910 GXMAP(type, XK_bracketright, GHOST_kKeyRightBracket);
1911 GXMAP(type, XK_Pause, GHOST_kKeyPause);
1912
1913 GXMAP(type, XK_Shift_L, GHOST_kKeyLeftShift);
1914 GXMAP(type, XK_Shift_R, GHOST_kKeyRightShift);
1915 GXMAP(type, XK_Control_L, GHOST_kKeyLeftControl);
1916 GXMAP(type, XK_Control_R, GHOST_kKeyRightControl);
1917 GXMAP(type, XK_Alt_L, GHOST_kKeyLeftAlt);
1918 GXMAP(type, XK_Alt_R, GHOST_kKeyRightAlt);
1919 GXMAP(type, XK_Super_L, GHOST_kKeyLeftOS);
1920 GXMAP(type, XK_Super_R, GHOST_kKeyRightOS);
1921
1922 GXMAP(type, XK_Insert, GHOST_kKeyInsert);
1923 GXMAP(type, XK_Delete, GHOST_kKeyDelete);
1924 GXMAP(type, XK_Home, GHOST_kKeyHome);
1925 GXMAP(type, XK_End, GHOST_kKeyEnd);
1926 GXMAP(type, XK_Page_Up, GHOST_kKeyUpPage);
1927 GXMAP(type, XK_Page_Down, GHOST_kKeyDownPage);
1928
1929 GXMAP(type, XK_Left, GHOST_kKeyLeftArrow);
1930 GXMAP(type, XK_Right, GHOST_kKeyRightArrow);
1931 GXMAP(type, XK_Up, GHOST_kKeyUpArrow);
1932 GXMAP(type, XK_Down, GHOST_kKeyDownArrow);
1933
1934 GXMAP(type, XK_Caps_Lock, GHOST_kKeyCapsLock);
1935 GXMAP(type, XK_Scroll_Lock, GHOST_kKeyScrollLock);
1936 GXMAP(type, XK_Num_Lock, GHOST_kKeyNumLock);
1937 GXMAP(type, XK_Menu, GHOST_kKeyApp);
1938
1939 /* keypad events */
1940
1941 GXMAP(type, XK_KP_0, GHOST_kKeyNumpad0);
1942 GXMAP(type, XK_KP_1, GHOST_kKeyNumpad1);
1943 GXMAP(type, XK_KP_2, GHOST_kKeyNumpad2);
1944 GXMAP(type, XK_KP_3, GHOST_kKeyNumpad3);
1945 GXMAP(type, XK_KP_4, GHOST_kKeyNumpad4);
1946 GXMAP(type, XK_KP_5, GHOST_kKeyNumpad5);
1947 GXMAP(type, XK_KP_6, GHOST_kKeyNumpad6);
1948 GXMAP(type, XK_KP_7, GHOST_kKeyNumpad7);
1949 GXMAP(type, XK_KP_8, GHOST_kKeyNumpad8);
1950 GXMAP(type, XK_KP_9, GHOST_kKeyNumpad9);
1951 GXMAP(type, XK_KP_Decimal, GHOST_kKeyNumpadPeriod);
1952
1953 GXMAP(type, XK_KP_Insert, GHOST_kKeyNumpad0);
1954 GXMAP(type, XK_KP_End, GHOST_kKeyNumpad1);
1955 GXMAP(type, XK_KP_Down, GHOST_kKeyNumpad2);
1956 GXMAP(type, XK_KP_Page_Down, GHOST_kKeyNumpad3);
1957 GXMAP(type, XK_KP_Left, GHOST_kKeyNumpad4);
1958 GXMAP(type, XK_KP_Begin, GHOST_kKeyNumpad5);
1959 GXMAP(type, XK_KP_Right, GHOST_kKeyNumpad6);
1960 GXMAP(type, XK_KP_Home, GHOST_kKeyNumpad7);
1961 GXMAP(type, XK_KP_Up, GHOST_kKeyNumpad8);
1962 GXMAP(type, XK_KP_Page_Up, GHOST_kKeyNumpad9);
1963 GXMAP(type, XK_KP_Delete, GHOST_kKeyNumpadPeriod);
1964
1965 GXMAP(type, XK_KP_Enter, GHOST_kKeyNumpadEnter);
1966 GXMAP(type, XK_KP_Add, GHOST_kKeyNumpadPlus);
1967 GXMAP(type, XK_KP_Subtract, GHOST_kKeyNumpadMinus);
1968 GXMAP(type, XK_KP_Multiply, GHOST_kKeyNumpadAsterisk);
1969 GXMAP(type, XK_KP_Divide, GHOST_kKeyNumpadSlash);
1970
1971 /* Media keys in some keyboards and laptops with XFree86/XORG. */
1972#ifdef WITH_XF86KEYSYM
1973 GXMAP(type, XF86XK_AudioPlay, GHOST_kKeyMediaPlay);
1974 GXMAP(type, XF86XK_AudioStop, GHOST_kKeyMediaStop);
1975 GXMAP(type, XF86XK_AudioPrev, GHOST_kKeyMediaFirst);
1976 GXMAP(type, XF86XK_AudioRewind, GHOST_kKeyMediaFirst);
1977 GXMAP(type, XF86XK_AudioNext, GHOST_kKeyMediaLast);
1978# ifdef XF86XK_AudioForward /* Debian lenny's XF86keysym.h has no XF86XK_AudioForward define */
1979 GXMAP(type, XF86XK_AudioForward, GHOST_kKeyMediaLast);
1980# endif
1981#endif
1982 default:
1983#ifdef WITH_GHOST_DEBUG
1984 printf("%s: unknown key: %lu / 0x%lx\n", __func__, key, key);
1985#endif
1986 type = GHOST_kKeyUnknown;
1987 break;
1988 }
1989 }
1990
1991 return type;
1992}
1993
1994#undef GXMAP
1995
1996#define MAKE_ID(a, b, c, d) (int(d) << 24 | int(c) << 16 | (b) << 8 | (a))
1997
1998static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode)
1999{
2000 GHOST_ASSERT(XkbKeyNameLength == 4, "Name length is invalid!");
2001 if (keycode >= xkb_descr->min_key_code && keycode <= xkb_descr->max_key_code) {
2002 const char *id_str = xkb_descr->names->keys[keycode].name;
2003 const uint32_t id = MAKE_ID(id_str[0], id_str[1], id_str[2], id_str[3]);
2004 switch (id) {
2005 case MAKE_ID('T', 'L', 'D', 'E'):
2006 return GHOST_kKeyAccentGrave;
2007 case MAKE_ID('L', 'S', 'G', 'T'):
2008 return GHOST_kKeyGrLess;
2009#ifdef WITH_GHOST_DEBUG
2010 default:
2011 printf("%s unhandled keycode: %.*s\n", __func__, XkbKeyNameLength, id_str);
2012 break;
2013#endif
2014 }
2015 }
2016 else if (keycode != 0) {
2017 GHOST_ASSERT(false, "KeyCode out of range!");
2018 }
2019 return GHOST_kKeyUnknown;
2020}
2021
2022#undef MAKE_ID
2023
2024/* From `xclip.c` #xcout() v0.11. */
2025
2026#define XCLIB_XCOUT_NONE 0 /* no context */
2027#define XCLIB_XCOUT_SENTCONVSEL 1 /* sent a request */
2028#define XCLIB_XCOUT_INCR 2 /* in an incr loop */
2029#define XCLIB_XCOUT_FALLBACK 3 /* STRING failed, need fallback to UTF8 */
2030#define XCLIB_XCOUT_FALLBACK_UTF8 4 /* UTF8 failed, move to compound. */
2031#define XCLIB_XCOUT_FALLBACK_COMP 5 /* compound failed, move to text. */
2032#define XCLIB_XCOUT_FALLBACK_TEXT 6
2033
2034/* Retrieves the contents of a selections. */
2036 const XEvent *evt, Atom sel, Atom target, uchar **txt, ulong *len, uint *context) const
2037{
2038 Atom pty_type;
2039 int pty_format;
2040 uchar *buffer;
2041 ulong pty_size, pty_items;
2042 uchar *ltxt = *txt;
2043
2044 const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
2045 vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
2046 GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
2047 Window win = window->getXWindow();
2048
2049 switch (*context) {
2050 /* There is no context, do an XConvertSelection() */
2051 case XCLIB_XCOUT_NONE:
2052 /* Initialize return length to 0. */
2053 if (*len > 0) {
2054 free(*txt);
2055 *len = 0;
2056 }
2057
2058 /* Send a selection request */
2059 XConvertSelection(m_display, sel, target, m_atom.XCLIP_OUT, win, CurrentTime);
2060 *context = XCLIB_XCOUT_SENTCONVSEL;
2061 return;
2062
2064 if (evt->type != SelectionNotify) {
2065 return;
2066 }
2067
2068 if (target == m_atom.UTF8_STRING && evt->xselection.property == None) {
2069 *context = XCLIB_XCOUT_FALLBACK_UTF8;
2070 return;
2071 }
2072 if (target == m_atom.COMPOUND_TEXT && evt->xselection.property == None) {
2073 *context = XCLIB_XCOUT_FALLBACK_COMP;
2074 return;
2075 }
2076 if (target == m_atom.TEXT && evt->xselection.property == None) {
2077 *context = XCLIB_XCOUT_FALLBACK_TEXT;
2078 return;
2079 }
2080
2081 /* find the size and format of the data in property */
2082 XGetWindowProperty(m_display,
2083 win,
2084 m_atom.XCLIP_OUT,
2085 0,
2086 0,
2087 False,
2088 AnyPropertyType,
2089 &pty_type,
2090 &pty_format,
2091 &pty_items,
2092 &pty_size,
2093 &buffer);
2094 XFree(buffer);
2095
2096 if (pty_type == m_atom.INCR) {
2097 /* start INCR mechanism by deleting property */
2098 XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2099 XFlush(m_display);
2100 *context = XCLIB_XCOUT_INCR;
2101 return;
2102 }
2103
2104 /* If it's not INCR, and not `format == 8`, then there's
2105 * nothing in the selection (that `xclip` understands, anyway). */
2106
2107 if (pty_format != 8) {
2108 *context = XCLIB_XCOUT_NONE;
2109 return;
2110 }
2111
2112 /* Not using INCR mechanism, just read the property. */
2113 XGetWindowProperty(m_display,
2114 win,
2115 m_atom.XCLIP_OUT,
2116 0,
2117 long(pty_size),
2118 False,
2119 AnyPropertyType,
2120 &pty_type,
2121 &pty_format,
2122 &pty_items,
2123 &pty_size,
2124 &buffer);
2125
2126 /* finished with property, delete it */
2127 XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2128
2129 /* copy the buffer to the pointer for returned data */
2130 ltxt = (uchar *)malloc(pty_items);
2131 memcpy(ltxt, buffer, pty_items);
2132
2133 /* set the length of the returned data */
2134 *len = pty_items;
2135 *txt = ltxt;
2136
2137 /* free the buffer */
2138 XFree(buffer);
2139
2140 *context = XCLIB_XCOUT_NONE;
2141
2142 /* complete contents of selection fetched, return 1 */
2143 return;
2144
2145 case XCLIB_XCOUT_INCR:
2146 /* To use the INCR method, we basically delete the
2147 * property with the selection in it, wait for an
2148 * event indicating that the property has been created,
2149 * then read it, delete it, etc. */
2150
2151 /* make sure that the event is relevant */
2152 if (evt->type != PropertyNotify) {
2153 return;
2154 }
2155
2156 /* skip unless the property has a new value */
2157 if (evt->xproperty.state != PropertyNewValue) {
2158 return;
2159 }
2160
2161 /* check size and format of the property */
2162 XGetWindowProperty(m_display,
2163 win,
2164 m_atom.XCLIP_OUT,
2165 0,
2166 0,
2167 False,
2168 AnyPropertyType,
2169 &pty_type,
2170 &pty_format,
2171 &pty_items,
2172 &pty_size,
2173 &buffer);
2174
2175 if (pty_format != 8) {
2176 /* property does not contain text, delete it
2177 * to tell the other X client that we have read
2178 * it and to send the next property */
2179 XFree(buffer);
2180 XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2181 return;
2182 }
2183
2184 if (pty_size == 0) {
2185 /* no more data, exit from loop */
2186 XFree(buffer);
2187 XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2188 *context = XCLIB_XCOUT_NONE;
2189
2190 /* this means that an INCR transfer is now
2191 * complete, return 1 */
2192 return;
2193 }
2194
2195 XFree(buffer);
2196
2197 /* if we have come this far, the property contains
2198 * text, we know the size. */
2199 XGetWindowProperty(m_display,
2200 win,
2201 m_atom.XCLIP_OUT,
2202 0,
2203 long(pty_size),
2204 False,
2205 AnyPropertyType,
2206 &pty_type,
2207 &pty_format,
2208 &pty_items,
2209 &pty_size,
2210 &buffer);
2211
2212 /* allocate memory to accommodate data in *txt */
2213 if (*len == 0) {
2214 *len = pty_items;
2215 ltxt = (uchar *)malloc(*len);
2216 }
2217 else {
2218 *len += pty_items;
2219 ltxt = (uchar *)realloc(ltxt, *len);
2220 }
2221
2222 /* add data to ltxt */
2223 memcpy(&ltxt[*len - pty_items], buffer, pty_items);
2224
2225 *txt = ltxt;
2226 XFree(buffer);
2227
2228 /* delete property to get the next item */
2229 XDeleteProperty(m_display, win, m_atom.XCLIP_OUT);
2230 XFlush(m_display);
2231 return;
2232 }
2233}
2234
2236{
2237 Atom sseln;
2238 Atom target = m_atom.UTF8_STRING;
2239 Window owner;
2240
2241 /* From `xclip.c` `doOut()` v0.11. */
2242 char *sel_buf;
2243 ulong sel_len = 0;
2244 XEvent evt;
2245 uint context = XCLIB_XCOUT_NONE;
2246
2247 if (selection == True) {
2248 sseln = m_atom.PRIMARY;
2249 }
2250 else {
2251 sseln = m_atom.CLIPBOARD;
2252 }
2253
2254 const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
2255 vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
2256 GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
2257 Window win = window->getXWindow();
2258
2259 /* check if we are the owner. */
2260 owner = XGetSelectionOwner(m_display, sseln);
2261 if (owner == win) {
2262 if (sseln == m_atom.CLIPBOARD) {
2263 size_t sel_buf_size = strlen(txt_cut_buffer) + 1;
2264 sel_buf = (char *)malloc(sel_buf_size);
2265 memcpy(sel_buf, txt_cut_buffer, sel_buf_size);
2266 return sel_buf;
2267 }
2268 size_t sel_buf_size = strlen(txt_select_buffer) + 1;
2269 sel_buf = (char *)malloc(sel_buf_size);
2270 memcpy(sel_buf, txt_select_buffer, sel_buf_size);
2271 return sel_buf;
2272 }
2273 if (owner == None) {
2274 return nullptr;
2275 }
2276
2277 /* Restore events so copy doesn't swallow other event types (keyboard/mouse). */
2278 vector<XEvent> restore_events;
2279
2280 while (true) {
2281 /* only get an event if xcout() is doing something */
2282 bool restore_this_event = false;
2283 if (context != XCLIB_XCOUT_NONE) {
2284 XNextEvent(m_display, &evt);
2285 restore_this_event = (evt.type != SelectionNotify);
2286 }
2287
2288 /* fetch the selection, or part of it */
2289 getClipboard_xcout(&evt, sseln, target, (uchar **)&sel_buf, &sel_len, &context);
2290
2291 if (restore_this_event) {
2292 restore_events.push_back(evt);
2293 }
2294
2295 /* Fallback is needed. Set #XA_STRING to target and restart the loop. */
2296 if (context == XCLIB_XCOUT_FALLBACK) {
2297 context = XCLIB_XCOUT_NONE;
2298 target = m_atom.STRING;
2299 continue;
2300 }
2301 if (context == XCLIB_XCOUT_FALLBACK_UTF8) {
2302 /* utf8 fail, move to compound text. */
2303 context = XCLIB_XCOUT_NONE;
2304 target = m_atom.COMPOUND_TEXT;
2305 continue;
2306 }
2307 if (context == XCLIB_XCOUT_FALLBACK_COMP) {
2308 /* Compound text fail, move to text. */
2309 context = XCLIB_XCOUT_NONE;
2310 target = m_atom.TEXT;
2311 continue;
2312 }
2313 if (context == XCLIB_XCOUT_FALLBACK_TEXT) {
2314 /* Text fail, nothing else to try, break. */
2315 context = XCLIB_XCOUT_NONE;
2316 }
2317
2318 /* Only continue if #xcout() is doing something. */
2319 if (context == XCLIB_XCOUT_NONE) {
2320 break;
2321 }
2322 }
2323
2324 while (!restore_events.empty()) {
2325 XPutBackEvent(m_display, &restore_events.back());
2326 restore_events.pop_back();
2327 }
2328
2329 if (sel_len) {
2330 /* Only print the buffer out, and free it, if it's not empty. */
2331 char *tmp_data = (char *)malloc(sel_len + 1);
2332 memcpy(tmp_data, (char *)sel_buf, sel_len);
2333 tmp_data[sel_len] = '\0';
2334
2335 if (sseln == m_atom.STRING) {
2336 XFree(sel_buf);
2337 }
2338 else {
2339 free(sel_buf);
2340 }
2341
2342 return tmp_data;
2343 }
2344 return nullptr;
2345}
2346
2347void GHOST_SystemX11::putClipboard(const char *buffer, bool selection) const
2348{
2349 Window m_window, owner;
2350
2351 const vector<GHOST_IWindow *> &win_vec = m_windowManager->getWindows();
2352 vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
2353 GHOST_WindowX11 *window = static_cast<GHOST_WindowX11 *>(*win_it);
2354 m_window = window->getXWindow();
2355
2356 if (buffer) {
2357 if (selection == False) {
2358 XSetSelectionOwner(m_display, m_atom.CLIPBOARD, m_window, CurrentTime);
2359 owner = XGetSelectionOwner(m_display, m_atom.CLIPBOARD);
2360 if (txt_cut_buffer) {
2361 free((void *)txt_cut_buffer);
2362 }
2363
2364 size_t buffer_size = strlen(buffer) + 1;
2365 txt_cut_buffer = (char *)malloc(buffer_size);
2366 memcpy(txt_cut_buffer, buffer, buffer_size);
2367 }
2368 else {
2369 XSetSelectionOwner(m_display, m_atom.PRIMARY, m_window, CurrentTime);
2370 owner = XGetSelectionOwner(m_display, m_atom.PRIMARY);
2371 if (txt_select_buffer) {
2372 free((void *)txt_select_buffer);
2373 }
2374
2375 size_t buffer_size = strlen(buffer) + 1;
2376 txt_select_buffer = (char *)malloc(buffer_size);
2377 memcpy(txt_select_buffer, buffer, buffer_size);
2378 }
2379
2380 if (owner != m_window) {
2381 fprintf(stderr, "failed to own primary\n");
2382 }
2383 }
2384}
2385
2386/* -------------------------------------------------------------------- */
2389
2391 public:
2392 /* Width of the dialog. */
2394 /* Height of the dialog. */
2396 /* Default padding (x direction) between controls and edge of dialog. */
2398 /* Default padding (y direction) between controls and edge of dialog. */
2400 /* Width of a single button. */
2402 /* Height of a single button. */
2404 /* Inset of a button to its text. */
2406 /* Size of the border of the button. */
2408 /* Height of a line of text */
2410 /* Offset of the text inside the button. */
2412
2413 /* Construct a new #DialogData with the default settings. */
2415 : width(640),
2416 height(175),
2417 padding_x(10),
2418 padding_y(5),
2419 button_width(130),
2420 button_height(24),
2421 button_inset_x(10),
2423 line_height(16)
2424 {
2426 }
2427
2428 void drawButton(Display *display,
2429 Window &window,
2430 GC &borderGC,
2431 GC &buttonGC,
2432 uint button_num,
2433 const char *label)
2434 {
2435 XFillRectangle(display,
2436 window,
2437 borderGC,
2438 width - (padding_x + button_width) * button_num,
2442
2443 XFillRectangle(display,
2444 window,
2445 buttonGC,
2446 width - (padding_x + button_width) * button_num + button_border_size,
2450
2451 XDrawString(display,
2452 window,
2453 borderGC,
2454 width - (padding_x + button_width) * button_num + button_inset_x,
2456 label,
2457 strlen(label));
2458 }
2459
2460 /* Is the mouse inside the given button */
2461 bool isInsideButton(const XEvent &e, uint button_num) const
2462 {
2463 return (
2464 (e.xmotion.y > int(height - padding_y - button_height)) &&
2465 (e.xmotion.y < int(height - padding_y)) &&
2466 (e.xmotion.x > int(width - (padding_x + button_width) * button_num)) &&
2467 (e.xmotion.x < int(width - padding_x - (padding_x + button_width) * (button_num - 1))));
2468 }
2469};
2470
2471static void split(const char *text, const char *seps, char ***str, int *count)
2472{
2473 const char *tok;
2474 char *data;
2475 int i;
2476 *count = 0;
2477
2478 data = strdup(text);
2479 for (tok = strtok(data, seps); tok != nullptr; tok = strtok(nullptr, seps)) {
2480 (*count)++;
2481 }
2482 free(data);
2483
2484 data = strdup(text);
2485 *str = (char **)malloc(size_t(*count) * sizeof(char *));
2486 for (i = 0, tok = strtok(data, seps); tok != nullptr; tok = strtok(nullptr, seps), i++) {
2487 (*str)[i] = strdup(tok);
2488 }
2489 free(data);
2490}
2491
2493 const char *message,
2494 const char *help_label,
2495 const char *continue_label,
2496 const char *link,
2497 GHOST_DialogOptions /*dialog_options*/) const
2498{
2499 char **text_splitted = nullptr;
2500 int textLines = 0;
2501 split(message, "\n", &text_splitted, &textLines);
2502
2503 DialogData dialog_data;
2504 XSizeHints hints;
2505
2506 Window window;
2507 XEvent e;
2508 int screen = DefaultScreen(m_display);
2509 window = XCreateSimpleWindow(m_display,
2510 RootWindow(m_display, screen),
2511 0,
2512 0,
2513 dialog_data.width,
2514 dialog_data.height,
2515 1,
2516 BlackPixel(m_display, screen),
2517 WhitePixel(m_display, screen));
2518
2519 /* Window Should not be resizable */
2520 {
2521 hints.flags = PSize | PMinSize | PMaxSize;
2522 hints.min_width = hints.max_width = hints.base_width = dialog_data.width;
2523 hints.min_height = hints.max_height = hints.base_height = dialog_data.height;
2524 XSetWMNormalHints(m_display, window, &hints);
2525 }
2526
2527 /* Set title */
2528 {
2529 Atom wm_Name = XInternAtom(m_display, "_NET_WM_NAME", False);
2530 Atom utf8Str = XInternAtom(m_display, "UTF8_STRING", False);
2531
2532 Atom winType = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE", False);
2533 Atom typeDialog = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
2534
2535 XChangeProperty(m_display,
2536 window,
2537 wm_Name,
2538 utf8Str,
2539 8,
2540 PropModeReplace,
2541 (const uchar *)title,
2542 int(strlen(title)));
2543
2544 XChangeProperty(
2545 m_display, window, winType, XA_ATOM, 32, PropModeReplace, (uchar *)&typeDialog, 1);
2546 }
2547
2548 /* Create buttons GC */
2549 XGCValues buttonBorderGCValues;
2550 buttonBorderGCValues.foreground = BlackPixel(m_display, screen);
2551 buttonBorderGCValues.background = WhitePixel(m_display, screen);
2552 XGCValues buttonGCValues;
2553 buttonGCValues.foreground = WhitePixel(m_display, screen);
2554 buttonGCValues.background = BlackPixel(m_display, screen);
2555
2556 GC buttonBorderGC = XCreateGC(m_display, window, GCForeground, &buttonBorderGCValues);
2557 GC buttonGC = XCreateGC(m_display, window, GCForeground, &buttonGCValues);
2558
2559 XSelectInput(m_display, window, ExposureMask | ButtonPressMask | ButtonReleaseMask);
2560 XMapWindow(m_display, window);
2561
2562 const bool has_link = link && strlen(link);
2563
2564 while (true) {
2565 XNextEvent(m_display, &e);
2566 if (e.type == Expose) {
2567 for (int i = 0; i < textLines; i++) {
2568 XDrawString(m_display,
2569 window,
2570 DefaultGC(m_display, screen),
2571 dialog_data.padding_x,
2572 dialog_data.padding_x + (i + 1) * dialog_data.line_height,
2573 text_splitted[i],
2574 int(strlen(text_splitted[i])));
2575 }
2576 dialog_data.drawButton(m_display, window, buttonBorderGC, buttonGC, 1, continue_label);
2577 if (has_link) {
2578 dialog_data.drawButton(m_display, window, buttonBorderGC, buttonGC, 2, help_label);
2579 }
2580 }
2581 else if (e.type == ButtonRelease) {
2582 if (dialog_data.isInsideButton(e, 1)) {
2583 break;
2584 }
2585 if (dialog_data.isInsideButton(e, 2)) {
2586 if (has_link) {
2587 string cmd = "xdg-open \"" + string(link) + "\"";
2588 if (system(cmd.c_str()) != 0) {
2589 GHOST_PRINTF("GHOST_SystemX11::showMessageBox: Unable to run system command [%s]",
2590 cmd.c_str());
2591 }
2592 }
2593 break;
2594 }
2595 }
2596 }
2597
2598 for (int i = 0; i < textLines; i++) {
2599 free(text_splitted[i]);
2600 }
2601 free(text_splitted);
2602
2603 XDestroyWindow(m_display, window);
2604 XFreeGC(m_display, buttonBorderGC);
2605 XFreeGC(m_display, buttonGC);
2606 return GHOST_kSuccess;
2607}
2608
2610
2611#ifdef WITH_XDND
2612GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType,
2613 GHOST_TDragnDropTypes draggedObjectType,
2614 GHOST_IWindow *window,
2615 int mouseX,
2616 int mouseY,
2617 void *data)
2618{
2619 GHOST_SystemX11 *system = ((GHOST_SystemX11 *)getSystem());
2620
2621 /* Caller has no timestamp. */
2622 const uint64_t event_ms = system->getMilliSeconds();
2623
2624 return system->pushEvent(new GHOST_EventDragnDrop(
2625 event_ms, eventType, draggedObjectType, window, mouseX, mouseY, data));
2626}
2627#endif
2635int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *event)
2636{
2638 if (!system->isDebugEnabled()) {
2639 return 0;
2640 }
2641
2642 char error_code_str[512];
2643
2644 XGetErrorText(display, event->error_code, error_code_str, sizeof(error_code_str));
2645
2646 fprintf(stderr,
2647 "Received X11 Error:\n"
2648 "\terror code: %d\n"
2649 "\trequest code: %d\n"
2650 "\tminor code: %d\n"
2651 "\terror text: %s\n",
2652 event->error_code,
2653 event->request_code,
2654 event->minor_code,
2655 error_code_str);
2656
2657 /* No exit! - but keep lint happy */
2658 return 0;
2659}
2660
2662{
2664 if (!system->isDebugEnabled()) {
2665 return 0;
2666 }
2667
2668 fprintf(stderr, "Ignoring Xlib error: error IO\n");
2669
2670 /* No exit! - but keep lint happy */
2671 return 0;
2672}
2673
2674#ifdef WITH_X11_XINPUT
2675
2676static bool is_filler_char(char c)
2677{
2678 return isspace(c) || ELEM(c, '_', '-', ';', ':');
2679}
2680
2681/* These C functions are copied from Wine 3.12's `wintab.c` */
2682static bool match_token(const char *haystack, const char *needle)
2683{
2684 const char *h, *n;
2685 for (h = haystack; *h;) {
2686 while (*h && is_filler_char(*h)) {
2687 h++;
2688 }
2689 if (!*h) {
2690 break;
2691 }
2692
2693 for (n = needle; *n && *h && tolower(*h) == tolower(*n); n++) {
2694 h++;
2695 }
2696 if (!*n && (is_filler_char(*h) || !*h)) {
2697 return true;
2698 }
2699
2700 while (*h && !is_filler_char(*h)) {
2701 h++;
2702 }
2703 }
2704 return false;
2705}
2706
2707/* Determining if an X device is a Tablet style device is an imperfect science.
2708 * We rely on common conventions around device names as well as the type reported
2709 * by WACOM tablets. This code will likely need to be expanded for alternate tablet types
2710 *
2711 * WINTAB refers to any device that interacts with the tablet as a cursor,
2712 * (stylus, eraser, tablet mouse, airbrush, etc)
2713 * this is not to be confused with WACOM X11 configuration "cursor" device.
2714 * WACOM tablets X11 configuration "cursor" refers to its device slot (which we mirror with
2715 * our `gSysCursors`) for puck like devices (tablet mice essentially).
2716 */
2717static GHOST_TTabletMode tablet_mode_from_name(const char *name, const char *type)
2718{
2719 int i;
2720 static const char *tablet_stylus_whitelist[] = {"stylus", "wizardpen", "acecad", "pen", nullptr};
2721
2722 static const char *type_blacklist[] = {"pad", "cursor", "touch", nullptr};
2723
2724 /* Skip some known unsupported types. */
2725 for (i = 0; type_blacklist[i] != nullptr; i++) {
2726 if (type && (strcasecmp(type, type_blacklist[i]) == 0)) {
2727 return GHOST_kTabletModeNone;
2728 }
2729 }
2730
2731 /* First check device type to avoid cases where name is "Pen and Eraser" and type is "ERASER" */
2732 for (i = 0; tablet_stylus_whitelist[i] != nullptr; i++) {
2733 if (type && match_token(type, tablet_stylus_whitelist[i])) {
2735 }
2736 }
2737 if (type && match_token(type, "eraser")) {
2739 }
2740 for (i = 0; tablet_stylus_whitelist[i] != nullptr; i++) {
2741 if (name && match_token(name, tablet_stylus_whitelist[i])) {
2743 }
2744 }
2745 if (name && match_token(name, "eraser")) {
2747 }
2748
2749 return GHOST_kTabletModeNone;
2750}
2751
2752/* End code copied from Wine. */
2753
2754void GHOST_SystemX11::refreshXInputDevices()
2755{
2756 if (m_xinput_version.present) {
2757 /* Close tablet devices. */
2758 clearXInputDevices();
2759
2760 /* Install our error handler to override Xlib's termination behavior */
2761 GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
2762
2763 {
2764 int device_count;
2765 XDeviceInfo *device_info = XListInputDevices(m_display, &device_count);
2766
2767 for (int i = 0; i < device_count; ++i) {
2768 char *device_type = device_info[i].type ? XGetAtomName(m_display, device_info[i].type) :
2769 nullptr;
2770 GHOST_TTabletMode tablet_mode = tablet_mode_from_name(device_info[i].name, device_type);
2771
2772 // printf("Tablet type:'%s', name:'%s', index:%d\n", device_type, device_info[i].name, i);
2773
2774 if (device_type) {
2775 XFree((void *)device_type);
2776 }
2777
2779 continue;
2780 }
2781
2782 GHOST_TabletX11 xtablet = {tablet_mode};
2783 xtablet.ID = device_info[i].id;
2784 xtablet.Device = XOpenDevice(m_display, xtablet.ID);
2785
2786 if (xtablet.Device != nullptr) {
2787 /* Find how many pressure levels tablet has */
2788 XAnyClassPtr ici = device_info[i].inputclassinfo;
2789
2790 if (ici != nullptr) {
2791 for (int j = 0; j < device_info[i].num_classes; ++j) {
2792 if (ici->c_class == ValuatorClass) {
2793 XValuatorInfo *xvi = (XValuatorInfo *)ici;
2794 if (xvi->axes != nullptr) {
2795 xtablet.PressureLevels = xvi->axes[2].max_value;
2796
2797 if (xvi->num_axes > 3) {
2798 /* This is assuming that the tablet has the same tilt resolution in both
2799 * positive and negative directions. It would be rather weird if it didn't. */
2800 xtablet.XtiltLevels = xvi->axes[3].max_value;
2801 xtablet.YtiltLevels = xvi->axes[4].max_value;
2802 }
2803 else {
2804 xtablet.XtiltLevels = 0;
2805 xtablet.YtiltLevels = 0;
2806 }
2807
2808 break;
2809 }
2810 }
2811
2812 ici = (XAnyClassPtr)(((char *)ici) + ici->length);
2813 }
2814 }
2815
2816 m_xtablets.push_back(xtablet);
2817 }
2818 }
2819
2820 XFreeDeviceList(device_info);
2821 }
2822
2823 GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
2824 }
2825}
2826
2827void GHOST_SystemX11::clearXInputDevices()
2828{
2829 for (GHOST_TabletX11 &xtablet : m_xtablets) {
2830 if (xtablet.Device) {
2831 XCloseDevice(m_display, xtablet.Device);
2832 }
2833 }
2834
2835 m_xtablets.clear();
2836}
2837
2838#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_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:96
@ GHOST_kCapabilityInputIME
@ GHOST_kCapabilityClipboardImages
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_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_kKeyLeftShift
@ GHOST_kKeyMediaLast
@ GHOST_kKeySpace
@ GHOST_kModifierKeyRightControl
@ GHOST_kModifierKeyLeftControl
@ GHOST_kModifierKeyRightAlt
@ GHOST_kModifierKeyRightShift
@ GHOST_kModifierKeyLeftAlt
@ GHOST_kModifierKeyLeftShift
@ GHOST_kModifierKeyLeftOS
@ GHOST_kModifierKeyRightOS
GHOST_TSuccess
Definition GHOST_Types.h:87
@ GHOST_kFailure
Definition GHOST_Types.h:87
@ GHOST_kSuccess
Definition GHOST_Types.h:87
@ GHOST_kFireTimeNever
@ GHOST_gpuStereoVisual
Definition GHOST_Types.h:76
@ GHOST_gpuDebugContext
Definition GHOST_Types.h:77
@ 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:80
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a vector
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
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
struct GHOST_SystemX11::@225015373161073305164235171050213346146315276316 m_atom
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
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
virtual GHOST_TSuccess exit()
GHOST_TSuccess pushEvent(const GHOST_IEvent *event)
virtual GHOST_TSuccess init()
GHOST_TimerManager * getTimerManager() const
GHOST_WindowManager * m_windowManager
GHOST_DisplayManager * m_displayManager
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
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
#define printf
int len
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
#define str(s)
uint top
int count
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
static ulong * next
static ulong state[N]
static int left
GPU_SHADER_INTERFACE_INFO(overlay_edit_curve_handle_iface, "vert").flat(Type pos vertex_in(1, Type::UINT, "data") .vertex_out(overlay_edit_curve_handle_iface) .geometry_layout(PrimitiveIn Frequency::GEOMETRY storage_buf(1, Qualifier::READ, "uint", "data[]", Frequency::GEOMETRY) .push_constant(Type Frequency::GEOMETRY selection[]
unsigned int uint32_t
Definition stdint.h:80
__int64 int64_t
Definition stdint.h:89
signed int int32_t
Definition stdint.h:77
#define UINT32_MAX
Definition stdint.h:142
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
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
PointerRNA * ptr
Definition wm_files.cc:4126