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