Blender V4.3
GHOST_SystemWayland.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10#include "GHOST_Context.hh"
11#include "GHOST_Event.hh"
12#include "GHOST_EventButton.hh"
13#include "GHOST_EventCursor.hh"
15#include "GHOST_EventKey.hh"
17#include "GHOST_EventWheel.hh"
18#include "GHOST_PathUtils.hh"
19#include "GHOST_TimerManager.hh"
20#include "GHOST_WaylandUtils.hh"
22#include "GHOST_utildefines.hh"
23
24#ifdef WITH_OPENGL_BACKEND
25# include "GHOST_ContextEGL.hh"
26#endif
27
28#ifdef WITH_VULKAN_BACKEND
29# include "GHOST_ContextVK.hh"
30#endif
31
32#ifdef WITH_INPUT_NDOF
34#endif
35
36#ifdef WITH_GHOST_WAYLAND_DYNLOAD
37# include <wayland_dynload_API.h> /* For `ghost_wl_dynload_libraries`. */
38#endif
39
40#ifdef WITH_OPENGL_BACKEND
41# ifdef WITH_GHOST_WAYLAND_DYNLOAD
42# include <wayland_dynload_egl.h>
43# endif
44# include <wayland-egl.h>
45#endif /* WITH_OPENGL_BACKEND */
46
47#include <algorithm>
48#include <atomic>
49#include <stdexcept>
50#include <thread>
51#include <unordered_map>
52#include <unordered_set>
53
54#ifdef WITH_GHOST_WAYLAND_DYNLOAD
56#endif
57#include <wayland-cursor.h>
58
59#include <xkbcommon/xkbcommon-compose.h>
60#include <xkbcommon/xkbcommon.h>
61
62/* Generated by `wayland-scanner`. */
63#include <fractional-scale-v1-client-protocol.h>
64#include <pointer-constraints-unstable-v1-client-protocol.h>
65#include <pointer-gestures-unstable-v1-client-protocol.h>
66#include <primary-selection-unstable-v1-client-protocol.h>
67#include <relative-pointer-unstable-v1-client-protocol.h>
68#include <tablet-unstable-v2-client-protocol.h>
69#include <viewporter-client-protocol.h>
70#include <xdg-activation-v1-client-protocol.h>
71#include <xdg-output-unstable-v1-client-protocol.h>
72#ifdef WITH_INPUT_IME
73# include <text-input-unstable-v3-client-protocol.h>
74#endif
75
76/* Decorations `xdg_decor`. */
77#include <xdg-decoration-unstable-v1-client-protocol.h>
78#include <xdg-shell-client-protocol.h>
79/* End `xdg_decor`. */
80
81#include <fcntl.h>
82#include <sys/mman.h>
83#include <unistd.h>
84
85#include <cstdlib> /* For `exit`. */
86#include <cstring>
87#include <mutex>
88
89#include <pthread.h> /* For setting the thread priority. */
90
91#ifdef HAVE_POLL
92# include <poll.h>
93#endif
94
95/* Logging, use `ghost.wl.*` prefix. */
96#include "CLG_log.h"
97
98#ifdef USE_EVENT_BACKGROUND_THREAD
99# include "GHOST_TimerTask.hh"
100#endif
101
102#ifdef WITH_GHOST_WAYLAND_LIBDECOR
103static bool use_libdecor = true;
104# ifdef WITH_GHOST_WAYLAND_DYNLOAD
105static bool has_libdecor = false;
106# else
107static bool has_libdecor = true;
108# endif
109#endif
110
112
113#include "IMB_imbuf.hh"
114#include "IMB_imbuf_types.hh"
115
116/* -------------------------------------------------------------------- */
121
122static void output_handle_done(void *data, wl_output *wl_output);
123
127
128static void gwl_seat_cursor_anim_begin(GWL_Seat *seat);
130static void gwl_seat_cursor_anim_end(GWL_Seat *seat);
131static void gwl_seat_cursor_anim_reset(GWL_Seat *seat);
132
134 uint32_t name,
135 int *r_interface_slot);
136static void gwl_registry_entry_remove_all(GWL_Display *display);
137
140static int gwl_registry_handler_interface_slot_from_string(const char *interface);
141static const GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(int interface_slot);
142
144 xkb_compose_state *compose_state,
145 xkb_state *state,
146 const xkb_keycode_t key,
147 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)]);
148
149#ifdef USE_EVENT_BACKGROUND_THREAD
151
152static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex);
153
155constexpr size_t events_pending_default_size = 4096 / sizeof(void *);
156
157#endif /* USE_EVENT_BACKGROUND_THREAD */
158
161#define pushEvent DONT_USE
162
165/* -------------------------------------------------------------------- */
176#define USE_GNOME_CONFINE_HACK
181// #define USE_GNOME_CONFINE_HACK_ALWAYS_ON
182
183#ifdef USE_GNOME_CONFINE_HACK
184static bool use_gnome_confine_hack = false;
185#endif
186
192#define USE_KDE_TABLET_HIDDEN_CURSOR_HACK
193
196/* -------------------------------------------------------------------- */
215// #define USE_VERBOSE_OLD_IFACE_PRINT
216
217#ifdef USE_VERBOSE_OLD_IFACE_PRINT
218# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
219 ((params_version > version_max) ? \
220 fprintf(stderr, \
221 "%s: version_max=%u, is smaller than run-time version=%u\n", \
222 __func__, \
223 version_max, \
224 params_version) : \
225 0)
226#else
227# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
228 ((void)(params_version), (version_max))
229#endif
230
231#define GWL_IFACE_VERSION_CLAMP(params_version, version_min, version_max) \
232 ((void)_VERBOSE_OLD_IFACE_PRINT(params_version, version_max), \
233 std::clamp(params_version, version_min, version_max))
234
239#define USE_NON_LATIN_KB_WORKAROUND
240
241#define WL_NAME_UNSET uint32_t(-1)
242
247#define WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy) \
248 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[0])), \
249 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[1])),
250
253/* -------------------------------------------------------------------- */
264enum {
265 BTN_LEFT = 0x110,
266 BTN_RIGHT = 0x111,
267 BTN_MIDDLE = 0x112,
268 BTN_SIDE = 0x113,
269 BTN_EXTRA = 0x114,
270 BTN_FORWARD = 0x115,
271 BTN_BACK = 0x116
273// #define BTN_TASK 0x117 /* UNUSED. */
274
275#define BTN_RANGE_MIN BTN_LEFT
276#define BTN_RANGE_MAX BTN_BACK
277
286enum {
288 BTN_STYLUS = 0x14b,
290 BTN_STYLUS2 = 0x14c,
292 BTN_STYLUS3 = 0x149,
293};
294
300enum {
308
309#ifdef USE_NON_LATIN_KB_WORKAROUND
310 KEY_1 = 2,
311 KEY_2 = 3,
312 KEY_3 = 4,
313 KEY_4 = 5,
314 KEY_5 = 6,
315 KEY_6 = 7,
316 KEY_7 = 8,
317 KEY_8 = 9,
318 KEY_9 = 10,
319 KEY_0 = 11,
320#endif
321};
322
325/* -------------------------------------------------------------------- */
331enum {
336};
337#define MOD_INDEX_NUM (MOD_INDEX_OS + 1)
338
346
348 /*MOD_INDEX_SHIFT*/
349 {
350 /*display_name*/ "Shift",
351 /*xkb_id*/ XKB_MOD_NAME_SHIFT,
352 /*key_l*/ GHOST_kKeyLeftShift,
353 /*key_r*/ GHOST_kKeyRightShift,
356 },
357 /*MOD_INDEX_ALT*/
358 {
359 /*display_name*/ "Alt",
360 /*xkb_id*/ XKB_MOD_NAME_ALT,
361 /*key_l*/ GHOST_kKeyLeftAlt,
362 /*key_r*/ GHOST_kKeyRightAlt,
365 },
366 /*MOD_INDEX_CTRL*/
367 {
368 /*display_name*/ "Control",
369 /*xkb_id*/ XKB_MOD_NAME_CTRL,
370 /*key_l*/ GHOST_kKeyLeftControl,
371 /*key_r*/ GHOST_kKeyRightControl,
374 },
375 /*MOD_INDEX_OS*/
376 {
377 /*display_name*/ "OS",
378 /*xkb_id*/ XKB_MOD_NAME_LOGO,
379 /*key_l*/ GHOST_kKeyLeftOS,
380 /*key_r*/ GHOST_kKeyRightOS,
381 /*mod_l*/ GHOST_kModifierKeyLeftOS,
383 },
384};
385
388/* -------------------------------------------------------------------- */
394 const char *data = nullptr;
395 size_t data_size = 0;
396};
397
399{
400 free(const_cast<char *>(buffer->data));
401 buffer->data = nullptr;
402 buffer->data_size = 0;
403}
404
406{
407 free(const_cast<char *>(buffer->data));
408 buffer->data_size = strlen(str);
409 char *data = static_cast<char *>(malloc(buffer->data_size));
410 std::memcpy(data, str, buffer->data_size);
411 buffer->data = data;
412}
413
416/* -------------------------------------------------------------------- */
424#define EVDEV_OFFSET 8
425
431 std::atomic<bool> exit_pending = false;
432};
433
435
437 struct {
439 wl_buffer *buffer = nullptr;
440 wl_cursor_image image = {0};
441 wl_cursor_theme *theme = nullptr;
443 const wl_cursor *theme_cursor = nullptr;
445 const char *theme_cursor_name = nullptr;
446 } wl;
447
448 bool visible = false;
455 bool is_hardware = true;
457 bool is_custom = false;
458 void *custom_data = nullptr;
461
464
469 std::string theme_name;
475 int theme_size = 0;
477};
478
481/* -------------------------------------------------------------------- */
489 Motion = 0,
490 Pressure,
491 Tilt,
492
493 Wheel,
494
495 /* NOTE: Keep buttons last (simplifies switch statement). */
496
497 /* Left mouse button. */
500 /* Middle mouse button. */
503 /* Right mouse button. */
506 /* Mouse button number 4. */
509
510#define GWL_TabletTool_FrameTypes_NUM (int(GWL_TabletTool_EventTypes::Stylus3_Up) + 1)
511};
512
514 GHOST_kButtonMaskLeft, /* `Stylus0_*`. */
515 GHOST_kButtonMaskMiddle, /* `Stylus1_*`. */
516 GHOST_kButtonMaskRight, /* `Stylus2_*`. */
517 GHOST_kButtonMaskButton4, /* `Stylus3_*`. */
518};
519
526
528 struct {
534 } wl;
535
536 GWL_Seat *seat = nullptr;
538 bool proximity = false;
539
541
543 int32_t xy[2] = {0};
544 bool has_xy = false;
545
549 struct {
555
556 struct {
560};
561
564{
565 const int ty_mask = 1 << int(ty);
566 /* Motion callback may run multiple times. */
567 if (tablet_tool->frame_pending.frame_types_mask & ty_mask) {
568 return;
569 }
570 tablet_tool->frame_pending.frame_types_mask |= ty_mask;
571 int i = tablet_tool->frame_pending.frame_types_num++;
572 tablet_tool->frame_pending.frame_types[i] = ty;
573}
574
576{
577 tablet_tool->frame_pending.frame_types_num = 0;
578 tablet_tool->frame_pending.frame_types_mask = 0;
579}
580
583/* -------------------------------------------------------------------- */
591
593 struct {
594 wl_data_offer *id = nullptr;
595 } wl;
596
597 std::unordered_set<std::string> types;
598
599 struct {
604 bool in_use = false;
611 enum wl_data_device_manager_dnd_action source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
612 enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
614 wl_fixed_t xy[2] = {0, 0};
616};
617
620/* -------------------------------------------------------------------- */
625
627 struct {
628 wl_data_source *source = nullptr;
629 } wl;
630
632};
633
636/* -------------------------------------------------------------------- */
649 GWL_Seat *seat = nullptr;
650
651 xkb_keycode_t key_code;
652
655
660 struct {
663};
664
667 bool use_lock = false;
668 bool use_confine = false;
669};
670
675
677 struct {
683 } wl;
684
698 wl_fixed_t xy[2] = {0, 0};
699
701 std::unordered_set<const GWL_Output *> outputs;
702
703 int theme_scale = 1;
704
707
709};
710
712 Motion = 0,
713
720 Scroll,
721
722 /* NOTE: Keep buttons last (simplifies switch statement). */
723
724 /* #BTN_LEFT mouse button. */
727 /* #BTN_RIGHT mouse button. */
730 /* #BTN_MIDDLE mouse button. */
733 /* #BTN_SIDE mouse button. */
736 /* #BTN_EXTRA mouse button. */
739 /* #BTN_FORWARD mouse button. */
742 /* #BTN_BACK mouse button. */
745
746#define GWL_SeatStatePointer_EventTypes_NUM (int(GWL_Pointer_EventTypes::Button6_Up) + 1)
747};
748
750 GHOST_kButtonMaskLeft, /* `Button0_*` / #BTN_LEFT. */
751 GHOST_kButtonMaskRight, /* `Button1_*` / #BTN_RIGHT. */
752 GHOST_kButtonMaskMiddle, /* `Button2_*` / #BTN_MIDDLE. */
753 GHOST_kButtonMaskButton4, /* `Button3_*` / #BTN_SIDE. */
754 GHOST_kButtonMaskButton5, /* `Button4_*` / #BTN_EXTRA. */
755 GHOST_kButtonMaskButton6, /* `Button5_*` / #BTN_FORWARD. */
756 GHOST_kButtonMaskButton7, /* `Button6_*` / #BTN_BACK. */
757};
758
761 "Buttons missing");
762
776
778 const GWL_Pointer_EventTypes ty,
779 const uint64_t event_ms)
780{
781 /* It's a quirk of WAYLAND that most scroll events don't have a time-stamp.
782 * Scroll events use their own time-stamp (see #GWL_SeatStatePointerScroll::event_ms usage).
783 * Ensure the API is used as intended. */
785 GHOST_ASSERT(event_ms == 0, "Scroll events must not have a time-stamp");
786 }
787 else {
788 GHOST_ASSERT(event_ms != 0, "Non-scroll events must have a time-stamp");
789 }
790
791 const int ty_mask = 1 << int(ty);
792 /* Motion callback may run multiple times. */
793 if (pointer_events->frame_pending.frame_types_mask & ty_mask) {
794 return;
795 }
796 pointer_events->frame_pending.frame_types_mask |= ty_mask;
797 int i = pointer_events->frame_pending.frame_types_num++;
798 pointer_events->frame_pending.frame_types[i] = ty;
799 pointer_events->frame_pending.frame_event_ms[i] = event_ms;
800}
801
803{
804 pointer_events->frame_pending.frame_types_num = 0;
805 pointer_events->frame_pending.frame_types_mask = 0;
806}
807
815static constexpr int smooth_as_discrete_steps = 2400;
816
823 wl_fixed_t smooth_xy[2] = {0, 0};
825 int32_t discrete_xy[2] = {0, 0};
831 bool inverted_xy[2] = {false, false};
833 enum wl_pointer_axis_source axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
834
839 bool has_event_ms = false;
842
844};
845
857 wl_fixed_t value = 0;
858 wl_fixed_t factor = 1;
859};
860
862 const wl_fixed_t add)
863{
864 const int result_prev = wl_fixed_to_int(sf->value * sf->factor);
865 sf->value += add;
866 const int result_curr = wl_fixed_to_int(sf->value * sf->factor);
867 return result_curr - result_prev;
868}
869
878
883
885 struct {
891 } wl;
892
895};
896
905
906#ifdef WITH_GHOST_WAYLAND_LIBDECOR
907struct GWL_LibDecor_System {
908 libdecor *context = nullptr;
909};
910
911static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor)
912{
913 if (decor->context) {
914 libdecor_unref(decor->context);
915 decor->context = nullptr;
916 }
917 delete decor;
918}
919#endif
920
922 xdg_wm_base *shell = nullptr;
924
925 zxdg_decoration_manager_v1 *manager = nullptr;
927};
928
930{
931 if (decor->manager) {
932 gwl_registry_entry_remove_by_name(display, decor->manager_name, nullptr);
933 GHOST_ASSERT(decor->manager == nullptr, "Internal registry error");
934 }
935 if (decor->shell) {
936 gwl_registry_entry_remove_by_name(display, decor->shell_name, nullptr);
937 GHOST_ASSERT(decor->shell == nullptr, "Internal registry error");
938 }
939 delete decor;
940}
941
943
945 struct {
946 zwp_primary_selection_offer_v1 *id = nullptr;
947 } wp;
948
949 std::unordered_set<std::string> types;
950};
951
953
955 struct {
956 zwp_primary_selection_source_v1 *source = nullptr;
957 } wp;
958
960};
961
971
973{
974 if (primary->data_offer == nullptr) {
975 return;
976 }
977 zwp_primary_selection_offer_v1_destroy(primary->data_offer->wp.id);
978 delete primary->data_offer;
979 primary->data_offer = nullptr;
980}
981
983{
984 GWL_PrimarySelection_DataSource *data_source = primary->data_source;
985 if (data_source == nullptr) {
986 return;
987 }
989 if (data_source->wp.source) {
990 zwp_primary_selection_source_v1_destroy(data_source->wp.source);
991 }
992 delete primary->data_source;
993 primary->data_source = nullptr;
994}
995
996#ifdef WITH_INPUT_IME
997struct GWL_SeatIME {
1005 wl_surface *surface_window = nullptr;
1006 GHOST_TEventImeData event_ime_data = {
1007 /*result_len*/ nullptr,
1008 /*composite_len*/ nullptr,
1009 /*result*/ nullptr,
1010 /*composite*/ nullptr,
1011 /*cursor_position*/ -1,
1012 /*target_start*/ -1,
1013 /*target_end*/ -1,
1014 };
1016 bool is_enabled = false;
1021 bool has_preedit = false;
1022
1024 std::string result;
1026 std::string composite;
1027
1029 bool result_is_null = false;
1031 bool composite_is_null = false;
1032
1034 bool has_preedit_string_callback = false;
1036 bool has_commit_string_callback = false;
1037
1039 struct {
1040 int x = -1;
1041 int y = -1;
1042 int w = -1;
1043 int h = -1;
1044 } rect;
1045};
1046#endif /* WITH_INPUT_IME */
1047
1048struct GWL_Seat {
1049
1051 struct {
1052 wl_seat *seat = nullptr;
1053 wl_pointer *pointer = nullptr;
1054 wl_touch *touch = nullptr;
1055 wl_keyboard *keyboard = nullptr;
1056
1058 wl_data_device *data_device = nullptr;
1060
1062 struct {
1063 zwp_tablet_seat_v2 *tablet_seat = nullptr;
1064
1065#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
1066 zwp_pointer_gesture_hold_v1 *pointer_gesture_hold = nullptr;
1067#endif
1068#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
1069 zwp_pointer_gesture_pinch_v1 *pointer_gesture_pinch = nullptr;
1070#endif
1071#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
1072 zwp_pointer_gesture_swipe_v1 *pointer_gesture_swipe = nullptr;
1073#endif
1074
1075 zwp_relative_pointer_v1 *relative_pointer = nullptr;
1076 zwp_locked_pointer_v1 *locked_pointer = nullptr;
1077 zwp_confined_pointer_v1 *confined_pointer = nullptr;
1078
1079 zwp_primary_selection_device_v1 *primary_selection_device = nullptr;
1080
1082 std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
1083
1084#ifdef WITH_INPUT_IME
1085 zwp_text_input_v3 *text_input = nullptr;
1086#endif
1088
1090 struct {
1091 xkb_context *context = nullptr;
1092
1094 xkb_compose_table *compose_table = nullptr;
1095
1097 xkb_compose_state *compose_state = nullptr;
1098
1099 xkb_state *state = nullptr;
1103 xkb_state *state_empty = nullptr;
1104
1109 xkb_state *state_empty_with_shift = nullptr;
1114 xkb_state *state_empty_with_numlock = nullptr;
1115
1119 xkb_layout_index_t layout_active = 0;
1121
1122#ifdef WITH_INPUT_IME
1123 GWL_SeatIME ime;
1124#endif
1125
1127
1128 std::string name;
1129
1132
1138
1141
1143
1144#ifdef USE_GNOME_CONFINE_HACK
1146#endif
1148 wl_fixed_t grab_lock_xy[2] = {0, 0};
1149
1151
1152#ifdef USE_NON_LATIN_KB_WORKAROUND
1154#endif
1155
1158
1164 xkb_mod_index_t xkb_keymap_mod_index[MOD_INDEX_NUM] = {XKB_MOD_INVALID};
1165
1166 /* Cache result for other modifiers which aren't stored in `xkb_keymap_mod_index`
1167 * since their depressed state isn't tracked. */
1168
1170 xkb_mod_index_t xkb_keymap_mod_index_mod2 = XKB_MOD_INVALID;
1172 xkb_mod_index_t xkb_keymap_mod_index_numlock = XKB_MOD_INVALID;
1173
1174 struct {
1187
1191
1195
1198
1200
1208};
1209
1211{
1212 if (seat->pointer.serial == seat->cursor_source_serial) {
1213 return &seat->pointer;
1214 }
1215 if (seat->tablet.serial == seat->cursor_source_serial) {
1216 return &seat->tablet;
1217 }
1218 return nullptr;
1219}
1220
1222 GWL_Seat *seat, const wl_surface *wl_surface)
1223{
1225 return &seat->pointer;
1226 }
1228 return &seat->tablet;
1229 }
1230 GHOST_ASSERT(0, "Surface found without pointer/tablet tag");
1231 return nullptr;
1232}
1233
1241{
1242 const xkb_layout_index_t group = seat->xkb.layout_active;
1243 const xkb_mod_index_t mod_shift = seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT];
1244 const xkb_mod_index_t mod_mod2 = seat->xkb_keymap_mod_index_mod2;
1245 const xkb_mod_index_t mod_numlock = seat->xkb_keymap_mod_index_numlock;
1246
1247 /* Handle `state_empty`. */
1248 xkb_state_update_mask(seat->xkb.state_empty, 0, 0, 0, 0, 0, group);
1249
1250 /* Handle `state_empty_with_shift`. */
1251 GHOST_ASSERT((mod_shift == XKB_MOD_INVALID) == (seat->xkb.state_empty_with_shift == nullptr),
1252 "Invalid state for SHIFT");
1253 if (seat->xkb.state_empty_with_shift) {
1254 xkb_state_update_mask(seat->xkb.state_empty_with_shift, 1 << mod_shift, 0, 0, 0, 0, group);
1255 }
1256
1257 /* Handle `state_empty_with_shift`. */
1258 GHOST_ASSERT((mod_mod2 == XKB_MOD_INVALID || mod_numlock == XKB_MOD_INVALID) ==
1259 (seat->xkb.state_empty_with_numlock == nullptr),
1260 "Invalid state for NUMLOCK");
1261 if (seat->xkb.state_empty_with_numlock) {
1262 xkb_state_update_mask(
1263 seat->xkb.state_empty_with_numlock, 1 << mod_mod2, 0, 1 << mod_numlock, 0, 0, group);
1264 }
1265}
1266
1269{
1270 GWL_KeyRepeatPlayload *payload = static_cast<GWL_KeyRepeatPlayload *>(task->getUserData());
1271
1272 GWL_Seat *seat = payload->seat;
1273 wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window;
1274 if (UNLIKELY(wl_surface_focus == nullptr)) {
1275 return;
1276 }
1277
1278 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
1279 GHOST_SystemWayland *system = seat->system;
1280 const uint64_t event_ms = payload->time_ms_init + time_ms;
1281 /* Calculate this value every time in case modifier keys are pressed. */
1282
1283 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
1284 if (seat->xkb.compose_state &&
1286 seat->xkb.compose_state, seat->xkb.state, payload->key_code, utf8_buf))
1287 {
1288 /* `utf8_buf` has been filled by a compose action. */
1289 }
1290 else {
1291 xkb_state_key_get_utf8(seat->xkb.state, payload->key_code, utf8_buf, sizeof(utf8_buf));
1292 }
1293
1295 event_ms, GHOST_kEventKeyDown, win, payload->key_data.gkey, true, utf8_buf));
1296}
1297
1302 GHOST_TimerProcPtr key_repeat_fn,
1303 GHOST_TUserDataPtr payload,
1304 const bool use_delay)
1305{
1306 GHOST_SystemWayland *system = seat->system;
1307 const uint64_t time_now = system->getMilliSeconds();
1308 const uint64_t time_step = 1000 / seat->key_repeat.rate;
1309 const uint64_t time_start = use_delay ? seat->key_repeat.delay : time_step;
1310
1311 static_cast<GWL_KeyRepeatPlayload *>(payload)->time_ms_init = time_now;
1312
1313#ifdef USE_EVENT_BACKGROUND_THREAD
1315 time_now + time_start, time_step, key_repeat_fn, payload);
1316 seat->key_repeat.timer = timer;
1318#else
1319 seat->key_repeat.timer = system->installTimer(time_start, time_step, key_repeat_fn, payload);
1320#endif
1321}
1322
1327{
1328 GHOST_SystemWayland *system = seat->system;
1329#ifdef USE_EVENT_BACKGROUND_THREAD
1331 static_cast<GHOST_TimerTask *>(seat->key_repeat.timer));
1332#else
1333 system->removeTimer(seat->key_repeat.timer);
1334#endif
1335 seat->key_repeat.timer = nullptr;
1336}
1337
1338#ifdef WITH_INPUT_IME
1339
1340static void gwl_seat_ime_full_reset(GWL_Seat *seat)
1341{
1342 const GWL_SeatIME ime_default{};
1343 /* Preserve the following members since they represent the state of the connection to Wayland.
1344 * or which callbacks have run (which shouldn't be reset). */
1345 wl_surface *surface_window = seat->ime.surface_window;
1346 const bool is_enabled = seat->ime.is_enabled;
1347 const bool has_preedit_string_callback = seat->ime.has_preedit_string_callback;
1348 const bool has_commit_string_callback = seat->ime.has_commit_string_callback;
1349
1350 seat->ime = ime_default;
1351
1352 seat->ime.surface_window = surface_window;
1353 seat->ime.is_enabled = is_enabled;
1354 seat->ime.has_preedit_string_callback = has_preedit_string_callback;
1355 seat->ime.has_commit_string_callback = has_commit_string_callback;
1356}
1357
1358static void gwl_seat_ime_result_reset(GWL_Seat *seat)
1359{
1360 seat->ime.result.clear();
1361 seat->ime.result_is_null = false;
1362
1363 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1364 event_ime_data.result_len = nullptr;
1365 event_ime_data.result = nullptr;
1366}
1367
1368static void gwl_seat_ime_preedit_reset(GWL_Seat *seat)
1369{
1370 seat->ime.composite.clear();
1371 seat->ime.composite_is_null = false;
1372
1373 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1374 event_ime_data.composite_len = nullptr;
1375 event_ime_data.composite = nullptr;
1376
1377 event_ime_data.cursor_position = -1;
1378 event_ime_data.target_start = -1;
1379 event_ime_data.target_end = -1;
1380}
1381
1382static void gwl_seat_ime_rect_reset(GWL_Seat *seat)
1383{
1384 seat->ime.rect.x = -1;
1385 seat->ime.rect.y = -1;
1386 seat->ime.rect.w = -1;
1387 seat->ime.rect.h = -1;
1388}
1389
1390#endif /* WITH_INPUT_IME */
1391
1394/* -------------------------------------------------------------------- */
1398struct GWL_RegistryEntry;
1399
1411 bool exact_match = false;
1413 uint64_t offset = 0;
1414};
1415
1417
1419 struct {
1420 wl_registry *registry = nullptr;
1421 wl_display *display = nullptr;
1422 wl_compositor *compositor = nullptr;
1423 wl_shm *shm = nullptr;
1424
1425 /* Managers. */
1426 wl_data_device_manager *data_device_manager = nullptr;
1428
1430 struct {
1431 /* Managers. */
1432 zwp_tablet_manager_v2 *tablet_manager = nullptr;
1433 zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
1434 zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = nullptr;
1435 wp_fractional_scale_manager_v1 *fractional_scale_manager = nullptr;
1436 wp_viewporter *viewporter = nullptr;
1437 zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
1438 zwp_pointer_gestures_v1 *pointer_gestures = nullptr;
1439#ifdef WITH_INPUT_IME
1440 zwp_text_input_manager_v3 *text_input_manager = nullptr;
1441#endif
1443
1445 struct {
1446 /* Managers. */
1447 zxdg_output_manager_v1 *output_manager = nullptr;
1448 xdg_activation_v1 *activation_manager = nullptr;
1450
1452
1454
1460
1463
1464#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1465 GWL_LibDecor_System *libdecor = nullptr;
1466#endif
1468
1474 std::vector<GWL_Output *> outputs;
1475 std::vector<GWL_Seat *> seats;
1488
1489 /* Threaded event handling. */
1490#ifdef USE_EVENT_BACKGROUND_THREAD
1495 pthread_t events_pthread = 0;
1498
1503 std::vector<const GHOST_IEvent *> events_pending;
1506
1516
1517#endif /* USE_EVENT_BACKGROUND_THREAD */
1518};
1519
1527{
1528#ifdef USE_EVENT_BACKGROUND_THREAD
1529 if (display->events_pthread) {
1530 ghost_wl_display_lock_without_input(display->wl.display, display->system->server_mutex);
1531 display->events_pthread_is_active = false;
1532 }
1533#endif
1534
1535 /* Stop all animated cursors (freeing their #GWL_Cursor_AnimHandle). */
1536 for (GWL_Seat *seat : display->seats) {
1538 }
1539
1540 /* For typical WAYLAND use this will always be set.
1541 * However when WAYLAND isn't running, this will early-exit and be null. */
1542 if (display->wl.registry) {
1543 wl_registry_destroy(display->wl.registry);
1544 display->wl.registry = nullptr;
1545 }
1546
1547 /* Unregister items in reverse order. */
1549
1550#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1551 if (use_libdecor) {
1552 if (display->libdecor) {
1553 gwl_libdecor_system_destroy(display->libdecor);
1554 display->libdecor = nullptr;
1555 }
1556 }
1557 else
1558#endif
1559 {
1560 if (display->xdg_decor) {
1561 gwl_xdg_decor_system_destroy(display, display->xdg_decor);
1562 display->xdg_decor = nullptr;
1563 }
1564 }
1565
1566#ifdef WITH_OPENGL_BACKEND
1567 if (display->wl.display) {
1568 if (eglGetDisplay) {
1569 ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl.display)));
1570 }
1571 }
1572#endif
1573
1574#ifdef USE_EVENT_BACKGROUND_THREAD
1575 if (display->events_pthread) {
1577 display->system->server_mutex->unlock();
1578 }
1579
1580 /* Important to remove after the seats which may have key repeat timers active. */
1581 if (display->ghost_timer_manager) {
1582 delete display->ghost_timer_manager;
1583 display->ghost_timer_manager = nullptr;
1584 }
1585 /* Pending events may be left unhandled. */
1586 for (const GHOST_IEvent *event : display->events_pending) {
1587 delete event;
1588 }
1589
1590#endif /* USE_EVENT_BACKGROUND_THREAD */
1591
1592 if (display->wl.display) {
1593 wl_display_disconnect(display->wl.display);
1594 }
1595
1596 delete display;
1597}
1598
1599static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat)
1600{
1601 std::vector<GWL_Seat *>::iterator iter = std::find(
1602 display->seats.begin(), display->seats.end(), seat);
1603 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
1604 -1;
1605 GHOST_ASSERT(index != -1, "invalid internal state");
1606 return index;
1607}
1608
1617{
1618 if (UNLIKELY(display->seats.empty())) {
1619 return nullptr;
1620 }
1621 return display->seats[display->seats_active_index];
1622}
1623
1624static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat)
1625{
1626 if (UNLIKELY(display->seats.empty())) {
1627 return false;
1628 }
1629 const int index = gwl_display_seat_index(display, seat);
1630 if (index == display->seats_active_index) {
1631 return false;
1632 }
1633 display->seats_active_index = index;
1634 return true;
1635}
1636
1639/* -------------------------------------------------------------------- */
1649
1658using GWL_RegistryHandler_AddFn = void (*)(GWL_Display *display,
1660
1662 uint32_t name = 0;
1666
1668 void *user_data = nullptr;
1669};
1670
1679
1690using GWL_RegistryEntry_RemoveFn = void (*)(GWL_Display *display, void *user_data, bool on_exit);
1691
1703
1706/* -------------------------------------------------------------------- */
1736
1739 void *user_data)
1740{
1742
1743 reg->interface_slot = params.interface_slot;
1744 reg->name = params.name;
1745 reg->version = params.version;
1746 reg->user_data = user_data;
1747
1748 reg->next = display->registry_entry;
1749 display->registry_entry = reg;
1750}
1751
1753 uint32_t name,
1754 int *r_interface_slot)
1755{
1756 GWL_RegistryEntry *reg = display->registry_entry;
1757 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1758 bool found = false;
1759
1760 if (r_interface_slot) {
1761 *r_interface_slot = -1;
1762 }
1763
1764 while (reg) {
1765 if (reg->name == name) {
1766 GWL_RegistryEntry *reg_next = reg->next;
1768 reg->interface_slot);
1769 handler->remove_fn(display, reg->user_data, false);
1770 if (r_interface_slot) {
1771 *r_interface_slot = reg->interface_slot;
1772 }
1773 delete reg;
1774 *reg_link_p = reg_next;
1775 found = true;
1776 break;
1777 }
1778 reg_link_p = &reg->next;
1779 reg = reg->next;
1780 }
1781 return found;
1782}
1783
1785 int interface_slot,
1786 bool on_exit)
1787{
1788 GWL_RegistryEntry *reg = display->registry_entry;
1789 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1790 bool found = false;
1791
1792 while (reg) {
1793 if (reg->interface_slot == interface_slot) {
1794 GWL_RegistryEntry *reg_next = reg->next;
1796 interface_slot);
1797 handler->remove_fn(display, reg->user_data, on_exit);
1798 delete reg;
1799 *reg_link_p = reg_next;
1800 reg = reg_next;
1801 found = true;
1802 continue;
1803 }
1804 reg_link_p = &reg->next;
1805 reg = reg->next;
1806 }
1807 return found;
1808}
1809
1814{
1815 const bool on_exit = true;
1816
1817 /* NOTE(@ideasman42): Free by slot instead of simply looping over
1818 * `display->registry_entry` so the order of freeing is always predictable.
1819 * Otherwise global objects would be feed in the order they are registered.
1820 * While this works in my tests, it could cause difficult to reproduce bugs
1821 * where lesser used compositors or changes to existing compositors could
1822 * crash on exit based on the order of freeing objects is out of our control.
1823 *
1824 * To give a concrete example of how this could fail, it's possible removing
1825 * a tablet interface could reference the pointer interface, or the output interface.
1826 * Even though references between interfaces shouldn't be necessary in most cases
1827 * when `on_exit` is enabled. */
1828 int interface_slot = gwl_registry_handler_interface_slot_max();
1829 while (interface_slot--) {
1830 gwl_registry_entry_remove_by_interface_slot(display, interface_slot, on_exit);
1831 }
1832
1833 GHOST_ASSERT(display->registry_entry == nullptr, "Failed to remove all entries!");
1834 display->registry_entry = nullptr;
1835}
1836
1853static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude)
1854{
1855 GHOST_ASSERT(interface_slot_exclude == -1 || (uint(interface_slot_exclude) <
1857 "Invalid exclude slot");
1858
1859 for (GWL_RegistryEntry *reg = display->registry_entry; reg; reg = reg->next) {
1860 if (reg->interface_slot == interface_slot_exclude) {
1861 continue;
1862 }
1864 reg->interface_slot);
1865 if (handler->update_fn == nullptr) {
1866 continue;
1867 }
1868
1870 params.name = reg->name;
1871 params.interface_slot = reg->interface_slot;
1872 params.version = reg->version;
1873 params.user_data = reg->user_data;
1874
1875 handler->update_fn(display, params);
1876 }
1877}
1878
1881/* -------------------------------------------------------------------- */
1885#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1886static const char *strchr_or_end(const char *str, const char ch)
1887{
1888 const char *p = str;
1889 while (!ELEM(*p, ch, '\0')) {
1890 p++;
1891 }
1892 return p;
1893}
1894
1895static bool string_elem_split_by_delim(const char *haystack, const char delim, const char *needle)
1896{
1897 /* Local copy of #BLI_string_elem_split_by_delim (would be a bad level call). */
1898
1899 /* May be zero, returns true when an empty span exists. */
1900 const size_t needle_len = strlen(needle);
1901 const char *p = haystack, *p_next;
1902 while (true) {
1903 p_next = strchr_or_end(p, delim);
1904 if ((size_t(p_next - p) == needle_len) && (memcmp(p, needle, needle_len) == 0)) {
1905 return true;
1906 }
1907 if (*p_next == '\0') {
1908 break;
1909 }
1910 p = p_next + 1;
1911 }
1912 return false;
1913}
1914#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
1915
1917{
1918 return a > b ? a - b : b - a;
1919}
1920
1925{
1926 return ((uint64_t(utime_hi) << 32) | uint64_t(utime_lo)) / 1000;
1927}
1928
1933{
1934 const char *locale = getenv("LC_ALL");
1935 if (!locale || !*locale) {
1936 locale = getenv("LC_CTYPE");
1937 if (!locale || !*locale) {
1938 locale = getenv("LANG");
1939 if (!locale || !*locale) {
1940 locale = "C";
1941 }
1942 }
1943 }
1944 return locale;
1945}
1946
1947static void ghost_wl_display_report_error_from_code(wl_display *display, const int ecode)
1948{
1949 GHOST_ASSERT(ecode, "Error not set!");
1950 if (ELEM(ecode, EPIPE, ECONNRESET)) {
1951 fprintf(stderr, "The Wayland connection broke. Did the Wayland compositor die?\n");
1952 return;
1953 }
1954
1955 if (ecode == EPROTO) {
1956 const wl_interface *interface = nullptr;
1957 const int ecode_proto = wl_display_get_protocol_error(display, &interface, nullptr);
1958 fprintf(stderr,
1959 "The Wayland connection experienced a protocol error %d in interface: %s\n",
1960 ecode_proto,
1961 interface ? interface->name : "<nil>");
1962 const char *env_debug = "WAYLAND_DEBUG";
1963 if (getenv(env_debug) == nullptr) {
1964 fprintf(stderr, "Run with the environment variable \"%s=1\" for details.\n", env_debug);
1965 }
1966 return;
1967 }
1968
1969 fprintf(stderr, "The Wayland connection experienced a fatal error: %s\n", strerror(ecode));
1970}
1971
1973{
1974 int ecode = wl_display_get_error(display);
1975 GHOST_ASSERT(ecode, "Error not set!");
1977
1978 /* NOTE(@ideasman42): The application is running,
1979 * however an error closes all windows and most importantly:
1980 * shuts down the GPU context (loosing all GPU state - shaders, bind codes etc),
1981 * so recovering from this effectively involves restarting.
1982 *
1983 * Keeping the GPU state alive doesn't seem to be supported as windows EGL context must use the
1984 * same connection as the used for all other WAYLAND interactions (see #wl_display_connect).
1985 * So in practice re-connecting to the display server isn't an option.
1986 *
1987 * Exit since leaving the process open will simply flood the output and do nothing.
1988 * Although as the process is in a valid state, auto-save for e.g. is possible, see: #100855. */
1989 ::exit(-1);
1990}
1991
1993{
1994 const int ecode = wl_display_get_error(display);
1995 if (ecode == 0) {
1996 return false;
1997 }
1999 return true;
2000}
2001
2002#ifdef __GNUC__
2003static void ghost_wayland_log_handler(const char *msg, va_list arg)
2004 __attribute__((format(printf, 1, 0)));
2005#endif
2006
2008
2015static void ghost_wayland_log_handler(const char *msg, va_list arg)
2016{
2017 /* This is fine in background mode, we will try to fall back to headless GPU context.
2018 * Happens when render farm process runs without user login session. */
2020 (strstr(msg, "error: XDG_RUNTIME_DIR not set in the environment") ||
2021 strstr(msg, "error: XDG_RUNTIME_DIR is invalid or not set in the environment")))
2022 {
2023 return;
2024 }
2025
2026 fprintf(stderr, "GHOST/Wayland: ");
2027 vfprintf(stderr, msg, arg); /* Includes newline. */
2028
2030 if (backtrace_fn) {
2031 backtrace_fn(stderr); /* Includes newline. */
2032 }
2033}
2034
2035#if defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND_LIBDECOR)
2041static bool ghost_wayland_is_x11_available()
2042{
2043 const char *x11_display = getenv("DISPLAY");
2044 if (x11_display && x11_display[0]) {
2045 return true;
2046 }
2047 return false;
2048}
2049#endif /* WITH_GHOST_X11 && WITH_GHOST_WAYLAND_LIBDECOR */
2050
2051static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
2052{
2053
2054 GHOST_TKey gkey;
2055 if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) {
2056 gkey = GHOST_TKey(sym);
2057 }
2058 else if (sym >= XKB_KEY_KP_0 && sym <= XKB_KEY_KP_9) {
2059 gkey = GHOST_TKey(GHOST_kKeyNumpad0 + sym - XKB_KEY_KP_0);
2060 }
2061 else if (sym >= XKB_KEY_A && sym <= XKB_KEY_Z) {
2062 gkey = GHOST_TKey(sym);
2063 }
2064 else if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) {
2065 gkey = GHOST_TKey(sym - XKB_KEY_a + XKB_KEY_A);
2066 }
2067 else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F24) {
2068 gkey = GHOST_TKey(GHOST_kKeyF1 + sym - XKB_KEY_F1);
2069 }
2070 else {
2071
2072#define GXMAP(k, x, y) \
2073 case x: \
2074 k = y; \
2075 break
2076
2077 switch (sym) {
2078 GXMAP(gkey, XKB_KEY_BackSpace, GHOST_kKeyBackSpace);
2079 GXMAP(gkey, XKB_KEY_Tab, GHOST_kKeyTab);
2080 GXMAP(gkey, XKB_KEY_Linefeed, GHOST_kKeyLinefeed);
2081 GXMAP(gkey, XKB_KEY_Clear, GHOST_kKeyClear);
2082 GXMAP(gkey, XKB_KEY_Return, GHOST_kKeyEnter);
2083
2084 GXMAP(gkey, XKB_KEY_Escape, GHOST_kKeyEsc);
2085 GXMAP(gkey, XKB_KEY_space, GHOST_kKeySpace);
2086 GXMAP(gkey, XKB_KEY_apostrophe, GHOST_kKeyQuote);
2087 GXMAP(gkey, XKB_KEY_comma, GHOST_kKeyComma);
2088 GXMAP(gkey, XKB_KEY_minus, GHOST_kKeyMinus);
2089 GXMAP(gkey, XKB_KEY_plus, GHOST_kKeyPlus);
2090 GXMAP(gkey, XKB_KEY_period, GHOST_kKeyPeriod);
2091 GXMAP(gkey, XKB_KEY_slash, GHOST_kKeySlash);
2092
2093 GXMAP(gkey, XKB_KEY_semicolon, GHOST_kKeySemicolon);
2094 GXMAP(gkey, XKB_KEY_equal, GHOST_kKeyEqual);
2095
2096 GXMAP(gkey, XKB_KEY_bracketleft, GHOST_kKeyLeftBracket);
2097 GXMAP(gkey, XKB_KEY_bracketright, GHOST_kKeyRightBracket);
2098 GXMAP(gkey, XKB_KEY_backslash, GHOST_kKeyBackslash);
2099 GXMAP(gkey, XKB_KEY_grave, GHOST_kKeyAccentGrave);
2100
2101 GXMAP(gkey, XKB_KEY_Shift_L, GHOST_kKeyLeftShift);
2102 GXMAP(gkey, XKB_KEY_Shift_R, GHOST_kKeyRightShift);
2103 GXMAP(gkey, XKB_KEY_Control_L, GHOST_kKeyLeftControl);
2104 GXMAP(gkey, XKB_KEY_Control_R, GHOST_kKeyRightControl);
2105 GXMAP(gkey, XKB_KEY_Alt_L, GHOST_kKeyLeftAlt);
2106 GXMAP(gkey, XKB_KEY_Alt_R, GHOST_kKeyRightAlt);
2107 GXMAP(gkey, XKB_KEY_Super_L, GHOST_kKeyLeftOS);
2108 GXMAP(gkey, XKB_KEY_Super_R, GHOST_kKeyRightOS);
2109 GXMAP(gkey, XKB_KEY_Menu, GHOST_kKeyApp);
2110
2111 GXMAP(gkey, XKB_KEY_Caps_Lock, GHOST_kKeyCapsLock);
2112 GXMAP(gkey, XKB_KEY_Num_Lock, GHOST_kKeyNumLock);
2113 GXMAP(gkey, XKB_KEY_Scroll_Lock, GHOST_kKeyScrollLock);
2114
2115 GXMAP(gkey, XKB_KEY_Left, GHOST_kKeyLeftArrow);
2116 GXMAP(gkey, XKB_KEY_Right, GHOST_kKeyRightArrow);
2117 GXMAP(gkey, XKB_KEY_Up, GHOST_kKeyUpArrow);
2118 GXMAP(gkey, XKB_KEY_Down, GHOST_kKeyDownArrow);
2119
2120 GXMAP(gkey, XKB_KEY_Print, GHOST_kKeyPrintScreen);
2121 GXMAP(gkey, XKB_KEY_Pause, GHOST_kKeyPause);
2122
2123 GXMAP(gkey, XKB_KEY_Insert, GHOST_kKeyInsert);
2124 GXMAP(gkey, XKB_KEY_Delete, GHOST_kKeyDelete);
2125 GXMAP(gkey, XKB_KEY_Home, GHOST_kKeyHome);
2126 GXMAP(gkey, XKB_KEY_End, GHOST_kKeyEnd);
2127 GXMAP(gkey, XKB_KEY_Page_Up, GHOST_kKeyUpPage);
2128 GXMAP(gkey, XKB_KEY_Page_Down, GHOST_kKeyDownPage);
2129
2130 GXMAP(gkey, XKB_KEY_KP_Decimal, GHOST_kKeyNumpadPeriod);
2131 GXMAP(gkey, XKB_KEY_KP_Enter, GHOST_kKeyNumpadEnter);
2132 GXMAP(gkey, XKB_KEY_KP_Add, GHOST_kKeyNumpadPlus);
2133 GXMAP(gkey, XKB_KEY_KP_Subtract, GHOST_kKeyNumpadMinus);
2134 GXMAP(gkey, XKB_KEY_KP_Multiply, GHOST_kKeyNumpadAsterisk);
2135 GXMAP(gkey, XKB_KEY_KP_Divide, GHOST_kKeyNumpadSlash);
2136
2137 GXMAP(gkey, XKB_KEY_XF86AudioPlay, GHOST_kKeyMediaPlay);
2138 GXMAP(gkey, XKB_KEY_XF86AudioStop, GHOST_kKeyMediaStop);
2139 GXMAP(gkey, XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst);
2140 GXMAP(gkey, XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast);
2141
2142 /* Additional keys for non US layouts. */
2143
2144 /* Uses the same physical key as #XKB_KEY_KP_Decimal for QWERTZ layout, see: #102287. */
2145 GXMAP(gkey, XKB_KEY_KP_Separator, GHOST_kKeyNumpadPeriod);
2146 GXMAP(gkey, XKB_KEY_less, GHOST_kKeyGrLess);
2147
2148 default:
2149 /* Rely on #xkb_map_gkey_or_scan_code to report when no key can be found. */
2150 gkey = GHOST_kKeyUnknown;
2151 }
2152#undef GXMAP
2153 }
2154
2155 return gkey;
2156}
2157
2165static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32_t key)
2166{
2167 GHOST_TKey gkey = xkb_map_gkey(sym);
2168
2169 if (UNLIKELY(gkey == GHOST_kKeyUnknown)) {
2170 /* Fall back to physical location for keys that would otherwise do nothing. */
2171 switch (key) {
2172 case KEY_GRAVE: {
2173 gkey = GHOST_kKeyAccentGrave;
2174 break;
2175 }
2176 case KEY_102ND: {
2177 gkey = GHOST_kKeyGrLess;
2178 break;
2179 }
2180 default: {
2182 /* Key-code. */
2183 "unhandled key: " << std::hex << std::showbase << sym << /* Hex. */
2184 std::dec << " (" << sym << "), " << /* Decimal. */
2185 /* Scan-code. */
2186 "scan-code: " << std::hex << std::showbase << key << /* Hex. */
2187 std::dec << " (" << key << ")" << /* Decimal. */
2188 std::endl);
2189 break;
2190 }
2191 }
2192 }
2193
2194 return gkey;
2195}
2196
2197static int pointer_axis_as_index(const uint32_t axis)
2198{
2199 switch (axis) {
2200 case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
2201 return 0;
2202 }
2203 case WL_POINTER_AXIS_VERTICAL_SCROLL: {
2204 return 1;
2205 }
2206 default: {
2207 return -1;
2208 }
2209 }
2210}
2211
2212static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type)
2213{
2214 switch (wp_tablet_tool_type) {
2215 case ZWP_TABLET_TOOL_V2_TYPE_ERASER: {
2217 }
2218 case ZWP_TABLET_TOOL_V2_TYPE_PEN:
2219 case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
2220 case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
2221 case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
2222 case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
2223 case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
2224 case ZWP_TABLET_TOOL_V2_TYPE_LENS: {
2226 }
2227 }
2228
2229 GHOST_PRINT("unknown tablet tool: " << wp_tablet_tool_type << std::endl);
2231}
2232
2233static const int default_cursor_size = 24;
2234
2236 const char *names[GHOST_kStandardCursorNumCursors] = {nullptr};
2237};
2238
2240 GWL_Cursor_ShapeInfo info{};
2241
2242#define CASE_CURSOR(shape_id, shape_name_in_theme) \
2243 case shape_id: \
2244 info.names[int(shape_id)] = shape_name_in_theme;
2245
2246 /* Use a switch to ensure missing values show a compiler warning. */
2251 CASE_CURSOR(GHOST_kStandardCursorInfo, "left_ptr_help");
2253 CASE_CURSOR(GHOST_kStandardCursorHelp, "question_arrow");
2277 CASE_CURSOR(GHOST_kStandardCursorStop, "not-allowed");
2278 CASE_CURSOR(GHOST_kStandardCursorUpDown, "sb_v_double_arrow");
2279 CASE_CURSOR(GHOST_kStandardCursorLeftRight, "sb_h_double_arrow");
2293 }
2294#undef CASE_CURSOR
2295
2296 return info;
2297}();
2298
2299static constexpr const char *ghost_wl_mime_text_plain = "text/plain";
2300static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8";
2301static constexpr const char *ghost_wl_mime_text_uri = "text/uri-list";
2302
2308/* Aligned to `ghost_wl_mime_preference_order`. */
2314
2315static const char *ghost_wl_mime_send[] = {
2316 "UTF8_STRING",
2317 "COMPOUND_TEXT",
2318 "TEXT",
2319 "STRING",
2320 "text/plain;charset=utf-8",
2321 "text/plain",
2322};
2323
2324#ifdef USE_EVENT_BACKGROUND_THREAD
2325static void pthread_set_min_priority(pthread_t handle)
2326{
2327 int policy;
2328 sched_param sch_params;
2329 if (pthread_getschedparam(handle, &policy, &sch_params) == 0) {
2330 sch_params.sched_priority = sched_get_priority_min(policy);
2331 pthread_setschedparam(handle, policy, &sch_params);
2332 }
2333}
2334
2335static void thread_set_min_priority(std::thread &thread)
2336{
2337 constexpr bool is_pthread = std::is_same<std::thread::native_handle_type, pthread_t>();
2338 if (!is_pthread) {
2339 return;
2340 }
2341 /* The cast is "safe" as non-matching types will have returned already.
2342 * This cast might be avoided with clever template use. */
2343 pthread_set_min_priority(reinterpret_cast<pthread_t>(thread.native_handle()));
2344}
2345#endif /* USE_EVENT_BACKGROUND_THREAD */
2346
2347static int memfd_create_sealed(const char *name)
2348{
2349#ifdef HAVE_MEMFD_CREATE
2350 const int fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
2351 if (fd >= 0) {
2352 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
2353 }
2354 return fd;
2355#else /* HAVE_MEMFD_CREATE */
2356 char *path = getenv("XDG_RUNTIME_DIR");
2357 if (!path) {
2358 errno = ENOENT;
2359 return -1;
2360 }
2361 char *tmpname;
2362 asprintf(&tmpname, "%s/%s-XXXXXX", path, name);
2363 const int fd = mkostemp(tmpname, O_CLOEXEC);
2364 if (fd >= 0) {
2365 unlink(tmpname);
2366 }
2367 free(tmpname);
2368 return fd;
2369#endif /* !HAVE_MEMFD_CREATE */
2370}
2371
2372#if defined(WITH_GHOST_WAYLAND_LIBDECOR) && defined(WITH_VULKAN_BACKEND)
2373int memfd_create_sealed_for_vulkan_hack(const char *name)
2374{
2375 return memfd_create_sealed(name);
2376}
2377#endif
2378
2379enum {
2383};
2384
2385static int file_descriptor_is_io_ready(int fd, const int flags, const int timeout_ms)
2386{
2387 int result;
2388
2389 GHOST_ASSERT(flags & (GWL_IOR_READ | GWL_IOR_WRITE), "X");
2390
2391 /* NOTE: We don't bother to account for elapsed time if we get #EINTR. */
2392 do {
2393#ifdef HAVE_POLL
2394 pollfd info;
2395
2396 info.fd = fd;
2397 info.events = 0;
2398 if (flags & GWL_IOR_READ) {
2399 info.events |= POLLIN | POLLPRI;
2400 }
2401 if (flags & GWL_IOR_WRITE) {
2402 info.events |= POLLOUT;
2403 }
2404 result = poll(&info, 1, timeout_ms);
2405#else
2406 fd_set rfdset, *rfdp = nullptr;
2407 fd_set wfdset, *wfdp = nullptr;
2408 struct timeval tv, *tvp = nullptr;
2409
2410 /* If this assert triggers we'll corrupt memory here */
2411 GHOST_ASSERT(fd >= 0 && fd < FD_SETSIZE, "X");
2412
2413 if (flags & GWL_IOR_READ) {
2414 FD_ZERO(&rfdset);
2415 FD_SET(fd, &rfdset);
2416 rfdp = &rfdset;
2417 }
2418 if (flags & GWL_IOR_WRITE) {
2419 FD_ZERO(&wfdset);
2420 FD_SET(fd, &wfdset);
2421 wfdp = &wfdset;
2422 }
2423
2424 if (timeout_ms >= 0) {
2425 tv.tv_sec = timeout_ms / 1000;
2426 tv.tv_usec = (timeout_ms % 1000) * 1000;
2427 tvp = &tv;
2428 }
2429
2430 result = select(fd + 1, rfdp, wfdp, nullptr, tvp);
2431#endif /* !HAVE_POLL */
2432 } while (result < 0 && errno == EINTR && !(flags & GWL_IOR_NO_RETRY));
2433
2434 return result;
2435}
2436
2438{
2439 /* Based on SDL's `Wayland_PumpEvents`. */
2440 int err;
2441
2442 /* NOTE: Without this, interactions with window borders via LIBDECOR doesn't function. */
2444
2446 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2449 {
2451 }
2452 else {
2454 err = 0;
2455 }
2456 }
2457 else {
2459 }
2460 return err;
2461}
2462
2463#ifdef USE_EVENT_BACKGROUND_THREAD
2464
2465static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex)
2466{
2467 const int fd = wl_display_get_fd(wl_display);
2468 int state;
2469 do {
2471 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2472 if (state == 0) {
2473 server_mutex->lock();
2475 if (state == 0) {
2476 break;
2477 }
2478 }
2479 } while (state == 0);
2480}
2481
2483 const int fd,
2484 std::mutex *server_mutex)
2485{
2486 /* Based on SDL's `Wayland_PumpEvents`. */
2487 server_mutex->lock();
2488 int err = 0;
2490 bool wait_on_fd = false;
2491 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2494 }
2495 else {
2497 /* Without this, the thread will loop continuously, 100% CPU. */
2498 wait_on_fd = true;
2499 }
2500
2501 server_mutex->unlock();
2502
2503 if (wait_on_fd) {
2504 /* Important this runs after unlocking. */
2506 }
2507 }
2508 else {
2509 server_mutex->unlock();
2510
2511 /* Wait for input (unlocked, so as not to block other threads). */
2513 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2514 if (state > 0) {
2515 server_mutex->lock();
2517 if (state > 0) {
2519 }
2520 server_mutex->unlock();
2521 }
2522 }
2523
2524 return err;
2525}
2526#endif /* USE_EVENT_BACKGROUND_THREAD */
2527
2528static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
2529{
2530 switch (format) {
2531 case WL_SHM_FORMAT_ARGB8888: {
2532 return 4;
2533 }
2534 default: {
2535 /* Support other formats as needed. */
2536 GHOST_ASSERT(0, "Unexpected format passed in!");
2537 return 4;
2538 }
2539 }
2540}
2541
2549static wl_buffer *ghost_wl_buffer_create_for_image(wl_shm *shm,
2550 const int32_t size_xy[2],
2551 enum wl_shm_format format,
2552 void **r_buffer_data,
2553 size_t *r_buffer_data_size)
2554{
2555 const int fd = memfd_create_sealed("ghost-wl-buffer");
2556 wl_buffer *buffer = nullptr;
2557 if (fd >= 0) {
2558 const int32_t buffer_stride = size_xy[0] * ghost_wl_shm_format_as_size(format);
2559 const int32_t buffer_size = buffer_stride * size_xy[1];
2560 if (posix_fallocate(fd, 0, buffer_size) == 0) {
2561 void *buffer_data = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
2562 if (buffer_data != MAP_FAILED) {
2563 wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer_size);
2564 buffer = wl_shm_pool_create_buffer(pool, 0, UNPACK2(size_xy), buffer_stride, format);
2565 wl_shm_pool_destroy(pool);
2566 if (buffer) {
2567 *r_buffer_data = buffer_data;
2568 *r_buffer_data_size = size_t(buffer_size);
2569 }
2570 else {
2571 /* Highly unlikely. */
2572 munmap(buffer_data, buffer_size);
2573 }
2574 }
2575 }
2576 close(fd);
2577 }
2578 return buffer;
2579}
2580
2585static ssize_t read_exhaustive(const int fd, void *data, size_t nbytes)
2586{
2587 ssize_t nbytes_read = read(fd, data, nbytes);
2588 if (nbytes_read > 0) {
2589 while (nbytes_read < nbytes) {
2590 const ssize_t nbytes_extra = read(
2591 fd, static_cast<char *>(data) + nbytes_read, nbytes - nbytes_read);
2592 if (nbytes_extra > 0) {
2593 nbytes_read += nbytes_extra;
2594 }
2595 else {
2596 if (UNLIKELY(nbytes_extra < 0)) {
2597 nbytes_read = nbytes_extra; /* Error. */
2598 }
2599 break;
2600 }
2601 }
2602 }
2603 return nbytes_read;
2604}
2605
2613static char *read_file_as_buffer(const int fd, const bool nil_terminate, size_t *r_len)
2614{
2615 struct ByteChunk {
2616 ByteChunk *next;
2617 char data[4096 - sizeof(ByteChunk *)];
2618 };
2619 bool ok = true;
2620 size_t len = 0;
2621
2622 ByteChunk *chunk_first = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2623 {
2624 ByteChunk **chunk_link_p = &chunk_first;
2625 ByteChunk *chunk = chunk_first;
2626 while (true) {
2627 if (UNLIKELY(chunk == nullptr)) {
2628 errno = ENOMEM;
2629 ok = false;
2630 break;
2631 }
2632 chunk->next = nullptr;
2633 /* Using `read` causes issues with GNOME, see: #106040). */
2634 const ssize_t len_chunk = read_exhaustive(fd, chunk->data, sizeof(ByteChunk::data));
2635 if (len_chunk <= 0) {
2636 if (UNLIKELY(len_chunk < 0)) {
2637 ok = false;
2638 }
2639 if (chunk == chunk_first) {
2640 chunk_first = nullptr;
2641 }
2642 free(chunk);
2643 break;
2644 }
2645 *chunk_link_p = chunk;
2646 chunk_link_p = &chunk->next;
2647 len += len_chunk;
2648
2649 if (len_chunk != sizeof(ByteChunk::data)) {
2650 break;
2651 }
2652 chunk = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2653 }
2654 }
2655
2656 char *buf = nullptr;
2657 if (ok) {
2658 buf = static_cast<char *>(malloc(len + (nil_terminate ? 1 : 0)));
2659 if (UNLIKELY(buf == nullptr)) {
2660 errno = ENOMEM;
2661 ok = false;
2662 }
2663 }
2664
2665 if (ok) {
2666 *r_len = len;
2667 if (nil_terminate) {
2668 buf[len] = '\0';
2669 }
2670 }
2671 else {
2672 *r_len = 0;
2673 }
2674
2675 char *buf_stride = buf;
2676 while (chunk_first) {
2677 if (ok) {
2678 const size_t len_chunk = std::min(len, sizeof(chunk_first->data));
2679 memcpy(buf_stride, chunk_first->data, len_chunk);
2680 buf_stride += len_chunk;
2681 len -= len_chunk;
2682 }
2683 ByteChunk *chunk = chunk_first->next;
2684 free(chunk_first);
2685 chunk_first = chunk;
2686 }
2687
2688 return buf;
2689}
2690
2693/* -------------------------------------------------------------------- */
2697static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image,
2698 wl_buffer *buffer,
2700 const int scale)
2701{
2702 const int32_t image_size_x = int32_t(wl_image->width);
2703 const int32_t image_size_y = int32_t(wl_image->height);
2704 GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0,
2705 "The size must be a multiple of the scale!");
2706
2707 wl_surface_set_buffer_scale(wl_surface, scale);
2708 wl_surface_attach(wl_surface, buffer, 0, 0);
2709 wl_surface_damage(wl_surface, 0, 0, image_size_x, image_size_y);
2710 wl_surface_commit(wl_surface);
2711}
2712
2716static int cursor_buffer_compatible_scale_from_image(const wl_cursor_image *wl_image, int scale)
2717{
2718 const int32_t image_size_x = int32_t(wl_image->width);
2719 const int32_t image_size_y = int32_t(wl_image->height);
2720 while (scale > 1) {
2721 if ((image_size_x % scale) == 0 && (image_size_y % scale) == 0) {
2722 break;
2723 }
2724 scale -= 1;
2725 }
2726 return scale;
2727}
2728
2729static const wl_cursor *gwl_seat_cursor_find_from_shape(GWL_Seat *seat,
2730 const GHOST_TStandardCursor shape,
2731 const char **r_cursor_name)
2732{
2733 /* Caller must lock `server_mutex`. */
2734 GWL_Cursor *cursor = &seat->cursor;
2735 wl_cursor *wl_cursor = nullptr;
2736
2737 const char *cursor_name = ghost_wl_cursors.names[shape];
2738 if (cursor_name[0] != '\0') {
2739 if (!cursor->wl.theme) {
2740 /* The cursor wl_surface hasn't entered an output yet. Initialize theme with scale 1. */
2741 cursor->wl.theme = wl_cursor_theme_load(
2742 (cursor->theme_name.empty() ? nullptr : cursor->theme_name.c_str()),
2743 cursor->theme_size,
2744 seat->system->wl_shm_get());
2745 }
2746
2747 if (cursor->wl.theme) {
2748 wl_cursor = wl_cursor_theme_get_cursor(cursor->wl.theme, cursor_name);
2749 if (!wl_cursor) {
2750 GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl);
2751 }
2752 }
2753 }
2754
2755 if (r_cursor_name && wl_cursor) {
2756 *r_cursor_name = cursor_name;
2757 }
2758 return wl_cursor;
2759}
2760
2768{
2769 const GWL_Cursor *cursor = &seat->cursor;
2770
2771 if (seat->wl.pointer) {
2772 const int scale = cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale;
2773 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2774 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2775 wl_pointer_set_cursor(
2776 seat->wl.pointer, seat->pointer.serial, cursor->wl.surface_cursor, hotspot_x, hotspot_y);
2777 }
2778
2779 if (!seat->wp.tablet_tools.empty()) {
2780 const int scale = cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale;
2781 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2782 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2783 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2784 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2785 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2786 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
2787 seat->tablet.serial,
2788 tablet_tool->wl.surface_cursor,
2789 hotspot_x,
2790 hotspot_y);
2791#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK
2792 wl_surface_commit(tablet_tool->wl.surface_cursor);
2793#endif
2794 }
2795 }
2796
2798}
2799
2807{
2809
2810 wl_pointer_set_cursor(seat->wl.pointer, seat->pointer.serial, nullptr, 0, 0);
2811 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2812 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, seat->tablet.serial, nullptr, 0, 0);
2813 }
2814}
2815
2816static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
2817 const wl_cursor_image *wl_image,
2818 wl_buffer *buffer)
2819{
2820 const GWL_Cursor *cursor = &seat->cursor;
2821 const bool visible = (cursor->visible && cursor->is_hardware);
2822
2823 /* This is a requirement of WAYLAND, when this isn't the case,
2824 * it causes Blender's window to close intermittently. */
2825 if (seat->wl.pointer) {
2827 wl_image, cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale);
2828 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
2829 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
2830 cursor_buffer_set_surface_impl(wl_image, buffer, cursor->wl.surface_cursor, scale);
2831 wl_pointer_set_cursor(seat->wl.pointer,
2832 seat->pointer.serial,
2833 visible ? cursor->wl.surface_cursor : nullptr,
2834 hotspot_x,
2835 hotspot_y);
2836 }
2837
2838 /* Set the cursor for all tablet tools as well. */
2839 if (!seat->wp.tablet_tools.empty()) {
2841 wl_image, cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale);
2842 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
2843 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
2844 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2845 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2846 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2847 cursor_buffer_set_surface_impl(wl_image, buffer, tablet_tool->wl.surface_cursor, scale);
2848 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
2849 seat->tablet.serial,
2850 visible ? tablet_tool->wl.surface_cursor : nullptr,
2851 hotspot_x,
2852 hotspot_y);
2853 }
2854 }
2855}
2856
2858{
2859 const GWL_Cursor *cursor = &seat->cursor;
2861 gwl_seat_cursor_buffer_set(seat, &cursor->wl.image, cursor->wl.buffer);
2863}
2864
2870
2872 const bool visible,
2873 const bool is_hardware,
2874 const enum eCursorSetMode set_mode)
2875{
2876 GWL_Cursor *cursor = &seat->cursor;
2877 const bool was_visible = cursor->is_hardware && cursor->visible;
2878 const bool use_visible = is_hardware && visible;
2879
2880 if (set_mode == CURSOR_VISIBLE_ALWAYS_SET) {
2881 /* Pass. */
2882 }
2883 else if (set_mode == CURSOR_VISIBLE_ONLY_SHOW) {
2884 if (!use_visible) {
2885 return;
2886 }
2887 }
2888 else if (set_mode == CURSOR_VISIBLE_ONLY_HIDE) {
2889 if (use_visible) {
2890 return;
2891 }
2892 }
2893
2894 if (use_visible) {
2895 if (!was_visible) {
2897 }
2898 }
2899 else {
2900 if (was_visible) {
2902 }
2903 }
2904 cursor->visible = visible;
2905 cursor->is_hardware = is_hardware;
2906}
2907
2910/* -------------------------------------------------------------------- */
2914#ifdef USE_EVENT_BACKGROUND_THREAD
2915
2917{
2918 const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
2919 if (!wl_cursor) {
2920 return false;
2921 }
2922 /* NOTE: return true to stress test animated cursor,
2923 * to ensure (otherwise rare) issues are triggered more frequently. */
2924 // return true;
2925
2926 return wl_cursor->image_count > 1;
2927}
2928
2930{
2931 /* Caller must lock `server_mutex`. */
2932 GHOST_ASSERT(seat->cursor.anim_handle == nullptr, "Must be cleared");
2933
2934 /* Callback for updating the cursor animation. */
2935 auto cursor_anim_frame_step_fn =
2936 [](GWL_Seat *seat, GWL_Cursor_AnimHandle *anim_handle, int delay) {
2937 /* It's possible the `wl_cursor` is reloaded while the cursor is animating.
2938 * Don't access outside the lock, that's why the `delay` is passed in. */
2939 std::mutex *server_mutex = seat->system->server_mutex;
2940 int frame = 0;
2941 while (!anim_handle->exit_pending.load()) {
2942 std::this_thread::sleep_for(std::chrono::milliseconds(delay));
2943 if (!anim_handle->exit_pending.load()) {
2944 std::lock_guard lock_server_guard{*server_mutex};
2945 if (!anim_handle->exit_pending.load()) {
2946 const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
2947 frame = (frame + 1) % wl_cursor->image_count;
2948 wl_cursor_image *image = wl_cursor->images[frame];
2949 wl_buffer *buffer = wl_cursor_image_get_buffer(image);
2950 gwl_seat_cursor_buffer_set(seat, image, buffer);
2951 delay = wl_cursor->images[frame]->delay;
2952 /* Without this the cursor won't update when other processes are occupied. */
2954 }
2955 }
2956 }
2957 delete anim_handle;
2958 };
2959
2960 /* Allocate so this can be set before the thread begins. */
2962 seat->cursor.anim_handle = anim_handle;
2963
2964 const int delay = seat->cursor.wl.theme_cursor->images[0]->delay;
2965 std::thread cursor_anim_thread(cursor_anim_frame_step_fn, seat, anim_handle, delay);
2966 /* Application logic should take priority. */
2967 thread_set_min_priority(cursor_anim_thread);
2968 cursor_anim_thread.detach();
2969}
2970
2972{
2973 if (gwl_seat_cursor_anim_check(seat)) {
2975 }
2976}
2977
2979{
2980 GWL_Cursor *cursor = &seat->cursor;
2981 if (cursor->anim_handle) {
2982 GWL_Cursor_AnimHandle *anim_handle = cursor->anim_handle;
2983 cursor->anim_handle = nullptr;
2984 anim_handle->exit_pending.store(true);
2985 }
2986}
2987
2993
2994#else
2995
2996/* Unfortunately cursor animation requires a background thread. */
2997[[maybe_unused]] static bool gwl_seat_cursor_anim_check(GWL_Seat * /*seat*/)
2998{
2999 return false;
3000}
3001[[maybe_unused]] static void gwl_seat_cursor_anim_begin(GWL_Seat * /*seat*/) {}
3002[[maybe_unused]] static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat * /*seat*/) {}
3003[[maybe_unused]] static void gwl_seat_cursor_anim_end(GWL_Seat * /*seat*/) {}
3004[[maybe_unused]] static void gwl_seat_cursor_anim_reset(GWL_Seat * /*seat*/) {}
3005
3006#endif /* !USE_EVENT_BACKGROUND_THREAD */
3007
3010/* -------------------------------------------------------------------- */
3018static CLG_LogRef LOG_WL_KEYBOARD_DEPRESSED_STATE = {"ghost.wl.keyboard.depressed"};
3019#define LOG (&LOG_WL_KEYBOARD_DEPRESSED_STATE)
3020
3022{
3023 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3024 seat->key_depressed.mods[i] = 0;
3025 }
3026}
3027
3029 const GHOST_TKey gkey,
3030 const GHOST_TEventType etype)
3031{
3032 if (GHOST_KEY_MODIFIER_CHECK(gkey)) {
3033 const int index = GHOST_KEY_MODIFIER_TO_INDEX(gkey);
3034 int16_t &value = seat->key_depressed.mods[index];
3035 if (etype == GHOST_kEventKeyUp) {
3036 value -= 1;
3037 if (UNLIKELY(value < 0)) {
3038 CLOG_WARN(LOG, "modifier (%d) has negative keys held (%d)!", index, value);
3039 value = 0;
3040 }
3041 }
3042 else {
3043 value += 1;
3044 }
3045 }
3046}
3047
3049 GWL_Seat *seat,
3050 GHOST_IWindow *win,
3051 const uint64_t event_ms,
3052 const GWL_KeyboardDepressedState &key_depressed_prev)
3053{
3054 /* Separate key up and down into separate passes so key down events always come after key up.
3055 * Do this so users of GHOST can use the last pressed or released modifier to check
3056 * if the modifier is held instead of counting modifiers pressed as is done here,
3057 * this isn't perfect but works well enough in practice. */
3058 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3059 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d < 0; d++) {
3062 new GHOST_EventKey(event_ms, GHOST_kEventKeyUp, win, gkey, false));
3063
3064 CLOG_INFO(LOG, 2, "modifier (%d) up", i);
3065 }
3066 }
3067
3068 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3069 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d > 0; d--) {
3072 new GHOST_EventKey(event_ms, GHOST_kEventKeyDown, win, gkey, false));
3073 CLOG_INFO(LOG, 2, "modifier (%d) down", i);
3074 }
3075 }
3076}
3077
3078#undef LOG
3079
3082/* -------------------------------------------------------------------- */
3089static CLG_LogRef LOG_WL_RELATIVE_POINTER = {"ghost.wl.handle.relative_pointer"};
3090#define LOG (&LOG_WL_RELATIVE_POINTER)
3091
3097 const wl_fixed_t xy[2],
3098 const uint64_t event_ms)
3099{
3100 seat->pointer.xy[0] = xy[0];
3101 seat->pointer.xy[1] = xy[1];
3102
3103#ifdef USE_GNOME_CONFINE_HACK
3104 if (seat->use_pointer_software_confine) {
3106 win->getClientBounds(bounds);
3107 /* Needed or the cursor is considered outside the window and doesn't restore the location. */
3108 bounds.m_r -= 1;
3109 bounds.m_b -= 1;
3110 bounds.m_l = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_l));
3111 bounds.m_t = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_t));
3112 bounds.m_r = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_r));
3113 bounds.m_b = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_b));
3114 bounds.clampPoint(UNPACK2(seat->pointer.xy));
3115 }
3116#endif
3117 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3119 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3120}
3121
3123 void *data,
3124 zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
3125 const uint32_t utime_hi,
3126 const uint32_t utime_lo,
3127 const wl_fixed_t dx,
3128 const wl_fixed_t dy,
3129 const wl_fixed_t /*dx_unaccel*/,
3130 const wl_fixed_t /*dy_unaccel*/)
3131{
3132 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3133 const uint64_t time = ghost_wl_ms_from_utime_pair(utime_hi, utime_lo);
3134 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3135
3136 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
3137 CLOG_INFO(LOG, 2, "relative_motion");
3138 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3139 const wl_fixed_t xy_next[2] = {
3140 seat->pointer.xy[0] + win->wl_fixed_from_window(dx),
3141 seat->pointer.xy[1] + win->wl_fixed_from_window(dy),
3142 };
3143 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
3144 }
3145 else {
3146 CLOG_INFO(LOG, 2, "relative_motion (skipped)");
3147 }
3148}
3149
3150static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
3152};
3153
3154#undef LOG
3155
3158/* -------------------------------------------------------------------- */
3162static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"};
3163#define LOG (&LOG_WL_DATA_SOURCE)
3164
3165static void dnd_events(const GWL_Seat *const seat,
3166 const GHOST_TEventType event,
3167 const uint64_t event_ms)
3168{
3169 /* NOTE: `seat->data_offer_dnd_mutex` must already be locked. */
3170 if (wl_surface *wl_surface_focus = seat->wl.surface_window_focus_dnd) {
3171 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3172 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->data_offer_dnd->dnd.xy)};
3173 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) {
3176 new GHOST_EventDragnDrop(event_ms, event, type, win, UNPACK2(event_xy), nullptr));
3177 }
3178 }
3179}
3180
3182 const char *mime_receive,
3183 std::mutex *mutex,
3184 const bool nil_terminate,
3185 size_t *r_len)
3186{
3187 int pipefd[2];
3188 const bool pipefd_ok = pipe(pipefd) == 0;
3189 if (pipefd_ok) {
3190 wl_data_offer_receive(data_offer->wl.id, mime_receive, pipefd[1]);
3191 close(pipefd[1]);
3192 }
3193 else {
3194 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3195 }
3196
3197 /* Only for DND (A no-op to disable for clipboard data-offer). */
3198 data_offer->dnd.in_use = false;
3199
3200 if (mutex) {
3201 mutex->unlock();
3202 }
3203 /* WARNING: `data_offer` may be freed from now on. */
3204 char *buf = nullptr;
3205 if (pipefd_ok) {
3206 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3207 if (buf == nullptr) {
3208 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3209 }
3210 close(pipefd[0]);
3211 }
3212 else {
3213 *r_len = 0;
3214 }
3215 return buf;
3216}
3217
3219 const char *mime_receive,
3220 std::mutex *mutex,
3221 const bool nil_terminate,
3222 size_t *r_len)
3223{
3224 int pipefd[2];
3225 const bool pipefd_ok = pipe(pipefd) == 0;
3226 if (pipefd_ok) {
3227 zwp_primary_selection_offer_v1_receive(data_offer->wp.id, mime_receive, pipefd[1]);
3228 close(pipefd[1]);
3229 }
3230 else {
3231 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3232 }
3233
3234 if (mutex) {
3235 mutex->unlock();
3236 }
3237 /* WARNING: `data_offer` may be freed from now on. */
3238 char *buf = nullptr;
3239 if (pipefd_ok) {
3240 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3241 if (buf == nullptr) {
3242 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3243 }
3244 close(pipefd[0]);
3245 }
3246 return buf;
3247}
3248
3255static void data_source_handle_target(void * /*data*/,
3256 wl_data_source * /*wl_data_source*/,
3257 const char * /*mime_type*/)
3258{
3259 CLOG_INFO(LOG, 2, "target");
3260}
3261
3262static void data_source_handle_send(void *data,
3263 wl_data_source * /*wl_data_source*/,
3264 const char * /*mime_type*/,
3265 const int32_t fd)
3266{
3267 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3268
3269 CLOG_INFO(LOG, 2, "send");
3270
3271 auto write_file_fn = [](GWL_Seat *seat, const int fd) {
3272 if (UNLIKELY(write(fd,
3274 seat->data_source->buffer_out.data_size) < 0))
3275 {
3276 CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno));
3277 }
3278 close(fd);
3279 seat->data_source_mutex.unlock();
3280 };
3281
3282 seat->data_source_mutex.lock();
3283 std::thread write_thread(write_file_fn, seat, fd);
3284 write_thread.detach();
3285}
3286
3287static void data_source_handle_cancelled(void *data, wl_data_source *wl_data_source)
3288{
3289 CLOG_INFO(LOG, 2, "cancelled");
3290 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3291 GWL_DataSource *data_source = seat->data_source;
3292 if (seat->data_source->wl.source == wl_data_source) {
3293 data_source->wl.source = nullptr;
3294 }
3295
3296 wl_data_source_destroy(wl_data_source);
3297}
3298
3306static void data_source_handle_dnd_drop_performed(void * /*data*/,
3307 wl_data_source * /*wl_data_source*/)
3308{
3309 CLOG_INFO(LOG, 2, "dnd_drop_performed");
3310}
3311
3319static void data_source_handle_dnd_finished(void * /*data*/, wl_data_source * /*wl_data_source*/)
3320{
3321 CLOG_INFO(LOG, 2, "dnd_finished");
3322}
3323
3331static void data_source_handle_action(void * /*data*/,
3332 wl_data_source * /*wl_data_source*/,
3333 const uint32_t dnd_action)
3334{
3335 CLOG_INFO(LOG, 2, "handle_action (dnd_action=%u)", dnd_action);
3336}
3337
3338static const wl_data_source_listener data_source_listener = {
3339 /*target*/ data_source_handle_target,
3340 /*send*/ data_source_handle_send,
3341 /*cancelled*/ data_source_handle_cancelled,
3342 /*dnd_drop_performed*/ data_source_handle_dnd_drop_performed,
3343 /*dnd_finished*/ data_source_handle_dnd_finished,
3344 /*action*/ data_source_handle_action,
3345};
3346
3347#undef LOG
3348
3351/* -------------------------------------------------------------------- */
3355static CLG_LogRef LOG_WL_DATA_OFFER = {"ghost.wl.handle.data_offer"};
3356#define LOG (&LOG_WL_DATA_OFFER)
3357
3358static void data_offer_handle_offer(void *data,
3359 wl_data_offer * /*wl_data_offer*/,
3360 const char *mime_type)
3361{
3362 CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type);
3363 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3364 data_offer->types.insert(mime_type);
3365}
3366
3368 wl_data_offer * /*wl_data_offer*/,
3369 const uint32_t source_actions)
3370{
3371 CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions);
3372 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3373 data_offer->dnd.source_actions = (enum wl_data_device_manager_dnd_action)source_actions;
3374}
3375
3376static void data_offer_handle_action(void *data,
3377 wl_data_offer * /*wl_data_offer*/,
3378 const uint32_t dnd_action)
3379{
3380 CLOG_INFO(LOG, 2, "actions (%u)", dnd_action);
3381 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3382 data_offer->dnd.action = (enum wl_data_device_manager_dnd_action)dnd_action;
3383}
3384
3385static const wl_data_offer_listener data_offer_listener = {
3386 /*offer*/ data_offer_handle_offer,
3387 /*source_actions*/ data_offer_handle_source_actions,
3388 /*action*/ data_offer_handle_action,
3389};
3390
3391#undef LOG
3392
3395/* -------------------------------------------------------------------- */
3399static CLG_LogRef LOG_WL_DATA_DEVICE = {"ghost.wl.handle.data_device"};
3400#define LOG (&LOG_WL_DATA_DEVICE)
3401
3402static void data_device_handle_data_offer(void * /*data*/,
3403 wl_data_device * /*wl_data_device*/,
3404 wl_data_offer *id)
3405{
3406 CLOG_INFO(LOG, 2, "data_offer");
3407
3408 /* The ownership of data-offer isn't so obvious:
3409 * At this point it's not known if this will be used for drag & drop or selection.
3410 *
3411 * The API docs state that the following callbacks run immediately after this callback:
3412 * - #wl_data_device_listener::enter (for drag & drop).
3413 * - #wl_data_device_listener::selection (for copy & paste).
3414 *
3415 * In the case of GHOST, this means they will be assigned to either:
3416 * - #GWL_Seat::data_offer_dnd
3417 * - #GWL_Seat::data_offer_copy_paste
3418 */
3419 GWL_DataOffer *data_offer = new GWL_DataOffer;
3420 data_offer->wl.id = id;
3421 wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
3422}
3423
3424static void data_device_handle_enter(void *data,
3425 wl_data_device * /*wl_data_device*/,
3426 const uint32_t serial,
3428 const wl_fixed_t x,
3429 const wl_fixed_t y,
3430 wl_data_offer *id)
3431{
3432 /* Always clear the current data-offer no matter what else happens. */
3433 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3434 const uint64_t event_ms = seat->system->getMilliSeconds();
3435
3436 std::lock_guard lock{seat->data_offer_dnd_mutex};
3437 if (seat->data_offer_dnd) {
3438 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3439 delete seat->data_offer_dnd;
3440 seat->data_offer_dnd = nullptr;
3441 }
3442 /* Clearing complete. */
3443
3444 /* Handle the new offer. */
3445 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3447 CLOG_INFO(LOG, 2, "enter (skipped)");
3448 wl_data_offer_destroy(data_offer->wl.id);
3449 delete data_offer;
3450 return;
3451 }
3452 CLOG_INFO(LOG, 2, "enter");
3453
3454 /* Transfer ownership of the `data_offer`. */
3455 seat->data_offer_dnd = data_offer;
3456
3457 data_offer->dnd.in_use = true;
3458 data_offer->dnd.xy[0] = x;
3459 data_offer->dnd.xy[1] = y;
3460
3461 wl_data_offer_set_actions(id,
3462 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
3463 WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
3464 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
3465
3466 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3467 const char *type = ghost_wl_mime_preference_order[i];
3468 wl_data_offer_accept(id, serial, type);
3469 }
3470
3472
3473 seat->system->seat_active_set(seat);
3474
3475 dnd_events(seat, GHOST_kEventDraggingEntered, event_ms);
3476}
3477
3478static void data_device_handle_leave(void *data, wl_data_device * /*wl_data_device*/)
3479{
3480 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3481 const uint64_t event_ms = seat->system->getMilliSeconds();
3482
3483 std::lock_guard lock{seat->data_offer_dnd_mutex};
3484 /* The user may have only dragged over the window decorations. */
3485 if (seat->data_offer_dnd == nullptr) {
3486 return;
3487 }
3488 CLOG_INFO(LOG, 2, "leave");
3489
3490 dnd_events(seat, GHOST_kEventDraggingExited, event_ms);
3491 seat->wl.surface_window_focus_dnd = nullptr;
3492
3493 if (seat->data_offer_dnd && !seat->data_offer_dnd->dnd.in_use) {
3494 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3495 delete seat->data_offer_dnd;
3496 seat->data_offer_dnd = nullptr;
3497 }
3498}
3499
3500static void data_device_handle_motion(void *data,
3501 wl_data_device * /*wl_data_device*/,
3502 const uint32_t time,
3503 const wl_fixed_t x,
3504 const wl_fixed_t y)
3505{
3506 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3507 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3508
3509 std::lock_guard lock{seat->data_offer_dnd_mutex};
3510 /* The user may have only dragged over the window decorations. */
3511 if (seat->data_offer_dnd == nullptr) {
3512 return;
3513 }
3514
3515 CLOG_INFO(LOG, 2, "motion");
3516
3517 seat->data_offer_dnd->dnd.xy[0] = x;
3518 seat->data_offer_dnd->dnd.xy[1] = y;
3519
3520 dnd_events(seat, GHOST_kEventDraggingUpdated, event_ms);
3521}
3522
3523static void data_device_handle_drop(void *data, wl_data_device * /*wl_data_device*/)
3524{
3525 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3526 std::lock_guard lock{seat->data_offer_dnd_mutex};
3527
3528 /* No need to check this for null (as other callbacks do).
3529 * because the data-offer has not been accepted (actions set... etc). */
3530 GWL_DataOffer *data_offer = seat->data_offer_dnd;
3531
3532 /* Take ownership of `data_offer` to prevent a double-free, see: #128766.
3533 * The thread this function spawns is responsible for freeing it. */
3534 seat->data_offer_dnd = nullptr;
3535
3536 /* Use a blank string for `mime_receive` to prevent crashes, although could also be `nullptr`.
3537 * Failure to set this to a known type just means the file won't have any special handling.
3538 * GHOST still generates a dropped file event.
3539 * NOTE: this string can be compared with `mime_text_plain`, `mime_text_uri` etc...
3540 * as the this always points to the same values. */
3541 const char *mime_receive = "";
3542 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3543 const char *type = ghost_wl_mime_preference_order[i];
3544 if (data_offer->types.count(type)) {
3545 mime_receive = type;
3546 break;
3547 }
3548 }
3549
3550 CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive);
3551
3552 auto read_drop_data_fn = [](GWL_Seat *const seat,
3553 GWL_DataOffer *data_offer,
3554 wl_surface *wl_surface_window,
3555 const char *mime_receive) {
3556 const uint64_t event_ms = seat->system->getMilliSeconds();
3557 const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)};
3558
3559 const bool nil_terminate = (mime_receive != ghost_wl_mime_text_uri);
3560 size_t data_buf_len = 0;
3561 const char *data_buf = read_buffer_from_data_offer(
3562 data_offer, mime_receive, nullptr, nil_terminate, &data_buf_len);
3563
3564 CLOG_INFO(LOG, 2, "read_drop_data mime_receive=%s, data_len=%zu", mime_receive, data_buf_len);
3565
3566 wl_data_offer_finish(data_offer->wl.id);
3567 wl_data_offer_destroy(data_offer->wl.id);
3568
3569 delete data_offer;
3570 data_offer = nullptr;
3571
3572 /* Don't generate a drop event if the data could not be read,
3573 * an error will have been logged. */
3574 if (data_buf != nullptr) {
3576 void *ghost_dnd_data = nullptr;
3577
3578 /* Failure to receive drop data. */
3579 if (mime_receive == ghost_wl_mime_text_uri) {
3580 const char file_proto[] = "file://";
3581 /* NOTE: some applications CRLF (`\r\n`) GTK3 for e.g. & others don't `pcmanfm-qt`.
3582 * So support both, once `\n` is found, strip the preceding `\r` if found. */
3583 const char lf = '\n';
3584
3585 const std::string_view data = std::string_view(data_buf, data_buf_len);
3586 std::vector<std::string_view> uris;
3587
3588 size_t pos = 0;
3589 while (pos != std::string::npos) {
3590 pos = data.find(file_proto, pos);
3591 if (pos == std::string::npos) {
3592 break;
3593 }
3594 const size_t start = pos + sizeof(file_proto) - 1;
3595 pos = data.find(lf, pos);
3596
3597 size_t end = pos;
3598 if (UNLIKELY(end == std::string::npos)) {
3599 /* Note that most well behaved file managers will add a trailing newline,
3600 * Gnome's web browser (44.3) doesn't, so support reading up until the last byte. */
3601 end = data.size();
3602 }
3603 /* Account for 'CRLF' case. */
3604 if (data[end - 1] == '\r') {
3605 end -= 1;
3606 }
3607
3608 std::string_view data_substr = data.substr(start, end - start);
3609 uris.push_back(data_substr);
3610 CLOG_INFO(LOG,
3611 2,
3612 "read_drop_data pos=%zu, text_uri=\"%.*s\"",
3613 start,
3614 int(data_substr.size()),
3615 data_substr.data());
3616 }
3617
3618 GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>(
3619 malloc(sizeof(GHOST_TStringArray)));
3620 flist->count = int(uris.size());
3621 flist->strings = static_cast<uint8_t **>(malloc(uris.size() * sizeof(uint8_t *)));
3622 for (size_t i = 0; i < uris.size(); i++) {
3623 flist->strings[i] = reinterpret_cast<uint8_t *>(
3624 GHOST_URL_decode_alloc(uris[i].data(), uris[i].size()));
3625 }
3626
3627 CLOG_INFO(LOG, 2, "read_drop_data file_count=%d", flist->count);
3628 ghost_dnd_type = GHOST_kDragnDropTypeFilenames;
3629 ghost_dnd_data = flist;
3630 }
3631 else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
3632 ghost_dnd_type = GHOST_kDragnDropTypeString;
3633 ghost_dnd_data = (void *)data_buf; /* Move ownership to the event. */
3634 data_buf = nullptr;
3635 }
3636
3637 if (ghost_dnd_type != GHOST_kDragnDropTypeUnknown) {
3638 GHOST_SystemWayland *const system = seat->system;
3639 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window);
3640 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)};
3641
3642 system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(event_ms,
3644 ghost_dnd_type,
3645 win,
3646 UNPACK2(event_xy),
3647 ghost_dnd_data));
3648
3650 }
3651 else {
3652 CLOG_INFO(LOG, 2, "read_drop_data, unhandled!");
3653 }
3654
3655 free(const_cast<char *>(data_buf));
3656 }
3657 };
3658
3659 /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the
3660 * leave callback (#data_device_handle_leave) will clear the value once this function starts. */
3661 std::thread read_thread(
3662 read_drop_data_fn, seat, data_offer, seat->wl.surface_window_focus_dnd, mime_receive);
3663 read_thread.detach();
3664}
3665
3666static void data_device_handle_selection(void *data,
3667 wl_data_device * /*wl_data_device*/,
3668 wl_data_offer *id)
3669{
3670 /* Always clear the current data-offer no matter what else happens. */
3671 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3672 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
3673 if (seat->data_offer_copy_paste) {
3674 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
3675 delete seat->data_offer_copy_paste;
3676 seat->data_offer_copy_paste = nullptr;
3677 }
3678 /* Clearing complete. */
3679
3680 /* Handle the new offer. */
3681 if (id == nullptr) {
3682 CLOG_INFO(LOG, 2, "selection: (skipped)");
3683 return;
3684 }
3685
3686 CLOG_INFO(LOG, 2, "selection");
3687 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3688 /* Transfer ownership of the `data_offer`. */
3689 seat->data_offer_copy_paste = data_offer;
3690}
3691
3692static const wl_data_device_listener data_device_listener = {
3693 /*data_offer*/ data_device_handle_data_offer,
3694 /*enter*/ data_device_handle_enter,
3695 /*leave*/ data_device_handle_leave,
3696 /*motion*/ data_device_handle_motion,
3697 /*drop*/ data_device_handle_drop,
3698 /*selection*/ data_device_handle_selection,
3699};
3700
3701#undef LOG
3702
3705/* -------------------------------------------------------------------- */
3709static CLG_LogRef LOG_WL_CURSOR_BUFFER = {"ghost.wl.handle.cursor_buffer"};
3710#define LOG (&LOG_WL_CURSOR_BUFFER)
3711
3712static void cursor_buffer_handle_release(void *data, wl_buffer *wl_buffer)
3713{
3714 CLOG_INFO(LOG, 2, "release");
3715
3716 GWL_Cursor *cursor = static_cast<GWL_Cursor *>(data);
3717 wl_buffer_destroy(wl_buffer);
3718
3719 if (wl_buffer == cursor->wl.buffer) {
3720 /* The mapped buffer was from a custom cursor. */
3721 cursor->wl.buffer = nullptr;
3722 }
3723}
3724
3725static const wl_buffer_listener cursor_buffer_listener = {
3726 /*release*/ cursor_buffer_handle_release,
3727};
3728
3729#undef LOG
3730
3733/* -------------------------------------------------------------------- */
3737static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"};
3738#define LOG (&LOG_WL_CURSOR_SURFACE)
3739
3741 wl_shm *shm,
3742 GWL_SeatStatePointer *seat_state_pointer,
3743 wl_surface *wl_surface_cursor)
3744{
3745 int scale = 0;
3746 for (const GWL_Output *output : seat_state_pointer->outputs) {
3747 int output_scale_floor = output->scale;
3748
3749 /* It's important to round down in the case of fractional scale,
3750 * otherwise the cursor can be scaled down to be unusably small.
3751 * This is especially a problem when:
3752 * - The cursor theme has one size (24px for the default cursor).
3753 * - The fractional scaling is set just above 1 (typically 125%).
3754 *
3755 * In this case the `output->scale` is rounded up to 2 and a larger cursor is requested.
3756 * It's assumed a large cursor is available but that's not always the case.
3757 * When only a smaller cursor is available it's still assumed to be large,
3758 * fractional scaling causes the cursor to be scaled down making it ~10px. see #105895. */
3759 if (output_scale_floor > 1 && output->has_scale_fractional) {
3760 output_scale_floor = std::max(1, output->scale_fractional / FRACTIONAL_DENOMINATOR);
3761 }
3762
3763 if (output_scale_floor > scale) {
3764 scale = output_scale_floor;
3765 }
3766 }
3767
3768 if (scale > 0 && seat_state_pointer->theme_scale != scale) {
3769 seat_state_pointer->theme_scale = scale;
3770 if (!cursor.is_custom) {
3771 if (wl_surface_cursor) {
3772 wl_surface_set_buffer_scale(wl_surface_cursor, scale);
3773 }
3774 }
3775 wl_cursor_theme_destroy(cursor.wl.theme);
3776 cursor.wl.theme = wl_cursor_theme_load(
3777 (cursor.theme_name.empty() ? nullptr : cursor.theme_name.c_str()),
3778 scale * cursor.theme_size,
3779 shm);
3780 if (cursor.wl.theme_cursor) {
3781 cursor.wl.theme_cursor = wl_cursor_theme_get_cursor(cursor.wl.theme,
3782 cursor.wl.theme_cursor_name);
3783 }
3784
3785 return true;
3786 }
3787 return false;
3788}
3789
3790static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_output *wl_output)
3791{
3792 if (!ghost_wl_output_own(wl_output)) {
3793 CLOG_INFO(LOG, 2, "handle_enter (skipped)");
3794 return;
3795 }
3796 CLOG_INFO(LOG, 2, "handle_enter");
3797
3798 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3800 seat, wl_surface);
3801 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3802 seat_state_pointer->outputs.insert(reg_output);
3803 update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
3804}
3805
3806static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
3807{
3808 if (!(wl_output && ghost_wl_output_own(wl_output))) {
3809 CLOG_INFO(LOG, 2, "handle_leave (skipped)");
3810 return;
3811 }
3812 CLOG_INFO(LOG, 2, "handle_leave");
3813
3814 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3816 seat, wl_surface);
3817 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3818 seat_state_pointer->outputs.erase(reg_output);
3819 update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
3820}
3821
3823 wl_surface * /*wl_surface*/,
3824 int32_t factor)
3825{
3826 /* Only available in interface version 6. */
3827 CLOG_INFO(LOG, 2, "handle_preferred_buffer_scale (factor=%d)", factor);
3828}
3829
3830#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3831 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3832static void cursor_surface_handle_preferred_buffer_transform(void * /*data*/,
3833 wl_surface * /*wl_surface*/,
3834 uint32_t transform)
3835{
3836 /* Only available in interface version 6. */
3837 CLOG_INFO(LOG, 2, "handle_preferred_buffer_transform (transform=%u)", transform);
3838}
3839#endif /* WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION && \
3840 * WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION */
3841
3845#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3846 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3847 /*preferred_buffer_scale*/ cursor_surface_handle_preferred_buffer_scale,
3848 /*preferred_buffer_transform*/ cursor_surface_handle_preferred_buffer_transform,
3849#endif
3850};
3851
3852#undef LOG
3853
3856/* -------------------------------------------------------------------- */
3860static CLG_LogRef LOG_WL_POINTER = {"ghost.wl.handle.pointer"};
3861#define LOG (&LOG_WL_POINTER)
3862
3863static void pointer_handle_enter(void *data,
3864 wl_pointer * /*wl_pointer*/,
3865 const uint32_t serial,
3867 const wl_fixed_t surface_x,
3868 const wl_fixed_t surface_y)
3869{
3870 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3871 const uint64_t event_ms = seat->system->getMilliSeconds();
3872
3873 /* Null when just destroyed. */
3875 CLOG_INFO(LOG, 2, "enter (skipped)");
3876 return;
3877 }
3878 CLOG_INFO(LOG, 2, "enter");
3879
3881
3882 seat->cursor_source_serial = serial;
3883 seat->pointer.serial = serial;
3884 seat->pointer.xy[0] = surface_x;
3885 seat->pointer.xy[1] = surface_y;
3886
3887 /* Resetting scroll events is likely unnecessary,
3888 * do this to avoid any possible problems as it's harmless. */
3890
3891 seat->pointer.wl.surface_window = wl_surface;
3892
3893 seat->system->seat_active_set(seat);
3894 win->cursor_shape_refresh();
3895
3896 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3898 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3899}
3900
3901static void pointer_handle_leave(void *data,
3902 wl_pointer * /*wl_pointer*/,
3903 const uint32_t /*serial*/,
3905{
3906 /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
3907 static_cast<GWL_Seat *>(data)->pointer.wl.surface_window = nullptr;
3909 CLOG_INFO(LOG, 2, "leave (skipped)");
3910 return;
3911 }
3912 CLOG_INFO(LOG, 2, "leave");
3913}
3914
3915static void pointer_handle_motion(void *data,
3916 wl_pointer * /*wl_pointer*/,
3917 const uint32_t time,
3918 const wl_fixed_t surface_x,
3919 const wl_fixed_t surface_y)
3920{
3921 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3922 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3923
3924 seat->pointer.xy[0] = surface_x;
3925 seat->pointer.xy[1] = surface_y;
3926
3927 CLOG_INFO(LOG, 2, "motion");
3928
3931}
3932
3933static void pointer_handle_button(void *data,
3934 wl_pointer * /*wl_pointer*/,
3935 const uint32_t serial,
3936 const uint32_t time,
3937 const uint32_t button,
3938 const uint32_t state)
3939{
3940 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3941
3942 CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
3943
3944 /* Always set the serial, even if the button event is not sent. */
3945 seat->data_source_serial = serial;
3946
3947 int button_release;
3948 switch (state) {
3949 case WL_POINTER_BUTTON_STATE_RELEASED:
3950 button_release = 1;
3951 break;
3952 case WL_POINTER_BUTTON_STATE_PRESSED:
3953 button_release = 0;
3954 break;
3955 default: {
3956 return;
3957 }
3958 }
3959
3960 const uint32_t button_index = button - BTN_RANGE_MIN;
3961 if (button_index >= (BTN_RANGE_MAX - BTN_RANGE_MIN)) {
3962 return;
3963 }
3964
3966 int(GWL_Pointer_EventTypes::Button0_Down) + ((button_index * 2) + button_release));
3967
3968 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3970}
3971
3972static void pointer_handle_axis(void *data,
3973 wl_pointer * /*wl_pointer*/,
3974 const uint32_t time,
3975 const uint32_t axis,
3976 const wl_fixed_t value)
3977{
3978 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3979 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
3980 seat->pointer_scroll.has_event_ms = true;
3981
3982 /* NOTE: this is used for touch based scrolling - or other input that doesn't scroll with
3983 * discrete "steps". This allows supporting smooth-scrolling without "touch" gesture support. */
3984 CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value);
3985 const int index = pointer_axis_as_index(axis);
3986 if (UNLIKELY(index == -1)) {
3987 return;
3988 }
3989 seat->pointer_scroll.smooth_xy[index] = value;
3990
3992}
3993
3994static void pointer_handle_frame(void *data, wl_pointer * /*wl_pointer*/)
3995{
3996 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3997
3998 CLOG_INFO(LOG, 2, "frame");
3999
4000 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4001 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4002 for (int ty_index = 0; ty_index < seat->pointer_events.frame_pending.frame_types_num;
4003 ty_index++)
4004 {
4006 const uint64_t event_ms = seat->pointer_events.frame_pending.frame_event_ms[ty_index];
4007 switch (ty) {
4008 /* Use motion for pressure and tilt as there are no explicit event types for these. */
4010 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4012 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
4013 break;
4014 }
4017
4018 /* The scroll data is "interpreted" before generating the events,
4019 * this is needed because data is accumulated
4020 * with support for handling smooth scroll as discrete events (for example). */
4021
4022 /* Most scroll events have no time, use `ps.event_ms` instead.
4023 * This is assigned for the scroll events that do set a time.
4024 * In practice the values tends to be set but fall back to the current time. */
4025 GHOST_ASSERT(event_ms == 0, "Scroll events are not expected to have a time!");
4026 /* Handle value120 to discrete steps first. */
4027 if (ps.discrete120_xy[0] || ps.discrete120_xy[1]) {
4028 for (int i = 0; i < 2; i++) {
4029 ps.discrete120_xy_accum[i] += ps.discrete120_xy[i];
4030 ps.discrete120_xy[i] = 0;
4031 /* The values will have been normalized so 120 represents a single click-step. */
4032 ps.discrete_xy[i] = ps.discrete120_xy_accum[i] / 120;
4033 ps.discrete120_xy_accum[i] -= ps.discrete_xy[i] * 120;
4034 }
4035 }
4036
4037 /* Multiple wheel events may have been generated and it's not known which.
4038 * The logic here handles prioritizing how they should be handled. */
4039 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4040 /* We never want mouse wheel events to be treated as smooth scrolling as this
4041 * causes mouse wheel scroll to orbit the view, see #120587.
4042 * Although it could be supported if the event system would forward
4043 * the source of the scroll action (a wheel or touch device). */
4044 ps.smooth_xy[0] = 0;
4045 ps.smooth_xy[1] = 0;
4046 }
4047 else if (ps.axis_source == WL_POINTER_AXIS_SOURCE_FINGER) {
4051 /* If discrete steps have been sent, use them as-is. */
4052 if ((ps.discrete_xy[0] == 0) && (ps.discrete_xy[1] == 0)) {
4053 /* Convert smooth to discrete. */
4054 for (int i = 0; i < 2; i++) {
4055 smooth_as_discrete.smooth_xy_accum[i] += ps.smooth_xy[i];
4056 if (std::abs(smooth_as_discrete.smooth_xy_accum[i]) >= smooth_as_discrete_steps)
4057 {
4058 ps.discrete_xy[i] = smooth_as_discrete.smooth_xy_accum[i] /
4060 smooth_as_discrete.smooth_xy_accum[i] -= ps.discrete_xy[i] *
4062 }
4063 }
4064 }
4065 ps.smooth_xy[0] = 0;
4066 ps.smooth_xy[1] = 0;
4067 }
4068 }
4069
4070 /* Both discrete and smooth events may be set at once, never generate events for both
4071 * as this will be handling the same event in to different ways.
4072 * Prioritize discrete axis events for the mouse wheel, otherwise smooth scroll. */
4073 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4074 if (ps.discrete_xy[0]) {
4075 ps.smooth_xy[0] = 0;
4076 }
4077 if (ps.discrete_xy[1]) {
4078 ps.smooth_xy[1] = 0;
4079 }
4080 }
4081 else {
4082 if (ps.smooth_xy[0]) {
4083 ps.discrete_xy[0] = 0;
4084 }
4085 if (ps.smooth_xy[1]) {
4086 ps.discrete_xy[1] = 0;
4087 }
4088 }
4089
4090 /* Done evaluating scroll input, generate the events. */
4091
4092 /* Discrete X axis currently unsupported. */
4093 if (ps.discrete_xy[0] || ps.discrete_xy[1]) {
4094 if (ps.discrete_xy[1]) {
4096 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4097 win,
4098 -ps.discrete_xy[1]));
4099 }
4100 ps.discrete_xy[0] = 0;
4101 ps.discrete_xy[1] = 0;
4102 }
4103
4104 if (ps.smooth_xy[0] || ps.smooth_xy[1]) {
4105 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4107 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4108 win,
4110 UNPACK2(event_xy),
4111 /* NOTE: scaling the delta doesn't seem necessary.
4112 * NOTE: inverting delta gives correct results, see: QTBUG-85767.
4113 * NOTE: the preference to invert scrolling (in GNOME at least)
4114 * has already been applied so there is no need to read this preference. */
4115 -wl_fixed_to_int(ps.smooth_xy[0]),
4116 -wl_fixed_to_int(ps.smooth_xy[1]),
4117 /* NOTE: GHOST does not support per-axis inversion.
4118 * Assume inversion is used or not. */
4119 ps.inverted_xy[0] || ps.inverted_xy[1]));
4120
4121 ps.smooth_xy[0] = 0;
4122 ps.smooth_xy[1] = 0;
4123 }
4124
4125 ps.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
4126 ps.inverted_xy[0] = false;
4127 ps.inverted_xy[1] = false;
4128
4129 ps.event_ms = 0;
4130 ps.has_event_ms = false;
4131
4132 break;
4133 }
4134#ifdef NDEBUG
4135 default:
4136#else /* Warn when any events aren't handled (in debug builds). */
4151#endif
4152 {
4153 const int button_enum_offset = int(ty) - int(GWL_Pointer_EventTypes::Button0_Down);
4154 const int button_index = button_enum_offset / 2;
4155 const bool button_down = (button_index * 2) == button_enum_offset;
4156 const GHOST_TButton ebutton = gwl_pointer_events_ebutton[button_index];
4157 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
4159 seat->pointer.buttons.set(ebutton, button_down);
4161 new GHOST_EventButton(event_ms, etype, win, ebutton, GHOST_TABLET_DATA_NONE));
4162 break;
4163 }
4164 }
4165 }
4166 }
4167
4169}
4170static void pointer_handle_axis_source(void *data,
4171 wl_pointer * /*wl_pointer*/,
4172 uint32_t axis_source)
4173{
4174 CLOG_INFO(LOG, 2, "axis_source (axis_source=%u)", axis_source);
4175 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4176 seat->pointer_scroll.axis_source = (enum wl_pointer_axis_source)axis_source;
4177}
4178static void pointer_handle_axis_stop(void *data,
4179 wl_pointer * /*wl_pointer*/,
4180 uint32_t time,
4181 uint32_t axis)
4182{
4183 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4184 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
4185 seat->pointer_scroll.has_event_ms = true;
4186
4188 /* Reset the scroll steps when the touch event ends.
4189 * Done so the user doesn't accidentally bump smooth scroll input a small number of steps
4190 * causing an unexpected discrete step caused by a very small amount of smooth-scrolling. */
4193 smooth_as_discrete.smooth_xy_accum[0] = 0;
4194 smooth_as_discrete.smooth_xy_accum[1] = 0;
4195 }
4196
4197 CLOG_INFO(LOG, 2, "axis_stop (axis=%u)", axis);
4198}
4199static void pointer_handle_axis_discrete(void *data,
4200 wl_pointer * /*wl_pointer*/,
4201 uint32_t axis,
4202 int32_t discrete)
4203{
4204 /* NOTE: a discrete axis are typically mouse wheel events.
4205 * The non-discrete version of this function is used for touch-pad. */
4206 CLOG_INFO(LOG, 2, "axis_discrete (axis=%u, discrete=%d)", axis, discrete);
4207 const int index = pointer_axis_as_index(axis);
4208 if (UNLIKELY(index == -1)) {
4209 return;
4210 }
4211 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4212 seat->pointer_scroll.discrete_xy[index] = discrete;
4213
4215}
4216static void pointer_handle_axis_value120(void *data,
4217 wl_pointer * /*wl_pointer*/,
4218 uint32_t axis,
4219 int32_t value120)
4220{
4221 /* Only available in interface version 8. */
4222 CLOG_INFO(LOG, 2, "axis_value120 (axis=%u, value120=%d)", axis, value120);
4223 const int index = pointer_axis_as_index(axis);
4224 if (UNLIKELY(index == -1)) {
4225 return;
4226 }
4227 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4228 seat->pointer_scroll.discrete120_xy[index] = value120;
4229
4231}
4232#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM /* Requires WAYLAND 1.22 or newer. */
4233static void pointer_handle_axis_relative_direction(void *data,
4234 wl_pointer * /*wl_pointer*/,
4235 uint32_t axis,
4236 uint32_t direction)
4237{
4238 /* Only available in interface version 9. */
4239 CLOG_INFO(LOG, 2, "axis_relative_direction (axis=%u, direction=%u)", axis, direction);
4240 const int index = pointer_axis_as_index(axis);
4241 if (UNLIKELY(index == -1)) {
4242 return;
4243 }
4244 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4245 seat->pointer_scroll.inverted_xy[index] = (direction ==
4246 WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED);
4247}
4248#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */
4249
4250static const wl_pointer_listener pointer_listener = {
4251 /*enter*/ pointer_handle_enter,
4252 /*leave*/ pointer_handle_leave,
4253 /*motion*/ pointer_handle_motion,
4254 /*button*/ pointer_handle_button,
4255 /*axis*/ pointer_handle_axis,
4256 /*frame*/ pointer_handle_frame,
4257 /*axis_source*/ pointer_handle_axis_source,
4258 /*axis_stop*/ pointer_handle_axis_stop,
4259 /*axis_discrete*/ pointer_handle_axis_discrete,
4260 /*axis_value120*/ pointer_handle_axis_value120,
4261#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM
4262 /*axis_relative_direction*/ pointer_handle_axis_relative_direction,
4263#endif
4264};
4265
4266#undef LOG
4267
4270/* -------------------------------------------------------------------- */
4274#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
4275static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"};
4276# define LOG (&LOG_WL_POINTER_GESTURE_HOLD)
4277
4278static void gesture_hold_handle_begin(
4279 void * /*data*/,
4280 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4281 uint32_t /*serial*/,
4282 uint32_t /*time*/,
4283 wl_surface * /*surface*/,
4284 uint32_t fingers)
4285{
4286 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4287}
4288
4289static void gesture_hold_handle_end(void * /*data*/,
4290 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4291 uint32_t /*serial*/,
4292 uint32_t /*time*/,
4293 int32_t cancelled)
4294{
4295 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4296}
4297
4298static const zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = {
4299 /*begin*/ gesture_hold_handle_begin,
4300 /*end*/ gesture_hold_handle_end,
4301};
4302
4303# undef LOG
4304#endif /* ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE */
4305
4308/* -------------------------------------------------------------------- */
4312#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
4313static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.pointer_gesture.pinch"};
4314# define LOG (&LOG_WL_POINTER_GESTURE_PINCH)
4315
4316static void gesture_pinch_handle_begin(void *data,
4317 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4318 uint32_t /*serial*/,
4319 uint32_t time,
4320 wl_surface * /*surface*/,
4321 uint32_t fingers)
4322{
4323 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4324 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4325
4326 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4327
4328 /* Reset defaults. */
4330
4331 const GHOST_WindowWayland *win = nullptr;
4332 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4333 win = ghost_wl_surface_user_data(wl_surface_focus);
4334 }
4335
4336 /* NOTE(@ideasman42): Scale factors match Blender's operators & default preferences.
4337 * For these values to work correctly, operator logic will need to be changed not to scale input
4338 * by the region size (as with 3D view zoom) or preference for 3D view orbit sensitivity.
4339 *
4340 * By working "correctly" I mean that a rotation action where the users fingers rotate to
4341 * opposite locations should always rotate the viewport 180d, since users will expect the
4342 * physical location of their fingers to match the viewport.
4343 * Similarly with zoom, the scale value from the pinch action can be mapped to a zoom level
4344 * although unlike rotation, an inexact mapping is less noticeable.
4345 * Users may even prefer the zoom level to be scaled - which could be a preference. */
4346 seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1);
4347 /* The value 300 matches a value used in clip & image zoom operators.
4348 * It seems OK for the 3D view too. */
4350 /* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation,
4351 * although preferences can scale the sensitivity (which would be skipped ideally). */
4353
4354 if (win) {
4355 /* NOTE(@ideasman42): Blender's use of trackpad coordinates is inconsistent and needs work.
4356 * This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
4357 * Some operators scale by the UI scale, some don't.
4358 * Even though the window scale is correct, it doesn't account for the UI scale preference
4359 * (which GHOST doesn't know about).
4360 *
4361 * If support for this were all that was needed it could be handled in GHOST,
4362 * however as the operators are not even using coordinates compatible with each other,
4363 * it would be better to resolve this by passing rotation & zoom levels directly,
4364 * instead of attempting to handle them as cursor coordinates. */
4365 const GWL_WindowScaleParams &scale_params = win->scale_params_get();
4367 scale_params, seat->pointer_gesture_pinch.scale.factor);
4369 scale_params, seat->pointer_gesture_pinch.rotation.factor);
4370 }
4371}
4372
4373static void gesture_pinch_handle_update(void *data,
4374 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4375 uint32_t time,
4376 wl_fixed_t dx,
4377 wl_fixed_t dy,
4378 wl_fixed_t scale,
4379 wl_fixed_t rotation)
4380{
4381 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4382 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4383
4384 CLOG_INFO(LOG,
4385 2,
4386 "update (dx=%.3f, dy=%.3f, scale=%.3f, rotation=%.3f)",
4387 wl_fixed_to_double(dx),
4388 wl_fixed_to_double(dy),
4389 wl_fixed_to_double(scale),
4390 wl_fixed_to_double(rotation));
4391
4392 GHOST_WindowWayland *win = nullptr;
4393
4394 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4395 win = ghost_wl_surface_user_data(wl_surface_focus);
4396 }
4397
4398 /* Scale defaults to `wl_fixed_from_int(1)` which may change while pinching.
4399 * This needs to be converted to a delta. */
4400 const wl_fixed_t scale_delta = scale - seat->pointer_gesture_pinch.scale.value;
4401 const int scale_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4402 &seat->pointer_gesture_pinch.scale, scale_delta);
4403
4404 /* Rotation in degrees, unlike scale this is a delta. */
4405 const int rotation_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4406 &seat->pointer_gesture_pinch.rotation, rotation);
4407
4408 if (win) {
4409 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4410 if (scale_as_delta_px) {
4412 win,
4414 event_xy[0],
4415 event_xy[1],
4416 scale_as_delta_px,
4417 0,
4418 false));
4419 }
4420
4421 if (rotation_as_delta_px) {
4423 win,
4425 event_xy[0],
4426 event_xy[1],
4427 rotation_as_delta_px,
4428 0,
4429 false));
4430 }
4431 }
4432}
4433
4434static void gesture_pinch_handle_end(void *data,
4435 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4436 uint32_t /*serial*/,
4437 uint32_t time,
4438 int32_t cancelled)
4439{
4440 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4441 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4442
4443 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4444}
4445
4446static const zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = {
4447 /*begin*/ gesture_pinch_handle_begin,
4448 /*update*/ gesture_pinch_handle_update,
4449 /*end*/ gesture_pinch_handle_end,
4450};
4451
4452# undef LOG
4453#endif /* ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE */
4454
4457/* -------------------------------------------------------------------- */
4466#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
4467static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"};
4468# define LOG (&LOG_WL_POINTER_GESTURE_SWIPE)
4469
4470static void gesture_swipe_handle_begin(
4471 void * /*data*/,
4472 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4473 uint32_t /*serial*/,
4474 uint32_t /*time*/,
4475 wl_surface * /*surface*/,
4476 uint32_t fingers)
4477{
4478 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4479}
4480
4481static void gesture_swipe_handle_update(
4482 void * /*data*/,
4483 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4484 uint32_t /*time*/,
4485 wl_fixed_t dx,
4486 wl_fixed_t dy)
4487{
4488 CLOG_INFO(LOG, 2, "update (dx=%.3f, dy=%.3f)", wl_fixed_to_double(dx), wl_fixed_to_double(dy));
4489}
4490
4491static void gesture_swipe_handle_end(
4492 void * /*data*/,
4493 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4494 uint32_t /*serial*/,
4495 uint32_t /*time*/,
4496 int32_t cancelled)
4497{
4498 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4499}
4500
4501static const zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = {
4502 /*begin*/ gesture_swipe_handle_begin,
4503 /*update*/ gesture_swipe_handle_update,
4504 /*end*/ gesture_swipe_handle_end,
4505};
4506
4507# undef LOG
4508#endif /* ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE */
4509
4512/* -------------------------------------------------------------------- */
4520static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"};
4521#define LOG (&LOG_WL_TOUCH)
4522
4523static void touch_seat_handle_down(void * /*data*/,
4524 wl_touch * /*wl_touch*/,
4525 uint32_t /*serial*/,
4526 uint32_t /*time*/,
4527 wl_surface * /*wl_surface*/,
4528 int32_t /*id*/,
4529 wl_fixed_t /*x*/,
4530 wl_fixed_t /*y*/)
4531{
4532 CLOG_INFO(LOG, 2, "down");
4533}
4534
4535static void touch_seat_handle_up(void * /*data*/,
4536 wl_touch * /*wl_touch*/,
4537 uint32_t /*serial*/,
4538 uint32_t /*time*/,
4539 int32_t /*id*/)
4540{
4541 CLOG_INFO(LOG, 2, "up");
4542}
4543
4544static void touch_seat_handle_motion(void * /*data*/,
4545 wl_touch * /*wl_touch*/,
4546 uint32_t /*time*/,
4547 int32_t /*id*/,
4548 wl_fixed_t /*x*/,
4549 wl_fixed_t /*y*/)
4550{
4551 CLOG_INFO(LOG, 2, "motion");
4552}
4553
4554static void touch_seat_handle_frame(void * /*data*/, wl_touch * /*wl_touch*/)
4555{
4556 CLOG_INFO(LOG, 2, "frame");
4557}
4558
4559static void touch_seat_handle_cancel(void * /*data*/, wl_touch * /*wl_touch*/)
4560{
4561
4562 CLOG_INFO(LOG, 2, "cancel");
4563}
4564
4565static void touch_seat_handle_shape(void * /*data*/,
4566 wl_touch * /*touch*/,
4567 int32_t /*id*/,
4568 wl_fixed_t /*major*/,
4569 wl_fixed_t /*minor*/)
4570{
4571 CLOG_INFO(LOG, 2, "shape");
4572}
4573
4574static void touch_seat_handle_orientation(void * /*data*/,
4575 wl_touch * /*touch*/,
4576 int32_t /*id*/,
4577 wl_fixed_t /*orientation*/)
4578{
4579 CLOG_INFO(LOG, 2, "orientation");
4580}
4581
4582static const wl_touch_listener touch_seat_listener = {
4583 /*down*/ touch_seat_handle_down,
4584 /*up*/ touch_seat_handle_up,
4585 /*motion*/ touch_seat_handle_motion,
4586 /*frame*/ touch_seat_handle_frame,
4587 /*cancel*/ touch_seat_handle_cancel,
4588 /*shape*/ touch_seat_handle_shape,
4589 /*orientation*/ touch_seat_handle_orientation,
4590};
4591
4592#undef LOG
4593
4596/* -------------------------------------------------------------------- */
4600static CLG_LogRef LOG_WL_TABLET_TOOL = {"ghost.wl.handle.tablet_tool"};
4601#define LOG (&LOG_WL_TABLET_TOOL)
4602
4603static void tablet_tool_handle_type(void *data,
4604 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4605 const uint32_t tool_type)
4606{
4607 CLOG_INFO(LOG, 2, "type (type=%u)", tool_type);
4608
4609 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4610
4611 tablet_tool->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type);
4612}
4613
4614static void tablet_tool_handle_hardware_serial(void * /*data*/,
4615 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4616 const uint32_t /*hardware_serial_hi*/,
4617 const uint32_t /*hardware_serial_lo*/)
4618{
4619 CLOG_INFO(LOG, 2, "hardware_serial");
4620}
4621
4622static void tablet_tool_handle_hardware_id_wacom(void * /*data*/,
4623 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4624 const uint32_t /*hardware_id_hi*/,
4625 const uint32_t /*hardware_id_lo*/)
4626{
4627 CLOG_INFO(LOG, 2, "hardware_id_wacom");
4628}
4629
4630static void tablet_tool_handle_capability(void * /*data*/,
4631 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4632 const uint32_t capability)
4633{
4634 CLOG_INFO(LOG,
4635 2,
4636 "capability (tilt=%d, distance=%d, rotation=%d, slider=%d, wheel=%d)",
4637 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_TILT) != 0,
4638 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE) != 0,
4639 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION) != 0,
4640 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER) != 0,
4641 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL) != 0);
4642}
4643
4644static void tablet_tool_handle_done(void * /*data*/, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4645{
4646 CLOG_INFO(LOG, 2, "done");
4647}
4648static void tablet_tool_handle_removed(void *data, zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
4649{
4650 CLOG_INFO(LOG, 2, "removed");
4651
4652 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4653 GWL_Seat *seat = tablet_tool->seat;
4654
4655 if (tablet_tool->wl.surface_cursor) {
4656 wl_surface_destroy(tablet_tool->wl.surface_cursor);
4657 }
4658 seat->wp.tablet_tools.erase(zwp_tablet_tool_v2);
4659
4660 delete tablet_tool;
4661}
4662static void tablet_tool_handle_proximity_in(void *data,
4663 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4664 const uint32_t serial,
4665 zwp_tablet_v2 * /*tablet*/,
4667{
4669 CLOG_INFO(LOG, 2, "proximity_in (skipped)");
4670 return;
4671 }
4672 CLOG_INFO(LOG, 2, "proximity_in");
4673
4674 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4675 tablet_tool->proximity = true;
4676
4677 GWL_Seat *seat = tablet_tool->seat;
4678 seat->cursor_source_serial = serial;
4680 seat->tablet.serial = serial;
4681
4682 seat->data_source_serial = serial;
4683
4684 seat->system->seat_active_set(seat);
4685
4687 win->cursor_shape_refresh();
4688
4689 /* Update #GHOST_TabletData. */
4690 GHOST_TabletData &td = tablet_tool->data;
4691 /* Reset, to avoid using stale tilt/pressure. */
4692 td.Xtilt = 0.0f;
4693 td.Ytilt = 0.0f;
4694 /* In case pressure isn't supported. */
4695 td.Pressure = 1.0f;
4696}
4698 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4699{
4700 CLOG_INFO(LOG, 2, "proximity_out");
4701 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4702 /* Defer clearing the wl_surface until the frame is handled.
4703 * Without this, the frame can not access the wl_surface. */
4704 tablet_tool->proximity = false;
4705}
4706
4707static void tablet_tool_handle_down(void *data,
4708 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4709 const uint32_t serial)
4710{
4711 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4712 GWL_Seat *seat = tablet_tool->seat;
4713
4714 CLOG_INFO(LOG, 2, "down");
4715
4716 seat->data_source_serial = serial;
4717
4719}
4720
4721static void tablet_tool_handle_up(void *data, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4722{
4723 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4724
4725 CLOG_INFO(LOG, 2, "up");
4726
4728}
4729
4730static void tablet_tool_handle_motion(void *data,
4731 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4732 const wl_fixed_t x,
4733 const wl_fixed_t y)
4734{
4735 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4736
4737 CLOG_INFO(LOG, 2, "motion");
4738
4739 tablet_tool->xy[0] = x;
4740 tablet_tool->xy[1] = y;
4741 tablet_tool->has_xy = true;
4742
4744}
4745
4746static void tablet_tool_handle_pressure(void *data,
4747 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4748 const uint32_t pressure)
4749{
4750 const float pressure_unit = float(pressure) / 65535;
4751 CLOG_INFO(LOG, 2, "pressure (%.4f)", pressure_unit);
4752
4753 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4754 GHOST_TabletData &td = tablet_tool->data;
4755 td.Pressure = pressure_unit;
4756
4758}
4759
4760static void tablet_tool_handle_distance(void * /*data*/,
4761 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4762 const uint32_t distance)
4763{
4764 CLOG_INFO(LOG, 2, "distance (distance=%u)", distance);
4765}
4766
4767static void tablet_tool_handle_tilt(void *data,
4768 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4769 const wl_fixed_t tilt_x,
4770 const wl_fixed_t tilt_y)
4771{
4772 /* Map degrees to `-1.0..1.0`. */
4773 const float tilt_unit[2] = {
4774 float(wl_fixed_to_double(tilt_x) / 90.0),
4775 float(wl_fixed_to_double(tilt_y) / 90.0),
4776 };
4777 CLOG_INFO(LOG, 2, "tilt (x=%.4f, y=%.4f)", UNPACK2(tilt_unit));
4778 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4779 GHOST_TabletData &td = tablet_tool->data;
4780 td.Xtilt = std::clamp(tilt_unit[0], -1.0f, 1.0f);
4781 td.Ytilt = std::clamp(tilt_unit[1], -1.0f, 1.0f);
4782
4784}
4785
4786static void tablet_tool_handle_rotation(void * /*data*/,
4787 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4788 const wl_fixed_t degrees)
4789{
4790 CLOG_INFO(LOG, 2, "rotation (degrees=%.4f)", wl_fixed_to_double(degrees));
4791}
4792
4793static void tablet_tool_handle_slider(void * /*data*/,
4794 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4795 const int32_t position)
4796{
4797 CLOG_INFO(LOG, 2, "slider (position=%d)", position);
4798}
4799static void tablet_tool_handle_wheel(void *data,
4800 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4801 const wl_fixed_t /*degrees*/,
4802 const int32_t clicks)
4803{
4804 if (clicks == 0) {
4805 return;
4806 }
4807
4808 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4809
4810 CLOG_INFO(LOG, 2, "wheel (clicks=%d)", clicks);
4811
4812 tablet_tool->frame_pending.wheel.clicks = clicks;
4813
4815}
4816
4817static void tablet_tool_handle_button(void *data,
4818 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4819 const uint32_t serial,
4820 const uint32_t button,
4821 const uint32_t state)
4822{
4823 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4824 GWL_Seat *seat = tablet_tool->seat;
4825
4826 CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
4827
4828 bool is_press = false;
4829 switch (state) {
4830 case WL_POINTER_BUTTON_STATE_RELEASED:
4831 is_press = false;
4832 break;
4833 case WL_POINTER_BUTTON_STATE_PRESSED:
4834 is_press = true;
4835 break;
4836 }
4837
4838 seat->data_source_serial = serial;
4839
4841 switch (button) {
4842 case BTN_STYLUS: {
4845 break;
4846 }
4847 case BTN_STYLUS2: {
4850 break;
4851 }
4852 case BTN_STYLUS3: {
4855 break;
4856 }
4857 }
4858
4860 gwl_tablet_tool_frame_event_add(tablet_tool, ty);
4861 }
4862}
4863static void tablet_tool_handle_frame(void *data,
4864 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4865 const uint32_t time)
4866{
4867 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4868 GWL_Seat *seat = tablet_tool->seat;
4869 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4870
4871 CLOG_INFO(LOG, 2, "frame");
4872
4873 /* No need to check the surfaces origin, it's already known to be owned by GHOST. */
4874 if (wl_surface *wl_surface_focus = seat->tablet.wl.surface_window) {
4875 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4876 bool has_motion = false;
4877
4878 for (int ty_index = 0; ty_index < tablet_tool->frame_pending.frame_types_num; ty_index++) {
4879 const GWL_TabletTool_EventTypes ty = tablet_tool->frame_pending.frame_types[ty_index];
4880 switch (ty) {
4881 /* Use motion for pressure and tilt as there are no explicit event types for these. */
4885 /* Only one motion event per frame. */
4886 if (has_motion) {
4887 break;
4888 }
4889 /* Can happen when there is pressure/tilt without motion. */
4890 if (tablet_tool->has_xy == false) {
4891 break;
4892 }
4893
4894 seat->tablet.xy[0] = tablet_tool->xy[0];
4895 seat->tablet.xy[1] = tablet_tool->xy[1];
4896
4897 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, tablet_tool->xy)};
4899 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), tablet_tool->data));
4900 has_motion = true;
4901 break;
4902 }
4903#ifdef NDEBUG
4904 default:
4905#else /* Warn when any events aren't handled (in debug builds). */
4914#endif
4915 {
4916 const int button_enum_offset = int(ty) - int(GWL_TabletTool_EventTypes::Stylus0_Down);
4917 const int button_index = button_enum_offset / 2;
4918 const bool button_down = (button_index * 2) == button_enum_offset;
4919 const GHOST_TButton ebutton = gwl_tablet_tool_ebutton[button_index];
4920 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
4922 seat->tablet.buttons.set(ebutton, button_down);
4924 new GHOST_EventButton(event_ms, etype, win, ebutton, tablet_tool->data));
4925 break;
4926 }
4929 new GHOST_EventWheel(event_ms, win, -tablet_tool->frame_pending.wheel.clicks));
4930 break;
4931 }
4932 }
4933 }
4934
4935 if (tablet_tool->proximity == false) {
4936 win->cursor_shape_refresh();
4937 }
4938 }
4939
4940 if (tablet_tool->proximity == false) {
4941 seat->tablet.wl.surface_window = nullptr;
4942 }
4943
4945}
4946
4947static const zwp_tablet_tool_v2_listener tablet_tool_listner = {
4948 /*type*/ tablet_tool_handle_type,
4949 /*hardware_serial*/ tablet_tool_handle_hardware_serial,
4950 /*hardware_id_wacom*/ tablet_tool_handle_hardware_id_wacom,
4951 /*capability*/ tablet_tool_handle_capability,
4952 /*done*/ tablet_tool_handle_done,
4953 /*removed*/ tablet_tool_handle_removed,
4954 /*proximity_in*/ tablet_tool_handle_proximity_in,
4955 /*proximity_out*/ tablet_tool_handle_proximity_out,
4956 /*down*/ tablet_tool_handle_down,
4957 /*up*/ tablet_tool_handle_up,
4958 /*motion*/ tablet_tool_handle_motion,
4959 /*pressure*/ tablet_tool_handle_pressure,
4960 /*distance*/ tablet_tool_handle_distance,
4961 /*tilt*/ tablet_tool_handle_tilt,
4962 /*rotation*/ tablet_tool_handle_rotation,
4963 /*slider*/ tablet_tool_handle_slider,
4964 /*wheel*/ tablet_tool_handle_wheel,
4965 /*button*/ tablet_tool_handle_button,
4966 /*frame*/ tablet_tool_handle_frame,
4967};
4968
4969#undef LOG
4970
4973/* -------------------------------------------------------------------- */
4977static CLG_LogRef LOG_WL_TABLET_SEAT = {"ghost.wl.handle.tablet_seat"};
4978#define LOG (&LOG_WL_TABLET_SEAT)
4979
4980static void tablet_seat_handle_tablet_added(void * /*data*/,
4981 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
4982 zwp_tablet_v2 *id)
4983{
4984 CLOG_INFO(LOG, 2, "tablet_added (id=%p)", id);
4985}
4986
4987static void tablet_seat_handle_tool_added(void *data,
4988 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
4989 zwp_tablet_tool_v2 *id)
4990{
4991 CLOG_INFO(LOG, 2, "tool_added (id=%p)", id);
4992
4993 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4994 GWL_TabletTool *tablet_tool = new GWL_TabletTool();
4995 tablet_tool->seat = seat;
4996
4997 /* Every tool has its own cursor wl_surface. */
4998 tablet_tool->wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
5000
5001 wl_surface_add_listener(
5002 tablet_tool->wl.surface_cursor, &cursor_surface_listener, static_cast<void *>(seat));
5003
5004 zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tablet_tool);
5005
5006 seat->wp.tablet_tools.insert(id);
5007}
5008
5009static void tablet_seat_handle_pad_added(void * /*data*/,
5010 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5011 zwp_tablet_pad_v2 *id)
5012{
5013 CLOG_INFO(LOG, 2, "pad_added (id=%p)", id);
5014}
5015
5016static const zwp_tablet_seat_v2_listener tablet_seat_listener = {
5017 /*tablet_added*/ tablet_seat_handle_tablet_added,
5018 /*tool_added*/ tablet_seat_handle_tool_added,
5019 /*pad_added*/ tablet_seat_handle_pad_added,
5020};
5021
5022#undef LOG
5023
5026/* -------------------------------------------------------------------- */
5030static CLG_LogRef LOG_WL_KEYBOARD = {"ghost.wl.handle.keyboard"};
5031#define LOG (&LOG_WL_KEYBOARD)
5032
5033static void keyboard_handle_keymap(void *data,
5034 wl_keyboard * /*wl_keyboard*/,
5035 const uint32_t format,
5036 const int32_t fd,
5037 const uint32_t size)
5038{
5039 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5040
5041 if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) {
5042 CLOG_INFO(LOG, 2, "keymap (no data or wrong version)");
5043 close(fd);
5044 return;
5045 }
5046
5047 char *map_str = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
5048 if (map_str == MAP_FAILED) {
5049 close(fd);
5050 CLOG_INFO(LOG, 2, "keymap mmap failed: %s", std::strerror(errno));
5051 return;
5052 }
5053
5054 xkb_keymap *keymap = xkb_keymap_new_from_string(
5055 seat->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
5056 munmap(map_str, size);
5057 close(fd);
5058
5059 if (!keymap) {
5060 CLOG_INFO(LOG, 2, "keymap (not found)");
5061 return;
5062 }
5063
5064 CLOG_INFO(LOG, 2, "keymap");
5065
5066 /* Reset in case there was a previous non-zero active layout for the last key-map.
5067 * Note that this is set later by `wl_keyboard_listener::modifiers`, it's possible that handling
5068 * the first modifier will run #xkb_state_update_mask again (if the active layout is non-zero)
5069 * however as this is only done when the layout changed, it's harmless.
5070 * With a single layout - in practice the active layout will be zero. */
5071 seat->xkb.layout_active = 0;
5072
5073 if (seat->xkb.compose_state) {
5074 xkb_compose_state_reset(seat->xkb.compose_state);
5075 }
5076 else if (seat->xkb.compose_table) {
5077 seat->xkb.compose_state = xkb_compose_state_new(seat->xkb.compose_table,
5078 XKB_COMPOSE_STATE_NO_FLAGS);
5079 }
5080
5081 /* In practice we can assume `xkb_state_new` always succeeds. */
5082 xkb_state_unref(seat->xkb.state);
5083 seat->xkb.state = xkb_state_new(keymap);
5084
5085 xkb_state_unref(seat->xkb.state_empty);
5086 seat->xkb.state_empty = xkb_state_new(keymap);
5087
5088 for (int i = 0; i < MOD_INDEX_NUM; i++) {
5089 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
5090 seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id);
5091 }
5092 seat->xkb_keymap_mod_index_mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
5093 seat->xkb_keymap_mod_index_numlock = xkb_keymap_mod_get_index(keymap, "NumLock");
5094
5095 xkb_state_unref(seat->xkb.state_empty_with_shift);
5096 seat->xkb.state_empty_with_shift = nullptr;
5097 if (seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT] != XKB_MOD_INVALID) {
5098 seat->xkb.state_empty_with_shift = xkb_state_new(keymap);
5099 }
5100
5101 xkb_state_unref(seat->xkb.state_empty_with_numlock);
5102 seat->xkb.state_empty_with_numlock = nullptr;
5103 if ((seat->xkb_keymap_mod_index_mod2 != XKB_MOD_INVALID) &&
5104 (seat->xkb_keymap_mod_index_numlock != XKB_MOD_INVALID))
5105 {
5106 seat->xkb.state_empty_with_numlock = xkb_state_new(keymap);
5107 }
5108
5110
5111#ifdef USE_NON_LATIN_KB_WORKAROUND
5112 seat->xkb_use_non_latin_workaround = false;
5113 if (seat->xkb.state_empty_with_shift) {
5114 seat->xkb_use_non_latin_workaround = true;
5115 for (xkb_keycode_t key_code = KEY_1 + EVDEV_OFFSET; key_code <= KEY_0 + EVDEV_OFFSET;
5116 key_code++)
5117 {
5118 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(seat->xkb.state_empty_with_shift,
5119 key_code);
5120 if (!(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9)) {
5121 seat->xkb_use_non_latin_workaround = false;
5122 break;
5123 }
5124 }
5125 }
5126#endif
5127
5129
5130 xkb_keymap_unref(keymap);
5131}
5132
5138static void keyboard_handle_enter(void *data,
5139 wl_keyboard * /*wl_keyboard*/,
5140 const uint32_t serial,
5142 wl_array *keys)
5143{
5144 /* Null when just destroyed. */
5146 CLOG_INFO(LOG, 2, "enter (skipped)");
5147 return;
5148 }
5149 CLOG_INFO(LOG, 2, "enter");
5150
5151 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5153
5154 seat->keyboard.serial = serial;
5155 seat->keyboard.wl.surface_window = wl_surface;
5156
5157 seat->system->seat_active_set(seat);
5158
5159 /* If there are any keys held when activating the window,
5160 * modifiers will be compared against the seat state,
5161 * only enabling modifiers that were previously disabled. */
5162 GWL_KeyboardDepressedState key_depressed_prev = seat->key_depressed;
5164
5165 /* Keep track of the last held repeating key, start the repeat timer if one exists. */
5166 struct {
5167 uint32_t key = std::numeric_limits<uint32_t>::max();
5168 xkb_keysym_t sym = 0;
5169 } repeat;
5170
5171 uint32_t *key;
5172 WL_ARRAY_FOR_EACH (key, keys) {
5173 const xkb_keycode_t key_code = *key + EVDEV_OFFSET;
5174 CLOG_INFO(LOG, 2, "enter (key_held=%d)", int(key_code));
5175 const xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->xkb.state, key_code);
5176 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, *key);
5177 if (gkey != GHOST_kKeyUnknown) {
5179 }
5180
5181 if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5182 repeat.key = *key;
5183 repeat.sym = sym;
5184 }
5185 }
5186
5187 /* Caller has no time-stamp, set from system. */
5188 const uint64_t event_ms = seat->system->getMilliSeconds();
5189 keyboard_depressed_state_push_events_from_change(seat, win, event_ms, key_depressed_prev);
5190
5191 if ((repeat.key != std::numeric_limits<uint32_t>::max()) && (seat->key_repeat.rate > 0)) {
5192 /* Since the key has been held, immediately send a press event.
5193 * This also ensures the key will be registered as pressed, see #117896. */
5194#ifdef USE_EVENT_BACKGROUND_THREAD
5195 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5196#endif
5197 /* Should have been cleared on leave, set here just in case. */
5198 if (UNLIKELY(seat->key_repeat.timer)) {
5200 }
5201
5202 const xkb_keycode_t key_code = repeat.key + EVDEV_OFFSET;
5203 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(repeat.sym, repeat.key);
5204
5205 GWL_KeyRepeatPlayload *key_repeat_payload = new GWL_KeyRepeatPlayload();
5206 key_repeat_payload->seat = seat;
5207 key_repeat_payload->key_code = key_code;
5208 key_repeat_payload->key_data.gkey = gkey;
5209
5210 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, false);
5211 /* Ensure there is a press event on enter so this is known to be held before any mouse
5212 * button events which may use a key-binding that depends on this key being held. */
5214 }
5215}
5216
5222static void keyboard_handle_leave(void *data,
5223 wl_keyboard * /*wl_keyboard*/,
5224 const uint32_t /*serial*/,
5226{
5228 CLOG_INFO(LOG, 2, "leave (skipped)");
5229 return;
5230 }
5231 CLOG_INFO(LOG, 2, "leave");
5232
5233 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5234 seat->keyboard.wl.surface_window = nullptr;
5235
5236 {
5237#ifdef USE_EVENT_BACKGROUND_THREAD
5238 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5239#endif
5240 /* Losing focus must stop repeating text. */
5241 if (seat->key_repeat.timer) {
5243 }
5244 }
5245}
5246
5252 xkb_state *xkb_state_empty,
5253 xkb_state *xkb_state_empty_with_numlock,
5254 xkb_state *xkb_state_empty_with_shift,
5255 const bool xkb_use_non_latin_workaround,
5256 const xkb_keycode_t key)
5257{
5258 /* Use an empty keyboard state to access key symbol without modifiers. */
5259 xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state_empty, key);
5260
5261 /* NOTE(@ideasman42): Only perform the number-locked lookup as a fallback
5262 * when a number-pad key has been pressed. This is important as some key-maps use number lock
5263 * for switching other layers (in particular `de(neo_qwertz)` turns on layer-4), see: #96170.
5264 * Alternative solutions could be to inspect the layout however this could get involved
5265 * and turning on the number-lock is only needed for a limited set of keys. */
5266
5267 /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled:
5268 * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */
5269 if (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete) {
5270 if (xkb_state_empty_with_numlock) {
5271 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key);
5272 if (sym_test != XKB_KEY_NoSymbol) {
5273 sym = sym_test;
5274 }
5275 }
5276 }
5277 else {
5278#ifdef USE_NON_LATIN_KB_WORKAROUND
5279 if (key >= (KEY_1 + EVDEV_OFFSET) && key <= (KEY_0 + EVDEV_OFFSET)) {
5280 if (xkb_state_empty_with_shift && xkb_use_non_latin_workaround) {
5281 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_shift, key);
5282 if (sym_test != XKB_KEY_NoSymbol) {
5283 /* Should never happen as enabling `xkb_use_non_latin_workaround` checks this. */
5284 GHOST_ASSERT(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9, "Unexpected key");
5285 sym = sym_test;
5286 }
5287 }
5288 }
5289#else
5290 (void)xkb_state_empty_with_shift;
5291 (void)xkb_use_non_latin_workaround;
5292#endif
5293 }
5294
5295 return sym;
5296}
5297
5299 xkb_compose_state *compose_state,
5300 xkb_state *state,
5301 const xkb_keycode_t key,
5302 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)])
5303{
5304 const xkb_keysym_t sym = xkb_state_key_get_one_sym(state, key);
5305 const xkb_compose_feed_result result = xkb_compose_state_feed(compose_state, sym);
5306 bool handled = false;
5307
5308 if (result == XKB_COMPOSE_FEED_ACCEPTED) {
5309 switch (xkb_compose_state_get_status(compose_state)) {
5310 case XKB_COMPOSE_NOTHING: {
5311 break;
5312 }
5313 case XKB_COMPOSE_COMPOSING: {
5314 r_utf8_buf[0] = '\0';
5315 handled = true;
5316 break;
5317 }
5318 case XKB_COMPOSE_COMPOSED: {
5319 char utf8_buf_compose[sizeof(GHOST_TEventKeyData::utf8_buf) + 1] = {'\0'};
5320 const int utf8_buf_compose_len = xkb_compose_state_get_utf8(
5321 compose_state, utf8_buf_compose, sizeof(utf8_buf_compose));
5322 if (utf8_buf_compose_len > 0) {
5323 memcpy(r_utf8_buf, utf8_buf_compose, utf8_buf_compose_len);
5324 handled = true;
5325 }
5326 break;
5327 }
5328 case XKB_COMPOSE_CANCELLED: {
5329 /* NOTE(@ideasman42): QT & GTK ignore these events as well as not inputting any text
5330 * so `<Compose><Backspace>` for e.g. causes a cancel and *not* back-space.
5331 * This isn't supported under GHOST at the moment.
5332 * The key-event could also be ignored but this means tracking held state of
5333 * keys wont work properly, so don't do any input and pass in the key-symbol. */
5334 r_utf8_buf[0] = '\0';
5335 handled = true;
5336 break;
5337 }
5338 }
5339 }
5340 return handled;
5341}
5342
5347{
5348 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
5349 delete static_cast<GWL_KeyRepeatPlayload *>(seat->key_repeat.timer->getUserData());
5350
5352}
5353
5361static void keyboard_handle_key_repeat_reset(GWL_Seat *seat, const bool use_delay)
5362{
5363 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
5364 GHOST_TimerProcPtr key_repeat_fn = seat->key_repeat.timer->getTimerProc();
5366
5368 gwl_seat_key_repeat_timer_add(seat, key_repeat_fn, payload, use_delay);
5369}
5370
5371static void keyboard_handle_key(void *data,
5372 wl_keyboard * /*wl_keyboard*/,
5373 const uint32_t serial,
5374 const uint32_t time,
5375 const uint32_t key,
5376 const uint32_t state)
5377{
5378 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5379 const uint64_t event_ms = seat->system->ms_from_input_time(time);
5380
5381 const xkb_keycode_t key_code = key + EVDEV_OFFSET;
5382
5383 const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers(
5384 seat->xkb.state_empty,
5389#else
5390 false,
5391#endif
5392 key_code);
5393 if (sym == XKB_KEY_NoSymbol) {
5394 CLOG_INFO(LOG, 2, "key (code=%d, state=%u, no symbol, skipped)", int(key_code), state);
5395 return;
5396 }
5397 CLOG_INFO(LOG, 2, "key (code=%d, state=%u)", int(key_code), state);
5398
5400 switch (state) {
5401 case WL_KEYBOARD_KEY_STATE_RELEASED:
5402 etype = GHOST_kEventKeyUp;
5403 break;
5404 case WL_KEYBOARD_KEY_STATE_PRESSED:
5405 etype = GHOST_kEventKeyDown;
5406 break;
5407 }
5408
5409#ifdef USE_EVENT_BACKGROUND_THREAD
5410 /* Any access to `seat->key_repeat.timer` must lock. */
5411 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5412#endif
5413
5414 GWL_KeyRepeatPlayload *key_repeat_payload = nullptr;
5415
5416 /* Delete previous timer. */
5417 if (seat->key_repeat.timer) {
5418 enum { NOP = 1, RESET, CANCEL } timer_action = NOP;
5419 key_repeat_payload = static_cast<GWL_KeyRepeatPlayload *>(
5420 seat->key_repeat.timer->getUserData());
5421
5422 if (seat->key_repeat.rate == 0) {
5423 /* Repeat was disabled (unlikely but possible). */
5424 timer_action = CANCEL;
5425 }
5426 else if (key_code == key_repeat_payload->key_code) {
5427 /* Releasing the key that was held always cancels. */
5428 timer_action = CANCEL;
5429 }
5430 else if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5431 if (etype == GHOST_kEventKeyDown) {
5432 /* Any other key-down always cancels (and may start its own repeat timer). */
5433 timer_action = CANCEL;
5434 }
5435 else {
5436 /* Key-up from keys that were not repeating cause the repeat timer to pause.
5437 *
5438 * NOTE(@ideasman42): This behavior isn't universal, some text input systems will
5439 * stop the repeat entirely. Choose to pause repeat instead as this is what GTK/WIN32 do,
5440 * and it fits better for keyboard input that isn't related to text entry. */
5441 timer_action = RESET;
5442 }
5443 }
5444
5445 switch (timer_action) {
5446 case NOP: {
5447 /* Don't add a new timer, leave the existing timer owning this `key_repeat_payload`. */
5448 key_repeat_payload = nullptr;
5449 break;
5450 }
5451 case RESET: {
5452 /* The payload will be added again. */
5454 break;
5455 }
5456 case CANCEL: {
5457 delete key_repeat_payload;
5458 key_repeat_payload = nullptr;
5460 break;
5461 }
5462 }
5463 }
5464
5465 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, key);
5466
5467 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
5468 if (etype == GHOST_kEventKeyDown) {
5469 /* Handle key-compose (dead-keys). */
5470 if (seat->xkb.compose_state &&
5472 seat->xkb.compose_state, seat->xkb.state, key_code, utf8_buf))
5473 {
5474 /* `utf8_buf` has been filled by a compose action. */
5475 }
5476 else {
5477 xkb_state_key_get_utf8(seat->xkb.state, key_code, utf8_buf, sizeof(utf8_buf));
5478 }
5479 }
5480
5481 seat->data_source_serial = serial;
5482
5483 keyboard_depressed_state_key_event(seat, gkey, etype);
5484
5485 if (wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window) {
5486 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
5488 new GHOST_EventKey(event_ms, etype, win, gkey, false, utf8_buf));
5489 }
5490
5491 /* An existing payload means the key repeat timer is reset and will be added again. */
5492 if (key_repeat_payload == nullptr) {
5493 /* Start timer for repeating key, if applicable. */
5494 if ((seat->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) &&
5495 xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code))
5496 {
5497 key_repeat_payload = new GWL_KeyRepeatPlayload();
5498 key_repeat_payload->seat = seat;
5499 key_repeat_payload->key_code = key_code;
5500 key_repeat_payload->key_data.gkey = gkey;
5501 }
5502 }
5503
5504 if (key_repeat_payload) {
5505 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, true);
5506 }
5507}
5508
5509static void keyboard_handle_modifiers(void *data,
5510 wl_keyboard * /*wl_keyboard*/,
5511 const uint32_t serial,
5512 const uint32_t mods_depressed,
5513 const uint32_t mods_latched,
5514 const uint32_t mods_locked,
5515 const uint32_t group)
5516{
5517 CLOG_INFO(LOG,
5518 2,
5519 "modifiers (depressed=%u, latched=%u, locked=%u, group=%u)",
5520 mods_depressed,
5521 mods_latched,
5522 mods_locked,
5523 group);
5524
5525 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5526 xkb_state_update_mask(seat->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
5527
5528 /* Account for the active layout changing within the same key-map,
5529 * needed so modifiers are detected from the expected layout, see: #115160. */
5530 if (group != seat->xkb.layout_active) {
5531 seat->xkb.layout_active = group;
5533 }
5534
5535 /* A modifier changed so reset the timer,
5536 * see comment in #keyboard_handle_key regarding this behavior. */
5537 {
5538#ifdef USE_EVENT_BACKGROUND_THREAD
5539 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5540#endif
5541 if (seat->key_repeat.timer) {
5543 }
5544 }
5545
5546 seat->data_source_serial = serial;
5547}
5548
5549static void keyboard_handle_repeat_info(void *data,
5550 wl_keyboard * /*wl_keyboard*/,
5551 const int32_t rate,
5552 const int32_t delay)
5553{
5554 CLOG_INFO(LOG, 2, "info (rate=%d, delay=%d)", rate, delay);
5555
5556 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5557 seat->key_repeat.rate = rate;
5558 seat->key_repeat.delay = delay;
5559
5560 {
5561#ifdef USE_EVENT_BACKGROUND_THREAD
5562 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5563#endif
5564 /* Unlikely possible this setting changes while repeating. */
5565 if (seat->key_repeat.timer) {
5567 }
5568 }
5569}
5570
5571static const wl_keyboard_listener keyboard_listener = {
5572 /*keymap*/ keyboard_handle_keymap,
5573 /*enter*/ keyboard_handle_enter,
5574 /*leave*/ keyboard_handle_leave,
5575 /*key*/ keyboard_handle_key,
5576 /*modifiers*/ keyboard_handle_modifiers,
5577 /*repeat_info*/ keyboard_handle_repeat_info,
5578};
5579
5580#undef LOG
5581
5584/* -------------------------------------------------------------------- */
5588static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"};
5589#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER)
5590
5591static void primary_selection_offer_offer(void *data,
5592 zwp_primary_selection_offer_v1 *id,
5593 const char *type)
5594{
5596 if (data_offer->wp.id != id) {
5597 CLOG_INFO(LOG, 2, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type);
5598 return;
5599 }
5600
5601 data_offer->types.insert(std::string(type));
5602}
5603
5604static const zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
5606};
5607
5608#undef LOG
5609
5612/* -------------------------------------------------------------------- */
5616static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"};
5617#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE)
5618
5620 void * /*data*/,
5621 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5622 zwp_primary_selection_offer_v1 *id)
5623{
5624 CLOG_INFO(LOG, 2, "data_offer");
5625
5627 data_offer->wp.id = id;
5628 zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer);
5629}
5630
5632 void *data,
5633 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5634 zwp_primary_selection_offer_v1 *id)
5635{
5636 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5637
5638 std::lock_guard lock{primary->data_offer_mutex};
5639
5640 /* Delete old data offer. */
5641 if (primary->data_offer != nullptr) {
5643 }
5644
5645 if (id == nullptr) {
5646 CLOG_INFO(LOG, 2, "selection: (skipped)");
5647 return;
5648 }
5649 CLOG_INFO(LOG, 2, "selection");
5650 /* Get new data offer. */
5652 zwp_primary_selection_offer_v1_get_user_data(id));
5653 primary->data_offer = data_offer;
5654}
5655
5656static const zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
5659};
5660
5661#undef LOG
5662
5665/* -------------------------------------------------------------------- */
5669static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"};
5670#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE)
5671
5672static void primary_selection_source_send(void *data,
5673 zwp_primary_selection_source_v1 * /*source*/,
5674 const char * /*mime_type*/,
5675 int32_t fd)
5676{
5677 CLOG_INFO(LOG, 2, "send");
5678
5679 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5680
5681 auto write_file_fn = [](GWL_PrimarySelection *primary, const int fd) {
5682 if (UNLIKELY(write(fd,
5683 primary->data_source->buffer_out.data,
5684 primary->data_source->buffer_out.data_size) < 0))
5685 {
5686 CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno));
5687 }
5688 close(fd);
5689 primary->data_source_mutex.unlock();
5690 };
5691
5692 primary->data_source_mutex.lock();
5693 std::thread write_thread(write_file_fn, primary, fd);
5694 write_thread.detach();
5695}
5696
5697static void primary_selection_source_cancelled(void *data, zwp_primary_selection_source_v1 *source)
5698{
5699 CLOG_INFO(LOG, 2, "cancelled");
5700
5701 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5702
5703 if (source == primary->data_source->wp.source) {
5705 }
5706}
5707
5708static const zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
5711};
5712
5713#undef LOG
5714
5717/* -------------------------------------------------------------------- */
5721#ifdef WITH_INPUT_IME
5722
5723class GHOST_EventIME : public GHOST_Event {
5724 public:
5731 GHOST_EventIME(uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, void *customdata)
5732 : GHOST_Event(msec, type, window)
5733 {
5734 this->m_data = customdata;
5735 }
5736};
5737
5738static CLG_LogRef LOG_WL_TEXT_INPUT = {"ghost.wl.handle.text_input"};
5739# define LOG (&LOG_WL_TEXT_INPUT)
5740
5741static void text_input_handle_enter(void *data,
5742 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5743 wl_surface *surface)
5744{
5745 if (!ghost_wl_surface_own(surface)) {
5746 return;
5747 }
5748 CLOG_INFO(LOG, 2, "enter");
5749 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5750 seat->ime.surface_window = surface;
5751}
5752
5753static void text_input_handle_leave(void *data,
5754 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5755 wl_surface *surface)
5756{
5757 /* Can be null when closing a window. */
5759 return;
5760 }
5761 CLOG_INFO(LOG, 2, "leave");
5762 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5763 if (seat->ime.surface_window == surface) {
5764 seat->ime.surface_window = nullptr;
5765 }
5766}
5767
5768static void text_input_handle_preedit_string(void *data,
5769 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5770 const char *text,
5771 int32_t cursor_begin,
5772 int32_t cursor_end)
5773{
5774 CLOG_INFO(LOG,
5775 2,
5776 "preedit_string (text=\"%s\", cursor_begin=%d, cursor_end=%d)",
5777 text ? text : "<null>",
5778 cursor_begin,
5779 cursor_end);
5780
5781 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5782 if (seat->ime.has_preedit == false) {
5783 /* Starting IME input. */
5784 gwl_seat_ime_full_reset(seat);
5785 }
5786
5787 seat->ime.composite_is_null = (text == nullptr);
5788 if (!seat->ime.composite_is_null) {
5789 seat->ime.composite = text;
5790 seat->ime.event_ime_data.composite = (void *)seat->ime.composite.c_str();
5791 seat->ime.event_ime_data.composite_len = (void *)seat->ime.composite.size();
5792
5793 seat->ime.event_ime_data.cursor_position = cursor_begin;
5794 seat->ime.event_ime_data.target_start = cursor_begin;
5795 seat->ime.event_ime_data.target_end = cursor_end;
5796 }
5797
5798 seat->ime.has_preedit_string_callback = true;
5799}
5800
5801static void text_input_handle_commit_string(void *data,
5802 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5803 const char *text)
5804{
5805 CLOG_INFO(LOG, 2, "commit_string (text=\"%s\")", text ? text : "<null>");
5806
5807 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5808 seat->ime.result_is_null = (text == nullptr);
5809 if (seat->ime.result_is_null) {
5810 seat->ime.result = "";
5811 }
5812 else {
5813 seat->ime.result = text;
5814 }
5815
5816 seat->ime.result_is_null = (text == nullptr);
5817 seat->ime.event_ime_data.result = (void *)seat->ime.result.c_str();
5818 seat->ime.event_ime_data.result_len = (void *)seat->ime.result.size();
5819 seat->ime.event_ime_data.cursor_position = seat->ime.result.size();
5820
5821 seat->ime.has_commit_string_callback = true;
5822}
5823
5824static void text_input_handle_delete_surrounding_text(void * /*data*/,
5825 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5826 uint32_t before_length,
5827 uint32_t after_length)
5828{
5829 CLOG_INFO(LOG,
5830 2,
5831 "delete_surrounding_text (before_length=%u, after_length=%u)",
5832 before_length,
5833 after_length);
5834
5835 /* NOTE: Currently unused, do we care about this event?
5836 * SDL ignores this event. */
5837}
5838
5839static void text_input_handle_done(void *data,
5840 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5841 uint32_t /*serial*/)
5842{
5843 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5844 GHOST_SystemWayland *system = seat->system;
5845 const uint64_t event_ms = seat->system->getMilliSeconds();
5846
5847 CLOG_INFO(LOG, 2, "done");
5848
5849 GHOST_WindowWayland *win = seat->ime.surface_window ?
5850 ghost_wl_surface_user_data(seat->ime.surface_window) :
5851 nullptr;
5852 if (seat->ime.has_commit_string_callback) {
5853 if (seat->ime.has_preedit) {
5854 const bool is_end = seat->ime.composite_is_null;
5855 if (is_end) {
5856 seat->ime.has_preedit = false;
5857 /* `commit_string` (end). */
5858 system->pushEvent_maybe_pending(new GHOST_EventIME(
5859 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5860 system->pushEvent_maybe_pending(new GHOST_EventIME(
5861 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5862 }
5863 else {
5864 /* `commit_string` (continues). */
5865 system->pushEvent_maybe_pending(new GHOST_EventIME(
5866 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5867 }
5868 }
5869 else {
5870 /* `commit_string` ran with no active IME popup, start & end to insert text. */
5871 system->pushEvent_maybe_pending(new GHOST_EventIME(
5872 event_ms, GHOST_kEventImeCompositionStart, win, &seat->ime.event_ime_data));
5873 system->pushEvent_maybe_pending(new GHOST_EventIME(
5874 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5875 system->pushEvent_maybe_pending(new GHOST_EventIME(
5876 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5877 }
5878
5879 if (seat->ime.has_preedit == false) {
5880 gwl_seat_ime_preedit_reset(seat);
5881 }
5882 }
5883 else if (seat->ime.has_preedit_string_callback) {
5884 const bool is_end = seat->ime.composite_is_null;
5885 if (is_end) {
5886 /* `preedit_string` (end). */
5887 seat->ime.has_preedit = false;
5888 system->pushEvent_maybe_pending(new GHOST_EventIME(
5889 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5890 }
5891 else {
5892 const bool is_start = seat->ime.has_preedit == false;
5893 /* `preedit_string` (start or continue). */
5894 seat->ime.has_preedit = true;
5895 system->pushEvent_maybe_pending(new GHOST_EventIME(
5896 event_ms,
5898 win,
5899 &seat->ime.event_ime_data));
5900 }
5901 }
5902
5903 seat->ime.has_preedit_string_callback = false;
5904 seat->ime.has_commit_string_callback = false;
5905}
5906
5907static zwp_text_input_v3_listener text_input_listener = {
5908 /*enter*/ text_input_handle_enter,
5909 /*leave*/ text_input_handle_leave,
5910 /*preedit_string*/ text_input_handle_preedit_string,
5911 /*commit_string*/ text_input_handle_commit_string,
5912 /*delete_surrounding_text*/ text_input_handle_delete_surrounding_text,
5913 /*done*/ text_input_handle_done,
5914};
5915
5916# undef LOG
5917
5918#endif /* WITH_INPUT_IME. */
5919
5922/* -------------------------------------------------------------------- */
5926static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"};
5927#define LOG (&LOG_WL_SEAT)
5928
5929static bool gwl_seat_capability_pointer_multitouch_check(const GWL_Seat *seat, const bool fallback)
5930{
5931 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
5932 if (pointer_gestures == nullptr) {
5933 return fallback;
5934 }
5935
5936 bool found = false;
5937#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
5938 if (seat->wp.pointer_gesture_hold) {
5939 return true;
5940 }
5941 found = true;
5942#endif
5943#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
5944 if (seat->wp.pointer_gesture_pinch) {
5945 return true;
5946 }
5947 found = true;
5948#endif
5949#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
5950 if (seat->wp.pointer_gesture_swipe) {
5951 return true;
5952 }
5953 found = true;
5954#endif
5955 if (seat->use_pointer_scroll_smooth_as_discrete == false) {
5956 return true;
5957 }
5958
5959 if (found == false) {
5960 return fallback;
5961 }
5962 return false;
5963}
5964
5966{
5967 /* Smooth to discrete handling. */
5970
5971 zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
5972 if (pointer_gestures == nullptr) {
5973 return;
5974 }
5975
5976 const uint pointer_gestures_version = zwp_pointer_gestures_v1_get_version(pointer_gestures);
5977#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
5978 if (pointer_gestures_version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION)
5979 { /* Hold gesture. */
5980 zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture(
5981 pointer_gestures, seat->wl.pointer);
5982 zwp_pointer_gesture_hold_v1_set_user_data(gesture, seat);
5983 zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat);
5984 seat->wp.pointer_gesture_hold = gesture;
5985 }
5986#endif
5987#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
5988 { /* Pinch gesture. */
5989 zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture(
5990 pointer_gestures, seat->wl.pointer);
5991 zwp_pointer_gesture_pinch_v1_set_user_data(gesture, seat);
5992 zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat);
5993 seat->wp.pointer_gesture_pinch = gesture;
5994 }
5995#endif
5996#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
5997 { /* Swipe gesture. */
5998 zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture(
5999 pointer_gestures, seat->wl.pointer);
6000 zwp_pointer_gesture_swipe_v1_set_user_data(gesture, seat);
6001 zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat);
6002 seat->wp.pointer_gesture_swipe = gesture;
6003 }
6004#endif
6005}
6006
6008{
6009 /* Smooth to discrete handling. */
6012 seat->pointer_scroll.smooth_xy[0] = 0;
6013 seat->pointer_scroll.smooth_xy[1] = 0;
6014
6015 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6016 if (pointer_gestures == nullptr) {
6017 return;
6018 }
6019#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6020 { /* Hold gesture. */
6021 zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp.pointer_gesture_hold;
6022 if (*gesture_p) {
6023 zwp_pointer_gesture_hold_v1_destroy(*gesture_p);
6024 *gesture_p = nullptr;
6025 }
6026 }
6027#endif
6028#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6029 { /* Pinch gesture. */
6030 zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp.pointer_gesture_pinch;
6031 if (*gesture_p) {
6032 zwp_pointer_gesture_pinch_v1_destroy(*gesture_p);
6033 *gesture_p = nullptr;
6034 }
6035 }
6036#endif
6037#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6038 { /* Swipe gesture. */
6039 zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp.pointer_gesture_swipe;
6040 if (*gesture_p) {
6041 zwp_pointer_gesture_swipe_v1_destroy(*gesture_p);
6042 *gesture_p = nullptr;
6043 }
6044 }
6045#endif
6046}
6047
6049{
6050 if (seat->wl.pointer) {
6051 return;
6052 }
6053 seat->wl.pointer = wl_seat_get_pointer(seat->wl.seat);
6054 seat->cursor.wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
6055 seat->cursor.visible = true;
6056 seat->cursor.wl.buffer = nullptr;
6057 {
6058 /* Use environment variables, falling back to defaults.
6059 * These environment variables are used by enough WAYLAND applications
6060 * that it makes sense to check them (see `Xcursor` man page). */
6061 const char *env;
6062
6063 env = getenv("XCURSOR_THEME");
6064 seat->cursor.theme_name = std::string(env ? env : "");
6065
6066 env = getenv("XCURSOR_SIZE");
6068
6069 if (env && (*env != '\0')) {
6070 char *env_end = nullptr;
6071 /* While clamping is not needed on the WAYLAND side,
6072 * GHOST's internal logic may get confused by negative values, so ensure it's at least 1. */
6073 const long value = strtol(env, &env_end, 10);
6074 if ((*env_end == '\0') && (value > 0)) {
6075 seat->cursor.theme_size = int(value);
6076 }
6077 }
6078 }
6079 wl_pointer_add_listener(seat->wl.pointer, &pointer_listener, seat);
6080
6081 wl_surface_add_listener(seat->cursor.wl.surface_cursor, &cursor_surface_listener, seat);
6083
6085}
6086
6088{
6089 if (!seat->wl.pointer) {
6090 return;
6091 }
6092
6094
6095 if (seat->cursor.wl.surface_cursor) {
6096 wl_surface_destroy(seat->cursor.wl.surface_cursor);
6097 seat->cursor.wl.surface_cursor = nullptr;
6098 }
6099 if (seat->cursor.wl.theme) {
6101 seat->cursor.wl.theme = nullptr;
6102 }
6103
6104 wl_pointer_destroy(seat->wl.pointer);
6105 seat->wl.pointer = nullptr;
6106}
6107
6109{
6110 if (seat->wl.keyboard) {
6111 return;
6112 }
6113 seat->wl.keyboard = wl_seat_get_keyboard(seat->wl.seat);
6114 wl_keyboard_add_listener(seat->wl.keyboard, &keyboard_listener, seat);
6115}
6116
6118{
6119 if (!seat->wl.keyboard) {
6120 return;
6121 }
6122
6123 {
6124#ifdef USE_EVENT_BACKGROUND_THREAD
6125 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
6126#endif
6127 if (seat->key_repeat.timer) {
6129 }
6130 }
6131 wl_keyboard_destroy(seat->wl.keyboard);
6132 seat->wl.keyboard = nullptr;
6133}
6134
6136{
6137 if (seat->wl.touch) {
6138 return;
6139 }
6140 seat->wl.touch = wl_seat_get_touch(seat->wl.seat);
6141 wl_touch_set_user_data(seat->wl.touch, seat);
6142 wl_touch_add_listener(seat->wl.touch, &touch_seat_listener, seat);
6143}
6144
6146{
6147 if (!seat->wl.touch) {
6148 return;
6149 }
6150 wl_touch_destroy(seat->wl.touch);
6151 seat->wl.touch = nullptr;
6152}
6153
6154static void seat_handle_capabilities(void *data,
6155 /* Only used in an assert. */
6156 [[maybe_unused]] wl_seat *wl_seat,
6157 const uint32_t capabilities)
6158{
6159 CLOG_INFO(LOG,
6160 2,
6161 "capabilities (pointer=%d, keyboard=%d, touch=%d)",
6162 (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0,
6163 (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0,
6164 (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0);
6165
6166 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6167 GHOST_ASSERT(seat->wl.seat == wl_seat, "Seat mismatch");
6168
6169 if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
6171 }
6172 else {
6174 }
6175
6176 if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
6178 }
6179 else {
6181 }
6182
6183 if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
6185 }
6186 else {
6188 }
6189}
6190
6191static void seat_handle_name(void *data, wl_seat * /*wl_seat*/, const char *name)
6192{
6193 CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
6194 static_cast<GWL_Seat *>(data)->name = std::string(name);
6195}
6196
6197static const wl_seat_listener seat_listener = {
6198 /*capabilities*/ seat_handle_capabilities,
6199 /*name*/ seat_handle_name,
6200};
6201
6202#undef LOG
6203
6206/* -------------------------------------------------------------------- */
6210static CLG_LogRef LOG_WL_XDG_OUTPUT = {"ghost.wl.handle.xdg_output"};
6211#define LOG (&LOG_WL_XDG_OUTPUT)
6212
6214 zxdg_output_v1 * /*xdg_output*/,
6215 const int32_t x,
6216 const int32_t y)
6217{
6218 CLOG_INFO(LOG, 2, "logical_position [%d, %d]", x, y);
6219
6220 GWL_Output *output = static_cast<GWL_Output *>(data);
6221 output->position_logical[0] = x;
6222 output->position_logical[1] = y;
6223 output->has_position_logical = true;
6224}
6225
6226static void xdg_output_handle_logical_size(void *data,
6227 zxdg_output_v1 * /*xdg_output*/,
6228 const int32_t width,
6229 const int32_t height)
6230{
6231 CLOG_INFO(LOG, 2, "logical_size [%d, %d]", width, height);
6232
6233 GWL_Output *output = static_cast<GWL_Output *>(data);
6234 if (output->size_logical[0] != 0 && output->size_logical[1] != 0) {
6235 /* Original comment from SDL. */
6236 /* FIXME(@flibit): GNOME has a bug where the logical size does not account for
6237 * scale, resulting in bogus viewport sizes.
6238 *
6239 * Until this is fixed, validate that _some_ kind of scaling is being
6240 * done (we can't match exactly because fractional scaling can't be
6241 * detected otherwise), then override if necessary. */
6242 if ((output->size_logical[0] == width) &&
6243 (output->scale_fractional == (1 * FRACTIONAL_DENOMINATOR)))
6244 {
6245 GHOST_PRINT("xdg_output scale did not match, overriding with wl_output scale\n");
6246
6247#ifdef USE_GNOME_CONFINE_HACK
6248 /* Use a bug in GNOME to check GNOME is in use. If the bug is fixed this won't cause an issue
6249 * as #98793 has been fixed up-stream too, but not in a release at time of writing. */
6251#endif
6252
6253 return;
6254 }
6255 }
6256
6257 output->size_logical[0] = width;
6258 output->size_logical[1] = height;
6259 output->has_size_logical = true;
6260}
6261
6262static void xdg_output_handle_done(void *data, zxdg_output_v1 * /*xdg_output*/)
6263{
6264 CLOG_INFO(LOG, 2, "done");
6265 /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol.
6266 * `wl-output.done` event will be emitted in version 3 or higher. */
6267 GWL_Output *output = static_cast<GWL_Output *>(data);
6268 if (zxdg_output_v1_get_version(output->xdg.output) < 3) {
6269 output_handle_done(data, output->wl.output);
6270 }
6271}
6272
6273static void xdg_output_handle_name(void * /*data*/,
6274 zxdg_output_v1 * /*xdg_output*/,
6275 const char *name)
6276{
6277 CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
6278}
6279
6280static void xdg_output_handle_description(void * /*data*/,
6281 zxdg_output_v1 * /*xdg_output*/,
6282 const char *description)
6283{
6284 CLOG_INFO(LOG, 2, "description (description=\"%s\")", description);
6285}
6286
6287static const zxdg_output_v1_listener xdg_output_listener = {
6288 /*logical_position*/ xdg_output_handle_logical_position,
6289 /*logical_size*/ xdg_output_handle_logical_size,
6290 /*done*/ xdg_output_handle_done,
6291 /*name*/ xdg_output_handle_name,
6292 /*description*/ xdg_output_handle_description,
6293};
6294
6295#undef LOG
6296
6299/* -------------------------------------------------------------------- */
6303static CLG_LogRef LOG_WL_OUTPUT = {"ghost.wl.handle.output"};
6304#define LOG (&LOG_WL_OUTPUT)
6305
6306static void output_handle_geometry(void *data,
6307 wl_output * /*wl_output*/,
6308 const int32_t /*x*/,
6309 const int32_t /*y*/,
6310 const int32_t physical_width,
6311 const int32_t physical_height,
6312 const int32_t /*subpixel*/,
6313 const char *make,
6314 const char *model,
6315 const int32_t transform)
6316{
6317 CLOG_INFO(LOG,
6318 2,
6319 "geometry (make=\"%s\", model=\"%s\", transform=%d, size=[%d, %d])",
6320 make,
6321 model,
6322 transform,
6323 physical_width,
6324 physical_height);
6325
6326 GWL_Output *output = static_cast<GWL_Output *>(data);
6327 output->transform = transform;
6328 output->make = std::string(make);
6329 output->model = std::string(model);
6330 output->size_mm[0] = physical_width;
6331 output->size_mm[1] = physical_height;
6332}
6333
6334static void output_handle_mode(void *data,
6335 wl_output * /*wl_output*/,
6336 const uint32_t flags,
6337 const int32_t width,
6338 const int32_t height,
6339 const int32_t /*refresh*/)
6340{
6341 if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
6342 CLOG_INFO(LOG, 2, "mode (skipped)");
6343 return;
6344 }
6345 CLOG_INFO(LOG, 2, "mode (size=[%d, %d], flags=%u)", width, height, flags);
6346
6347 GWL_Output *output = static_cast<GWL_Output *>(data);
6348 output->size_native[0] = width;
6349 output->size_native[1] = height;
6350
6351 /* Don't rotate this yet, `wl-output` coordinates are transformed in
6352 * handle_done and `xdg-output` coordinates are pre-transformed. */
6353 if (!output->has_size_logical) {
6354 output->size_logical[0] = width;
6355 output->size_logical[1] = height;
6356 }
6357}
6358
6367static void output_handle_done(void *data, wl_output * /*wl_output*/)
6368{
6369 CLOG_INFO(LOG, 2, "done");
6370
6371 GWL_Output *output = static_cast<GWL_Output *>(data);
6372 int32_t size_native[2] = {UNPACK2(output->size_native)};
6373 if (ELEM(output->transform, WL_OUTPUT_TRANSFORM_90, WL_OUTPUT_TRANSFORM_270)) {
6374 std::swap(size_native[0], size_native[1]);
6375 }
6376
6377 /* If `xdg-output` is present, calculate the true scale of the desktop */
6378 if (output->has_size_logical) {
6379
6380 /* NOTE: it's not necessary to divide these values by their greatest-common-denominator
6381 * as even a 64k screen resolution doesn't approach overflowing an `int32_t`. */
6382
6383 GHOST_ASSERT(size_native[0] && output->size_logical[0],
6384 "Screen size values were not set when they were expected to be.");
6385
6386 output->scale_fractional = (size_native[0] * FRACTIONAL_DENOMINATOR) / output->size_logical[0];
6387 output->has_scale_fractional = true;
6388 }
6389}
6390
6391static void output_handle_scale(void *data, wl_output * /*wl_output*/, const int32_t factor)
6392{
6393 CLOG_INFO(LOG, 2, "scale");
6394 GWL_Output *output = static_cast<GWL_Output *>(data);
6395 output->scale = factor;
6396 output->system->output_scale_update(output);
6397}
6398
6399static void output_handle_name(void * /*data*/, wl_output * /*wl_output*/, const char *name)
6400{
6401 /* Only available in interface version 4. */
6402 CLOG_INFO(LOG, 2, "name (%s)", name);
6403}
6404static void output_handle_description(void * /*data*/,
6405 wl_output * /*wl_output*/,
6406 const char *description)
6407{
6408 /* Only available in interface version 4. */
6409 CLOG_INFO(LOG, 2, "description (%s)", description);
6410}
6411
6412static const wl_output_listener output_listener = {
6413 /*geometry*/ output_handle_geometry,
6414 /*mode*/ output_handle_mode,
6415 /*done*/ output_handle_done,
6416 /*scale*/ output_handle_scale,
6417 /*name*/ output_handle_name,
6418 /*description*/ output_handle_description,
6419};
6420
6421#undef LOG
6422
6425/* -------------------------------------------------------------------- */
6429static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.xdg_wm_base"};
6430#define LOG (&LOG_WL_XDG_WM_BASE)
6431
6432static void shell_handle_ping(void * /*data*/, xdg_wm_base *xdg_wm_base, const uint32_t serial)
6433{
6434 CLOG_INFO(LOG, 2, "ping");
6435 xdg_wm_base_pong(xdg_wm_base, serial);
6436}
6437
6438static const xdg_wm_base_listener shell_listener = {
6439 /*ping*/ shell_handle_ping,
6440};
6441
6442#undef LOG
6443
6446/* -------------------------------------------------------------------- */
6450#ifdef WITH_GHOST_WAYLAND_LIBDECOR
6451
6452static CLG_LogRef LOG_WL_LIBDECOR = {"ghost.wl.handle.libdecor"};
6453# define LOG (&LOG_WL_LIBDECOR)
6454
6455static void decor_handle_error(libdecor * /*context*/,
6456 enum libdecor_error error,
6457 const char *message)
6458{
6459 CLOG_INFO(LOG, 2, "error (id=%d, message=%s)", error, message);
6460
6461 (void)(error);
6462 (void)(message);
6463 GHOST_PRINT("decoration error (" << error << "): " << message << std::endl);
6464 exit(EXIT_FAILURE);
6465}
6466
6467static libdecor_interface libdecor_interface = {
6468 decor_handle_error,
6469};
6470
6471# undef LOG
6472
6473#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
6474
6477/* -------------------------------------------------------------------- */
6481static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"};
6482#define LOG (&LOG_WL_REGISTRY)
6483
6484/* #GWL_Display.wl_compositor */
6485
6488{
6489 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 6u);
6490
6491 display->wl.compositor = static_cast<wl_compositor *>(
6492 wl_registry_bind(display->wl.registry, params.name, &wl_compositor_interface, version));
6493 gwl_registry_entry_add(display, params, nullptr);
6494}
6496 void * /*user_data*/,
6497 const bool /*on_exit*/)
6498{
6499 wl_compositor **value_p = &display->wl.compositor;
6500 wl_compositor_destroy(*value_p);
6501 *value_p = nullptr;
6502}
6503
6504/* #GWL_Display.xdg_decor.shell */
6505
6508{
6509 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 6u);
6510
6511 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6512 decor.shell = static_cast<xdg_wm_base *>(
6513 wl_registry_bind(display->wl.registry, params.name, &xdg_wm_base_interface, version));
6514 xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr);
6515 decor.shell_name = params.name;
6516 gwl_registry_entry_add(display, params, nullptr);
6517}
6519 void * /*user_data*/,
6520 const bool /*on_exit*/)
6521{
6522 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6523 xdg_wm_base **value_p = &decor.shell;
6524 uint32_t *name_p = &decor.shell_name;
6525 xdg_wm_base_destroy(*value_p);
6526 *value_p = nullptr;
6527 *name_p = WL_NAME_UNSET;
6528}
6529
6530/* #GWL_Display.xdg_decor.manager */
6531
6534{
6535 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6536
6537 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6538 decor.manager = static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind(
6539 display->wl.registry, params.name, &zxdg_decoration_manager_v1_interface, version));
6540 decor.manager_name = params.name;
6541 gwl_registry_entry_add(display, params, nullptr);
6542}
6544 void * /*user_data*/,
6545 const bool /*on_exit*/)
6546{
6547 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6548 zxdg_decoration_manager_v1 **value_p = &decor.manager;
6549 uint32_t *name_p = &decor.manager_name;
6550 zxdg_decoration_manager_v1_destroy(*value_p);
6551 *value_p = nullptr;
6552 *name_p = WL_NAME_UNSET;
6553}
6554
6555/* #GWL_Display.xdg_output_manager */
6556
6559{
6560 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 2u, 3u);
6561
6562 display->xdg.output_manager = static_cast<zxdg_output_manager_v1 *>(wl_registry_bind(
6563 display->wl.registry, params.name, &zxdg_output_manager_v1_interface, version));
6564 gwl_registry_entry_add(display, params, nullptr);
6565}
6567 void * /*user_data*/,
6568 const bool /*on_exit*/)
6569{
6570 zxdg_output_manager_v1 **value_p = &display->xdg.output_manager;
6571 zxdg_output_manager_v1_destroy(*value_p);
6572 *value_p = nullptr;
6573}
6574
6575/* #GWL_Display.wl_output */
6576
6578{
6579 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 2u, 4u);
6580
6581 GWL_Output *output = new GWL_Output;
6582 output->system = display->system;
6583 output->wl.output = static_cast<wl_output *>(
6584 wl_registry_bind(display->wl.registry, params.name, &wl_output_interface, version));
6585 ghost_wl_output_tag(output->wl.output);
6586 wl_output_set_user_data(output->wl.output, output);
6587
6588 display->outputs.push_back(output);
6589 wl_output_add_listener(output->wl.output, &output_listener, output);
6590 gwl_registry_entry_add(display, params, static_cast<void *>(output));
6591}
6594{
6595 GWL_Output *output = static_cast<GWL_Output *>(params.user_data);
6596 if (display->xdg.output_manager) {
6597 if (output->xdg.output == nullptr) {
6598 output->xdg.output = zxdg_output_manager_v1_get_xdg_output(display->xdg.output_manager,
6599 output->wl.output);
6600 zxdg_output_v1_add_listener(output->xdg.output, &xdg_output_listener, output);
6601 }
6602 }
6603 else {
6604 output->xdg.output = nullptr;
6605 }
6606}
6608 void *user_data,
6609 const bool on_exit)
6610{
6611 /* While windows & cursors hold references to outputs, there is no need to manually remove
6612 * these references as the compositor will remove references via #wl_surface_listener.leave.
6613 *
6614 * WARNING: this is not the case for WLROOTS based compositors which have a (bug?)
6615 * where surface leave events don't run. So `system->output_leave(..)` is needed
6616 * until the issue is resolved in WLROOTS. */
6617 GWL_Output *output = static_cast<GWL_Output *>(user_data);
6618
6619 if (!on_exit) {
6620 /* Needed for WLROOTS, does nothing if surface leave callbacks have already run. */
6621 if (output->system->output_unref(output->wl.output)) {
6622 CLOG_WARN(LOG,
6623 "mis-behaving compositor failed to call \"surface_listener.leave\" "
6624 "window scale may be invalid!");
6625 }
6626 }
6627
6628 if (output->xdg.output) {
6629 zxdg_output_v1_destroy(output->xdg.output);
6630 }
6631 wl_output_destroy(output->wl.output);
6632 std::vector<GWL_Output *>::iterator iter = std::find(
6633 display->outputs.begin(), display->outputs.end(), output);
6634 const int index = (iter != display->outputs.cend()) ?
6635 std::distance(display->outputs.begin(), iter) :
6636 -1;
6637 GHOST_ASSERT(index != -1, "invalid internal state");
6638 /* NOTE: always erase even when `on_exit` because `output->xdg_output` is cleared later. */
6639 display->outputs.erase(display->outputs.begin() + index);
6640 delete output;
6641}
6642
6643/* #GWL_Display.seats */
6644
6646{
6647 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 5u, 9u);
6648
6649 GWL_Seat *seat = new GWL_Seat;
6650 seat->system = display->system;
6651 seat->xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
6652
6653 /* May be null (skip dead-key support in this case). */
6654 seat->xkb.compose_table = xkb_compose_table_new_from_locale(
6655 seat->xkb.context, ghost_wl_locale_from_env_with_default(), XKB_COMPOSE_COMPILE_NO_FLAGS);
6656
6657 seat->data_source = new GWL_DataSource;
6658 seat->wl.seat = static_cast<wl_seat *>(
6659 wl_registry_bind(display->wl.registry, params.name, &wl_seat_interface, version));
6660 display->seats.push_back(seat);
6661 wl_seat_add_listener(seat->wl.seat, &seat_listener, seat);
6662 gwl_registry_entry_add(display, params, static_cast<void *>(seat));
6663
6665}
6668{
6669 GWL_Seat *seat = static_cast<GWL_Seat *>(params.user_data);
6670
6671 /* Register data device per seat for IPC between WAYLAND clients. */
6672 if (display->wl.data_device_manager) {
6673 if (seat->wl.data_device == nullptr) {
6674 seat->wl.data_device = wl_data_device_manager_get_data_device(
6675 display->wl.data_device_manager, seat->wl.seat);
6676 wl_data_device_add_listener(seat->wl.data_device, &data_device_listener, seat);
6677 }
6678 }
6679 else {
6680 seat->wl.data_device = nullptr;
6681 }
6682
6683 if (display->wp.tablet_manager) {
6684 if (seat->wp.tablet_seat == nullptr) {
6685 seat->wp.tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display->wp.tablet_manager,
6686 seat->wl.seat);
6687 zwp_tablet_seat_v2_add_listener(seat->wp.tablet_seat, &tablet_seat_listener, seat);
6688 }
6689 }
6690 else {
6691 seat->wp.tablet_seat = nullptr;
6692 }
6693
6694 if (display->wp.primary_selection_device_manager) {
6695 if (seat->wp.primary_selection_device == nullptr) {
6696 seat->wp.primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
6697 display->wp.primary_selection_device_manager, seat->wl.seat);
6698
6699 zwp_primary_selection_device_v1_add_listener(seat->wp.primary_selection_device,
6701 &seat->primary_selection);
6702 }
6703 }
6704 else {
6705 seat->wp.primary_selection_device = nullptr;
6706 }
6707
6708#ifdef WITH_INPUT_IME
6709 if (display->wp.text_input_manager) {
6710 if (seat->wp.text_input == nullptr) {
6711 seat->wp.text_input = zwp_text_input_manager_v3_get_text_input(
6712 display->wp.text_input_manager, seat->wl.seat);
6713 zwp_text_input_v3_set_user_data(seat->wp.text_input, seat);
6714 zwp_text_input_v3_add_listener(seat->wp.text_input, &text_input_listener, seat);
6715 }
6716 }
6717 else {
6718 seat->wp.text_input = nullptr;
6719 }
6720#endif /* WITH_INPUT_IME */
6721}
6722static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
6723{
6724 GWL_Seat *seat = static_cast<GWL_Seat *>(user_data);
6725
6726 /* First handle members that require locking.
6727 * While highly unlikely, it's possible they are being used while this function runs. */
6728 {
6729 std::lock_guard lock{seat->data_source_mutex};
6730 if (seat->data_source) {
6732 if (seat->data_source->wl.source) {
6733 wl_data_source_destroy(seat->data_source->wl.source);
6734 }
6735 delete seat->data_source;
6736 }
6737 }
6738
6739 {
6740 std::lock_guard lock{seat->data_offer_dnd_mutex};
6741 if (seat->data_offer_dnd) {
6742 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
6743 delete seat->data_offer_dnd;
6744 }
6745 }
6746
6747 {
6748 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
6749 if (seat->data_offer_copy_paste) {
6750 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
6751 delete seat->data_offer_copy_paste;
6752 }
6753 }
6754
6755 {
6756 GWL_PrimarySelection *primary = &seat->primary_selection;
6757 std::lock_guard lock{primary->data_offer_mutex};
6759 }
6760
6761 {
6762 GWL_PrimarySelection *primary = &seat->primary_selection;
6763 std::lock_guard lock{primary->data_source_mutex};
6765 }
6766
6767 if (seat->wp.primary_selection_device) {
6768 zwp_primary_selection_device_v1_destroy(seat->wp.primary_selection_device);
6769 }
6770
6771 if (seat->wl.data_device) {
6772 wl_data_device_release(seat->wl.data_device);
6773 }
6774
6775 if (seat->wp.tablet_seat) {
6776 zwp_tablet_seat_v2_destroy(seat->wp.tablet_seat);
6777 }
6778
6779 if (seat->cursor.custom_data) {
6780 munmap(seat->cursor.custom_data, seat->cursor.custom_data_size);
6781 }
6782
6783 /* Disable all capabilities as a way to free:
6784 * - `seat.wl_pointer` (and related cursor variables).
6785 * - `seat.wl_touch`.
6786 * - `seat.wl_keyboard`.
6787 */
6791
6792 /* Un-referencing checks for nullptr case. */
6793 xkb_state_unref(seat->xkb.state);
6794 xkb_state_unref(seat->xkb.state_empty);
6795 xkb_state_unref(seat->xkb.state_empty_with_shift);
6796 xkb_state_unref(seat->xkb.state_empty_with_numlock);
6797
6798 xkb_compose_state_unref(seat->xkb.compose_state);
6799 xkb_compose_table_unref(seat->xkb.compose_table);
6800
6801 xkb_context_unref(seat->xkb.context);
6802
6803 /* Remove the seat. */
6804 wl_seat_destroy(seat->wl.seat);
6805
6806 std::vector<GWL_Seat *>::iterator iter = std::find(
6807 display->seats.begin(), display->seats.end(), seat);
6808 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
6809 -1;
6810 GHOST_ASSERT(index != -1, "invalid internal state");
6811
6812 if (!on_exit) {
6813 if (display->seats_active_index >= index) {
6814 display->seats_active_index -= 1;
6815 }
6816 display->seats.erase(display->seats.begin() + index);
6817 }
6818 delete seat;
6819}
6820
6821/* #GWL_Display.wl_shm */
6822
6824{
6825 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6826
6827 display->wl.shm = static_cast<wl_shm *>(
6828 wl_registry_bind(display->wl.registry, params.name, &wl_shm_interface, version));
6829 gwl_registry_entry_add(display, params, nullptr);
6830}
6832 void * /*user_data*/,
6833 const bool /*on_exit*/)
6834{
6835 wl_shm **value_p = &display->wl.shm;
6836 wl_shm_destroy(*value_p);
6837 *value_p = nullptr;
6838}
6839
6840/* #GWL_Display.wl_data_device_manager */
6841
6844{
6845 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
6846
6847 display->wl.data_device_manager = static_cast<wl_data_device_manager *>(wl_registry_bind(
6848 display->wl.registry, params.name, &wl_data_device_manager_interface, version));
6849 gwl_registry_entry_add(display, params, nullptr);
6850}
6852 void * /*user_data*/,
6853 const bool /*on_exit*/)
6854{
6855 wl_data_device_manager **value_p = &display->wl.data_device_manager;
6856 wl_data_device_manager_destroy(*value_p);
6857 *value_p = nullptr;
6858}
6859
6860/* #GWL_Display.wp_tablet_manager */
6861
6864{
6865 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6866
6867 display->wp.tablet_manager = static_cast<zwp_tablet_manager_v2 *>(wl_registry_bind(
6868 display->wl.registry, params.name, &zwp_tablet_manager_v2_interface, version));
6869 gwl_registry_entry_add(display, params, nullptr);
6870}
6872 void * /*user_data*/,
6873 const bool /*on_exit*/)
6874{
6875 zwp_tablet_manager_v2 **value_p = &display->wp.tablet_manager;
6876 zwp_tablet_manager_v2_destroy(*value_p);
6877 *value_p = nullptr;
6878}
6879
6880/* #GWL_Display.wp_relative_pointer_manager */
6881
6884{
6885 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6886
6887 display->wp.relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>(
6888 wl_registry_bind(
6889 display->wl.registry, params.name, &zwp_relative_pointer_manager_v1_interface, version));
6890 gwl_registry_entry_add(display, params, nullptr);
6891}
6893 void * /*user_data*/,
6894 const bool /*on_exit*/)
6895{
6896 zwp_relative_pointer_manager_v1 **value_p = &display->wp.relative_pointer_manager;
6897 zwp_relative_pointer_manager_v1_destroy(*value_p);
6898 *value_p = nullptr;
6899}
6900
6901/* #GWL_Display.wp_pointer_constraints */
6902
6905{
6906 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6907
6908 display->wp.pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind(
6909 display->wl.registry, params.name, &zwp_pointer_constraints_v1_interface, version));
6910 gwl_registry_entry_add(display, params, nullptr);
6911}
6913 void * /*user_data*/,
6914 const bool /*on_exit*/)
6915{
6916 zwp_pointer_constraints_v1 **value_p = &display->wp.pointer_constraints;
6917 zwp_pointer_constraints_v1_destroy(*value_p);
6918 *value_p = nullptr;
6919}
6920
6921/* #GWL_Display.wp_pointer_gestures */
6922
6925{
6926 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
6927
6928 display->wp.pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>(
6929 wl_registry_bind(display->wl.registry,
6930 params.name,
6931 &zwp_pointer_gestures_v1_interface,
6932 std::min(params.version, version)));
6933 gwl_registry_entry_add(display, params, nullptr);
6934}
6936 void * /*user_data*/,
6937 const bool /*on_exit*/)
6938{
6939 zwp_pointer_gestures_v1 **value_p = &display->wp.pointer_gestures;
6940 zwp_pointer_gestures_v1_destroy(*value_p);
6941 *value_p = nullptr;
6942}
6943
6944/* #GWL_Display.xdg_activation */
6945
6948{
6949 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6950
6951 display->xdg.activation_manager = static_cast<xdg_activation_v1 *>(
6952 wl_registry_bind(display->wl.registry, params.name, &xdg_activation_v1_interface, version));
6953 gwl_registry_entry_add(display, params, nullptr);
6954}
6956 void * /*user_data*/,
6957 const bool /*on_exit*/)
6958{
6959 xdg_activation_v1 **value_p = &display->xdg.activation_manager;
6960 xdg_activation_v1_destroy(*value_p);
6961 *value_p = nullptr;
6962}
6963
6964/* #GWL_Display.wp_fractional_scale_manger */
6965
6968{
6969 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6970
6971 display->wp.fractional_scale_manager = static_cast<wp_fractional_scale_manager_v1 *>(
6972 wl_registry_bind(
6973 display->wl.registry, params.name, &wp_fractional_scale_manager_v1_interface, version));
6974 gwl_registry_entry_add(display, params, nullptr);
6975}
6977 void * /*user_data*/,
6978 const bool /*on_exit*/)
6979{
6980 wp_fractional_scale_manager_v1 **value_p = &display->wp.fractional_scale_manager;
6981 wp_fractional_scale_manager_v1_destroy(*value_p);
6982 *value_p = nullptr;
6983}
6984
6985/* #GWL_Display.wl_viewport */
6986
6989{
6990 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6991
6992 display->wp.viewporter = static_cast<wp_viewporter *>(
6993 wl_registry_bind(display->wl.registry, params.name, &wp_viewporter_interface, version));
6994 gwl_registry_entry_add(display, params, nullptr);
6995}
6997 void * /*user_data*/,
6998 const bool /*on_exit*/)
6999{
7000 wp_viewporter **value_p = &display->wp.viewporter;
7001 wp_viewporter_destroy(*value_p);
7002 *value_p = nullptr;
7003}
7004
7005/* #GWL_Display.wp_primary_selection_device_manager */
7006
7009{
7010 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7011
7012 display->wp.primary_selection_device_manager =
7013 static_cast<zwp_primary_selection_device_manager_v1 *>(
7014 wl_registry_bind(display->wl.registry,
7015 params.name,
7016 &zwp_primary_selection_device_manager_v1_interface,
7017 version));
7018 gwl_registry_entry_add(display, params, nullptr);
7019}
7021 void * /*user_data*/,
7022 const bool /*on_exit*/)
7023{
7024 zwp_primary_selection_device_manager_v1 **value_p =
7025 &display->wp.primary_selection_device_manager;
7026 zwp_primary_selection_device_manager_v1_destroy(*value_p);
7027 *value_p = nullptr;
7028}
7029
7030#ifdef WITH_INPUT_IME
7031
7032/* #GWL_Display.wp_text_input_manager */
7033
7034static void gwl_registry_wp_text_input_manager_add(GWL_Display *display,
7036{
7037 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7038
7039 display->wp.text_input_manager = static_cast<zwp_text_input_manager_v3 *>(wl_registry_bind(
7040 display->wl.registry, params.name, &zwp_text_input_manager_v3_interface, version));
7041 gwl_registry_entry_add(display, params, nullptr);
7042}
7043static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display,
7044 void * /*user_data*/,
7045 const bool /*on_exit*/)
7046{
7047 zwp_text_input_manager_v3 **value_p = &display->wp.text_input_manager;
7048 zwp_text_input_manager_v3_destroy(*value_p);
7049 *value_p = nullptr;
7050}
7051
7052#endif /* WITH_INPUT_IME */
7053
7065 /* Low level interfaces. */
7066 {
7067 /*interface_p*/ &wl_compositor_interface.name,
7068 /*add_fn*/ gwl_registry_compositor_add,
7069 /*update_fn*/ nullptr,
7070 /*remove_fn*/ gwl_registry_compositor_remove,
7071 },
7072 {
7073 /*interface_p*/ &wl_shm_interface.name,
7074 /*add_fn*/ gwl_registry_wl_shm_add,
7075 /*update_fn*/ nullptr,
7076 /*remove_fn*/ gwl_registry_wl_shm_remove,
7077 },
7078 {
7079 /*interface_p*/ &xdg_wm_base_interface.name,
7081 /*update_fn*/ nullptr,
7082 /*remove_fn*/ gwl_registry_xdg_wm_base_remove,
7083 },
7084 /* Managers. */
7085 {
7086 /*interface_p*/ &zxdg_decoration_manager_v1_interface.name,
7088 /*update_fn*/ nullptr,
7090 },
7091 {
7092 /*interface_p*/ &zxdg_output_manager_v1_interface.name,
7094 /*update_fn*/ nullptr,
7096 },
7097 {
7098 /*interface_p*/ &wl_data_device_manager_interface.name,
7100 /*update_fn*/ nullptr,
7102 },
7103 {
7104 /*interface_p*/ &zwp_primary_selection_device_manager_v1_interface.name,
7106 /*update_fn*/ nullptr,
7108 },
7109 {
7110 /*interface_p*/ &zwp_tablet_manager_v2_interface.name,
7112 /*update_fn*/ nullptr,
7114 },
7115 {
7116 /*interface_p*/ &zwp_relative_pointer_manager_v1_interface.name,
7118 /*update_fn*/ nullptr,
7120 },
7121#ifdef WITH_INPUT_IME
7122 {
7123 /*interface_p*/ &zwp_text_input_manager_v3_interface.name,
7124 /*add_fn*/ gwl_registry_wp_text_input_manager_add,
7125 /*update_fn*/ nullptr,
7126 /*remove_fn*/ gwl_registry_wp_text_input_manager_remove,
7127 },
7128#endif
7129 /* Higher level interfaces. */
7130 {
7131 /*interface_p*/ &zwp_pointer_constraints_v1_interface.name,
7133 /*update_fn*/ nullptr,
7135 },
7136 {
7137 /*interface_p*/ &zwp_pointer_gestures_v1_interface.name,
7139 /*update_fn*/ nullptr,
7141 },
7142 {
7143 /*interface_p*/ &xdg_activation_v1_interface.name,
7145 /*update_fn*/ nullptr,
7147 },
7148 {
7149 /*interface_p*/ &wp_fractional_scale_manager_v1_interface.name,
7151 /*update_fn*/ nullptr,
7153 },
7154 {
7155 /*interface_p*/ &wp_viewporter_interface.name,
7157 /*update_fn*/ nullptr,
7159 },
7160 /* Display outputs. */
7161 {
7162 /*interface_p*/ &wl_output_interface.name,
7163 /*add_fn*/ gwl_registry_wl_output_add,
7164 /*update_fn*/ gwl_registry_wl_output_update,
7165 /*remove_fn*/ gwl_registry_wl_output_remove,
7166 },
7167 /* Seats.
7168 * Keep the seat near the end to ensure other types are created first.
7169 * as the seat creates data based on other interfaces. */
7170 {
7171 /*interface_p*/ &wl_seat_interface.name,
7172 /*add_fn*/ gwl_registry_wl_seat_add,
7173 /*update_fn*/ gwl_registry_wl_seat_update,
7174 /*remove_fn*/ gwl_registry_wl_seat_remove,
7175 },
7176
7177 {nullptr},
7178};
7179
7188
7190{
7191 for (const GWL_RegistryHandler *handler = gwl_registry_handlers; handler->interface_p != nullptr;
7192 handler++)
7193 {
7194 if (STREQ(interface, *handler->interface_p)) {
7195 return int(handler - gwl_registry_handlers);
7196 }
7197 }
7198 return -1;
7199}
7200
7202{
7204 "Index out of range");
7205 return &gwl_registry_handlers[interface_slot];
7206}
7207
7208static void global_handle_add(void *data,
7209 [[maybe_unused]] wl_registry *wl_registry,
7210 const uint32_t name,
7211 const char *interface,
7212 const uint32_t version)
7213{
7214 /* Log last since it's useful to know if the interface was handled or not. */
7215 GWL_Display *display = static_cast<GWL_Display *>(data);
7216 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7217
7218 const int interface_slot = gwl_registry_handler_interface_slot_from_string(interface);
7219 bool added = false;
7220
7221 if (interface_slot != -1) {
7222 const GWL_RegistryHandler *handler = &gwl_registry_handlers[interface_slot];
7223 const GWL_RegistryEntry *registry_entry_prev = display->registry_entry;
7224
7225 /* The interface name that is ensured not to be freed. */
7227 params.name = name;
7228 params.interface_slot = interface_slot;
7229 params.version = version;
7230
7231 handler->add_fn(display, params);
7232
7233 added = display->registry_entry != registry_entry_prev;
7234 }
7235
7236 CLOG_INFO(LOG,
7237 2,
7238 "add %s(interface=%s, version=%u, name=%u)",
7239 (interface_slot != -1) ? (added ? "" : "(found but not added)") : "(skipped), ",
7240 interface,
7241 version,
7242 name);
7243
7244 /* Initialization avoids excessive calls by calling update after all have been initialized. */
7245 if (added) {
7246 if (display->registry_skip_update_all == false) {
7247 /* See doc-string for rationale on updating all on add/removal. */
7248 gwl_registry_entry_update_all(display, interface_slot);
7249 }
7250 }
7251}
7252
7262static void global_handle_remove(void *data,
7263 [[maybe_unused]] wl_registry *wl_registry,
7264 const uint32_t name)
7265{
7266 GWL_Display *display = static_cast<GWL_Display *>(data);
7267 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7268
7269 int interface_slot = 0;
7270 const bool removed = gwl_registry_entry_remove_by_name(display, name, &interface_slot);
7271
7272 CLOG_INFO(LOG,
7273 2,
7274 "remove (name=%u, interface=%s)",
7275 name,
7276 removed ? *gwl_registry_handlers[interface_slot].interface_p : "(unknown)");
7277
7278 if (removed) {
7279 if (display->registry_skip_update_all == false) {
7280 /* See doc-string for rationale on updating all on add/removal. */
7281 gwl_registry_entry_update_all(display, interface_slot);
7282 }
7283 }
7284}
7285
7286static const wl_registry_listener registry_listener = {
7287 /*global*/ global_handle_add,
7288 /*global_remove*/ global_handle_remove,
7289};
7290
7291#undef LOG
7292
7295/* -------------------------------------------------------------------- */
7299#ifdef USE_EVENT_BACKGROUND_THREAD
7300
7301static void *gwl_display_event_thread_fn(void *display_voidp)
7302{
7303 GWL_Display *display = static_cast<GWL_Display *>(display_voidp);
7304 const int fd = wl_display_get_fd(display->wl.display);
7305 while (display->events_pthread_is_active) {
7306 /* Wait for an event, this thread is dedicated to event handling. */
7308 display->wl.display, fd, display->system->server_mutex) == -1)
7309 {
7310 break;
7311 }
7312 }
7313
7314 /* Wait until the main thread cancels this thread, otherwise this thread may exit
7315 * before cancel is called, causing a crash on exit. */
7316 while (true) {
7317 pause();
7318 }
7319
7320 return nullptr;
7321}
7322
7323/* Event reading thread. */
7325{
7326 GHOST_ASSERT(display->events_pthread == 0, "Only call once");
7327 display->events_pending.reserve(events_pending_default_size);
7328 display->events_pthread_is_active = true;
7329 pthread_create(&display->events_pthread, nullptr, gwl_display_event_thread_fn, display);
7330 /* Application logic should take priority, this only ensures events don't accumulate when busy
7331 * which typically takes a while (5+ seconds of frantic mouse motion for e.g.). */
7332 pthread_set_min_priority(display->events_pthread);
7333 pthread_detach(display->events_pthread);
7334}
7335
7337{
7338 pthread_cancel(display->events_pthread);
7339}
7340
7341#endif /* USE_EVENT_BACKGROUND_THREAD */
7342
7345/* -------------------------------------------------------------------- */
7352 : GHOST_System(),
7354 server_mutex(new std::mutex),
7355 timer_mutex(new std::mutex),
7356 main_thread_id(std::this_thread::get_id()),
7357#endif
7358 display_(new GWL_Display)
7359{
7362
7363 display_->system = this;
7364 /* Connect to the Wayland server. */
7365 display_->wl.display = wl_display_connect(nullptr);
7366 if (!display_->wl.display) {
7367 display_destroy_and_free_all();
7368 throw std::runtime_error("unable to connect to display!");
7369 }
7370
7371 /* This may be removed later if decorations are required, needed as part of registration. */
7372 display_->xdg_decor = new GWL_XDG_Decor_System;
7373
7374 /* Register interfaces. */
7375 {
7376 display_->registry_skip_update_all = true;
7377 wl_registry *registry = wl_display_get_registry(display_->wl.display);
7378 display_->wl.registry = registry;
7379 wl_registry_add_listener(registry, &registry_listener, display_);
7380 /* First round-trip to receive all registry objects. */
7381 wl_display_roundtrip(display_->wl.display);
7382 /* Second round-trip to receive all output events. */
7383 wl_display_roundtrip(display_->wl.display);
7384
7385 /* Account for dependencies between interfaces. */
7386 gwl_registry_entry_update_all(display_, -1);
7387
7388 display_->registry_skip_update_all = false;
7389 }
7390
7391#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7392 bool libdecor_required = false;
7393 if (const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP")) {
7394 /* See the free-desktop specifications for details on `XDG_CURRENT_DESKTOP`.
7395 * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html */
7396 if (string_elem_split_by_delim(xdg_current_desktop, ':', "GNOME")) {
7397 libdecor_required = true;
7398 }
7399 }
7400
7401 if (libdecor_required) {
7402 /* Ignore windowing requirements when running in background mode,
7403 * as it doesn't make sense to fall back to X11 because of windowing functionality
7404 * in background mode, also LIBDECOR is crashing in background mode `blender -b -f 1`
7405 * for e.g. while it could be fixed, requiring the library at all makes no sense. */
7406 if (background) {
7407 libdecor_required = false;
7408 }
7409# ifdef WITH_GHOST_X11
7410 else if (!has_libdecor && !ghost_wayland_is_x11_available()) {
7411 /* Only require LIBDECOR when X11 is available, otherwise there is nothing to fall back to.
7412 * It's better to open without window decorations than failing entirely. */
7413 libdecor_required = false;
7414 }
7415# endif /* WITH_GHOST_X11 */
7416 }
7417
7418 if (libdecor_required) {
7419 gwl_xdg_decor_system_destroy(display_, display_->xdg_decor);
7420 display_->xdg_decor = nullptr;
7421
7422 if (!has_libdecor) {
7423# ifdef WITH_GHOST_X11
7424 /* LIBDECOR was the only reason X11 was used, let the user know they need it installed. */
7425 fprintf(stderr,
7426 "WAYLAND found but libdecor was not, install libdecor for Wayland support, "
7427 "falling back to X11\n");
7428# endif
7429 display_destroy_and_free_all();
7430 throw std::runtime_error("unable to find libdecor!");
7431 }
7432 }
7433 else {
7434 use_libdecor = false;
7435 }
7436#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
7437
7438#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7439 if (use_libdecor) {
7440 display_->libdecor = new GWL_LibDecor_System;
7441 GWL_LibDecor_System &decor = *display_->libdecor;
7442 decor.context = libdecor_new(display_->wl.display, &libdecor_interface);
7443 if (!decor.context) {
7444 display_destroy_and_free_all();
7445 throw std::runtime_error("unable to create window decorations!");
7446 }
7447 }
7448 else
7449#else
7450 (void)background;
7451#endif
7452 {
7453 const GWL_XDG_Decor_System &decor = *display_->xdg_decor;
7454 if (!decor.shell) {
7455 display_destroy_and_free_all();
7456 throw std::runtime_error("unable to access xdg_shell!");
7457 }
7458 }
7459
7460 /* Without this, the output fractional size from `display->xdg.output_manager` isn't known,
7461 * while this isn't essential, the first window creation uses this for setting the size.
7462 * Supporting both XDG initialized/uninitialized outputs is possible it complicates logic.
7463 * see: #113328 for an example of size on startup issues. */
7464 wl_display_roundtrip(display_->wl.display);
7465
7466#ifdef USE_EVENT_BACKGROUND_THREAD
7468
7469 display_->ghost_timer_manager = new GHOST_TimerManager();
7470#endif
7471}
7472
7473void GHOST_SystemWayland::display_destroy_and_free_all()
7474{
7475 gwl_display_destroy(display_);
7476
7477#ifdef USE_EVENT_BACKGROUND_THREAD
7478 delete server_mutex;
7479 delete timer_mutex;
7480#endif
7481}
7482
7484{
7485 display_destroy_and_free_all();
7486}
7487
7489{
7491
7492 if (success) {
7493#ifdef WITH_INPUT_NDOF
7494 m_ndofManager = new GHOST_NDOFManagerUnix(*this);
7495#endif
7496 return GHOST_kSuccess;
7497 }
7498
7499 return GHOST_kFailure;
7500}
7501
7502#undef pushEvent
7503
7505{
7506 bool any_processed = false;
7507
7508#ifdef USE_EVENT_BACKGROUND_THREAD
7509 if (UNLIKELY(has_pending_actions_for_window.exchange(false))) {
7510 std::lock_guard lock_server_guard{*server_mutex};
7511 for (GHOST_IWindow *iwin : getWindowManager()->getWindows()) {
7512 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
7514 }
7515 }
7516
7517 {
7518 std::lock_guard lock{display_->events_pending_mutex};
7519 for (const GHOST_IEvent *event : display_->events_pending) {
7520
7521 /* Perform actions that aren't handled in a thread. */
7522 switch (event->getType()) {
7524 getWindowManager()->setActiveWindow(event->getWindow());
7525 break;
7526 }
7528 getWindowManager()->setWindowInactive(event->getWindow());
7529 break;
7530 }
7531 default: {
7532 break;
7533 }
7534 }
7535
7536 pushEvent(event);
7537 }
7538 display_->events_pending.clear();
7539
7540 if (UNLIKELY(display_->events_pending.capacity() > events_pending_default_size)) {
7541 /* Avoid over allocation in the case of occasional delay between processing events
7542 * causing many events to be collected and making this into a large array. */
7543 display_->events_pending.shrink_to_fit();
7545 }
7546 }
7547#endif /* USE_EVENT_BACKGROUND_THREAD */
7548
7549 {
7550 const uint64_t now = getMilliSeconds();
7551#ifdef USE_EVENT_BACKGROUND_THREAD
7552 {
7553 std::lock_guard lock_timer_guard{*display_->system->timer_mutex};
7554 if (ghost_timer_manager()->fireTimers(now)) {
7555 any_processed = true;
7556 }
7557 }
7558#endif
7559 if (getTimerManager()->fireTimers(now)) {
7560 any_processed = true;
7561 }
7562 }
7563
7564#ifdef WITH_INPUT_NDOF
7565 if (static_cast<GHOST_NDOFManagerUnix *>(m_ndofManager)->processEvents()) {
7566 /* As NDOF bypasses WAYLAND event handling,
7567 * never wait for an event when an NDOF event was found. */
7568 waitForEvent = false;
7569 any_processed = true;
7570 }
7571#endif /* WITH_INPUT_NDOF */
7572
7573 if (waitForEvent) {
7574#ifdef USE_EVENT_BACKGROUND_THREAD
7575 std::lock_guard lock_server_guard{*server_mutex};
7576#endif
7577 if (wl_display_dispatch(display_->wl.display) == -1) {
7579 }
7580 }
7581 else {
7582#ifdef USE_EVENT_BACKGROUND_THREAD
7583 /* NOTE: this works, but as the events are being read in a thread,
7584 * this could be removed and event handling still works.. */
7585 if (server_mutex->try_lock()) {
7586 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7588 }
7589 server_mutex->unlock();
7590 }
7591#else
7592 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7594 }
7595#endif /* !USE_EVENT_BACKGROUND_THREAD */
7596 }
7597
7598 if (getEventManager()->getNumEvents() > 0) {
7599 any_processed = true;
7600 }
7601
7602 return any_processed;
7603}
7604
7606{
7607 return false;
7608}
7609
7611{
7612#ifdef USE_EVENT_BACKGROUND_THREAD
7613 std::lock_guard lock_server_guard{*server_mutex};
7614#endif
7615
7616 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7617 if (UNLIKELY(!seat)) {
7618 return GHOST_kFailure;
7619 }
7620
7621 /* Only read the underlying `seat->xkb_state` when there is an active window.
7622 * Without this, the following situation occurs:
7623 *
7624 * - A window is activated (before the #wl_keyboard_listener::enter has run).
7625 * - The modifiers from `seat->xkb_state` don't match `seat->key_depressed`.
7626 * - Dummy values are written into `seat->key_depressed` to account for the discrepancy
7627 * (as `seat->xkb_state` is the source of truth), however the number of held modifiers
7628 * is not longer valid (because it's not known from dummy values).
7629 * - #wl_keyboard_listener::enter runs, however the events generated from the state change
7630 * may not match the physically held keys because the dummy values are not accurate.
7631 *
7632 * As this is an edge-case caused by the order of callbacks that run on window activation,
7633 * don't attempt to *fix* the values in `seat->key_depressed` before the keyboard enter
7634 * handler runs. This means the result of `getModifierKeys` may be momentarily incorrect
7635 * however it's corrected once #wl_keyboard_listener::enter runs.
7636 */
7637 const bool is_keyboard_active = seat->keyboard.wl.surface_window != nullptr;
7638 const xkb_mod_mask_t state = is_keyboard_active ?
7639 xkb_state_serialize_mods(seat->xkb.state,
7640 XKB_STATE_MODS_DEPRESSED) :
7641 0;
7642
7643 /* Use local #GWL_KeyboardDepressedState to check which key is pressed.
7644 * Use XKB as the source of truth, if there is any discrepancy. */
7645 for (int i = 0; i < MOD_INDEX_NUM; i++) {
7646 if (UNLIKELY(seat->xkb_keymap_mod_index[i] == XKB_MOD_INVALID)) {
7647 continue;
7648 }
7649
7650 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
7651 /* NOTE(@ideasman42): it's important to write the XKB state back to #GWL_KeyboardDepressedState
7652 * otherwise changes to modifiers in the future wont generate events.
7653 * This can cause modifiers to be stuck when switching between windows in GNOME because
7654 * window activation is handled before the keyboard enter callback runs, see: #107314.
7655 * Now resolved upstream, keep this for GNOME 45 and older releases & misbehaving compositors
7656 * as the workaround doesn't have significant down-sides. */
7657 int16_t &depressed_l = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_l)];
7658 int16_t &depressed_r = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_r)];
7659 bool val_l = depressed_l > 0;
7660 bool val_r = depressed_r > 0;
7661
7662 if (is_keyboard_active) {
7663 const bool val = (state & (1 << seat->xkb_keymap_mod_index[i])) != 0;
7664 /* This shouldn't be needed, but guard against any possibility of modifiers being stuck.
7665 * Warn so if this happens it can be investigated. */
7666 if (val) {
7667 if (UNLIKELY(!(val_l || val_r))) {
7669 "modifier (%s) state is inconsistent (GHOST held keys do not match XKB)",
7670 mod_info.display_name);
7671
7672 /* Picking the left is arbitrary. */
7673 val_l = true;
7674 depressed_l = 1;
7675 }
7676 }
7677 else {
7678 if (UNLIKELY(val_l || val_r)) {
7680 "modifier (%s) state is inconsistent (GHOST released keys do not match XKB)",
7681 mod_info.display_name);
7682 val_l = false;
7683 val_r = false;
7684 depressed_l = 0;
7685 depressed_r = 0;
7686 }
7687 }
7688 }
7689
7690 keys.set(mod_info.mod_l, val_l);
7691 keys.set(mod_info.mod_r, val_r);
7692 }
7693
7694 return GHOST_kSuccess;
7695}
7696
7698{
7699#ifdef USE_EVENT_BACKGROUND_THREAD
7700 std::lock_guard lock_server_guard{*server_mutex};
7701#endif
7702
7703 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7704 if (UNLIKELY(!seat)) {
7705 return GHOST_kFailure;
7706 }
7707 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
7708 if (!seat_state_pointer) {
7709 return GHOST_kFailure;
7710 }
7711
7712 buttons = seat_state_pointer->buttons;
7713 return GHOST_kSuccess;
7714}
7715
7721 const std::unordered_set<std::string> &data_offer_types)
7722{
7723 const char *ghost_supported_types[] = {ghost_wl_mime_text_utf8, ghost_wl_mime_text_plain};
7724 for (size_t i = 0; i < ARRAY_SIZE(ghost_supported_types); i++) {
7725 if (data_offer_types.count(ghost_supported_types[i])) {
7726 return ghost_supported_types[i];
7727 }
7728 }
7729 return nullptr;
7730}
7731
7733{
7734 GWL_Seat *seat = gwl_display_seat_active_get(display);
7735 if (UNLIKELY(!seat)) {
7736 return nullptr;
7737 }
7738 GWL_PrimarySelection *primary = &seat->primary_selection;
7739 std::mutex &mutex = primary->data_offer_mutex;
7740
7741 mutex.lock();
7742 bool mutex_locked = true;
7743 char *data = nullptr;
7744
7745 GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer;
7746 if (data_offer != nullptr) {
7747 const char *mime_receive = system_clipboard_text_mime_type(data_offer->types);
7748 if (mime_receive) {
7749 /* Receive the clipboard in a thread, performing round-trips while waiting.
7750 * This is needed so pasting contents from our own `primary->data_source` doesn't hang. */
7751 struct ThreadResult {
7752 char *data = nullptr;
7753 std::atomic<bool> done = false;
7754 } thread_result;
7755 auto read_clipboard_fn = [](GWL_PrimarySelection_DataOffer *data_offer,
7756 const char *mime_receive,
7757 std::mutex *mutex,
7758 ThreadResult *thread_result) {
7759 size_t data_len = 0;
7760 thread_result->data = read_buffer_from_primary_selection_offer(
7761 data_offer, mime_receive, mutex, true, &data_len);
7762 thread_result->done = true;
7763 };
7764 std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result);
7765 read_thread.detach();
7766
7767 while (!thread_result.done) {
7768 wl_display_roundtrip(display->wl.display);
7769 }
7770 data = thread_result.data;
7771
7772 /* Reading the data offer unlocks the mutex. */
7773 mutex_locked = false;
7774 }
7775 }
7776 if (mutex_locked) {
7777 mutex.unlock();
7778 }
7779 return data;
7780}
7781
7782static char *system_clipboard_get(GWL_Display *display)
7783{
7784 GWL_Seat *seat = gwl_display_seat_active_get(display);
7785 if (UNLIKELY(!seat)) {
7786 return nullptr;
7787 }
7788 std::mutex &mutex = seat->data_offer_copy_paste_mutex;
7789
7790 mutex.lock();
7791 bool mutex_locked = true;
7792 char *data = nullptr;
7793
7794 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
7795 if (data_offer != nullptr) {
7796 const char *mime_receive = system_clipboard_text_mime_type(data_offer->types);
7797 if (mime_receive) {
7798 /* Receive the clipboard in a thread, performing round-trips while waiting.
7799 * This is needed so pasting contents from our own `seat->data_source` doesn't hang. */
7800 struct ThreadResult {
7801 char *data = nullptr;
7802 std::atomic<bool> done = false;
7803 } thread_result;
7804 auto read_clipboard_fn = [](GWL_DataOffer *data_offer,
7805 const char *mime_receive,
7806 std::mutex *mutex,
7807 ThreadResult *thread_result) {
7808 size_t data_len = 0;
7809 thread_result->data = read_buffer_from_data_offer(
7810 data_offer, mime_receive, mutex, true, &data_len);
7811 thread_result->done = true;
7812 };
7813 std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result);
7814 read_thread.detach();
7815
7816 while (!thread_result.done) {
7817 wl_display_roundtrip(display->wl.display);
7818 }
7819 data = thread_result.data;
7820
7821 /* Reading the data offer unlocks the mutex. */
7822 mutex_locked = false;
7823 }
7824 }
7825 if (mutex_locked) {
7826 mutex.unlock();
7827 }
7828 return data;
7829}
7830
7831char *GHOST_SystemWayland::getClipboard(bool selection) const
7832{
7833#ifdef USE_EVENT_BACKGROUND_THREAD
7834 std::lock_guard lock_server_guard{*server_mutex};
7835#endif
7836
7837 char *data = nullptr;
7838 if (selection) {
7840 }
7841 else {
7842 data = system_clipboard_get(display_);
7843 }
7844 return data;
7845}
7846
7847static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer)
7848{
7849 if (!display->wp.primary_selection_device_manager) {
7850 return;
7851 }
7852 GWL_Seat *seat = gwl_display_seat_active_get(display);
7853 if (UNLIKELY(!seat)) {
7854 return;
7855 }
7856 GWL_PrimarySelection *primary = &seat->primary_selection;
7857
7858 std::lock_guard lock{primary->data_source_mutex};
7859
7861
7863 primary->data_source = data_source;
7864
7865 /* Copy buffer. */
7866 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
7867
7868 data_source->wp.source = zwp_primary_selection_device_manager_v1_create_source(
7869 display->wp.primary_selection_device_manager);
7870
7871 zwp_primary_selection_source_v1_add_listener(
7872 data_source->wp.source, &primary_selection_source_listener, primary);
7873
7874 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
7875 zwp_primary_selection_source_v1_offer(data_source->wp.source, ghost_wl_mime_send[i]);
7876 }
7877
7878 if (seat->wp.primary_selection_device) {
7879 zwp_primary_selection_device_v1_set_selection(
7880 seat->wp.primary_selection_device, data_source->wp.source, seat->data_source_serial);
7881 }
7882}
7883
7884static void system_clipboard_put(GWL_Display *display, const char *buffer)
7885{
7886 if (!display->wl.data_device_manager) {
7887 return;
7888 }
7889 GWL_Seat *seat = gwl_display_seat_active_get(display);
7890 if (UNLIKELY(!seat)) {
7891 return;
7892 }
7893 std::lock_guard lock{seat->data_source_mutex};
7894
7895 GWL_DataSource *data_source = seat->data_source;
7896
7897 /* Copy buffer. */
7898 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
7899
7900 data_source->wl.source = wl_data_device_manager_create_data_source(
7901 display->wl.data_device_manager);
7902
7903 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
7904
7905 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
7906 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_send[i]);
7907 }
7908
7909 if (seat->wl.data_device) {
7910 wl_data_device_set_selection(
7911 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
7912 }
7913}
7914
7915void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
7916{
7917#ifdef USE_EVENT_BACKGROUND_THREAD
7918 std::lock_guard lock_server_guard{*server_mutex};
7919#endif
7920
7921 if (selection) {
7923 }
7924 else {
7925 system_clipboard_put(display_, buffer);
7926 }
7927}
7928
7929static constexpr const char *ghost_wl_mime_img_png = "image/png";
7930
7932{
7933#ifdef USE_EVENT_BACKGROUND_THREAD
7934 std::lock_guard lock_server_guard{*server_mutex};
7935#endif
7936
7937 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7938 if (UNLIKELY(!seat)) {
7939 return GHOST_kFailure;
7940 }
7941
7942 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
7943 if (data_offer) {
7944 if (data_offer->types.count(ghost_wl_mime_img_png)) {
7945 return GHOST_kSuccess;
7946 }
7947 }
7948
7949 return GHOST_kFailure;
7950}
7951
7952uint *GHOST_SystemWayland::getClipboardImage(int *r_width, int *r_height) const
7953{
7954#ifdef USE_EVENT_BACKGROUND_THREAD
7955 std::lock_guard lock_server_guard{*server_mutex};
7956#endif
7957
7958 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7959 if (UNLIKELY(!seat)) {
7960 return nullptr;
7961 }
7962
7963 std::mutex &mutex = seat->data_offer_copy_paste_mutex;
7964 mutex.lock();
7965 bool mutex_locked = true;
7966
7967 uint *rgba = nullptr;
7968
7969 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
7970 if (data_offer) {
7971 /* Check if the source offers a supported mime type.
7972 * This check could be skipped, because the paste option is not supposed to be enabled
7973 * otherwise. */
7974 if (data_offer->types.count(ghost_wl_mime_img_png)) {
7975 /* Receive the clipboard in a thread, performing round-trips while waiting,
7976 * so pasting content from own `primary->data_source` doesn't hang. */
7977 struct ThreadResult {
7978 char *data = nullptr;
7979 size_t data_len = 0;
7980 std::atomic<bool> done = false;
7981 } thread_result;
7982
7983 auto read_clipboard_fn = [](GWL_DataOffer *data_offer,
7984 const char *mime_receive,
7985 std::mutex *mutex,
7986 ThreadResult *thread_result) {
7987 thread_result->data = read_buffer_from_data_offer(
7988 data_offer, mime_receive, mutex, false, &thread_result->data_len);
7989 thread_result->done = true;
7990 };
7991 std::thread read_thread(
7992 read_clipboard_fn, data_offer, ghost_wl_mime_img_png, &mutex, &thread_result);
7993 read_thread.detach();
7994
7995 while (!thread_result.done) {
7996 wl_display_roundtrip(display_->wl.display);
7997 }
7998
7999 if (thread_result.data) {
8000 /* Generate the image buffer with the received data. */
8001 ImBuf *ibuf = IMB_ibImageFromMemory((uint8_t *)thread_result.data,
8002 thread_result.data_len,
8003 IB_rect,
8004 nullptr,
8005 "<clipboard>");
8006 free(thread_result.data);
8007
8008 if (ibuf) {
8009 *r_width = ibuf->x;
8010 *r_height = ibuf->y;
8011 const size_t byte_count = size_t(ibuf->x) * size_t(ibuf->y) * 4;
8012 rgba = (uint *)malloc(byte_count);
8013 std::memcpy(rgba, ibuf->byte_buffer.data, byte_count);
8014 IMB_freeImBuf(ibuf);
8015 }
8016 }
8017
8018 /* After reading the data offer, the mutex gets unlocked. */
8019 mutex_locked = false;
8020 }
8021 }
8022
8023 if (mutex_locked) {
8024 mutex.unlock();
8025 }
8026 return rgba;
8027}
8028
8030{
8031#ifdef USE_EVENT_BACKGROUND_THREAD
8032 std::lock_guard lock_server_guard{*server_mutex};
8033#endif
8034
8035 /* Create a #wl_data_source object. */
8036 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8037 if (UNLIKELY(!seat)) {
8038 return GHOST_kFailure;
8039 }
8040 std::lock_guard lock(seat->data_source_mutex);
8041
8042 GWL_DataSource *data_source = seat->data_source;
8043
8044 /* Load buffer into an #ImBuf and convert to PNG. */
8045 ImBuf *ibuf = IMB_allocFromBuffer(reinterpret_cast<uint8_t *>(rgba), nullptr, width, height, 32);
8046 ibuf->ftype = IMB_FTYPE_PNG;
8047 ibuf->foptions.quality = 15;
8048 if (!IMB_saveiff(ibuf, "<memory>", IB_rect | IB_mem)) {
8049 IMB_freeImBuf(ibuf);
8050 return GHOST_kFailure;
8051 }
8052
8053 /* Copy #ImBuf encoded_buffer to data source. */
8054 GWL_SimpleBuffer *imgbuffer = &data_source->buffer_out;
8055 gwl_simple_buffer_free_data(imgbuffer);
8056 imgbuffer->data_size = ibuf->encoded_buffer_size;
8057 char *data = static_cast<char *>(malloc(imgbuffer->data_size));
8058 std::memcpy(data, ibuf->encoded_buffer.data, ibuf->encoded_buffer_size);
8059 imgbuffer->data = data;
8060
8061 data_source->wl.source = wl_data_device_manager_create_data_source(
8062 display_->wl.data_device_manager);
8063 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
8064
8065 /* Advertise the mime types supported. */
8066 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_img_png);
8067
8068 if (seat->wl.data_device) {
8069 wl_data_device_set_selection(
8070 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
8071 }
8072
8073 IMB_freeImBuf(ibuf);
8074 return GHOST_kSuccess;
8075}
8076
8078{
8079#ifdef USE_EVENT_BACKGROUND_THREAD
8080 std::lock_guard lock_server_guard{*server_mutex};
8081#endif
8082
8083 return display_ ? uint8_t(display_->outputs.size()) : 0;
8084}
8085
8087{
8088 /* Match the timing method used by LIBINPUT, so the result is closer to WAYLAND's time-stamps. */
8089 timespec ts = {0, 0};
8090 clock_gettime(CLOCK_MONOTONIC, &ts);
8091 return (uint64_t(ts.tv_sec) * 1000) + uint64_t(ts.tv_nsec / 1000000);
8092}
8093
8095 const GWL_SeatStatePointer *seat_state_pointer,
8096 const GHOST_WindowWayland *win,
8097 int32_t &x,
8098 int32_t &y)
8099{
8100
8101 if (win->getCursorGrabModeIsWarp()) {
8102 /* As the cursor is restored at the warped location,
8103 * apply warping when requesting the cursor location. */
8104 GHOST_Rect wrap_bounds{};
8105 if (win->getCursorGrabBounds(wrap_bounds) == GHOST_kFailure) {
8106 win->getClientBounds(wrap_bounds);
8107 }
8108 int xy_wrap[2] = {
8109 seat_state_pointer->xy[0],
8110 seat_state_pointer->xy[1],
8111 };
8112
8113 GHOST_Rect wrap_bounds_scale;
8114
8115 wrap_bounds_scale.m_l = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_l));
8116 wrap_bounds_scale.m_t = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_t));
8117 wrap_bounds_scale.m_r = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_r));
8118 wrap_bounds_scale.m_b = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_b));
8119 wrap_bounds_scale.wrapPoint(UNPACK2(xy_wrap), 0, win->getCursorGrabAxis());
8120
8121 x = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[0]));
8122 y = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[1]));
8123 }
8124 else {
8125 x = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[0]));
8126 y = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[1]));
8127 }
8128
8129 return GHOST_kSuccess;
8130}
8131
8134 const int32_t x,
8135 const int32_t y)
8136{
8137 /* NOTE: WAYLAND doesn't support warping the cursor.
8138 * However when grab is enabled, we already simulate a cursor location
8139 * so that can be set to a new location. */
8140 if (!seat->wp.relative_pointer) {
8141 return GHOST_kFailure;
8142 }
8143 const wl_fixed_t xy_next[2]{
8144 win->wl_fixed_from_window(wl_fixed_from_int(x)),
8145 win->wl_fixed_from_window(wl_fixed_from_int(y)),
8146 };
8147
8148 /* As the cursor was "warped" generate an event at the new location. */
8149 const uint64_t event_ms = seat->system->getMilliSeconds();
8150 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
8151
8152 return GHOST_kSuccess;
8153}
8154
8156 int32_t &x,
8157 int32_t &y) const
8158{
8159#ifdef USE_EVENT_BACKGROUND_THREAD
8160 std::lock_guard lock_server_guard{*server_mutex};
8161#endif
8162
8163 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8164 if (UNLIKELY(!seat)) {
8165 return GHOST_kFailure;
8166 }
8167 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8168 if (!seat_state_pointer || !seat_state_pointer->wl.surface_window) {
8169 return GHOST_kFailure;
8170 }
8171 const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window);
8172 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8173}
8174
8176 const int32_t x,
8177 const int32_t y)
8178{
8179#ifdef USE_EVENT_BACKGROUND_THREAD
8180 std::lock_guard lock_server_guard{*server_mutex};
8181#endif
8182
8183 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8184 if (UNLIKELY(!seat)) {
8185 return GHOST_kFailure;
8186 }
8187 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window);
8188 return setCursorPositionClientRelative_impl(seat, win, x, y);
8189}
8190
8192{
8193#ifdef USE_EVENT_BACKGROUND_THREAD
8194 std::lock_guard lock_server_guard{*server_mutex};
8195#endif
8196
8197 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8198 if (UNLIKELY(!seat)) {
8199 return GHOST_kFailure;
8200 }
8201 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8202 if (!seat_state_pointer) {
8203 return GHOST_kFailure;
8204 }
8205
8206 if (wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window) {
8207 const GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8208 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8209 }
8210 return GHOST_kFailure;
8211}
8212
8214{
8215#ifdef USE_EVENT_BACKGROUND_THREAD
8216 std::lock_guard lock_server_guard{*server_mutex};
8217#endif
8218
8219 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8220 if (UNLIKELY(!seat)) {
8221 return GHOST_kFailure;
8222 }
8223
8224 /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
8225 * In the case of setting the cursor location, tablets don't support this. */
8226 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
8227 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8228 return setCursorPositionClientRelative_impl(seat, win, x, y);
8229 }
8230 return GHOST_kFailure;
8231}
8232
8234{
8235#ifdef USE_EVENT_BACKGROUND_THREAD
8236 std::lock_guard lock_server_guard{*server_mutex};
8237#endif
8238
8239 if (!display_->outputs.empty()) {
8240 /* We assume first output as main. */
8241 const GWL_Output *output = display_->outputs[0];
8242 int32_t size_native[2] = {UNPACK2(output->size_native)};
8243 if (ELEM(output->transform, WL_OUTPUT_TRANSFORM_90, WL_OUTPUT_TRANSFORM_270)) {
8244 std::swap(size_native[0], size_native[1]);
8245 }
8246 width = uint32_t(size_native[0]);
8247 height = uint32_t(size_native[1]);
8248 }
8249}
8250
8252{
8253#ifdef USE_EVENT_BACKGROUND_THREAD
8254 std::lock_guard lock_server_guard{*server_mutex};
8255#endif
8256 if (!display_->outputs.empty()) {
8257 int32_t xy_min[2] = {INT32_MAX, INT32_MAX};
8258 int32_t xy_max[2] = {INT32_MIN, INT32_MIN};
8259
8260 for (const GWL_Output *output : display_->outputs) {
8261 int32_t xy[2] = {0, 0};
8262 int32_t size_native[2] = {UNPACK2(output->size_native)};
8263 if (output->has_position_logical) {
8264 xy[0] = output->position_logical[0];
8265 xy[1] = output->position_logical[1];
8266 }
8267 if (ELEM(output->transform, WL_OUTPUT_TRANSFORM_90, WL_OUTPUT_TRANSFORM_270)) {
8268 std::swap(size_native[0], size_native[1]);
8269 }
8270 xy_min[0] = std::min(xy_min[0], xy[0]);
8271 xy_min[1] = std::min(xy_min[1], xy[1]);
8272 xy_max[0] = std::max(xy_max[0], xy[0] + size_native[0]);
8273 xy_max[1] = std::max(xy_max[1], xy[1] + size_native[1]);
8274 }
8275
8276 width = xy_max[0] - xy_min[0];
8277 height = xy_max[1] - xy_min[1];
8278 }
8279}
8280
8282{
8283#ifdef USE_EVENT_BACKGROUND_THREAD
8284 std::lock_guard lock_server_guard{*server_mutex};
8285#endif
8286
8287 const bool debug_context = (gpuSettings.flags & GHOST_gpuDebugContext) != 0;
8288
8289 switch (gpuSettings.context_type) {
8290
8291#ifdef WITH_VULKAN_BACKEND
8292 case GHOST_kDrawingContextTypeVulkan: {
8293 /* Create new off-screen surface only for vulkan. */
8294 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8295
8296 GHOST_Context *context = new GHOST_ContextVK(false,
8297 GHOST_kVulkanPlatformWayland,
8298 0,
8299 nullptr,
8300 wl_surface,
8301 display_->wl.display,
8302 nullptr,
8303 1,
8304 2,
8305 debug_context,
8306 gpuSettings.preferred_device);
8307
8308 if (context->initializeDrawingContext()) {
8309 context->setUserData(wl_surface);
8310 return context;
8311 }
8312 delete context;
8313
8314 if (wl_surface) {
8315 wl_surface_destroy(wl_surface);
8316 }
8317 return nullptr;
8318 }
8319#endif /* WITH_VULKAN_BACKEND */
8320
8321#ifdef WITH_OPENGL_BACKEND
8322 case GHOST_kDrawingContextTypeOpenGL: {
8323 /* Create new off-screen window. */
8324 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8325 wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
8326
8327 for (int minor = 6; minor >= 3; --minor) {
8328 /* Caller must lock `system->server_mutex`. */
8329 GHOST_Context *context = new GHOST_ContextEGL(
8330 this,
8331 false,
8332 EGLNativeWindowType(egl_window),
8333 EGLNativeDisplayType(display_->wl.display),
8334 EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
8335 4,
8336 minor,
8338 (debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
8340 EGL_OPENGL_API);
8341
8342 if (context->initializeDrawingContext()) {
8343 wl_surface_set_user_data(wl_surface, egl_window);
8344 context->setUserData(wl_surface);
8345 return context;
8346 }
8347 delete context;
8348 }
8349
8350 GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
8351 if (wl_surface) {
8352 wl_surface_destroy(wl_surface);
8353 }
8354 if (egl_window) {
8355 wl_egl_window_destroy(egl_window);
8356 }
8357 return nullptr;
8358 }
8359#endif /* WITH_OPENGL_BACKEND */
8360
8361 default:
8362 /* Unsupported backend. */
8363 return nullptr;
8364 }
8365}
8366
8368{
8369#ifdef USE_EVENT_BACKGROUND_THREAD
8370 std::lock_guard lock_server_guard{*server_mutex};
8371#endif
8372
8374#ifdef WITH_OPENGL_BACKEND
8375 if (dynamic_cast<GHOST_ContextEGL *>(context)) {
8376 type = GHOST_kDrawingContextTypeOpenGL;
8377 }
8378#endif /* WITH_OPENGL_BACKEND */
8379#ifdef WITH_VULKAN_BACKEND
8380 if (dynamic_cast<GHOST_ContextVK *>(context)) {
8381 type = GHOST_kDrawingContextTypeVulkan;
8382 }
8383#endif /* WITH_VULKAN_BACKEND */
8384
8385 wl_surface *wl_surface = static_cast<struct wl_surface *>(
8386 (static_cast<GHOST_Context *>(context))->getUserData());
8387
8388 /* Delete the context before the window so the context is able to release
8389 * native resources (such as the #EGLSurface) before WAYLAND frees them. */
8390 delete context;
8391
8392#ifdef WITH_OPENGL_BACKEND
8393 if (type == GHOST_kDrawingContextTypeOpenGL) {
8394 wl_egl_window *egl_window = static_cast<wl_egl_window *>(wl_surface_get_user_data(wl_surface));
8395 if (egl_window != nullptr) {
8396 wl_egl_window_destroy(egl_window);
8397 }
8398 }
8399#endif /* WITH_OPENGL_BACKEND */
8400
8401 wl_surface_destroy(wl_surface);
8402
8403 (void)type; /* Maybe unused. */
8404
8405 return GHOST_kSuccess;
8406}
8407
8409 const int32_t left,
8410 const int32_t top,
8411 const uint32_t width,
8412 const uint32_t height,
8414 const GHOST_GPUSettings gpuSettings,
8415 const bool exclusive,
8416 const bool is_dialog,
8417 const GHOST_IWindow *parentWindow)
8418{
8419 /* Globally store pointer to window manager. */
8421 this,
8422 title,
8423 left,
8424 top,
8425 width,
8426 height,
8427 state,
8428 parentWindow,
8429 gpuSettings.context_type,
8430 is_dialog,
8431 ((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
8432 exclusive,
8433 (gpuSettings.flags & GHOST_gpuDebugContext) != 0,
8434 gpuSettings.preferred_device);
8435
8436 if (window) {
8437 if (window->getValid()) {
8438 m_windowManager->addWindow(window);
8440 const uint64_t event_ms = getMilliSeconds();
8441 pushEvent(new GHOST_Event(event_ms, GHOST_kEventWindowSize, window));
8442 }
8443 else {
8444 delete window;
8445 window = nullptr;
8446 }
8447 }
8448
8449 return window;
8450}
8451
8452static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
8453{
8454 if (mode == GHOST_kGrabWrap) {
8455 return true;
8456 }
8457#ifdef USE_GNOME_CONFINE_HACK
8458 if (mode == GHOST_kGrabNormal) {
8459 if (use_software_confine) {
8460 return true;
8461 }
8462 }
8463#else
8464 (void)use_software_confine;
8465#endif
8466 return false;
8467}
8468
8470{
8471 /* Caller must lock `server_mutex`. */
8472
8473 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8474 if (UNLIKELY(!seat)) {
8475 return GHOST_kFailure;
8476 }
8477
8478 const char *cursor_name = nullptr;
8479 const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, shape, &cursor_name);
8480 if (wl_cursor == nullptr) {
8481 return GHOST_kFailure;
8482 }
8483
8484 GWL_Cursor *cursor = &seat->cursor;
8485 wl_cursor_image *image = wl_cursor->images[0];
8486 wl_buffer *buffer = wl_cursor_image_get_buffer(image);
8487 if (!buffer) {
8488 return GHOST_kFailure;
8489 }
8490
8491 cursor->visible = true;
8492 cursor->is_custom = false;
8493 cursor->wl.buffer = buffer;
8494 cursor->wl.image = *image;
8495 cursor->wl.theme_cursor = wl_cursor;
8496 cursor->wl.theme_cursor_name = cursor_name;
8497
8499
8500 return GHOST_kSuccess;
8501}
8502
8504{
8505 /* No need to lock `server_mutex`. */
8506 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8507 if (UNLIKELY(!seat)) {
8508 return GHOST_kFailure;
8509 }
8510
8511 const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, cursorShape, nullptr);
8512 if (wl_cursor == nullptr) {
8513 return GHOST_kFailure;
8514 }
8515 return GHOST_kSuccess;
8516}
8517
8519 const uint8_t *mask,
8520 const int sizex,
8521 const int sizey,
8522 const int hotX,
8523 const int hotY,
8524 const bool /*canInvertColor*/)
8525{
8526 /* Caller needs to lock `server_mutex`. */
8527 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8528 if (UNLIKELY(!seat)) {
8529 return GHOST_kFailure;
8530 }
8531
8532 GWL_Cursor *cursor = &seat->cursor;
8533 if (cursor->custom_data) {
8534 munmap(cursor->custom_data, cursor->custom_data_size);
8535 cursor->custom_data = nullptr;
8536 cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */
8537 }
8538
8539 const int32_t size_xy[2] = {sizex, sizey};
8540 wl_buffer *buffer = ghost_wl_buffer_create_for_image(display_->wl.shm,
8541 size_xy,
8542 WL_SHM_FORMAT_ARGB8888,
8543 &cursor->custom_data,
8544 &cursor->custom_data_size);
8545 if (buffer == nullptr) {
8546 return GHOST_kFailure;
8547 }
8548
8549 wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor);
8550
8551 static constexpr uint32_t black = 0xFF000000;
8552 static constexpr uint32_t white = 0xFFFFFFFF;
8553 static constexpr uint32_t transparent = 0x00000000;
8554
8555 uint8_t datab = 0, maskb = 0;
8556
8557 for (int y = 0; y < sizey; ++y) {
8558 uint32_t *pixel = &static_cast<uint32_t *>(cursor->custom_data)[y * sizex];
8559 for (int x = 0; x < sizex; ++x) {
8560 if ((x % 8) == 0) {
8561 datab = *bitmap++;
8562 maskb = *mask++;
8563
8564 /* Reverse bit order. */
8565 datab = uint8_t((datab * 0x0202020202ULL & 0x010884422010ULL) % 1023);
8566 maskb = uint8_t((maskb * 0x0202020202ULL & 0x010884422010ULL) % 1023);
8567 }
8568
8569 if (maskb & 0x80) {
8570 *pixel++ = (datab & 0x80) ? white : black;
8571 }
8572 else {
8573 *pixel++ = (datab & 0x80) ? white : transparent;
8574 }
8575 datab <<= 1;
8576 maskb <<= 1;
8577 }
8578 }
8579
8580 cursor->visible = true;
8581 cursor->is_custom = true;
8582 cursor->custom_scale = 1; /* TODO: support Hi-DPI custom cursors. */
8583 cursor->wl.buffer = buffer;
8584 cursor->wl.image.width = uint32_t(sizex);
8585 cursor->wl.image.height = uint32_t(sizey);
8586 cursor->wl.image.hotspot_x = uint32_t(hotX);
8587 cursor->wl.image.hotspot_y = uint32_t(hotY);
8588 cursor->wl.theme_cursor = nullptr;
8589 cursor->wl.theme_cursor_name = nullptr;
8590
8592
8593 return GHOST_kSuccess;
8594}
8595
8597{
8598 /* Caller must lock `server_mutex`. */
8599 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8600 if (UNLIKELY(!seat)) {
8601 return GHOST_kFailure;
8602 }
8603
8604 GWL_Cursor *cursor = &seat->cursor;
8605 if (cursor->custom_data == nullptr) {
8606 return GHOST_kFailure;
8607 }
8608 if (!cursor->is_custom) {
8609 return GHOST_kFailure;
8610 }
8611
8612 bitmap->data_size[0] = cursor->wl.image.width;
8613 bitmap->data_size[1] = cursor->wl.image.height;
8614
8615 bitmap->hot_spot[0] = cursor->wl.image.hotspot_x;
8616 bitmap->hot_spot[1] = cursor->wl.image.hotspot_y;
8617
8618 bitmap->data = static_cast<uint8_t *>(cursor->custom_data);
8619
8620 return GHOST_kSuccess;
8621}
8622
8624{
8625 /* Caller must lock `server_mutex`. */
8626 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8627 if (UNLIKELY(!seat)) {
8628 return GHOST_kFailure;
8629 }
8630
8632 return GHOST_kSuccess;
8633}
8634
8636{
8637 /* It's possible there are no seats, ignore the value in this case. */
8638 GHOST_ASSERT(((gwl_display_seat_active_get(display_) == nullptr) ||
8640 "The trackpad direction was expected to be initialized");
8641
8642 return GHOST_TCapabilityFlag(
8644 ~(
8645 /* WAYLAND doesn't support accessing the window position. */
8647 /* WAYLAND doesn't support setting the cursor position directly,
8648 * this is an intentional choice, forcing us to use a software cursor in this case. */
8650 /* Some drivers don't support front-buffer reading, see: #98462 & #106264.
8651 *
8652 * NOTE(@ideasman42): the EGL flag `EGL_BUFFER_PRESERVED` is intended request support for
8653 * front-buffer reading however in my tests requesting the flag didn't work with AMD,
8654 * and it's not even requirement - so we can't rely on this feature being supported.
8655 *
8656 * Instead of assuming this is not supported, the graphics card driver could be inspected
8657 * (enable for NVIDIA for e.g.), but the advantage in supporting this is minimal.
8658 * In practice it means an off-screen buffer is used to redraw the window for the
8659 * screen-shot and eye-dropper sampling logic, both operations where the overhead
8660 * is negligible. */
8662 /* This WAYLAND back-end has not yet implemented desktop color sample. */
8664 /* This flag will eventually be removed. */
8666 0 :
8668}
8669
8671{
8672 /* Caller must lock `server_mutex`. */
8673 const GWL_Seat *seat = gwl_display_seat_active_get(display_);
8674 if (UNLIKELY(!seat)) {
8675 return false;
8676 }
8677
8678#ifdef USE_GNOME_CONFINE_HACK
8679 const bool use_software_confine = seat->use_pointer_software_confine;
8680#else
8681 const bool use_software_confine = false;
8682#endif
8683
8684 return cursor_is_software(mode, use_software_confine);
8685}
8686
8687#ifdef USE_GNOME_CONFINE_HACK
8690{
8691# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
8692 if (use_gnome_confine_hack == false) {
8693 return false;
8694 }
8695# endif
8696 if (mode != GHOST_kGrabNormal) {
8697 return false;
8698 }
8700 if (!win) {
8701 return false;
8702 }
8703
8704# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
8705 if (win->scale_get() <= 1) {
8706 return false;
8707 }
8708# endif
8709 return true;
8710}
8711#endif
8712
8714 const bool use_software_confine)
8715{
8716 /* Initialize all members. */
8717 GWL_SeatStateGrab grab_state;
8718 /* Warping happens to require software cursor which also hides. */
8719 grab_state.use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine;
8720 grab_state.use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false);
8721 return grab_state;
8722}
8723
8725{
8726 if (m_multitouchGestures == use) {
8727 return;
8728 }
8730
8731#ifdef USE_EVENT_BACKGROUND_THREAD
8732 /* Ensure this listeners aren't removed while events are generated. */
8733 std::lock_guard lock_server_guard{*server_mutex};
8734#endif
8735 for (GWL_Seat *seat : display_->seats) {
8736 if (use == gwl_seat_capability_pointer_multitouch_check(seat, use)) {
8737 continue;
8738 }
8739 if (use) {
8741 }
8742 else {
8744 }
8745 }
8746}
8747
8750/* -------------------------------------------------------------------- */
8754static const char *ghost_wl_output_tag_id = "GHOST-output";
8755static const char *ghost_wl_surface_tag_id = "GHOST-window";
8756static const char *ghost_wl_surface_cursor_pointer_tag_id = "GHOST-cursor-pointer";
8757static const char *ghost_wl_surface_cursor_tablet_tag_id = "GHOST-cursor-tablet";
8758
8759bool ghost_wl_output_own(const wl_output *wl_output)
8760{
8761 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_output);
8762 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_output_tag_id;
8763}
8764
8766{
8767 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8768 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_tag_id;
8769}
8770
8775
8777{
8778 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8779 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) ==
8781}
8782
8784{
8785 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8786 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_cursor_tablet_tag_id;
8787}
8788
8789void ghost_wl_output_tag(wl_output *wl_output)
8790{
8791 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_output);
8793}
8794
8796{
8797 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8799}
8800
8802{
8803 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8805}
8806
8808{
8809 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8811}
8812
8815/* -------------------------------------------------------------------- */
8822{
8823 return display_->wl.display;
8824}
8825
8827{
8828 return display_->wl.compositor;
8829}
8830
8831zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager_get()
8832{
8833 return display_->wp.primary_selection_device_manager;
8834}
8835
8837{
8838 return display_->xdg.activation_manager;
8839}
8840
8842{
8843 return display_->wp.fractional_scale_manager;
8844}
8846{
8847 return display_->wp.viewporter;
8848}
8849
8851{
8852 return display_->wp.pointer_gestures;
8853}
8854
8855/* This value is expected to match the base name of the `.desktop` file. see #101805.
8856 *
8857 * NOTE: the XDG desktop-entry-spec defines that this should follow the "reverse DNS" convention.
8858 * For e.g. `org.blender.Blender` - however the `.desktop` file distributed with Blender is
8859 * simply called `blender.desktop`, so the it's important to follow that name.
8860 * Other distributions such as SNAP & FLATPAK may need to change this value #101779.
8861 * Currently there isn't a way to configure this, we may want to support that. */
8862static const char *ghost_wl_app_id = (
8863#ifdef WITH_GHOST_WAYLAND_APP_ID
8864 STRINGIFY(WITH_GHOST_WAYLAND_APP_ID)
8865#else
8866 "blender"
8867#endif
8868);
8869
8871{
8872 return ghost_wl_app_id;
8873}
8874
8875#ifdef WITH_GHOST_WAYLAND_LIBDECOR
8876
8877libdecor *GHOST_SystemWayland::libdecor_context_get()
8878{
8879 return display_->libdecor->context;
8880}
8881
8882#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
8883
8885{
8886 return display_->xdg_decor->shell;
8887}
8888
8890{
8891 return display_->xdg_decor->manager;
8892}
8893
8894/* End `xdg_decor`. */
8895
8896const std::vector<GWL_Output *> &GHOST_SystemWayland::outputs_get() const
8897{
8898 return display_->outputs;
8899}
8900
8902{
8903 return display_->wl.shm;
8904}
8905
8906#ifdef USE_EVENT_BACKGROUND_THREAD
8911#endif
8912
8915/* -------------------------------------------------------------------- */
8921#ifdef WITH_INPUT_IME
8922
8924 int32_t x,
8925 int32_t y,
8926 int32_t w,
8927 int32_t h,
8928 bool completed) const
8929{
8930 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8931 if (UNLIKELY(!seat)) {
8932 return;
8933 }
8934 if (seat->wp.text_input == nullptr) {
8935 return;
8936 }
8937
8938 /* Prevent a feedback loop because the commits from this function cause
8939 * #zwp_text_input_v3_listener::preedit_string to run again which sends an event,
8940 * refreshing the position, running this function again. */
8941 gwl_seat_ime_result_reset(seat);
8942
8943 /* Don't re-enable if we're already enabled. */
8944 if (seat->ime.is_enabled && completed) {
8945 return;
8946 }
8947
8948 bool force_rect_update = false;
8949 if (seat->ime.is_enabled == false) {
8950 seat->ime.has_preedit = false;
8951 seat->ime.is_enabled = true;
8952
8953 /* NOTE(@flibit): For some reason this has to be done twice,
8954 * it appears to be a bug in mutter? Maybe? */
8955 zwp_text_input_v3_enable(seat->wp.text_input);
8956 zwp_text_input_v3_commit(seat->wp.text_input);
8957 zwp_text_input_v3_enable(seat->wp.text_input);
8958 zwp_text_input_v3_commit(seat->wp.text_input);
8959
8960 /* Now that it's enabled, set the input properties. */
8961 zwp_text_input_v3_set_content_type(seat->wp.text_input,
8962 ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
8963 ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
8964
8965 gwl_seat_ime_rect_reset(seat);
8966 force_rect_update = true;
8967 }
8968
8969 if ((force_rect_update == false) && /* Was just created, always update. */
8970 (seat->ime.rect.x == x) && /* X. */
8971 (seat->ime.rect.y == y) && /* Y. */
8972 (seat->ime.rect.w == w) && /* W. */
8973 (seat->ime.rect.h == h)) /* H. */
8974 {
8975 /* Only re-update the rectangle as needed. */
8976 }
8977 else {
8978 const int rect_x = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(x)));
8979 const int rect_y = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(y)));
8980 const int rect_w = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(w))) + 1;
8981 const int rect_h = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(h))) + 1;
8982
8983 zwp_text_input_v3_set_cursor_rectangle(seat->wp.text_input, rect_x, rect_y, rect_w, rect_h);
8984
8985 zwp_text_input_v3_commit(seat->wp.text_input);
8986
8987 seat->ime.rect.x = x;
8988 seat->ime.rect.y = y;
8989 seat->ime.rect.w = w;
8990 seat->ime.rect.h = h;
8991 }
8992}
8993
8994void GHOST_SystemWayland::ime_end(const GHOST_WindowWayland * /*window*/) const
8995{
8996 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8997 if (UNLIKELY(!seat)) {
8998 return;
8999 }
9000
9001 seat->ime.is_enabled = false;
9002
9003 gwl_seat_ime_rect_reset(seat);
9004
9005 if (seat->wp.text_input == nullptr) {
9006 return;
9007 }
9008
9009 zwp_text_input_v3_disable(seat->wp.text_input);
9010 zwp_text_input_v3_commit(seat->wp.text_input);
9011}
9012
9013#endif /* WITH_INPUT_IME */
9014
9017/* -------------------------------------------------------------------- */
9022{
9023 GHOST_ASSERT(wl_output, "output must not be nullptr");
9024 GHOST_ASSERT(ghost_wl_output_own(wl_output), "output is not owned by GHOST");
9025 GWL_Output *output = static_cast<GWL_Output *>(wl_output_get_user_data(wl_output));
9026 return output;
9027}
9028
9030{
9031 GHOST_ASSERT(wl_surface, "wl_surface must not be nullptr");
9032 GHOST_ASSERT(ghost_wl_surface_own(wl_surface), "wl_surface is not owned by GHOST");
9033 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(
9034 wl_surface_get_user_data(wl_surface));
9035 return win;
9036}
9037
9040/* -------------------------------------------------------------------- */
9047{
9048 /* NOTE(@ideasman42): Return a time compatible with `getMilliSeconds()`,
9049 * this is needed as WAYLAND time-stamps don't have a well defined beginning
9050 * use `timestamp_as_uint` to calculate an offset which is applied to future events.
9051 * This is updated because time may have passed between generating the time-stamp and `now`.
9052 * The method here is used by SDL. */
9053 uint64_t timestamp = uint64_t(timestamp_as_uint);
9054
9055 GWL_DisplayTimeStamp &input_timestamp = display_->input_timestamp;
9056 if (UNLIKELY(timestamp_as_uint < input_timestamp.last)) {
9057 /* NOTE(@ideasman42): Sometimes event times are out of order,
9058 * while this should _never_ happen, it occasionally does:
9059 * - When resizing the window then clicking on the window with GNOME+LIBDECOR.
9060 * - With accepting IME text with GNOME-v45.2 the timestamp is in seconds, see:
9061 * https://gitlab.gnome.org/GNOME/mutter/-/issues/3214
9062 * Accept events must occur within ~25 days, out-of-order time-stamps above this time-frame
9063 * will be treated as a wrapped integer. */
9064 if (input_timestamp.last - timestamp_as_uint > std::numeric_limits<uint32_t>::max() / 2) {
9065 /* Finally check to avoid invalid rollover,
9066 * ensure the rolled over time is closer to "now" than it is currently. */
9067 const uint64_t offset_test = input_timestamp.offset +
9068 uint64_t(std::numeric_limits<uint32_t>::max()) + 1;
9069 const uint64_t now = getMilliSeconds();
9070 if (sub_abs_u64(now, timestamp + offset_test) <
9071 sub_abs_u64(now, timestamp + input_timestamp.offset))
9072 {
9073 /* 32-bit timer rollover, bump the offset. */
9074 input_timestamp.offset = offset_test;
9075 }
9076 }
9077 }
9078 input_timestamp.last = timestamp_as_uint;
9079
9080 if (input_timestamp.exact_match) {
9081 timestamp += input_timestamp.offset;
9082 }
9083 else {
9084 const uint64_t now = getMilliSeconds();
9085 const uint32_t now_as_uint32 = uint32_t(now);
9086 if (now_as_uint32 == timestamp_as_uint) {
9087 input_timestamp.exact_match = true;
9088 /* For systems with up times exceeding 47 days
9089 * it's possible we need to begin with an offset. */
9090 input_timestamp.offset = now - uint64_t(now_as_uint32);
9091 timestamp = now;
9092 }
9093 else {
9094 if (!input_timestamp.offset) {
9095 input_timestamp.offset = (now - timestamp);
9096 }
9097 timestamp += input_timestamp.offset;
9098
9099 if (timestamp > now) {
9100 input_timestamp.offset -= (timestamp - now);
9101 timestamp = now;
9102 }
9103 }
9104 }
9105
9106 return timestamp;
9107}
9108
9110{
9111#ifdef USE_EVENT_BACKGROUND_THREAD
9112 if (main_thread_id != std::this_thread::get_id()) {
9113 std::lock_guard lock{display_->events_pending_mutex};
9114 display_->events_pending.push_back(event);
9115 return GHOST_kSuccess;
9116 }
9117#endif
9118 return pushEvent(event);
9119}
9120
9122{
9123 gwl_display_seat_active_set(display_, seat);
9124}
9125
9127{
9128 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9129 if (UNLIKELY(!seat)) {
9130 return nullptr;
9131 }
9132
9133 serial = seat->data_source_serial;
9134 return seat->wl.seat;
9135}
9136
9138{
9139 bool changed = false;
9140#define SURFACE_CLEAR_PTR(surface_test) \
9141 if (surface_test == wl_surface) { \
9142 surface_test = nullptr; \
9143 changed = true; \
9144 } \
9145 ((void)0);
9146
9147 /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
9148 for (GWL_Seat *seat : display_->seats) {
9149 SURFACE_CLEAR_PTR(seat->pointer.wl.surface_window);
9151 SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window);
9153#ifdef WITH_INPUT_IME
9154 SURFACE_CLEAR_PTR(seat->ime.surface_window);
9155#endif
9156 }
9157#undef SURFACE_CLEAR_PTR
9158
9159 return changed;
9160}
9161
9162bool GHOST_SystemWayland::output_unref(wl_output *wl_output)
9163{
9164 bool changed = false;
9165 if (!ghost_wl_output_own(wl_output)) {
9166 return changed;
9167 }
9168
9169 /* NOTE: keep in sync with `output_scale_update`. */
9170 GWL_Output *output = ghost_wl_output_user_data(wl_output);
9171 const GHOST_WindowManager *window_manager = getWindowManager();
9172 if (window_manager) {
9173 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9174 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9175 if (win->outputs_leave(output)) {
9177 changed = true;
9178 }
9179 }
9180 }
9181 for (GWL_Seat *seat : display_->seats) {
9182 if (seat->pointer.outputs.erase(output)) {
9183 changed = true;
9184 }
9185 if (seat->tablet.outputs.erase(output)) {
9186 changed = true;
9187 }
9188 }
9189 return changed;
9190}
9191
9193{
9194 /* NOTE: keep in sync with `output_unref`. */
9195 GHOST_WindowManager *window_manager = getWindowManager();
9196 if (window_manager) {
9197 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9198 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9199 const std::vector<GWL_Output *> &outputs = win->outputs_get();
9200 if (!(std::find(outputs.begin(), outputs.end(), output) == outputs.cend())) {
9202 }
9203 }
9204 }
9205
9206 for (GWL_Seat *seat : display_->seats) {
9207 if (seat->pointer.outputs.count(output)) {
9209 seat->system->wl_shm_get(),
9210 &seat->pointer,
9211 seat->cursor.wl.surface_cursor);
9212 }
9213
9214 if (seat->tablet.outputs.count(output)) {
9215 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
9216 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
9217 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
9219 seat->system->wl_shm_get(),
9220 &seat->tablet,
9221 tablet_tool->wl.surface_cursor);
9222 }
9223 }
9224 }
9225}
9226
9228 const GHOST_TGrabCursorMode mode_current,
9229 int32_t init_grab_xy[2],
9230 const GHOST_Rect *wrap_bounds,
9231 const GHOST_TAxisFlag wrap_axis,
9233 const GWL_WindowScaleParams &scale_params)
9234{
9235 /* Caller must lock `server_mutex`. */
9236
9237 /* Ignore, if the required protocols are not supported. */
9238 if (UNLIKELY(!display_->wp.relative_pointer_manager || !display_->wp.pointer_constraints)) {
9239 return false;
9240 }
9241
9242 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9243 if (UNLIKELY(!seat)) {
9244 return false;
9245 }
9246 /* No change, success. */
9247 if (mode == mode_current) {
9248 return true;
9249 }
9250
9251#ifdef USE_GNOME_CONFINE_HACK
9252 const bool was_software_confine = seat->use_pointer_software_confine;
9253 const bool use_software_confine = setCursorGrab_use_software_confine(mode, wl_surface);
9254#else
9255 const bool was_software_confine = false;
9256 const bool use_software_confine = false;
9257#endif
9258
9259 const GWL_SeatStateGrab grab_state_prev = seat_grab_state_from_mode(mode_current,
9260 was_software_confine);
9261 const GWL_SeatStateGrab grab_state_next = seat_grab_state_from_mode(mode, use_software_confine);
9262
9263 /* Check for wrap as #GHOST_kCapabilityCursorWarp isn't supported. */
9264 const bool use_visible = !(ELEM(mode, GHOST_kGrabHide, GHOST_kGrabWrap) || use_software_confine);
9265 const bool is_hardware_cursor = !cursor_is_software(mode, use_software_confine);
9266
9267 /* Only hide so the cursor is not made visible before it's location is restored.
9268 * This function is called again at the end of this function which only shows. */
9269 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_HIDE);
9270
9271 /* Switching from one grab mode to another,
9272 * in this case disable the current locks as it makes logic confusing,
9273 * postpone changing the cursor to avoid flickering. */
9274 if (!grab_state_next.use_lock) {
9275 if (seat->wp.relative_pointer) {
9276 zwp_relative_pointer_v1_destroy(seat->wp.relative_pointer);
9277 seat->wp.relative_pointer = nullptr;
9278 }
9279 if (seat->wp.locked_pointer) {
9280 /* Potentially add a motion event so the application has updated X/Y coordinates. */
9281 int32_t xy_motion[2] = {0, 0};
9282 bool xy_motion_create_event = false;
9283
9284 /* Request location to restore to. */
9285 if (mode_current == GHOST_kGrabWrap) {
9286 /* Since this call is initiated by Blender, we can be sure the window wasn't closed
9287 * by logic outside this function - as the window was needed to make this call. */
9288 int32_t xy_next[2] = {UNPACK2(seat->pointer.xy)};
9289
9290 GHOST_Rect bounds_scale;
9291
9292 bounds_scale.m_l = gwl_window_scale_wl_fixed_from(scale_params,
9293 wl_fixed_from_int(wrap_bounds->m_l));
9294 bounds_scale.m_t = gwl_window_scale_wl_fixed_from(scale_params,
9295 wl_fixed_from_int(wrap_bounds->m_t));
9296 bounds_scale.m_r = gwl_window_scale_wl_fixed_from(scale_params,
9297 wl_fixed_from_int(wrap_bounds->m_r));
9298 bounds_scale.m_b = gwl_window_scale_wl_fixed_from(scale_params,
9299 wl_fixed_from_int(wrap_bounds->m_b));
9300
9301 bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis);
9302
9303 /* Push an event so the new location is registered. */
9304 if ((xy_next[0] != seat->pointer.xy[0]) || (xy_next[1] != seat->pointer.xy[1])) {
9305 xy_motion[0] = xy_next[0];
9306 xy_motion[1] = xy_next[1];
9307 xy_motion_create_event = true;
9308 }
9309 seat->pointer.xy[0] = xy_next[0];
9310 seat->pointer.xy[1] = xy_next[1];
9311
9312 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer, UNPACK2(xy_next));
9313 wl_surface_commit(wl_surface);
9314 }
9315 else if (mode_current == GHOST_kGrabHide) {
9316 if ((init_grab_xy[0] != seat->grab_lock_xy[0]) ||
9317 (init_grab_xy[1] != seat->grab_lock_xy[1]))
9318 {
9319 const wl_fixed_t xy_next[2] = {
9320 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[0])),
9321 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[1])),
9322 };
9323 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9324 UNPACK2(xy_next));
9325 wl_surface_commit(wl_surface);
9326
9327 /* NOTE(@ideasman42): The new cursor position is a hint,
9328 * it's possible the hint is ignored. It doesn't seem like there is a good way to
9329 * know if the hint will be used or not, at least not immediately. */
9330 xy_motion[0] = xy_next[0];
9331 xy_motion[1] = xy_next[1];
9332 xy_motion_create_event = true;
9333 }
9334 }
9335#ifdef USE_GNOME_CONFINE_HACK
9336 else if (mode_current == GHOST_kGrabNormal) {
9337 if (was_software_confine) {
9338 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9339 UNPACK2(seat->pointer.xy));
9340 wl_surface_commit(wl_surface);
9341 }
9342 }
9343#endif
9344
9345 if (xy_motion_create_event) {
9346 /* Caller has no time-stamp. */
9347 const uint64_t event_ms = getMilliSeconds();
9349 event_ms,
9352 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[0])),
9353 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[1])),
9355 }
9356
9357 zwp_locked_pointer_v1_destroy(seat->wp.locked_pointer);
9358 seat->wp.locked_pointer = nullptr;
9359 }
9360 }
9361
9362 if (!grab_state_next.use_confine) {
9363 if (seat->wp.confined_pointer) {
9364 zwp_confined_pointer_v1_destroy(seat->wp.confined_pointer);
9365 seat->wp.confined_pointer = nullptr;
9366 }
9367 }
9368
9369 if (mode != GHOST_kGrabDisable) {
9370 if (grab_state_next.use_lock) {
9371 if (!grab_state_prev.use_lock) {
9372 /* As WAYLAND does not support setting the cursor coordinates programmatically,
9373 * #GHOST_kGrabWrap cannot be supported by positioning the cursor directly.
9374 * Instead the cursor is locked in place, using a software cursor that is warped.
9375 * Then WAYLAND's #zwp_locked_pointer_v1_set_cursor_position_hint is used to restore
9376 * the cursor to the warped location. */
9377 seat->wp.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
9378 display_->wp.relative_pointer_manager, seat->wl.pointer);
9379 zwp_relative_pointer_v1_add_listener(
9381 seat->wp.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
9382 display_->wp.pointer_constraints,
9383 wl_surface,
9384 seat->wl.pointer,
9385 nullptr,
9386 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9387 }
9388 if (mode == GHOST_kGrabHide) {
9389 /* Set the initial position to detect any changes when un-grabbing,
9390 * otherwise the unlocked cursor defaults to un-locking in-place. */
9391 init_grab_xy[0] = wl_fixed_to_int(
9392 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[0]));
9393 init_grab_xy[1] = wl_fixed_to_int(
9394 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[1]));
9395 seat->grab_lock_xy[0] = init_grab_xy[0];
9396 seat->grab_lock_xy[1] = init_grab_xy[1];
9397 }
9398 }
9399 else if (grab_state_next.use_confine) {
9400 if (!grab_state_prev.use_confine) {
9401 seat->wp.confined_pointer = zwp_pointer_constraints_v1_confine_pointer(
9402 display_->wp.pointer_constraints,
9403 wl_surface,
9404 seat->wl.pointer,
9405 nullptr,
9406 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9407 }
9408 }
9409 }
9410
9411 /* Only show so the cursor is made visible as the last step. */
9412 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_SHOW);
9413
9414#ifdef USE_GNOME_CONFINE_HACK
9415 seat->use_pointer_software_confine = use_software_confine;
9416#endif
9417
9418 return true;
9419}
9420
9421#ifdef WITH_GHOST_WAYLAND_LIBDECOR
9422bool GHOST_SystemWayland::use_libdecor_runtime()
9423{
9424 return use_libdecor;
9425}
9426#endif
9427
9428#ifdef WITH_GHOST_WAYLAND_DYNLOAD
9429bool ghost_wl_dynload_libraries_init()
9430{
9431# ifdef WITH_GHOST_X11
9432 /* When running in WAYLAND, let the user know when a missing library is the only reason
9433 * WAYLAND could not be used. Otherwise it's not obvious why X11 is used as a fallback.
9434 * Otherwise when X11 is used, reporting WAYLAND library warnings is unwelcome noise. */
9435 bool verbose = getenv("WAYLAND_DISPLAY") != nullptr;
9436# else
9437 bool verbose = true;
9438# endif /* !WITH_GHOST_X11 */
9439
9440 if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */
9441 wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */
9442# ifdef WITH_OPENGL_BACKEND
9443 wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */
9444# else
9445 true
9446# endif
9447 )
9448 {
9449# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9450 has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */
9451# endif
9452 return true;
9453 }
9454
9457# ifdef WITH_OPENGL_BACKEND
9459# endif
9460
9461 return false;
9462}
9463
9464void ghost_wl_dynload_libraries_exit()
9465{
9468# ifdef WITH_OPENGL_BACKEND
9470# endif
9471# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9473# endif
9474}
9475
9476#endif /* WITH_GHOST_WAYLAND_DYNLOAD */
9477
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
#define UNPACK2(a)
#define ARRAY_SIZE(arr)
#define STRINGIFY(x)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
ThreadMutex mutex
#define GHOST_OPENGL_EGL_CONTEXT_FLAGS
#define GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY
#define wl_display
#define wl_surface
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
char * GHOST_URL_decode_alloc(const char *buf_src, const int buf_src_len)
static void keyboard_handle_repeat_info(void *data, wl_keyboard *, const int32_t rate, const int32_t delay)
void(*)(GWL_Display *display, void *user_data, bool on_exit) GWL_RegistryEntry_RemoveFn
static constexpr int smooth_as_discrete_steps
static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex)
static void gwl_registry_wp_viewporter_remove(GWL_Display *display, void *, const bool)
static void tablet_tool_handle_button(void *data, zwp_tablet_tool_v2 *, const uint32_t serial, const uint32_t button, const uint32_t state)
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE
#define USE_NON_LATIN_KB_WORKAROUND
#define WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)
static const char * ghost_wl_mime_send[]
static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_output *wl_output)
static void tablet_tool_handle_distance(void *, zwp_tablet_tool_v2 *, const uint32_t distance)
static void data_source_handle_target(void *, wl_data_source *, const char *)
static void primary_selection_device_handle_selection(void *data, zwp_primary_selection_device_v1 *, zwp_primary_selection_offer_v1 *id)
static void global_handle_add(void *data, wl_registry *wl_registry, const uint32_t name, const char *interface, const uint32_t version)
static void gwl_registry_wp_relative_pointer_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static int gwl_registry_handler_interface_slot_from_string(const char *interface)
static const wl_keyboard_listener keyboard_listener
static CLG_LogRef LOG_WL_TABLET_TOOL
static void gwl_registry_xdg_wm_base_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void touch_seat_handle_cancel(void *, wl_touch *)
static void keyboard_depressed_state_push_events_from_change(GWL_Seat *seat, GHOST_IWindow *win, const uint64_t event_ms, const GWL_KeyboardDepressedState &key_depressed_prev)
static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type)
#define BTN_RANGE_MAX
static void keyboard_handle_modifiers(void *data, wl_keyboard *, const uint32_t serial, const uint32_t mods_depressed, const uint32_t mods_latched, const uint32_t mods_locked, const uint32_t group)
#define pushEvent
static const GWL_Cursor_ShapeInfo ghost_wl_cursors
static void seat_handle_capabilities(void *data, wl_seat *wl_seat, const uint32_t capabilities)
static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
static const char * ghost_wl_surface_cursor_tablet_tag_id
static const xdg_wm_base_listener shell_listener
static CLG_LogRef LOG_WL_KEYBOARD_DEPRESSED_STATE
static void keyboard_handle_keymap(void *data, wl_keyboard *, const uint32_t format, const int32_t fd, const uint32_t size)
static void data_source_handle_cancelled(void *data, wl_data_source *wl_data_source)
bool ghost_wl_surface_own(const wl_surface *wl_surface)
#define LOG
static void tablet_seat_handle_tool_added(void *data, zwp_tablet_seat_v2 *, zwp_tablet_tool_v2 *id)
static void pointer_handle_frame(void *data, wl_pointer *)
static void pointer_handle_axis(void *data, wl_pointer *, const uint32_t time, const uint32_t axis, const wl_fixed_t value)
static const char * ghost_wl_app_id
static void xdg_output_handle_name(void *, zxdg_output_v1 *, const char *name)
static GHOST_TSuccess getCursorPositionClientRelative_impl(const GWL_SeatStatePointer *seat_state_pointer, const GHOST_WindowWayland *win, int32_t &x, int32_t &y)
static void gwl_primary_selection_discard_offer(GWL_PrimarySelection *primary)
static void pthread_set_min_priority(pthread_t handle)
static void gwl_registry_wl_data_device_manager_remove(GWL_Display *display, void *, const bool)
static char * read_buffer_from_primary_selection_offer(GWL_PrimarySelection_DataOffer *data_offer, const char *mime_receive, std::mutex *mutex, const bool nil_terminate, size_t *r_len)
static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat)
static void gwl_seat_cursor_anim_end(GWL_Seat *seat)
static void tablet_tool_handle_type(void *data, zwp_tablet_tool_v2 *, const uint32_t tool_type)
static void gwl_registry_wp_relative_pointer_manager_remove(GWL_Display *display, void *, const bool)
static CLG_LogRef LOG_WL_TABLET_SEAT
static void data_device_handle_drop(void *data, wl_data_device *)
static void data_source_handle_send(void *data, wl_data_source *, const char *, const int32_t fd)
static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat)
static const zwp_relative_pointer_v1_listener relative_pointer_listener
static const wl_cursor * gwl_seat_cursor_find_from_shape(GWL_Seat *seat, const GHOST_TStandardCursor shape, const char **r_cursor_name)
static void tablet_tool_handle_proximity_out(void *data, zwp_tablet_tool_v2 *)
void ghost_wl_output_tag(wl_output *wl_output)
static void gwl_registry_xdg_decoration_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void output_handle_geometry(void *data, wl_output *, const int32_t, const int32_t, const int32_t physical_width, const int32_t physical_height, const int32_t, const char *make, const char *model, const int32_t transform)
static void gwl_registry_xdg_decoration_manager_remove(GWL_Display *display, void *, const bool)
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER
static void data_device_handle_selection(void *data, wl_data_device *, wl_data_offer *id)
static int memfd_create_sealed(const char *name)
static uint64_t ghost_wl_ms_from_utime_pair(uint32_t utime_hi, uint32_t utime_lo)
#define GWL_TabletTool_FrameTypes_NUM
static void keyboard_handle_key_repeat_cancel(GWL_Seat *seat)
GHOST_WindowWayland * ghost_wl_surface_user_data(wl_surface *wl_surface)
#define GXMAP(k, x, y)
static void gwl_seat_key_repeat_timer_remove(GWL_Seat *seat)
static const GHOST_TButton gwl_pointer_events_ebutton[]
static CLG_LogRef LOG_WL_TOUCH
static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display *display, void *, const bool)
static GHOST_TSuccess setCursorPositionClientRelative_impl(GWL_Seat *seat, GHOST_WindowWayland *win, const int32_t x, const int32_t y)
static const zxdg_output_v1_listener xdg_output_listener
static void keyboard_handle_key_repeat_reset(GWL_Seat *seat, const bool use_delay)
static void global_handle_remove(void *data, wl_registry *wl_registry, const uint32_t name)
static void primary_selection_source_send(void *data, zwp_primary_selection_source_v1 *, const char *, int32_t fd)
static const wl_buffer_listener cursor_buffer_listener
static void gwl_registry_wp_pointer_constraints_remove(GWL_Display *display, void *, const bool)
static void tablet_tool_handle_done(void *, zwp_tablet_tool_v2 *)
static void output_handle_done(void *data, wl_output *wl_output)
static void gwl_registry_wl_seat_update(GWL_Display *display, const GWL_RegisteryUpdate_Params &params)
static void gwl_seat_capability_keyboard_enable(GWL_Seat *seat)
static ssize_t read_exhaustive(const int fd, void *data, size_t nbytes)
@ GWL_IOR_NO_RETRY
static CLG_LogRef LOG_WL_RELATIVE_POINTER
static void thread_set_min_priority(std::thread &thread)
static void relative_pointer_handle_relative_motion_impl(GWL_Seat *seat, GHOST_WindowWayland *win, const wl_fixed_t xy[2], const uint64_t event_ms)
static uint64_t sub_abs_u64(const uint64_t a, const uint64_t b)
static bool gwl_registry_entry_remove_by_name(GWL_Display *display, uint32_t name, int *r_interface_slot)
static void gwl_registry_compositor_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
void ghost_wl_surface_tag_cursor_tablet(wl_surface *wl_surface)
static void gwl_registry_entry_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params, void *user_data)
#define BTN_RANGE_MIN
static void xdg_output_handle_description(void *, zxdg_output_v1 *, const char *description)
bool ghost_wl_surface_own_cursor_tablet(const wl_surface *wl_surface)
static CLG_LogRef LOG_WL_XDG_OUTPUT
static void gwl_seat_cursor_anim_begin(GWL_Seat *seat)
static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat, const wl_cursor_image *wl_image, wl_buffer *buffer)
static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude)
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE
GWL_Output * ghost_wl_output_user_data(wl_output *wl_output)
static void touch_seat_handle_orientation(void *, wl_touch *, int32_t, wl_fixed_t)
static char * read_buffer_from_data_offer(GWL_DataOffer *data_offer, const char *mime_receive, std::mutex *mutex, const bool nil_terminate, size_t *r_len)
static bool gwl_seat_capability_pointer_multitouch_check(const GWL_Seat *seat, const bool fallback)
static const wl_registry_listener registry_listener
static void keyboard_depressed_state_reset(GWL_Seat *seat)
static const zwp_tablet_seat_v2_listener tablet_seat_listener
static void gwl_tablet_tool_frame_event_reset(GWL_TabletTool *tablet_tool)
static GWL_SeatStatePointer * gwl_seat_state_pointer_active(GWL_Seat *seat)
static constexpr const char * ghost_wl_mime_text_plain
static void tablet_seat_handle_tablet_added(void *, zwp_tablet_seat_v2 *, zwp_tablet_v2 *id)
static void tablet_tool_handle_frame(void *data, zwp_tablet_tool_v2 *, const uint32_t time)
static const zwp_primary_selection_offer_v1_listener primary_selection_offer_listener
static int pointer_axis_as_index(const uint32_t axis)
static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
static const wl_pointer_listener pointer_listener
static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer)
static void touch_seat_handle_up(void *, wl_touch *, uint32_t, uint32_t, int32_t)
static void data_source_handle_dnd_finished(void *, wl_data_source *)
static void gwl_registry_xdg_output_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void gwl_registry_wp_tablet_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void tablet_tool_handle_hardware_id_wacom(void *, zwp_tablet_tool_v2 *, const uint32_t, const uint32_t)
static void gwl_seat_capability_pointer_disable(GWL_Seat *seat)
static void gwl_registry_wp_pointer_gestures_remove(GWL_Display *display, void *, const bool)
static const wl_data_offer_listener data_offer_listener
static bool update_cursor_scale(GWL_Cursor &cursor, wl_shm *shm, GWL_SeatStatePointer *seat_state_pointer, wl_surface *wl_surface_cursor)
static void ghost_wl_display_report_error_from_code(wl_display *display, const int ecode)
static void gwl_pointer_handle_frame_event_reset(GWL_SeatStatePointer_Events *pointer_events)
static void tablet_tool_handle_motion(void *data, zwp_tablet_tool_v2 *, const wl_fixed_t x, const wl_fixed_t y)
static const GWL_RegistryHandler * gwl_registry_handler_from_interface_slot(int interface_slot)
static CLG_LogRef LOG_WL_OUTPUT
static const wl_data_source_listener data_source_listener
static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
void(*)(GWL_Display *display, const GWL_RegisteryAdd_Params &params) GWL_RegistryHandler_AddFn
static void keyboard_depressed_state_key_event(GWL_Seat *seat, const GHOST_TKey gkey, const GHOST_TEventType etype)
static const zwp_tablet_tool_v2_listener tablet_tool_listner
static void tablet_seat_handle_pad_added(void *, zwp_tablet_seat_v2 *, zwp_tablet_pad_v2 *id)
static void tablet_tool_handle_hardware_serial(void *, zwp_tablet_tool_v2 *, const uint32_t, const uint32_t)
#define WL_NAME_UNSET
static bool gwl_registry_entry_remove_by_interface_slot(GWL_Display *display, int interface_slot, bool on_exit)
void ghost_wl_surface_tag(wl_surface *wl_surface)
static wl_buffer * ghost_wl_buffer_create_for_image(wl_shm *shm, const int32_t size_xy[2], enum wl_shm_format format, void **r_buffer_data, size_t *r_buffer_data_size)
static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
static void gwl_registry_xdg_activation_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static const wl_data_device_listener data_device_listener
static void pointer_handle_axis_value120(void *data, wl_pointer *, uint32_t axis, int32_t value120)
static void touch_seat_handle_shape(void *, wl_touch *, int32_t, wl_fixed_t, wl_fixed_t)
static void pointer_handle_axis_stop(void *data, wl_pointer *, uint32_t time, uint32_t axis)
static void gwl_seat_capability_touch_disable(GWL_Seat *seat)
static bool gwl_seat_cursor_anim_check(GWL_Seat *seat)
static void ghost_wl_display_report_error(wl_display *display)
static void tablet_tool_handle_tilt(void *data, zwp_tablet_tool_v2 *, const wl_fixed_t tilt_x, const wl_fixed_t tilt_y)
bool ghost_wl_display_report_error_if_set(wl_display *display)
static void tablet_tool_handle_removed(void *data, zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
static const wl_touch_listener touch_seat_listener
static void gwl_registry_wl_seat_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void pointer_handle_button(void *data, wl_pointer *, const uint32_t serial, const uint32_t time, const uint32_t button, const uint32_t state)
static void gwl_seat_key_layout_active_state_update_mask(GWL_Seat *seat)
static void gwl_display_event_thread_destroy(GWL_Display *display)
static constexpr const char * ghost_wl_mime_text_uri
static CLG_LogRef LOG_WL_KEYBOARD
static bool ghost_wayland_log_handler_is_background
static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32_t key)
static void keyboard_handle_key(void *data, wl_keyboard *, const uint32_t serial, const uint32_t time, const uint32_t key, const uint32_t state)
void ghost_wl_surface_tag_cursor_pointer(wl_surface *wl_surface)
static void ghost_wayland_log_handler(const char *msg, va_list arg)
static void gwl_seat_cursor_buffer_show(GWL_Seat *seat)
static CLG_LogRef LOG_WL_REGISTRY
static const wl_surface_listener cursor_surface_listener
static void primary_selection_source_cancelled(void *data, zwp_primary_selection_source_v1 *source)
@ CURSOR_VISIBLE_ONLY_HIDE
@ CURSOR_VISIBLE_ALWAYS_SET
@ CURSOR_VISIBLE_ONLY_SHOW
static CLG_LogRef LOG_WL_POINTER
static void gwl_seat_cursor_buffer_hide(GWL_Seat *seat)
static void pointer_handle_axis_discrete(void *data, wl_pointer *, uint32_t axis, int32_t discrete)
static void keyboard_handle_leave(void *data, wl_keyboard *, const uint32_t, wl_surface *wl_surface)
static constexpr const char * ghost_wl_mime_img_png
static void data_source_handle_action(void *, wl_data_source *, const uint32_t dnd_action)
static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(xkb_state *xkb_state_empty, xkb_state *xkb_state_empty_with_numlock, xkb_state *xkb_state_empty_with_shift, const bool xkb_use_non_latin_workaround, const xkb_keycode_t key)
static void gwl_registry_wp_tablet_manager_remove(GWL_Display *display, void *, const bool)
static void xdg_output_handle_logical_position(void *data, zxdg_output_v1 *, const int32_t x, const int32_t y)
static const zwp_primary_selection_source_v1_listener primary_selection_source_listener
static void data_device_handle_data_offer(void *, wl_data_device *, wl_data_offer *id)
static void pointer_handle_enter(void *data, wl_pointer *, const uint32_t serial, wl_surface *wl_surface, const wl_fixed_t surface_x, const wl_fixed_t surface_y)
static void cursor_surface_handle_preferred_buffer_scale(void *, wl_surface *, int32_t factor)
static void tablet_tool_handle_capability(void *, zwp_tablet_tool_v2 *, const uint32_t capability)
static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image, wl_buffer *buffer, wl_surface *wl_surface, const int scale)
static void gwl_registry_wl_output_remove(GWL_Display *display, void *user_data, const bool on_exit)
static void pointer_handle_axis_source(void *data, wl_pointer *, uint32_t axis_source)
static void pointer_handle_motion(void *data, wl_pointer *, const uint32_t time, const wl_fixed_t surface_x, const wl_fixed_t surface_y)
static void touch_seat_handle_motion(void *, wl_touch *, uint32_t, int32_t, wl_fixed_t, wl_fixed_t)
static void primary_selection_device_handle_data_offer(void *, zwp_primary_selection_device_v1 *, zwp_primary_selection_offer_v1 *id)
static const zwp_primary_selection_device_v1_listener primary_selection_device_listener
static void cursor_buffer_handle_release(void *data, wl_buffer *wl_buffer)
static const char * system_clipboard_text_mime_type(const std::unordered_set< std::string > &data_offer_types)
static void gwl_registry_wl_shm_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
bool ghost_wl_output_own(const wl_output *wl_output)
static void * gwl_display_event_thread_fn(void *display_voidp)
static const char * ghost_wl_locale_from_env_with_default()
#define EVDEV_OFFSET
static void gwl_registry_wp_viewporter_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void pointer_handle_leave(void *data, wl_pointer *, const uint32_t, wl_surface *wl_surface)
static void gwl_registry_wl_data_device_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static CLG_LogRef LOG_WL_DATA_OFFER
static void data_offer_handle_source_actions(void *data, wl_data_offer *, const uint32_t source_actions)
static void tablet_tool_handle_rotation(void *, zwp_tablet_tool_v2 *, const wl_fixed_t degrees)
static void gwl_seat_capability_pointer_enable(GWL_Seat *seat)
static CLG_LogRef LOG_WL_SEAT
static void data_offer_handle_action(void *data, wl_data_offer *, const uint32_t dnd_action)
static void gwl_seat_cursor_buffer_set_current(GWL_Seat *seat)
static void tablet_tool_handle_proximity_in(void *data, zwp_tablet_tool_v2 *, const uint32_t serial, zwp_tablet_v2 *, wl_surface *wl_surface)
static void gwl_display_event_thread_create(GWL_Display *display)
#define SURFACE_CLEAR_PTR(surface_test)
static void seat_handle_name(void *data, wl_seat *, const char *name)
static void gwl_tablet_tool_frame_event_add(GWL_TabletTool *tablet_tool, const GWL_TabletTool_EventTypes ty)
static void tablet_tool_handle_wheel(void *data, zwp_tablet_tool_v2 *, const wl_fixed_t, const int32_t clicks)
static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary)
static void tablet_tool_handle_down(void *data, zwp_tablet_tool_v2 *, const uint32_t serial)
static void gwl_seat_cursor_visible_set(GWL_Seat *seat, const bool visible, const bool is_hardware, const enum eCursorSetMode set_mode)
static CLG_LogRef LOG_WL_CURSOR_SURFACE
static void output_handle_scale(void *data, wl_output *, const int32_t factor)
static void data_device_handle_leave(void *data, wl_data_device *)
static const char * ghost_wl_surface_cursor_pointer_tag_id
static void gwl_display_destroy(GWL_Display *display)
static void touch_seat_handle_down(void *, wl_touch *, uint32_t, uint32_t, wl_surface *, int32_t, wl_fixed_t, wl_fixed_t)
static void gwl_seat_capability_touch_enable(GWL_Seat *seat)
static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat *seat)
static bool xkb_compose_state_feed_and_get_utf8(xkb_compose_state *compose_state, xkb_state *state, const xkb_keycode_t key, char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)])
static void primary_selection_offer_offer(void *data, zwp_primary_selection_offer_v1 *id, const char *type)
static const wl_output_listener output_listener
static void gwl_registry_entry_remove_all(GWL_Display *display)
static void keyboard_handle_enter(void *data, wl_keyboard *, const uint32_t serial, wl_surface *wl_surface, wl_array *keys)
static int ghost_wl_display_event_pump(wl_display *wl_display)
static CLG_LogRef LOG_WL_XDG_WM_BASE
static void output_handle_mode(void *data, wl_output *, const uint32_t flags, const int32_t width, const int32_t height, const int32_t)
static void gwl_seat_key_repeat_timer_fn(GHOST_ITimerTask *task, uint64_t time_ms)
static void relative_pointer_handle_relative_motion(void *data, zwp_relative_pointer_v1 *, const uint32_t utime_hi, const uint32_t utime_lo, const wl_fixed_t dx, const wl_fixed_t dy, const wl_fixed_t, const wl_fixed_t)
static GWL_Seat * gwl_display_seat_active_get(const GWL_Display *display)
void(*)(GWL_Display *display, const GWL_RegisteryUpdate_Params &params) GWL_RegistryHandler_UpdateFn
static const char * ghost_wl_surface_tag_id
static void xdg_output_handle_logical_size(void *data, zxdg_output_v1 *, const int32_t width, const int32_t height)
static signed char has_wl_trackpad_physical_direction
constexpr size_t events_pending_default_size
static void gwl_registry_xdg_wm_base_remove(GWL_Display *display, void *, const bool)
static bool setCursorGrab_use_software_confine(const GHOST_TGrabCursorMode mode, wl_surface *wl_surface)
static void data_offer_handle_offer(void *data, wl_data_offer *, const char *mime_type)
static const GHOST_TDragnDropTypes ghost_wl_mime_preference_order_type[]
static char * read_file_as_buffer(const int fd, const bool nil_terminate, size_t *r_len)
static void data_device_handle_motion(void *data, wl_data_device *, const uint32_t time, const wl_fixed_t x, const wl_fixed_t y)
static void gwl_registry_wl_output_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
@ MOD_INDEX_SHIFT
@ MOD_INDEX_ALT
@ MOD_INDEX_CTRL
static void gwl_registry_wp_fractional_scale_manager_remove(GWL_Display *display, void *, const bool)
static void gwl_registry_wp_fractional_scale_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
#define MOD_INDEX_NUM
GWL_TabletTool_EventTypes
static void gwl_seat_capability_pointer_multitouch_disable(GWL_Seat *seat)
#define CASE_CURSOR(shape_id, shape_name_in_theme)
static void output_handle_description(void *, wl_output *, const char *description)
static void gwl_registry_wp_pointer_gestures_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static CLG_LogRef LOG_WL_DATA_SOURCE
bool ghost_wl_surface_own_cursor_pointer(const wl_surface *wl_surface)
static void gwl_simple_buffer_free_data(GWL_SimpleBuffer *buffer)
static CLG_LogRef LOG_WL_CURSOR_BUFFER
static void touch_seat_handle_frame(void *, wl_touch *)
GWL_Pointer_EventTypes
static CLG_LogRef LOG_WL_DATA_DEVICE
static void gwl_seat_cursor_anim_reset(GWL_Seat *seat)
static void gwl_seat_capability_pointer_multitouch_enable(GWL_Seat *seat)
static const GHOST_TButton gwl_tablet_tool_ebutton[]
static void gwl_registry_xdg_output_manager_remove(GWL_Display *display, void *, const bool)
static GWL_SeatStateGrab seat_grab_state_from_mode(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
static const int default_cursor_size
static const wl_seat_listener seat_listener
static const GWL_ModifierInfo g_modifier_info_table[MOD_INDEX_NUM]
static constexpr const char * ghost_wl_mime_text_utf8
static void gwl_seat_key_repeat_timer_add(GWL_Seat *seat, GHOST_TimerProcPtr key_repeat_fn, GHOST_TUserDataPtr payload, const bool use_delay)
static void data_source_handle_dnd_drop_performed(void *, wl_data_source *)
static void tablet_tool_handle_up(void *data, zwp_tablet_tool_v2 *)
static void gwl_xdg_decor_system_destroy(GWL_Display *display, GWL_XDG_Decor_System *decor)
static char * system_clipboard_get(GWL_Display *display)
static void gwl_simple_buffer_set_from_string(GWL_SimpleBuffer *buffer, const char *str)
static GWL_SeatStatePointer * gwl_seat_state_pointer_from_cursor_surface(GWL_Seat *seat, const wl_surface *wl_surface)
static void gwl_registry_xdg_activation_remove(GWL_Display *display, void *, const bool)
static int gwl_scaled_fixed_t_add_and_calc_rounded_delta(GWL_ScaledFixedT *sf, const wl_fixed_t add)
static const GWL_RegistryHandler gwl_registry_handlers[]
static void gwl_registry_wl_shm_remove(GWL_Display *display, void *, const bool)
static bool use_gnome_confine_hack
static void tablet_tool_handle_slider(void *, zwp_tablet_tool_v2 *, const int32_t position)
bool ghost_wl_surface_own_with_null_check(const wl_surface *wl_surface)
static int ghost_wl_display_event_pump_from_thread(wl_display *wl_display, const int fd, std::mutex *server_mutex)
static void output_handle_name(void *, wl_output *, const char *name)
static void data_device_handle_enter(void *data, wl_data_device *, const uint32_t serial, wl_surface *wl_surface, const wl_fixed_t x, const wl_fixed_t y, wl_data_offer *id)
static void system_clipboard_put(GWL_Display *display, const char *buffer)
static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat)
static const char * ghost_wl_output_tag_id
static const char * ghost_wl_mime_preference_order[]
static void shell_handle_ping(void *, xdg_wm_base *xdg_wm_base, const uint32_t serial)
static int file_descriptor_is_io_ready(int fd, const int flags, const int timeout_ms)
static void gwl_pointer_handle_frame_event_add(GWL_SeatStatePointer_Events *pointer_events, const GWL_Pointer_EventTypes ty, const uint64_t event_ms)
static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event, const uint64_t event_ms)
static void tablet_tool_handle_pressure(void *data, zwp_tablet_tool_v2 *, const uint32_t pressure)
static int cursor_buffer_compatible_scale_from_image(const wl_cursor_image *wl_image, int scale)
static void gwl_registry_wp_pointer_constraints_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
#define GWL_IFACE_VERSION_CLAMP(params_version, version_min, version_max)
static char * system_clipboard_get_primary_selection(GWL_Display *display)
static int gwl_registry_handler_interface_slot_max()
static void gwl_registry_wl_output_update(GWL_Display *display, const GWL_RegisteryUpdate_Params &params)
static void xdg_output_handle_done(void *data, zxdg_output_v1 *)
static void gwl_registry_wp_primary_selection_device_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
static void gwl_registry_compositor_remove(GWL_Display *display, void *, const bool)
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params, wl_fixed_t value)
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params, wl_fixed_t value)
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value)
#define FRACTIONAL_DENOMINATOR
bool ghost_wl_output_own(const struct wl_output *wl_output)
struct GWL_Output * ghost_wl_output_user_data(struct wl_output *wl_output)
GHOST_WindowWayland * ghost_wl_surface_user_data(struct wl_surface *wl_surface)
@ GHOST_kTrackpadEventMagnify
@ GHOST_kTrackpadEventRotate
@ GHOST_kTrackpadEventScroll
GHOST_TWindowState
void * GHOST_TUserDataPtr
Definition GHOST_Types.h:85
GHOST_TStandardCursor
@ GHOST_kStandardCursorLeftHandle
@ GHOST_kStandardCursorHandClosed
@ GHOST_kStandardCursorHandOpen
@ GHOST_kStandardCursorBottomLeftCorner
@ GHOST_kStandardCursorZoomIn
@ GHOST_kStandardCursorVerticalSplit
@ GHOST_kStandardCursorHelp
@ GHOST_kStandardCursorCopy
@ GHOST_kStandardCursorWait
@ GHOST_kStandardCursorRightHandle
@ GHOST_kStandardCursorHorizontalSplit
@ GHOST_kStandardCursorTopSide
@ GHOST_kStandardCursorStop
@ GHOST_kStandardCursorCrosshair
@ GHOST_kStandardCursorCustom
@ GHOST_kStandardCursorNSEWScroll
@ GHOST_kStandardCursorLeftRight
@ GHOST_kStandardCursorPencil
@ GHOST_kStandardCursorNSScroll
@ GHOST_kStandardCursorCrosshairA
@ GHOST_kStandardCursorUpDown
@ GHOST_kStandardCursorUpArrow
@ GHOST_kStandardCursorHandPoint
@ GHOST_kStandardCursorBottomSide
@ GHOST_kStandardCursorBothHandles
@ GHOST_kStandardCursorInfo
@ GHOST_kStandardCursorTopLeftCorner
@ GHOST_kStandardCursorEyedropper
@ GHOST_kStandardCursorKnife
@ GHOST_kStandardCursorMove
@ GHOST_kStandardCursorCrosshairB
@ GHOST_kStandardCursorBottomRightCorner
@ GHOST_kStandardCursorDownArrow
@ GHOST_kStandardCursorEraser
@ GHOST_kStandardCursorDefault
@ GHOST_kStandardCursorEWScroll
@ GHOST_kStandardCursorRightSide
@ GHOST_kStandardCursorRightArrow
@ GHOST_kStandardCursorTopRightCorner
@ GHOST_kStandardCursorDestroy
@ GHOST_kStandardCursorCrosshairC
@ GHOST_kStandardCursorZoomOut
@ GHOST_kStandardCursorLeftSide
@ GHOST_kStandardCursorText
@ GHOST_kStandardCursorLeftArrow
#define GHOST_KEY_MODIFIER_TO_INDEX(key)
#define GHOST_kStandardCursorNumCursors
GHOST_TEventType
@ GHOST_kEventWindowSize
@ GHOST_kEventDraggingDropDone
@ GHOST_kEventDraggingExited
@ GHOST_kEventImeComposition
@ GHOST_kEventCursorMove
@ GHOST_kEventDraggingUpdated
@ GHOST_kEventDraggingEntered
@ GHOST_kEventButtonUp
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowDeactivate
@ GHOST_kEventButtonDown
@ GHOST_kEventKeyDown
@ GHOST_kEventImeCompositionStart
@ GHOST_kEventImeCompositionEnd
@ GHOST_kEventUnknown
@ GHOST_kEventKeyUp
static const GHOST_TabletData GHOST_TABLET_DATA_NONE
#define GHOST_KEY_MODIFIER_CHECK(key)
#define GHOST_KEY_MODIFIER_NUM
GHOST_TTabletMode
@ GHOST_kTabletModeEraser
@ GHOST_kTabletModeStylus
#define GHOST_kButtonNum
GHOST_TCapabilityFlag
Definition GHOST_Types.h:96
@ GHOST_kCapabilityWindowPosition
@ GHOST_kCapabilityGPUReadFrontBuffer
@ GHOST_kCapabilityCursorWarp
@ GHOST_kCapabilityTrackpadPhysicalDirection
@ GHOST_kCapabilityDesktopSample
GHOST_TAxisFlag
#define GHOST_CAPABILITY_FLAG_ALL
#define GHOST_KEY_MODIFIER_FROM_INDEX(key)
void(* GHOST_TimerProcPtr)(struct GHOST_TimerTaskHandle__ *task, uint64_t time)
GHOST_TKey
@ GHOST_kKeyLeftOS
@ GHOST_kKeyInsert
@ GHOST_kKeySemicolon
@ GHOST_kKeyMediaPlay
@ GHOST_kKeyQuote
@ GHOST_kKeyAccentGrave
@ GHOST_kKeyLeftAlt
@ GHOST_kKeyRightShift
@ GHOST_kKeyNumLock
@ GHOST_kKeyEnter
@ GHOST_kKeyNumpadSlash
@ GHOST_kKeyRightArrow
@ GHOST_kKeyPause
@ GHOST_kKeyCapsLock
@ GHOST_kKeyApp
@ GHOST_kKeyMinus
@ GHOST_kKeyMediaStop
@ GHOST_kKeyBackSpace
@ GHOST_kKeyDownPage
@ GHOST_kKeyGrLess
@ GHOST_kKeyDownArrow
@ GHOST_kKeyRightOS
@ GHOST_kKeyClear
@ GHOST_kKeyNumpadPeriod
@ GHOST_kKeyF1
@ GHOST_kKeyNumpadAsterisk
@ GHOST_kKeyPrintScreen
@ GHOST_kKeyLeftControl
@ GHOST_kKeyLeftBracket
@ GHOST_kKeyTab
@ GHOST_kKeyComma
@ GHOST_kKeyRightBracket
@ GHOST_kKeyBackslash
@ GHOST_kKeyLinefeed
@ GHOST_kKeyRightAlt
@ GHOST_kKeyPeriod
@ GHOST_kKeyNumpadPlus
@ GHOST_kKeyUpPage
@ GHOST_kKeyLeftArrow
@ GHOST_kKeyEqual
@ GHOST_kKeyHome
@ GHOST_kKeyEnd
@ GHOST_kKeyUpArrow
@ GHOST_kKeyDelete
@ GHOST_kKeyNumpad0
@ GHOST_kKeyMediaFirst
@ GHOST_kKeyRightControl
@ GHOST_kKeyEsc
@ GHOST_kKeyPlus
@ GHOST_kKeyUnknown
@ GHOST_kKeyScrollLock
@ GHOST_kKeySlash
@ GHOST_kKeyNumpadEnter
@ GHOST_kKeyNumpadMinus
@ GHOST_kKeyLeftShift
@ GHOST_kKeyMediaLast
@ GHOST_kKeySpace
GHOST_TDrawingContextType
@ GHOST_kDrawingContextTypeNone
GHOST_TModifierKey
@ 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_gpuStereoVisual
Definition GHOST_Types.h:76
@ GHOST_gpuDebugContext
Definition GHOST_Types.h:77
void(* GHOST_TBacktraceFn)(void *file_handle)
Definition GHOST_Types.h:63
GHOST_TGrabCursorMode
@ GHOST_kGrabWrap
@ GHOST_kGrabDisable
@ GHOST_kGrabHide
@ GHOST_kGrabNormal
GHOST_TDragnDropTypes
@ GHOST_kDragnDropTypeUnknown
@ GHOST_kDragnDropTypeFilenames
@ GHOST_kDragnDropTypeString
GHOST_TButton
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskButton4
@ GHOST_kButtonMaskNone
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskButton7
@ GHOST_kButtonMaskButton6
@ GHOST_kButtonMaskButton5
@ GHOST_kButtonMaskMiddle
GHOST_TConsoleWindowState
#define WL_ARRAY_FOR_EACH(pos, array)
static const wl_surface_listener wl_surface_listener
#define USE_EVENT_BACKGROUND_THREAD
@ IMB_FTYPE_PNG
Contains defines and structs used throughout the imbuf module.
@ IB_mem
@ IB_rect
volatile int lock
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
btAlignedObjectArray< btScalar > m_data
#define output
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
static int verbose
Definition cineonlib.cc:31
static GHOST_TBacktraceFn getBacktraceFn()
virtual GHOST_TUserDataPtr getUserData() const =0
virtual GHOST_TimerProcPtr getTimerProc() const =0
int32_t m_l
int32_t m_r
int32_t m_b
virtual void wrapPoint(int32_t &x, int32_t &y, int32_t ofs, GHOST_TAxisFlag axis)
int32_t m_t
struct zwp_primary_selection_device_manager_v1 * wp_primary_selection_manager_get()
static const char * xdg_app_id_get()
GHOST_TSuccess init() override
GHOST_TSuccess pushEvent_maybe_pending(const GHOST_IEvent *event)
bool window_surface_unref(const wl_surface *wl_surface)
uint64_t getMilliSeconds() const override
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override
bool window_cursor_grab_set(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, int32_t init_grab_xy[2], const GHOST_Rect *wrap_bounds, GHOST_TAxisFlag wrap_axis, wl_surface *wl_surface, const struct GWL_WindowScaleParams &scale_params)
GHOST_TSuccess cursor_visibility_set(bool visible)
std::thread::id main_thread_id
struct wl_display * wl_display_get()
char * getClipboard(bool selection) const override
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override
GHOST_TSuccess cursor_shape_check(GHOST_TStandardCursor cursorShape)
bool cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode)
uint64_t ms_from_input_time(const uint32_t timestamp_as_uint)
GHOST_TimerManager * ghost_timer_manager()
uint * getClipboardImage(int *r_width, int *r_height) const override
struct xdg_wm_base * xdg_decor_shell_get()
struct wl_compositor * wl_compositor_get()
struct wp_fractional_scale_manager_v1 * wp_fractional_scale_manager_get()
void setMultitouchGestures(const bool use) override
struct wl_shm * wl_shm_get() const
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override
GHOST_TSuccess cursor_shape_set(GHOST_TStandardCursor shape)
const std::vector< GWL_Output * > & outputs_get() const
GHOST_TSuccess disposeContext(GHOST_IContext *context) 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, const bool is_dialog, const GHOST_IWindow *parentWindow) override
struct wp_viewporter * wp_viewporter_get()
struct zxdg_decoration_manager_v1 * xdg_decor_manager_get()
struct wl_seat * wl_seat_active_get_with_input_serial(uint32_t &serial)
GHOST_TSuccess cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap)
GHOST_TSuccess cursor_shape_custom_set(const uint8_t *bitmap, const uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor)
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override
void ime_begin(const GHOST_WindowWayland *win, int32_t x, int32_t y, int32_t w, int32_t h, bool completed) const
uint8_t getNumDisplays() const override
bool output_unref(struct wl_output *wl_output)
void putClipboard(const char *buffer, bool selection) const override
void output_scale_update(GWL_Output *output)
GHOST_TCapabilityFlag getCapabilities() const override
std::atomic< bool > has_pending_actions_for_window
bool processEvents(bool waitForEvent) override
void seat_active_set(const struct GWL_Seat *seat)
GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window, int32_t &x, int32_t &y) const override
GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window, int32_t x, int32_t y) override
GHOST_TSuccess hasClipboardImage() const override
bool setConsoleWindowState(GHOST_TConsoleWindowState action) override
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override
struct xdg_activation_v1 * xdg_activation_manager_get()
struct zwp_pointer_gestures_v1 * wp_pointer_gestures_get()
void ime_end(const GHOST_WindowWayland *win) const
GHOST_IContext * createOffscreenContext(GHOST_GPUSettings gpuSettings) override
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override
GHOST_EventManager * getEventManager() const
GHOST_WindowManager * getWindowManager() const
GHOST_TSuccess removeTimer(GHOST_ITimerTask *timerTask)
bool m_multitouchGestures
virtual GHOST_TSuccess init()
GHOST_TimerManager * getTimerManager() const
GHOST_WindowManager * m_windowManager
GHOST_ITimerTask * installTimer(uint64_t delay, uint64_t interval, GHOST_TimerProcPtr timerProc, GHOST_TUserDataPtr userData=nullptr)
GHOST_TSuccess removeTimer(GHOST_TimerTask *timer)
GHOST_TSuccess addTimer(GHOST_TimerTask *timer)
GHOST_TSuccess addWindow(GHOST_IWindow *window)
const std::vector< GHOST_IWindow * > & getWindows() const
GHOST_TSuccess setActiveWindow(GHOST_IWindow *window)
void setWindowInactive(const GHOST_IWindow *window)
const struct GWL_WindowScaleParams & scale_params_get() const
bool outputs_leave(GWL_Output *output)
wl_fixed_t wl_fixed_to_window(wl_fixed_t value) const
bool getValid() const override
wl_fixed_t wl_fixed_from_window(wl_fixed_t value) const
void getClientBounds(GHOST_Rect &bounds) const override
GHOST_TSuccess cursor_shape_refresh()
const std::vector< GWL_Output * > & outputs_get()
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override
GHOST_TAxisFlag getCursorGrabAxis() const
bool getCursorGrabModeIsWarp() const
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
local_group_size(16, 16) .push_constant(Type b
static __attribute__((constructor)) void cpu_check()
Definition cpu_check.cc:94
#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)
struct ImBuf * IMB_ibImageFromMemory(const unsigned char *, size_t, int, char[IM_MAX_SPACE], const char *)
bool IMB_saveiff(struct ImBuf *, const char *, int)
struct ImBuf * IMB_allocFromBuffer(const uint8_t *, const float *, unsigned int, unsigned int, unsigned int)
void IMB_freeImBuf(ImBuf *)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
static ulong * next
static ulong state[N]
static void error(const char *str)
static void add(blender::Map< std::string, std::string > &messages, Message &msg)
Definition msgfmt.cc:227
bool is_enabled(const Sculpt &sd, const Object &object, const Brush *br)
#define INT32_MAX
Definition stdint.h:137
signed short int16_t
Definition stdint.h:76
unsigned int uint32_t
Definition stdint.h:80
#define INT32_MIN
Definition stdint.h:136
signed int int32_t
Definition stdint.h:77
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
void set(GHOST_TButton mask, bool down)
const uint8_t * data
Definition GHOST_Types.h:70
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
void set(GHOST_TModifierKey mask, bool down)
GHOST_TUserDataPtr composite_len
GHOST_TUserDataPtr result_len
GHOST_TUserDataPtr result
GHOST_TUserDataPtr composite
GHOST_TTabletMode Active
std::atomic< bool > exit_pending
const char * names[GHOST_kStandardCursorNumCursors]
std::string theme_name
wl_cursor_theme * theme
struct GWL_Cursor::@1482 wl
GWL_Cursor_AnimHandle * anim_handle
const wl_cursor * theme_cursor
wl_surface * surface_cursor
const char * theme_cursor_name
std::unordered_set< std::string > types
wl_data_offer * id
struct GWL_DataOffer::@1487 dnd
enum wl_data_device_manager_dnd_action source_actions
struct GWL_DataOffer::@1486 wl
enum wl_data_device_manager_dnd_action action
GWL_SimpleBuffer buffer_out
wl_data_source * source
struct GWL_DataSource::@1488 wl
wp_viewporter * viewporter
zwp_primary_selection_device_manager_v1 * primary_selection_device_manager
std::vector< GWL_Seat * > seats
GHOST_TimerManager * ghost_timer_manager
std::vector< const GHOST_IEvent * > events_pending
std::vector< GWL_Output * > outputs
zwp_pointer_gestures_v1 * pointer_gestures
struct GWL_Display::@1501 xdg
GWL_DisplayTimeStamp input_timestamp
GWL_XDG_Decor_System * xdg_decor
GWL_RegistryEntry * registry_entry
struct GWL_Display::@1500 wp
zwp_pointer_constraints_v1 * pointer_constraints
zwp_relative_pointer_manager_v1 * relative_pointer_manager
wl_compositor * compositor
zxdg_output_manager_v1 * output_manager
wl_registry * registry
xdg_activation_v1 * activation_manager
wl_data_device_manager * data_device_manager
zwp_tablet_manager_v2 * tablet_manager
struct GWL_Display::@1499 wl
GHOST_SystemWayland * system
std::mutex events_pending_mutex
wp_fractional_scale_manager_v1 * fractional_scale_manager
struct GWL_KeyRepeatPlayload::@1489 key_data
int16_t mods[GHOST_KEY_MODIFIER_NUM]
GHOST_TModifierKey mod_l
GHOST_TModifierKey mod_r
int32_t position_logical[2]
struct GWL_Output::@1503 xdg
int32_t size_native[2]
GHOST_SystemWayland * system
zwp_primary_selection_offer_v1 * id
struct GWL_PrimarySelection_DataOffer::@1493 wp
std::unordered_set< std::string > types
zwp_primary_selection_source_v1 * source
struct GWL_PrimarySelection_DataSource::@1494 wp
GWL_PrimarySelection_DataSource * data_source
GWL_PrimarySelection_DataOffer * data_offer
GWL_RegistryEntry * next
GWL_RegistryHandler_AddFn add_fn
const char *const * interface_p
GWL_RegistryEntry_RemoveFn remove_fn
GWL_RegistryHandler_UpdateFn update_fn
struct GWL_SeatStateKeyboard::@1492 wl
enum wl_pointer_axis_source axis_source
GWL_SeatStatePointerScroll_SmoothAsDiscrete smooth_as_discrete
GWL_Pointer_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
uint64_t frame_event_ms[GWL_TabletTool_FrameTypes_NUM]
struct GWL_SeatStatePointer_Events::@1491 frame_pending
struct GWL_SeatStatePointer::@1490 wl
std::unordered_set< const GWL_Output * > outputs
wl_keyboard * keyboard
xkb_context * context
GHOST_SystemWayland * system
GWL_SeatStatePointerScroll pointer_scroll
struct GWL_Seat::@1497 xkb
GWL_KeyboardDepressedState key_depressed
GWL_SeatStatePointer_Events pointer_events
GWL_DataSource * data_source
uint32_t data_source_serial
struct GWL_Seat::@1498 key_repeat
std::unordered_set< zwp_tablet_tool_v2 * > tablet_tools
GWL_DataOffer * data_offer_copy_paste
zwp_primary_selection_device_v1 * primary_selection_device
GWL_SeatStatePointer pointer
struct GWL_Seat::@1495 wl
std::mutex data_source_mutex
xkb_mod_index_t xkb_keymap_mod_index[MOD_INDEX_NUM]
zwp_relative_pointer_v1 * relative_pointer
xkb_compose_state * compose_state
xkb_state * state_empty_with_shift
std::mutex data_offer_dnd_mutex
xkb_state * state_empty_with_numlock
GWL_PrimarySelection primary_selection
std::mutex data_offer_copy_paste_mutex
xkb_state * state_empty
wl_pointer * pointer
uint32_t cursor_source_serial
GWL_SeatStatePointer tablet
bool use_pointer_scroll_smooth_as_discrete
zwp_tablet_seat_v2 * tablet_seat
wl_surface * surface_window_focus_dnd
xkb_layout_index_t layout_active
zwp_confined_pointer_v1 * confined_pointer
xkb_compose_table * compose_table
wl_data_device * data_device
GHOST_ITimerTask * timer
xkb_mod_index_t xkb_keymap_mod_index_numlock
bool xkb_use_non_latin_workaround
xkb_state * state
wl_fixed_t grab_lock_xy[2]
GWL_DataOffer * data_offer_dnd
xkb_mod_index_t xkb_keymap_mod_index_mod2
zwp_locked_pointer_v1 * locked_pointer
GWL_SeatStatePointerGesture_Pinch pointer_gesture_pinch
GWL_SeatStateKeyboard keyboard
bool use_pointer_software_confine
struct GWL_Seat::@1496 wp
struct GWL_TabletTool::@1484::@1485 wheel
struct GWL_TabletTool::@1484 frame_pending
GHOST_TabletData data
wl_surface * surface_cursor
GWL_TabletTool_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
struct GWL_TabletTool::@1483 wl
zxdg_decoration_manager_v1 * manager
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
unsigned int encoded_buffer_size
ImBufByteBuffer encoded_buffer
wmTimer * timer
void wayland_dynload_client_exit(void)
void wayland_dynload_cursor_exit(void)
bool wayland_dynload_cursor_init(bool verbose)
bool wayland_dynload_client_init(bool verbose)
#define wl_proxy_get_tag(...)
#define wl_display_connect(...)
#define wl_log_set_handler_client(...)
#define wl_display_dispatch_pending(...)
#define wl_proxy_set_tag(...)
#define wl_display_get_error(...)
#define wl_display_cancel_read(...)
#define wl_display_dispatch(...)
#define wl_display_get_fd(...)
#define wl_display_prepare_read(...)
#define wl_display_flush(...)
#define wl_display_get_protocol_error(...)
#define wl_display_disconnect(...)
#define wl_display_read_events(...)
#define wl_display_roundtrip(...)
#define wl_cursor_theme_get_cursor(...)
#define wl_cursor_theme_load(...)
#define wl_cursor_image_get_buffer(...)
#define wl_cursor_theme_destroy(...)
bool wayland_dynload_egl_init(const bool verbose)
void wayland_dynload_egl_exit(void)
#define wl_egl_window_create(...)
#define wl_egl_window_destroy(...)
bool wayland_dynload_libdecor_init(const bool verbose)
void wayland_dynload_libdecor_exit(void)
#define libdecor_unref(...)
#define libdecor_new(...)
int xy[2]
Definition wm_draw.cc:170